Referência da API¶
Superfície pública completa do tempest-db-js. O núcleo importa do nível do pacote; as
migrações vivem no subcaminho tempest-db-js/migrations:
import {
Model, column, sql,
type InferModel, type InferInsert,
select, insert, update, del,
and, or, not,
createEngine, createSyncEngine,
join, hasMany, belongsTo, loadRelations,
BaseRepository,
} from "tempest-db-js";
import { reflectSchema, diffSchema, MigrationRunner } from "tempest-db-js/migrations";
Referência viva
Esta página resume toda a superfície pública atual. A fonte da verdade são os docstrings JSDoc no código — o editor mostra a assinatura completa de cada símbolo no autocomplete.
Schema¶
Model¶
Classe base abstrata de toda tabela. Subclasses definem static tablename e campos
de coluna.
column¶
Fábrica de colunas tipadas (espelha os tipos genéricos do SQLAlchemy).
| Método | Tipo TS | Tipo SQL |
|---|---|---|
column.array(element) |
T[] |
TEXT[] / INTEGER[] (PostgreSQL) |
column.smallInteger() |
number |
SMALLINT |
column.integer() |
number |
INTEGER |
column.bigInteger() |
bigint |
BIGINT |
column.numeric(p?, s?) / column.decimal(p?, s?) |
string |
NUMERIC(p,s) |
column.real() |
number |
REAL |
column.double() |
number |
DOUBLE PRECISION |
column.varchar(n) / column.string(n) |
string |
VARCHAR(n) |
column.char(n) |
string |
CHAR(n) |
column.text() |
string |
TEXT |
column.boolean() |
boolean |
BOOLEAN |
column.date() |
Date |
DATE |
column.time({ timezone? }) |
string |
TIME |
column.datetime({ timezone? }) |
Date |
DATETIME/TIMESTAMP |
column.timestamp({ timezone? }) |
Date |
TIMESTAMP |
column.blob() |
Uint8Array |
BLOB/BYTEA |
column.json<T>() |
T |
JSON |
column.jsonb<T>() |
T |
JSONB |
column.uuid() |
string |
UUID |
column.enum(...vals) |
união literal | ENUM |
Modificadores encadeáveis (retornam um novo Column com a flag aplicada):
| Modificador | Efeito |
|---|---|
.primaryKey() |
Marca como PK; implica hasDefault. |
.notNull() |
Torna o tipo inferido não-anulável. |
.default(value) |
Default no insert (valor T ou expressão de sql); marca opcional no insert. |
.onUpdate(value) |
Reaplica a cada UPDATE (ex.: updated_at). |
sql — defaults portáveis¶
Expressões server-side, renderizadas por dialeto (à la func do SQLAlchemy):
| Função | Render | Uso |
|---|---|---|
sql.now() |
CURRENT_TIMESTAMP / now() |
created_at/updated_at |
sql.currentDate() |
CURRENT_DATE |
data de criação |
sql.currentTime() |
CURRENT_TIME |
hora |
sql.uuidv4() |
gen_random_uuid() / fallback |
PK UUID |
sql.raw(expr) |
verbatim | escape hatch |
sql.expr`...${v}` |
fragmento parametrizado | expressão com valor ligado |
O default fica guardado em column.<campo>.defaultValue / .onUpdateValue —
alimenta o IR de migração.
Toda expressão sql.* é marcada (isSqlExpression) e serve tanto como default
de coluna quanto como valor de escrita em .set() / .values(), onde é renderizada
inline em vez de virar parâmetro:
sql.expr não pode ser default de coluna
Um DEFAULT não tem onde ligar parâmetros — .default(sql.expr\...`)lança
erro na hora. Usesql.raw()` ali.
Nomes de coluna¶
| Símbolo | Faz |
|---|---|
.name("coluna") |
Mapeia a propriedade para outro nome de coluna. |
static naming |
"preserve" (default) ou "snake_case" para a tabela toda. |
columnNamesOf(Model) |
Mapa propriedade → coluna, ou null sem renomeações. |
columnPropsOf(Model) |
O mapa inverso, coluna → propriedade. |
toSnakeCase(name) |
A conversão usada pela estratégia "snake_case". |
Veja a receita Nomes de coluna.
columnsOf(Model)¶
Reflete a classe nos seus Column em runtime (Record<string, Column>). Base da
serialização e do reflector de schema das migrações.
InferModel<typeof Model>¶
Tipo da linha lida. Colunas notNull/primaryKey são não-anuláveis; as demais
viram T | null.
InferInsert<typeof Model>¶
Tipo da linha a inserir. Colunas com default (ou PK) são opcionais (?); o resto
é obrigatório.
SELECT¶
select(Model) / select(Model, columns)¶
| Forma | Resultado inferido |
|---|---|
select(User) |
InferModel<typeof User>[] |
select(User, ["id", "name"]) |
Pick<InferModel<typeof User>, "id" \| "name">[] |
SelectBuilder<Full, Proj>¶
| Método | Descrição |
|---|---|
.where(input) |
Filtra; chaves tipadas contra Full, operadores tipados por coluna. |
.orderBy(column, direction?) |
Ordena por coluna ("asc" | "desc", default "asc"). |
.limit(n) |
Limita o número de linhas. |
.offset(n) |
Pula as primeiras n linhas. |
.forUpdate(options?) |
FOR UPDATE [OF ...] [SKIP LOCKED \| NOWAIT]. PostgreSQL/MySQL; SQLite lança. |
.forShare(options?) |
Idem, com lock compartilhado (FOR SHARE). |
.aggregate(groupBy, spec) |
Agrupa; o builder passa a aceitar .having(). |
.having(input) |
HAVING, por alias de agregação ou coluna agrupada. Só depois de .aggregate(). |
.asSubquery(coluna) |
Projeta uma coluna e marca o SELECT como operando de in/notIn. |
.node |
A AST SelectNode (read-only). |
LockOptions = { skipLocked?: boolean; noWait?: boolean; of?: readonly string[] }.
skipLocked e noWait são mutuamente exclusivos, e um lock combinado com
DISTINCT/agregação lança erro na compilação.
Operadores de where (OperatorsFor<T>)¶
Cada valor de where aceita um match exato (shorthand de eq) ou um objeto de
operador restrito ao tipo da coluna:
| Tipo | Operadores permitidos |
|---|---|
string |
eq, ne, in, notIn, like, ilike, ieq, isNull |
number / bigint / Date |
eq, ne, in, notIn, gt, gte, lt, lte, between, isNull |
boolean |
eq, ne, isNull |
T[] (array) |
eq, ne, in, notIn, contains, containedBy, overlaps, isNull |
| json / blob | eq, ne, in, notIn, isNull |
ilike é pattern, ieq é igualdade
% e _ são coringas em like/ilike — { ilike: "%" } casa todas as
linhas. Para lookup case-insensitive (login, e-mail) use ieq, que compila
para lower(col) = lower($1) e casa índice funcional. Veja
Comparação case-insensitive.
Os operadores de array (contains → @>, containedBy → <@, overlaps →
&&) são nativos do PostgreSQL; os outros dialetos lançam erro explícito.
OPERATORS (runtime) e o tipo Operator listam o conjunto completo. Operador
inválido pro tipo = erro de compilação.
Expressões: col, val, fn¶
Para comparar coluna com coluna, ou aplicar função SQL:
| Símbolo | Faz |
|---|---|
col<Row>("coluna") |
Referência de coluna (nome de propriedade, checado contra Row). |
val(x) |
Valor ligado — necessário dentro de fn.*, onde string significa coluna. |
fn.lower/upper/trim/length/abs/coalesce |
Funções portáveis nos 3 dialetos. |
fn.call(nome, ...args) |
Qualquer função; o nome é validado como identificador e interpolado. |
Os métodos de comparação (eq, ne, gt, gte, lt, lte, like, ilike,
ieq, in, notIn, between, isNull) devolvem uma Condition. Operando que
não é expressão vira parâmetro ligado; in/between recusam expressão.
select(Order).where(col<OrderRow>("total").gt(col<OrderRow>("paid")));
select(User).where(fn.lower("email").eq(fn.lower(val(probe))));
Detalhes em Expressões no where.
Subquery em in / notIn¶
in/notIn aceitam uma lista ou um Subquery de uma coluna:
update(Outbound).set({ status: "sending" }).where({
id: { in: select(Outbound).where({ status: "queued" }).asSubquery("id") },
});
A subquery carrega o próprio mapa de nomes e binda seus parâmetros na posição em
que aparece. O MySQL recusa LIMIT dentro dela (erro explícito na compilação).
Combinadores and / or / not¶
A forma objeto é AND implícito. Pra lógica composta, use os combinadores (em select/update/delete/join):
| Símbolo | Faz |
|---|---|
and(...args) |
(...) AND (...) |
or(...args) |
(...) OR (...) |
not(arg) |
NOT (...) |
Cada arg é a forma objeto ({ col: ... }) ou outro combinador. Passe o tipo da
linha (or<UserRow>(...)) pra key-safety dentro do combinador.
INSERT¶
insert(Model)¶
Retorna InsertBuilder.
| Método | Descrição |
|---|---|
.values(row \| rows) |
Tipado por InferInsert<typeof Model>. Aceita 1 ou N. |
.onConflictDoNothing(target, options?) |
ON CONFLICT (target) [WHERE ...] DO NOTHING. |
.onConflictDoUpdate(target, set, options?) |
Upsert, com indexWhere/updateWhere opcionais. |
.returning() |
Resultado vira a linha completa. |
.returning(columns) |
Resultado vira Pick das colunas. |
Sem returning, o resultado da execução é number (linhas afetadas).
OnConflictOptions = { where? } (o predicado de um índice único parcial —
obrigatório no PostgreSQL para que ele case como conflict target).
OnConflictUpdateOptions = { indexWhere?, updateWhere? }. Ambos aceitam a mesma
linguagem de condição do where. O MySQL lança erro para qualquer predicado.
Valores de .values() e .set() aceitam o valor da coluna ou uma expressão
sql.*; qualquer outro objeto levanta ValidationError na montagem da query.
UPDATE¶
update(Model)¶
Retorna UpdateBuilder<Full, false> (não-guarded).
| Método | Descrição |
|---|---|
.set(values) |
WritePatch<Full> — só as colunas informadas mudam; aceita expressão sql.*. |
.where(input) |
Filtra e marca Guarded = true. |
.unguarded() |
Opt-in explícito pra atualizar todas as linhas (Guarded = true). |
.returning() / .returning(cols) |
Como no insert. |
DELETE¶
del(Model)¶
Retorna DeleteBuilder<Full, false> (del porque delete é reservado).
| Método | Descrição |
|---|---|
.where(input) |
Filtra e marca Guarded = true. |
.unguarded() |
Opt-in explícito pra deletar todas as linhas. |
.returning() / .returning(cols) |
Como no insert. |
Tipos da AST¶
Expostos pra ferramentas e dialetos: SelectNode, InsertNode, UpdateNode,
DeleteNode, OrderTerm, SortDirection, WhereInput, Returning, LockClause,
LockOptions, OnConflict, OnConflictOptions, OnConflictUpdateOptions,
WriteValues, WritePatch, NameMap, SqlExpression, ExprNode, Expression,
Subquery.
URL do banco¶
parseDatabaseUrl(url)¶
Analisa uma string de conexão e identifica o dialeto, igual ao make_url do
SQLAlchemy. Aceita (e ignora) sufixo de driver async (+asyncpg, +aiosqlite).
import { parseDatabaseUrl, detectDialect } from "tempest-db-js";
parseDatabaseUrl("postgresql://app:secret@localhost:5432/mydb");
// { dialect: "postgresql", host: "localhost", port: 5432, user: "app",
// password: "secret", database: "mydb", driver: null, options: {}, raw: "..." }
parseDatabaseUrl("sqlite:///app.db"); // { dialect: "sqlite", database: "app.db", ... }
detectDialect("sqlite://:memory:"); // "sqlite"
| Símbolo | Descrição |
|---|---|
parseDatabaseUrl(url) |
ParsedDatabaseUrl (dialeto + partes de conexão). |
detectDialect(url) |
Só o Dialect ("sqlite" \| "postgresql"). |
ParsedDatabaseUrl |
Tipo do resultado. |
InvalidDatabaseUrl |
Erro lançado em URL sem scheme ou dialeto desconhecido. |
Serialização¶
Converte entre linha (valores nativos), dict e JSON, com coerção por tipo de
coluna — à la model_dump / model_validate do Pydantic.
import { toDict, toJSON, stringify, fromDict, parse } from "tempest-db-js";
toJSON(User, row); // { ...JSON-safe: Date→ISO, bigint→string, blob→base64 }
toDict(User, row); // { ...nativos, só colunas conhecidas }
stringify(User, row); // string JSON
fromDict(User, payload); // linha validada (coage string→Date/bigint/Uint8Array; JSON.parse)
parse(User, jsonString); // fromDict(JSON.parse(...))
| Função | Faz |
|---|---|
toDict(Model, row) |
Dict de valores nativos, restrito às colunas. |
toJSON(Model, row) |
Objeto JSON-safe (Date→ISO, bigint→string, Uint8Array→base64). |
stringify(Model, row) |
JSON.stringify(toJSON(...)). |
fromDict(Model, data) |
Linha validada a partir de um dict; coage tipos; valida obrigatórios. |
parse(Model, json) |
fromDict(Model, JSON.parse(json)). |
ValidationError |
Lançado quando uma coluna obrigatória falta. |
Compilação SQL (dialetos)¶
A AST de um builder vira SQL parametrizado via um dialeto — o único lugar onde
SQL nasce. Sempre placeholders (? no SQLite, $1 no Postgres), nunca interpolação
(injection-safe por construção). O compile só monta a SQL; quem roda é a sessão
(veja Execução abaixo).
import { getDialect, select, Model, column } from "tempest-db-js";
const sqlite = getDialect("sqlite");
const compiled = sqlite.compile(
select(User).where({ age: { gte: 18 } }).orderBy("name").limit(10).node,
);
// { sql: 'SELECT * FROM "users" WHERE "age" >= ? ORDER BY "name" ASC LIMIT ?',
// params: [18, 10] }
| Símbolo | Descrição |
|---|---|
getDialect("sqlite" \| "postgresql") |
Instância de dialeto. |
BaseDialect.compile(node) |
CompiledQuery ({ sql, params }). |
SqliteDialect / PostgresDialect |
Implementações concretas. |
CompiledQuery |
{ sql: string; params: readonly unknown[] }. |
QueryNode |
União das ASTs compiláveis. |
Diferenças por dialeto: placeholder (? vs $1), ilike (nativo ILIKE no
Postgres; LIKE no SQLite, case-insensitive em ASCII), lock de linha
(FOR UPDATE no Postgres/MySQL, erro no SQLite), predicado de ON CONFLICT
(Postgres/SQLite, erro no MySQL) e arrays nativos (só Postgres).
Execução (engine / sessão)¶
Banco identificado pela URL; execução async por padrão, sync opcional pra SQLite.
| Símbolo | Descrição |
|---|---|
createEngine(url, opts?) |
AsyncEngine (SQLite ou PostgreSQL). |
createSyncEngine(url, opts?) |
SyncEngine (SQLite; lança em Postgres). |
engine.session() |
Abre uma Session/SyncSession. |
engine.transaction(fn) |
Bloco transacional (commit/rollback automático). |
engine.close() |
Fecha o driver. |
session.execute(builder) |
Roda e coage; retorna um Result. |
session.raw(sql, params?, opts?) |
Statement cru parametrizado; mesmo Result. { as: Model } coage as linhas. |
toAsyncDriver(driver) |
Adapta driver sync ou async à interface async (usado pelo CLI). |
session.stream(builder) |
Iteração preguiçosa (sync: Iterable; async: AsyncIterable). |
session.beginNested(fn) |
Savepoint (transação aninhada), nas duas sessions. Recuperável: falha só descarta o savepoint. |
transaction(fn, { isolation, readOnly }) |
Características do bloco. SQLite só aceita serializable; nível/readOnly em bloco aninhado lança. |
session.unitOfWork() |
Identity map + change log; get/track/add/remove e flush() numa transação. |
session.transactionDepth / session.inTransaction |
Profundidade dos blocos abertos; transaction() aninhado adere ao de fora. |
createEngine(url, { pool }) |
PoolOptions (size/idleTimeoutMs/connectTimeoutMs/prePing/recycleMs) — PostgreSQL. |
engine.explain(fn, opts?) |
Plano de todo statement do bloco. analyze executa e é recusado em escrita. |
createEngine(url, { onQueryEnd, slowQueryMs }) |
Hook depois do statement: durationMs, rowCount, error. slowQueryMs filtra por limiar. |
createEngine(url, { onNotice }) |
Notices do servidor (PG NOTICE); sem ele, nada é impresso. |
createEngine(url, { driverOptions }) |
Repasse direto ao driver, aplicado por último (vence pool/onNotice). |
createEngine(url, { driver }) |
Escolhe o driver do SQLite: "node:sqlite" (padrão) ou "better-sqlite3". Nome desconhecido lança. |
NodeSqliteDriver / BetterSqliteDriver |
Os dois drivers SQLite; .open(path, options?) abre um handle direto. |
withTimestamps(Base) / withSoftDelete(Base) / withAudit(Base, actor?) |
Mixins de modelo: createdAt/updatedAt, deletedAt, createdBy/updatedBy. |
notDeleted() / onlyDeleted() |
Fragmentos de where para o mixin de soft delete. |
outboxModel(tabela) / OutboxRepository |
Schema do outbox e o relay: publish, pending, claim, markSent, markFailed. |
TenantScopedRepository(Model, session, { column, id }) |
Injeta o tenant em toda leitura e carimba em toda escrita; outro tenant lança. |
repo.existsExcluding(filtros, chave) |
Unicidade num update, ignorando a própria linha. |
repo.bulkUpsert(linhas, opts) |
ON CONFLICT em lote, com sql.excluded() no SET. |
repo.softDelete(chave) / repo.restore(chave) |
Exigem deletedAt (mixin); lançam sem ele. |
repo.deleteBatch(chaves) |
DELETE ... IN (...); PK composta lança. |
repo.changesSince(filter) |
Delta sync: página + serverTime como marca d'água. |
repo.cursorPaginate(filter) |
Página por cursor: { items, nextCursor }, sem COUNT(*), PK como desempate. InvalidCursor em cursor inválido. |
select(...).compute({ alias: expr }) |
Projeta expressão por alias, sem agrupar — é onde a janela entra. |
over(fn, { partitionBy, orderBy, frame }) |
rowNumber/rank/denseRank/lag/lead/firstValue/lastValue e agregações. |
cte(nome, Model, corpo) / cteRecursive(nome, Model, self => corpo) |
WITH / WITH RECURSIVE; .model é usável onde um model é. |
join(...).pick(alias) |
Projeta um source do join, flat — o que um ramo de UNION/CTE exige. |
union / unionAll / intersect / except |
Combinam SELECTs; forma dos ramos checada no tipo. ORDER BY/LIMIT do builder valem para o conjunto. |
aliased(Model, alias) |
Lê o mesmo model sob outro nome (FROM t AS a) — self-join e subquery correlacionada na mesma tabela. |
exists(sub) / notExists(sub) |
EXISTS (...) correlacionado; col("tabela.coluna") referencia a query externa. |
scalar(sub) |
Subquery de uma coluna como valor; exige .asSubquery(col). |
insert(M).fromSelect(cols, query) |
INSERT ... SELECT — as linhas não passam pelo processo. |
update(M).from(Other, alias) / del(M).using(Other, alias) |
Escrita que lê outra tabela; erro explícito onde o dialeto não tem. |
caseWhen(branches, else?) / cast(expr, tipo) |
CASE WHEN ... END e CAST(x AS tipo); o tipo é portátil e mapeado por dialeto. |
sum/avg/min/max(coluna \| expressão) |
Agregação sobre expressão — é o que dá SUM(CASE WHEN ...). |
parseIntegrityError(error, Model?) |
Erro do driver → { violation, constraint, table, columns, detail }, ou null. |
contains(cols, termo, opts?) / { iContains } |
Substring case-insensitive escapada, portátil. |
fullText(cols, termo, opts?) / fullTextRank(...) |
Full-text no PostgreSQL; cai para contains nos demais. |
escapeLike(valor) |
Escapa %, _ e \\ para usar com like/ilike crus. |
auditLogModel(tabela) / enableAudit(Model, opts) |
Trilha append-only com diff; escreve na mesma transação da mudança. |
onSignal(Model, signal, handler) |
preSave/postSave/preDelete/postDelete no caminho do repository; pre* que lança veta. |
hasHandlers / clearSignals |
Se há handler (o repository evita o SELECT extra sem eles); limpeza em teste. |
backupDatabase(url, file) / restoreDatabase(url, file) |
pg_dump/pg_restore/psql, ou VACUUM INTO no SQLite. Também tempest-db backup\|restore. |
customType({ base, toDb, fromDb }) |
Tipo de coluna próprio; converte na escrita, leitura e no where. Schema usa o tipo base. |
check(condição, opts?) / index(colunas, opts?) |
tableArgs com CHECK e índice (incl. parcial e único). Drift compara índice, não CHECK. |
primaryKeysOf(Model) / primaryKeyFilter(Model, key) |
Colunas da chave primária (array — composta é mais de uma) e o filtro que identifica uma linha. |
createEngine(url, { sqlite }) |
Pragmas por conexão: foreignKeys (default true), journalMode, busyTimeoutMs, synchronous. Recusado pelo SQLite ⇒ erro. |
Terminais do Result (async retornam Promise):
| Terminal | Retorna |
|---|---|
.all() |
Row[] |
.first() |
Row \| null |
.one() |
Row (erro NoResultError se ≠ 1) |
.oneOrNull() |
Row \| null (erro se > 1) |
.scalar() |
valor da 1ª coluna \| null |
.scalars() |
valores da 1ª coluna [] |
.rowsAffected() |
number |
Drivers: SQLite via node:sqlite embutido (NodeSqliteDriver); PostgreSQL via
postgres.js (lazy). O guard de update/del é exigido por execute (tipo
Executable).
session.raw é a saída para a query que o builder ainda não expressa — passa pelo
mesmo onQuery, pelo mesmo QueryExecutionError e pela conexão reservada da
transação. Nunca interpole valores na string: eles vão em params. Veja
SQL cru em runtime.
Joins¶
| Símbolo | Descrição |
|---|---|
join(Model, alias) |
Inicia um JoinBuilder<{ [alias]: Row }>. |
.innerJoin(Model, alias, on) |
Inner join; adiciona { [alias]: Row }. |
.leftJoin(Model, alias, on) |
Left join; adiciona { [alias]: Row \| null }. |
.where(input) |
Filtra por refs alias.column tipadas. |
.orderBy(ref, dir?) / .limit(n) / .offset(n) |
Como no select. |
on mapeia refs de fontes existentes pra refs da nova tabela (igualdade):
{ "user.id": "order.userId" }. O resultado é um objeto por alias, coagido por
modelo; leftJoin produz null quando não há correspondência.
Relations¶
| Símbolo | Faz |
|---|---|
hasMany(() => Target, { localKey, foreignKey }) |
Relação 1-N. |
belongsTo(() => Target, { localKey, foreignKey }) |
Relação N-1. |
loadRelations(session, rows, spec) |
Eager-load (1 query/relação); resultado tipado. |
hasMany → Row[]; belongsTo → Row | null.
Migrações (tempest-db-js/migrations)¶
| Símbolo | Faz |
|---|---|
reflectSchema(models) / reflectTable(model) |
Modelo → Schema IR. |
diffSchema(atual, alvo) |
IR × IR → Operation[]. |
invert / invertAll |
Inverso de operações (pro down()). |
renderOperation(op, dialect) |
Operação → SQL. |
generateMigration(draft) |
Codegen de arquivo TS. |
topoOrder / heads |
Ordenação + pontas do DAG. |
MigrationRunner / Op |
Aplica/reverte; version table. |
replaySchema(migrations) |
IR "atual" sem banco. |
introspectSqlite / checkDrift |
Schema vivo + drift (SQLite). |
introspectPostgres / checkDriftPostgres |
Idem (PostgreSQL, estrutural). |
runMigrationCli(argv, config) |
CLI: upgrade/downgrade/check/revision... |