Skip to content

A interface é projetada para ser simples e performática, usando template literals marcadas para consultas e oferecendo recursos como pooling de conexões, transações e prepared statements.

ts
import { sql, SQL } from "bun";

// PostgreSQL (padrão)
const users = await sql`
  SELECT * FROM users
  WHERE active = ${true}
  LIMIT ${10}
`;

// Com MySQL
const mysql = new SQL("mysql://user:pass@localhost:3306/mydb");
const mysqlResults = await mysql`
  SELECT * FROM users 
  WHERE active = ${true}
`;

// Com SQLite
const sqlite = new SQL("sqlite://myapp.db");
const sqliteResults = await sqlite`
  SELECT * FROM users 
  WHERE active = ${1}
`;

Recursos

  • Template literals marcadas para proteger contra injeção de SQL
  • Transações
  • Parâmetros nomeados e posicionais
  • Pooling de conexões
  • Suporte a BigInt
  • Suporte a autenticação SASL (SCRAM-SHA-256), MD5 e Clear Text
  • Timeouts de conexão
  • Retorno de linhas como objetos de dados, arrays de arrays ou Buffer
  • Suporte a protocolo binário torna mais rápido
  • Suporte a TLS (e modo de autenticação)
  • Configuração automática com variáveis de ambiente

Suporte a Bancos de Dados

Bun.SQL fornece uma API unificada para múltiplos sistemas de banco de dados:

PostgreSQL

PostgreSQL é usado quando:

  • A string de conexão não corresponde aos padrões SQLite ou MySQL (é o adaptador fallback)
  • A string de conexão usa explicitamente os protocolos postgres:// ou postgresql://
  • Nenhuma string de conexão é fornecida e variáveis de ambiente apontam para PostgreSQL
db.ts
ts
import { sql } from "bun";
// Usa PostgreSQL se DATABASE_URL não estiver definida ou for uma URL PostgreSQL
await sql`SELECT ...`;

import { SQL } from "bun";
const pg = new SQL("postgres://user:pass@localhost:5432/mydb");
await pg`SELECT ...`;

MySQL

Suporte MySQL é built-in no Bun.SQL, fornecendo a mesma interface de template literal marcada com compatibilidade total para MySQL 5.7+ e MySQL 8.0+:

db.ts
ts
import { SQL } from "bun";

// Conexão MySQL
const mysql = new SQL("mysql://user:password@localhost:3306/database");
const mysql2 = new SQL("mysql2://user:password@localhost:3306/database"); // protocolo mysql2 também funciona

// Usando objeto de opções
const mysql3 = new SQL({
  adapter: "mysql",
  hostname: "localhost",
  port: 3306,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",
});

// Funciona com parâmetros - usa automaticamente prepared statements
const users = await mysql`SELECT * FROM users WHERE id = ${userId}`;

// Transações funcionam igual ao PostgreSQL
await mysql.begin(async tx => {
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
  await tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = ${userId}`;
});

// Inserts em massa
const newUsers = [
  { name: "Alice", email: "alice@example.com" },
  { name: "Bob", email: "bob@example.com" },
];
await mysql`INSERT INTO users ${mysql(newUsers)}`;

Formatos de String de Conexão MySQL">

MySQL aceita vários formatos de URL para strings de conexão:

ts
// Protocolo mysql:// padrão
new SQL("mysql://user:pass@localhost:3306/database");
new SQL("mysql://user:pass@localhost/database"); // Porta padrão 3306

// Protocolo mysql2:// (compatibilidade com pacote npm mysql2)
new SQL("mysql2://user:pass@localhost:3306/database");

// Com parâmetros de query
new SQL("mysql://user:pass@localhost/db?ssl=true");

// Conexão via socket Unix
new SQL("mysql://user:pass@/database?socket=/var/run/mysqld/mysqld.sock");

Recursos Específicos do MySQL">

Bancos de dados MySQL suportam:

  • Prepared statements: Automaticamente criados para consultas parametrizadas com cache de statements
  • Protocolo binário: Para melhor performance com prepared statements e manipulação precisa de tipos
  • Múltiplos result sets: Suporte para stored procedures retornando múltiplos result sets
  • Plugins de autenticação: Suporte para mysql_native_password, caching_sha2_password (padrão MySQL 8.0), e sha256_password
  • Conexões SSL/TLS: Modos SSL configuráveis similares ao PostgreSQL
  • Atributos de conexão: Informações do cliente enviadas ao servidor para monitoramento
  • Pipelining de queries: Executar múltiplos prepared statements sem esperar respostas

SQLite

Suporte SQLite é built-in no Bun.SQL, fornecendo a mesma interface de template literal marcada:

ts
import { SQL } from "bun";

// Banco de dados em memória
const memory = new SQL(":memory:");
const memory2 = new SQL("sqlite://:memory:");

// Banco de dados baseado em arquivo
const sql1 = new SQL("sqlite://myapp.db");

// Usando objeto de opções
const sql2 = new SQL({
  adapter: "sqlite",
  filename: "./data/app.db",
});

// Para nomes de arquivo simples, especifique adapter explicitamente
const sql3 = new SQL("myapp.db", { adapter: "sqlite" });

Formatos de String de Conexão SQLite">

SQLite aceita vários formatos de URL para strings de conexão:

ts
// Protocolo sqlite:// padrão
new SQL("sqlite://path/to/database.db");
new SQL("sqlite:path/to/database.db"); // Sem barras

// Protocolo file:// (também reconhecido como SQLite)
new SQL("file://path/to/database.db");
new SQL("file:path/to/database.db");

// Banco de dados especial :memory:
new SQL(":memory:");
new SQL("sqlite://:memory:");
new SQL("file://:memory:");

// Caminhos relativos e absolutos
new SQL("sqlite://./local.db"); // Relativo ao diretório atual
new SQL("sqlite://../parent/db.db"); // Diretório pai
new SQL("sqlite:///absolute/path.db"); // Caminho absoluto

// Com parâmetros de query
new SQL("sqlite://data.db?mode=ro"); // Modo apenas leitura
new SQL("sqlite://data.db?mode=rw"); // Modo leitura-escrita (sem criar)
new SQL("sqlite://data.db?mode=rwc"); // Modo leitura-escrita-criar (padrão)

NOTE

Nomes de arquivo simples sem protocolo (como `"myapp.db"`) requerem especificar explicitamente `{ adapter: "sqlite" }` para evitar ambiguidade com PostgreSQL.

Opções Específicas do SQLite">

Bancos de dados SQLite suportam opções de configuração adicionais:

ts
const sql = new SQL({
  adapter: "sqlite",
  filename: "app.db",

  // Opções específicas do SQLite
  readonly: false, // Abrir em modo apenas leitura
  create: true, // Criar banco de dados se não existir
  readwrite: true, // Abrir para leitura e escrita

  // Opções adicionais do Bun:sqlite
  strict: true, // Habilitar modo estrito
  safeIntegers: false, // Usar números JavaScript para inteiros
});

Parâmetros de query na URL são parseados para definir estas opções:

  • ?mode=roreadonly: true
  • ?mode=rwreadonly: false, create: false
  • ?mode=rwcreadonly: false, create: true (padrão)

Inserindo dados

Você pode passar valores JavaScript diretamente para a template literal SQL e o escaping será tratado para você.

ts
import { sql } from "bun";

// Insert básico com valores diretos
const [user] = await sql`
  INSERT INTO users (name, email) 
  VALUES (${name}, ${email})
  RETURNING *
`;

// Usando helper de objeto para sintaxe mais limpa
const userData = {
  name: "Alice",
  email: "alice@example.com",
};

const [newUser] = await sql`
  INSERT INTO users ${sql(userData)}
  RETURNING *
`;
// Expande para: INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')

Insert em Massa

Você também pode passar arrays de objetos para a template literal SQL e será expandido para um statement INSERT INTO ... VALUES ....

ts
const users = [
  { name: "Alice", email: "alice@example.com" },
  { name: "Bob", email: "bob@example.com" },
  { name: "Charlie", email: "charlie@example.com" },
];

await sql`INSERT INTO users ${sql(users)}`;

Escolhendo colunas para insert

Você pode usar sql(object, ...string) para escolher quais colunas inserir. Cada uma das colunas deve estar definida no objeto.

ts
const user = {
  name: "Alice",
  email: "alice@example.com",
  age: 25,
};

await sql`INSERT INTO users ${sql(user, "name", "email")}`;
// Insere apenas colunas name e email, ignorando outros campos

Resultados de Query

Por padrão, o cliente SQL do Bun retorna resultados de query como arrays de objetos, onde cada objeto representa uma linha com nomes de colunas como chaves. No entanto, há casos onde você pode querer os dados em um formato diferente. O cliente fornece dois métodos adicionais para este propósito.

Formato sql``.values()

O método sql``.values() retorna linhas como arrays de valores em vez de objetos. Cada linha se torna um array onde os valores estão na mesma ordem que as colunas na sua query.

ts
const rows = await sql`SELECT * FROM users`.values();
console.log(rows);

Isso retorna algo como:

ts
[
  ["Alice", "alice@example.com"],
  ["Bob", "bob@example.com"],
];

sql``.values() é especialmente útil se nomes de colunas duplicados são retornados nos resultados da query. Ao usar objetos (o padrão), o último nome de coluna é usado como chave no objeto, o que significa que nomes de colunas duplicados se sobrescrevem — mas ao usar sql``.values(), cada coluna está presente no array então você pode acessar os valores de colunas duplicadas por índice.

Formato sql``.raw()

O método .raw() retorna linhas como arrays de objetos Buffer. Isso pode ser útil para trabalhar com dados binários ou por razões de performance.

ts
const rows = await sql`SELECT * FROM users`.raw();
console.log(rows); // [[Buffer, Buffer], [Buffer, Buffer], [Buffer, Buffer]]

Fragmentos SQL

Uma necessidade comum em aplicações de banco de dados é a capacidade de construir queries dinamicamente baseado em condições de runtime. Bun fornece maneiras seguras de fazer isso sem risco de injeção de SQL.

Nomes de Tabelas Dinâmicas

Quando você precisa referenciar tabelas ou schemas dinamicamente, use o helper sql() para garantir escaping adequado:

ts
// Referencia tabelas com segurança de forma dinâmica
await sql`SELECT * FROM ${sql("users")}`;

// Com qualificação de schema
await sql`SELECT * FROM ${sql("public.users")}`;

Queries Condicionais

Você pode usar o helper sql() para construir queries com cláusulas condicionais. Isso permite criar queries flexíveis que se adaptam às necessidades da sua aplicação:

ts
// Cláusulas WHERE opcionais
const filterAge = true;
const minAge = 21;
const ageFilter = sql`AND age > ${minAge}`;
await sql`
  SELECT * FROM users
  WHERE active = ${true}
  ${filterAge ? ageFilter : sql``}
`;

Colunas dinâmicas em updates

Você pode usar sql(object, ...string) para escolher quais colunas atualizar. Cada uma das colunas deve estar definida no objeto. Se as colunas não forem informadas, todas as chaves serão usadas para atualizar a linha.

ts
await sql`UPDATE users SET ${sql(user, "name", "email")} WHERE id = ${user.id}`;
// usa todas as chaves do objeto para atualizar a linha
await sql`UPDATE users SET ${sql(user)} WHERE id = ${user.id}`;

Valores dinâmicos e where in

Listas de valores também podem ser criadas dinamicamente, tornando queries where in simples também. Opcionalmente você pode passar um array de objetos e informar qual chave usar para criar a lista.

ts
await sql`SELECT * FROM users WHERE id IN ${sql([1, 2, 3])}`;

const users = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" },
  { id: 3, name: "Charlie" },
];
await sql`SELECT * FROM users WHERE id IN ${sql(users, "id")}`;

Helper sql.array

O helper sql.array cria literais de array PostgreSQL a partir de arrays JavaScript:

ts
// Cria literais de array para PostgreSQL
await sql`INSERT INTO tags (items) VALUES (${sql.array(["red", "blue", "green"])})`;
// Gera: INSERT INTO tags (items) VALUES (ARRAY['red', 'blue', 'green'])

// Funciona com arrays numéricos também
await sql`SELECT * FROM products WHERE ids = ANY(${sql.array([1, 2, 3])})`;
// Gera: SELECT * FROM products WHERE ids = ANY(ARRAY[1, 2, 3])

NOTE

`sql.array` é apenas para PostgreSQL. Arrays multidimensionais e elementos NULL podem não ser suportados ainda.

sql``.simple()

O protocolo wire do PostgreSQL suporta dois tipos de queries: "simple" e "extended". Queries simple podem conter múltiplos statements mas não suportam parâmetros, enquanto queries extended (o padrão) suportam parâmetros mas permitem apenas um statement.

Para rodar múltiplos statements em uma única query, use sql``.simple():

ts
// Múltiplos statements em uma query
await sql`
  SELECT 1;
  SELECT 2;
`.simple();

Queries simple são frequentemente úteis para migrações de banco de dados e scripts de setup.

Note que queries simple não podem usar parâmetros (${value}). Se você precisa de parâmetros, deve dividir sua query em statements separados.

Queries em arquivos

Você pode usar o método sql.file para ler uma query de um arquivo e executá-la, se o arquivo incluir $1, $2, etc você pode passar parâmetros para a query. Se nenhum parâmetro for usado, pode executar múltiplos comandos por arquivo.

ts
const result = await sql.file("query.sql", [1, 2, 3]);

Queries Unsafe

Você pode usar a função sql.unsafe para executar strings SQL brutas. Use isso com cautela, pois não fará escape de input do usuário. Executar mais de um comando por query é permitido se nenhum parâmetro for usado.

ts
// Múltiplos comandos sem parâmetros
const result = await sql.unsafe(`
  SELECT ${userColumns} FROM users;
  SELECT ${accountColumns} FROM accounts;
`);

// Usando parâmetros (apenas um comando é permitido)
const result = await sql.unsafe("SELECT " + dangerous + " FROM users WHERE id = $1", [id]);

Executar e Cancelar Queries

O SQL do Bun é lazy, o que significa que só começará a executar quando awaited ou executado com .execute(). Você pode cancelar uma query que está executando atualmente chamando o método cancel() no objeto da query.

ts
const query = sql`SELECT * FROM users`.execute();
setTimeout(() => query.cancel(), 100);
await query;

Variáveis de Ambiente do Banco de Dados

Parâmetros de conexão sql podem ser configurados usando variáveis de ambiente. O cliente verifica estas variáveis em uma ordem específica de precedência e detecta automaticamente o tipo de banco de dados baseado no formato da string de conexão.

Detecção Automática de Banco de Dados

Ao usar Bun.sql() sem argumentos ou new SQL() com uma string de conexão, o adaptador é automaticamente detectado baseado no formato da URL:

Detecção Automática do MySQL

MySQL é automaticamente selecionado quando a string de conexão corresponde a estes padrões:

  • mysql://... - URLs de protocolo MySQL
  • mysql2://... - URLs de protocolo MySQL2 (alias de compatibilidade)
ts
// Todos estes usam MySQL automaticamente (sem adapter necessário)
const sql1 = new SQL("mysql://user:pass@localhost/mydb");
const sql2 = new SQL("mysql2://user:pass@localhost:3306/mydb");

// Funciona com variável de ambiente DATABASE_URL
DATABASE_URL="mysql://user:pass@localhost/mydb" bun run app.js
DATABASE_URL="mysql2://user:pass@localhost:3306/mydb" bun run app.js

Detecção Automática do SQLite

SQLite é automaticamente selecionado quando a string de conexão corresponde a estes padrões:

  • :memory: - Banco de dados em memória
  • sqlite://... - URLs de protocolo SQLite
  • sqlite:... - Protocolo SQLite sem barras
  • file://... - URLs de protocolo de arquivo
  • file:... - Protocolo de arquivo sem barras
ts
// Todos estes usam SQLite automaticamente (sem adapter necessário)
const sql1 = new SQL(":memory:");
const sql2 = new SQL("sqlite://app.db");
const sql3 = new SQL("file://./database.db");

// Funciona com variável de ambiente DATABASE_URL
DATABASE_URL=":memory:" bun run app.js
DATABASE_URL="sqlite://myapp.db" bun run app.js
DATABASE_URL="file://./data/app.db" bun run app.js

Detecção Automática do PostgreSQL

PostgreSQL é o padrão para strings de conexão que não correspondem aos padrões MySQL ou SQLite:

bash
# PostgreSQL é detectado para estes padrões
DATABASE_URL="postgres://user:pass@localhost:5432/mydb" bun run app.js
DATABASE_URL="postgresql://user:pass@localhost:5432/mydb" bun run app.js

# Ou qualquer URL que não corresponda aos padrões MySQL ou SQLite
DATABASE_URL="localhost:5432/mydb" bun run app.js

Variáveis de Ambiente do MySQL

Conexões MySQL podem ser configuradas via variáveis de ambiente:

bash
# URL de conexão primária (verificada primeiro)
MYSQL_URL="mysql://user:pass@localhost:3306/mydb"

# Alternativa: DATABASE_URL com protocolo MySQL
DATABASE_URL="mysql://user:pass@localhost:3306/mydb"
DATABASE_URL="mysql2://user:pass@localhost:3306/mydb"

Se nenhuma URL de conexão for fornecida, MySQL verifica estes parâmetros individuais:

Variável de AmbienteValor PadrãoDescrição
MYSQL_HOSTlocalhostHost do banco de dados
MYSQL_PORT3306Porta do banco de dados
MYSQL_USERrootUsuário do banco de dados
MYSQL_PASSWORD(vazio)Senha do banco de dados
MYSQL_DATABASEmysqlNome do banco de dados
MYSQL_URL(vazio)URL de conexão primária do MySQL
TLS_MYSQL_DATABASE_URL(vazio)URL de conexão SSL/TLS

Variáveis de Ambiente do PostgreSQL

As seguintes variáveis de ambiente podem ser usadas para definir a conexão PostgreSQL:

Variável de AmbienteDescrição
POSTGRES_URLURL de conexão primária para PostgreSQL
DATABASE_URLURL de conexão alternativa (auto-detect)
PGURLURL de conexão alternativa
PG_URLURL de conexão alternativa
TLS_POSTGRES_DATABASE_URLURL de conexão SSL/TLS
TLS_DATABASE_URLURL de conexão SSL/TLS alternativa

Se nenhuma URL de conexão for fornecida, o sistema verifica estes parâmetros individuais:

Variável de AmbienteVariáveis FallbackValor PadrãoDescrição
PGHOST-localhostHost do banco
PGPORT-5432Porta do banco
PGUSERNAMEPGUSER, USER, USERNAMEpostgresUsuário do banco
PGPASSWORD-(vazio)Senha do banco
PGDATABASE-usernameNome do banco

Variáveis de Ambiente do SQLite

Conexões SQLite podem ser configuradas via DATABASE_URL quando contém uma URL compatível com SQLite:

bash
# Todos estes são reconhecidos como SQLite
DATABASE_URL=":memory:"
DATABASE_URL="sqlite://./app.db"
DATABASE_URL="file:///absolute/path/to/db.sqlite"

Nota: Variáveis de ambiente específicas do PostgreSQL (POSTGRES_URL, PGHOST, etc.) são ignoradas ao usar SQLite.


Pré-conexão em Runtime

O Bun pode pré-conectar ao PostgreSQL na inicialização para melhorar performance estabelecendo conexões de banco de dados antes do seu código de aplicação rodar. Isso é útil para reduzir latência de conexão na primeira query de banco de dados.

bash
# Habilitar pré-conexão PostgreSQL
bun --sql-preconnect index.js

# Funciona com variável de ambiente DATABASE_URL
DATABASE_URL=postgres://user:pass@localhost:5432/db bun --sql-preconnect index.js

# Pode ser combinado com outras flags de runtime
bun --sql-preconnect --hot index.js

A flag --sql-preconnect estabelecerá automaticamente uma conexão PostgreSQL usando suas variáveis de ambiente configuradas na inicialização. Se a conexão falhar, não quebrará sua aplicação - o erro será tratado graciosamente.


Opções de Conexão

Você pode configurar sua conexão de banco de dados manualmente passando opções para o construtor SQL. Opções variam dependendo do adaptador de banco de dados:

Opções do MySQL

ts
import { SQL } from "bun";

const sql = new SQL({
  // Requerido para MySQL ao usar objeto de opções
  adapter: "mysql",

  // Detalhes de conexão
  hostname: "localhost",
  port: 3306,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",

  // Conexão via socket Unix (alternativa a hostname/port)
  // socket: "/var/run/mysqld/mysqld.sock",

  // Configurações de pool de conexões
  max: 20, // Máximo de conexões no pool (padrão: 10)
  idleTimeout: 30, // Fecha conexões ociosas após 30s
  maxLifetime: 0, // Tempo de vida da conexão em segundos (0 = para sempre)
  connectionTimeout: 30, // Timeout ao estabelecer novas conexões

  // Opções SSL/TLS
  ssl: "prefer", // ou "disable", "require", "verify-ca", "verify-full"
  // tls: {
  //   rejectUnauthorized: true,
  //   ca: "path/to/ca.pem",
  //   key: "path/to/key.pem",
  //   cert: "path/to/cert.pem",
  // },

  // Callbacks
  onconnect: client => {
    console.log("Conectado ao MySQL");
  },
  onclose: (client, err) => {
    if (err) {
      console.error("Erro de conexão MySQL:", err);
    } else {
      console.log("Conexão MySQL fechada");
    }
  },
});

Opções do PostgreSQL

ts
import { SQL } from "bun";

const sql = new SQL({
  // Detalhes de conexão (adapter é auto-detectado como PostgreSQL)
  url: "postgres://user:pass@localhost:5432/dbname",

  // Parâmetros de conexão alternativos
  hostname: "localhost",
  port: 5432,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",

  // Configurações de pool de conexões
  max: 20, // Máximo de conexões no pool
  idleTimeout: 30, // Fecha conexões ociosas após 30s
  maxLifetime: 0, // Tempo de vida da conexão em segundos (0 = para sempre)
  connectionTimeout: 30, // Timeout ao estabelecer novas conexões

  // Opções SSL/TLS
  tls: true,
  // tls: {
  //   rejectUnauthorized: true,
  //   requestCert: true,
  //   ca: "path/to/ca.pem",
  //   key: "path/to/key.pem",
  //   cert: "path/to/cert.pem",
  //   checkServerIdentity(hostname, cert) {
  //     ...
  //   },
  // },

  // Callbacks
  onconnect: client => {
    console.log("Conectado ao PostgreSQL");
  },
  onclose: client => {
    console.log("Conexão PostgreSQL fechada");
  },
});

Opções do SQLite

ts
import { SQL } from "bun";

const sql = new SQL({
  // Requerido para SQLite
  adapter: "sqlite",
  filename: "./data/app.db", // ou ":memory:" para banco em memória

  // Modos de acesso específicos do SQLite
  readonly: false, // Abrir em modo apenas leitura
  create: true, // Criar banco de dados se não existir
  readwrite: true, // Permitir operações de leitura e escrita

  // Manipulação de dados SQLite
  strict: true, // Habilitar modo estrito para melhor segurança de tipos
  safeIntegers: false, // Usar BigInt para inteiros excedendo range de número JS

  // Callbacks
  onconnect: client => {
    console.log("Banco de dados SQLite aberto");
  },
  onclose: client => {
    console.log("Banco de dados SQLite fechado");
  },
});

Notas de Conexão SQLite">

  • Pooling de Conexões: SQLite não usa pooling de conexões pois é um banco de dados baseado em arquivo. Cada instância SQL representa uma única conexão.
  • Transações: SQLite suporta transações aninhadas através de savepoints, similar ao PostgreSQL.
  • Acesso Concorrente: SQLite gerencia acesso concorrente através de locking de arquivo. Use modo WAL para melhor concorrência.
  • Bancos de Dados em Memória: Usar :memory: cria um banco de dados temporário que existe apenas durante o tempo de vida da conexão.

Senhas Dinâmicas

Quando clientes precisam usar esquemas de autenticação alternativos como tokens de acesso ou conexões a bancos de dados com senhas rotativas, forneça uma função síncrona ou assíncrona que resolverá o valor da senha dinâmica no momento da conexão.

ts
import { SQL } from "bun";

const sql = new SQL(url, {
  // Outra config de conexão
  ...
  // Função de senha para o usuário do banco de dados
  password: async () => await signer.getAuthToken(),
});

Recursos Específicos do SQLite

Execução de Queries

SQLite executa queries sincronamente, diferente do PostgreSQL que usa I/O assíncrono. No entanto, a API permanece consistente usando Promises:

ts
const sqlite = new SQL("sqlite://app.db");

// Funciona igual ao PostgreSQL, mas executa sincronamente por baixo
const users = await sqlite`SELECT * FROM users`;

// Parâmetros funcionam identicamente
const user = await sqlite`SELECT * FROM users WHERE id = ${userId}`;

Pragmas do SQLite

Você pode usar statements PRAGMA para configurar comportamento do SQLite:

ts
const sqlite = new SQL("sqlite://app.db");

// Habilitar chaves estrangeiras
await sqlite`PRAGMA foreign_keys = ON`;

// Definir modo de journal para WAL para melhor concorrência
await sqlite`PRAGMA journal_mode = WAL`;

// Verificar integridade
const integrity = await sqlite`PRAGMA integrity_check`;

Diferenças de Tipos de Dados

SQLite tem um sistema de tipos mais flexível que PostgreSQL:

ts
// SQLite armazena dados em 5 classes de armazenamento: NULL, INTEGER, REAL, TEXT, BLOB
const sqlite = new SQL("sqlite://app.db");

// SQLite é mais leniente com tipos
await sqlite`
  CREATE TABLE flexible (
    id INTEGER PRIMARY KEY,
    data TEXT,        -- Pode armazenar números como strings
    value NUMERIC,    -- Pode armazenar inteiros, reais ou texto
    blob BLOB         -- Dados binários
  )
`;

// Valores JavaScript são automaticamente convertidos
await sqlite`INSERT INTO flexible VALUES (${1}, ${"text"}, ${123.45}, ${Buffer.from("binary")})`;

Transações

Para iniciar uma nova transação, use sql.begin. Este método funciona tanto para PostgreSQL quanto para SQLite. Para PostgreSQL, reserva uma conexão dedicada do pool. Para SQLite, inicia uma transação na conexão única.

O comando BEGIN é enviado automaticamente, incluindo quaisquer configurações opcionais que você especificar. Se um erro ocorrer durante a transação, um ROLLBACK é acionado para garantir que o processo continue suavemente.

Transações Básicas

ts
await sql.begin(async tx => {
  // Todas as queries nesta função rodam em uma transação
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
  await tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = 1`;

  // Transação automaticamente faz commit se nenhum erro for lançado
  // Faz rollback se qualquer erro ocorrer
});

Também é possível pipeline das requests em uma transação se necessário retornando um array com queries da função callback assim:

ts
await sql.begin(async tx => {
  return [
    tx`INSERT INTO users (name) VALUES (${"Alice"})`,
    tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = 1`,
  ];
});

Savepoints

Savepoints em SQL criam checkpoints intermediários dentro de uma transação, permitindo rollbacks parciais sem afetar toda a operação. São úteis em transações complexas, permitindo recuperação de erros e mantendo resultados consistentes.

ts
await sql.begin(async tx => {
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;

  await tx.savepoint(async sp => {
    // Esta parte pode ser rolled back separadamente
    await sp`UPDATE users SET status = 'active'`;
    if (someCondition) {
      throw new Error("Rollback para savepoint");
    }
  });

  // Continua com transação mesmo se savepoint rolled back
  await tx`INSERT INTO audit_log (action) VALUES ('user_created')`;
});

Transações Distribuídas

Two-Phase Commit (2PC) é um protocolo de transação distribuída onde a Fase 1 tem o coordenador preparando nós garantindo que dados estão escritos e prontos para commit, enquanto a Fase 2 finaliza com nós fazendo commit ou rollback baseado na decisão do coordenador. Este processo garante durabilidade de dados e gerenciamento adequado de locks.

Em PostgreSQL e MySQL, transações distribuídas persistem além de sua sessão original, permitindo que usuários ou coordenadores privilegiados façam commit ou rollback delas depois. Isso suporta transações distribuídas robustas, processos de recuperação e operações administrativas.

Cada sistema de banco de dados implementa transações distribuídas de forma diferente:

PostgreSQL suporta nativamente através de transações preparadas, enquanto MySQL usa Transações XA.

Se qualquer exceção ocorrer durante a transação distribuída e não for capturada, o sistema automaticamente fará rollback de todas as mudanças. Quando tudo procede normalmente, você mantém a flexibilidade de fazer commit ou rollback da transação depois.

ts
// Inicia uma transação distribuída
await sql.beginDistributed("tx1", async tx => {
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
});

// Depois, faz commit ou rollback
await sql.commitDistributed("tx1");
// ou
await sql.rollbackDistributed("tx1");

Autenticação

Bun suporta autenticação SCRAM-SHA-256 (SASL), MD5 e Clear Text. SASL é recomendado para melhor segurança. Verifique Autenticação SASL do Postgres para mais informações.

Visão Geral de Modos SSL

PostgreSQL suporta diferentes modos SSL/TLS para controlar como conexões seguras são estabelecidas. Estes modos determinam o comportamento ao conectar e o nível de verificação de certificado realizado.

ts
const sql = new SQL({
  hostname: "localhost",
  username: "user",
  password: "password",
  ssl: "disable", // | "prefer" | "require" | "verify-ca" | "verify-full"
});
Modo SSLDescrição
disableNenhum SSL/TLS usado. Conexões falham se servidor requer SSL.
preferTenta SSL primeiro, fallback para não-SSL se SSL falhar. Modo padrão se nenhum especificado.
requireRequer SSL sem verificação de certificado. Falha se SSL não puder ser estabelecido.
verify-caVerifica se certificado do servidor é assinado por CA confiável. Falha se verificação falhar.
verify-fullModo mais seguro. Verifica certificado e correspondência de hostname. Protege contra certificados não confiáveis e ataques MITM.

Usando Com Strings de Conexão

O modo SSL também pode ser especificado em strings de conexão:

ts
// Usando modo prefer
const sql = new SQL("postgres://user:password@localhost/mydb?sslmode=prefer");

// Usando modo verify-full
const sql = new SQL("postgres://user:password@localhost/mydb?sslmode=verify-full");

Pooling de Conexões

O cliente SQL do Bun gerencia automaticamente um pool de conexões, que é um pool de conexões de banco de dados que são reutilizadas para múltiplas queries. Isso ajuda a reduzir o overhead de estabelecer e fechar conexões para cada query, e também ajuda a gerenciar o número de conexões concorrentes ao banco de dados.

ts
const sql = new SQL({
  // Configuração do pool
  max: 20, // Máximo 20 conexões concorrentes
  idleTimeout: 30, // Fecha conexões ociosas após 30s
  maxLifetime: 3600, // Tempo de vida máximo da conexão 1 hora
  connectionTimeout: 10, // Timeout de conexão 10s
});

Nenhuma conexão será feita até que uma query seja feita.

ts
const sql = Bun.SQL(); // nenhuma conexão é criada

await sql`...`; // pool é iniciado até max ser atingido (se possível), primeira conexão disponível é usada
await sql`...`; // conexão anterior é reutilizada

// duas conexões são usadas agora ao mesmo tempo
await Promise.all([
  sql`INSERT INTO users ${sql({ name: "Alice" })}`,
  sql`UPDATE users SET name = ${user.name} WHERE id = ${user.id}`,
]);

await sql.close(); // aguarda todas as queries terminarem e fecha todas as conexões do pool
await sql.close({ timeout: 5 }); // espera 5 segundos e fecha todas as conexões do pool
await sql.close({ timeout: 0 }); // fecha todas as conexões do pool imediatamente

Conexões Reservadas

Bun permite reservar uma conexão do pool e retorna um cliente que envolve a conexão única. Isso pode ser usado para rodar queries em uma conexão isolada.

ts
// Obtém conexão exclusiva do pool
const reserved = await sql.reserve();

try {
  await reserved`INSERT INTO users (name) VALUES (${"Alice"})`;
} finally {
  // Importante: Libera conexão de volta ao pool
  reserved.release();
}

// Ou usando Symbol.dispose
{
  using reserved = await sql.reserve();
  await reserved`SELECT 1`;
} // Automaticamente liberada

Prepared Statements

Por padrão, o cliente SQL do Bun automaticamente cria prepared statements nomeados para queries onde pode ser inferido que a query é estática. Isso fornece melhor performance. No entanto, você pode mudar este comportamento definindo prepare: false nas opções de conexão:

ts
const sql = new SQL({
  // ... outras opções ...
  prepare: false, // Desabilita persistência de prepared statements nomeados no servidor
});

Quando prepare: false está definido:

Queries ainda são executadas usando o protocolo "extended", mas são executadas usando prepared statements sem nome, um prepared statement sem nome dura apenas até o próximo statement Parse especificando o statement sem nome como destino ser emitido.

  • Binding de parâmetros ainda é seguro contra injeção de SQL
  • Cada query é parseada e planejada do zero pelo servidor
  • Queries não serão pipelined

Você pode querer usar prepare: false quando:

  • Usando PGBouncer em modo transaction (embora desde PGBouncer 1.21.0, prepared statements nomeados em nível de protocolo são suportados quando configurados apropriadamente)
  • Debugando planos de execução de query
  • Trabalhando com SQL dinâmico onde planos de query precisam ser regenerados frequentemente
  • Mais de um comando por query não será suportado (a menos que você use sql``.simple())

Note que desabilitar prepared statements pode impactar performance para queries que são executadas frequentemente com parâmetros diferentes, pois o servidor precisa parsear e planejar cada query do zero.


Tratamento de Erros

O cliente fornece erros tipados para diferentes cenários de falha. Erros são específicos do banco de dados e estendem de classes de erro base:

Classes de Erro

ts
import { SQL } from "bun";

try {
  await sql`SELECT * FROM users`;
} catch (error) {
  if (error instanceof SQL.PostgresError) {
    // Erro específico do PostgreSQL
    console.log(error.code); // Código de erro do PostgreSQL
    console.log(error.detail); // Mensagem de erro detalhada
    console.log(error.hint); // Dica útil do PostgreSQL
  } else if (error instanceof SQL.SQLiteError) {
    // Erro específico do SQLite
    console.log(error.code); // Código de erro do SQLite (e.g., "SQLITE_CONSTRAINT")
    console.log(error.errno); // Número de erro do SQLite
    console.log(error.byteOffset); // Offset de bytes no statement SQL (se disponível)
  } else if (error instanceof SQL.SQLError) {
    // Erro SQL genérico (classe base)
    console.log(error.message);
  }
}

Códigos de Erro Específicos do PostgreSQL">

Erros de Conexão PostgreSQL

Erros de ConexãoDescrição
ERR_POSTGRES_CONNECTION_CLOSEDConexão foi terminada ou nunca estabelecida
ERR_POSTGRES_CONNECTION_TIMEOUTFalha ao estabelecer conexão dentro do timeout
ERR_POSTGRES_IDLE_TIMEOUTConexão fechada por inatividade
ERR_POSTGRES_LIFETIME_TIMEOUTConexão excedeu tempo de vida máximo
ERR_POSTGRES_TLS_NOT_AVAILABLEConexão SSL/TLS não disponível
ERR_POSTGRES_TLS_UPGRADE_FAILEDFalha ao upgrade de conexão para SSL/TLS

Erros de Autenticação

Erros de AutenticaçãoDescrição
ERR_POSTGRES_AUTHENTICATION_FAILED_PBKDF2Autenticação de senha falhou
ERR_POSTGRES_UNKNOWN_AUTHENTICATION_METHODServidor solicitou método auth desconhecido
ERR_POSTGRES_UNSUPPORTED_AUTHENTICATION_METHODServidor solicitou método auth não suportado
ERR_POSTGRES_INVALID_SERVER_KEYChave de servidor inválida durante autenticação
ERR_POSTGRES_INVALID_SERVER_SIGNATUREAssinatura de servidor inválida
ERR_POSTGRES_SASL_SIGNATURE_INVALID_BASE64Codificação de assinatura SASL inválida
ERR_POSTGRES_SASL_SIGNATURE_MISMATCHVerificação de assinatura SASL falhou

Erros de Query

Erros de QueryDescrição
ERR_POSTGRES_SYNTAX_ERRORSintaxe SQL inválida (estende SyntaxError)
ERR_POSTGRES_SERVER_ERRORErro geral do servidor PostgreSQL
ERR_POSTGRES_INVALID_QUERY_BINDINGBinding de parâmetro inválido
ERR_POSTGRES_QUERY_CANCELLEDQuery foi cancelada
ERR_POSTGRES_NOT_TAGGED_CALLQuery foi chamada sem tagged call

Erros de Tipo de Dado

Erros de Tipo de DadoDescrição
ERR_POSTGRES_INVALID_BINARY_DATAFormato de dados binários inválido
ERR_POSTGRES_INVALID_BYTE_SEQUENCESequência de bytes inválida
ERR_POSTGRES_INVALID_BYTE_SEQUENCE_FOR_ENCODINGErro de encoding
ERR_POSTGRES_INVALID_CHARACTERCaractere inválido nos dados
ERR_POSTGRES_OVERFLOWOverflow numérico
ERR_POSTGRES_UNSUPPORTED_BYTEA_FORMATFormato binário não suportado
ERR_POSTGRES_UNSUPPORTED_INTEGER_SIZETamanho de inteiro não suportado
ERR_POSTGRES_MULTIDIMENSIONAL_ARRAY_NOT_SUPPORTED_YETArrays multidimensionais não suportados
ERR_POSTGRES_NULLS_IN_ARRAY_NOT_SUPPORTED_YETValores NULL em arrays não suportados

Erros de Protocolo

Erros de ProtocoloDescrição
ERR_POSTGRES_EXPECTED_REQUESTEsperado request do cliente
ERR_POSTGRES_EXPECTED_STATEMENTEsperado prepared statement
ERR_POSTGRES_INVALID_BACKEND_KEY_DATADados de chave de backend inválidos
ERR_POSTGRES_INVALID_MESSAGEMensagem de protocolo inválida
ERR_POSTGRES_INVALID_MESSAGE_LENGTHComprimento de mensagem inválido
ERR_POSTGRES_UNEXPECTED_MESSAGETipo de mensagem inesperado

Erros de Transação

Erros de TransaçãoDescrição
ERR_POSTGRES_UNSAFE_TRANSACTIONOperação de transação insegura detectada
ERR_POSTGRES_INVALID_TRANSACTION_STATEEstado de transação inválido

Erros Específicos do SQLite

Erros do SQLite fornecem códigos e números de erro que correspondem aos códigos de erro padrão do SQLite:

Códigos de Erro Comuns do SQLite">

Código de ErroerrnoDescrição
SQLITE_CONSTRAINT19Violação de constraint (UNIQUE, CHECK, NOT NULL, etc.)
SQLITE_BUSY5Banco de dados está travado
SQLITE_LOCKED6Tabela no banco de dados está travada
SQLITE_READONLY8Tentativa de escrita em banco apenas leitura
SQLITE_IOERR10Erro de I/O de disco
SQLITE_CORRUPT11Imagem de disco do banco está malformada
SQLITE_FULL13Banco de dados ou disco está cheio
SQLITE_CANTOPEN14Incapaz de abrir arquivo de banco de dados
SQLITE_PROTOCOL15Erro de protocolo de lock do banco de dados
SQLITE_SCHEMA17Schema do banco de dados mudou
SQLITE_TOOBIG18String ou BLOB excede limite de tamanho
SQLITE_MISMATCH20Incompatibilidade de tipo de dado
SQLITE_MISUSE21Biblioteca usada incorretamente
SQLITE_AUTH23Autorização negada

Exemplo de tratamento de erro:

ts
const sqlite = new SQL("sqlite://app.db");

try {
  await sqlite`INSERT INTO users (id, name) VALUES (1, 'Alice')`;
  await sqlite`INSERT INTO users (id, name) VALUES (1, 'Bob')`; // ID duplicado
} catch (error) {
  if (error instanceof SQL.SQLiteError) {
    if (error.code === "SQLITE_CONSTRAINT") {
      console.log("Violação de constraint:", error.message);
      // Lida com violação de constraint unique
    }
  }
}

Números e BigInt

O cliente SQL do Bun inclui manipulação especial para números grandes que excedem o range de um inteiro de 53-bit. Veja como funciona:

ts
import { sql } from "bun";

const [{ x, y }] = await sql`SELECT 9223372036854777 as x, 12345 as y`;

console.log(typeof x, x); // "string" "9223372036854777"
console.log(typeof y, y); // "number" 12345

BigInt em vez de Strings

Se você precisa de números grandes como BigInt em vez de strings, pode habilitar isso definindo a opção bigint como true ao inicializar o cliente SQL:

ts
const sql = new SQL({
  bigint: true,
});

const [{ x }] = await sql`SELECT 9223372036854777 as x`;

console.log(typeof x, x); // "bigint" 9223372036854777n

Roadmap

Ainda há algumas coisas que não terminamos.

  • Pré-carregamento de conexão via flag --db-preconnect do Bun CLI
  • Transforms de nome de coluna (e.g. snake_case para camelCase). Isso está majoritariamente bloqueado em uma implementação unicode-aware de mudança de case em C++ usando WTF::String do WebKit.
  • Transforms de tipo de coluna

Recursos Específicos do Banco de Dados

Métodos de Autenticação

MySQL suporta múltiplos plugins de autenticação que são automaticamente negociados:

  • mysql_native_password - Autenticação MySQL tradicional, amplamente compatível
  • caching_sha2_password - Padrão no MySQL 8.0+, mais seguro com troca de chave RSA
  • sha256_password - Autenticação baseada em SHA-256

O cliente automaticamente lida com troca de plugin de autenticação quando solicitado pelo servidor, incluindo troca segura de senha sobre conexões não-SSL.

Prepared Statements & Performance

MySQL usa prepared statements do lado do servidor para todas as queries parametrizadas:

ts
// Isto automaticamente cria um prepared statement no servidor
const user = await mysql`SELECT * FROM users WHERE id = ${userId}`;

// Prepared statements são cacheados e reutilizados para queries idênticas
for (const id of userIds) {
  // Mesmo prepared statement é reutilizado
  await mysql`SELECT * FROM users WHERE id = ${id}`;
}

// Query pipelining - múltiplos statements enviados sem esperar
const [users, orders, products] = await Promise.all([
  mysql`SELECT * FROM users WHERE active = ${true}`,
  mysql`SELECT * FROM orders WHERE status = ${"pending"}`,
  mysql`SELECT * FROM products WHERE in_stock = ${true}`,
]);

Múltiplos Result Sets

MySQL pode retornar múltiplos result sets de queries multi-statement:

ts
const mysql = new SQL("mysql://user:pass@localhost/mydb");

// Queries multi-statement com método simple()
const multiResults = await mysql`
  SELECT * FROM users WHERE id = 1;
  SELECT * FROM orders WHERE user_id = 1;
`.simple();

Character Sets & Collations

Bun.SQL automaticamente usa character set utf8mb4 para conexões MySQL, garantindo suporte Unicode completo incluindo emojis. Este é o character set recomendado para aplicações MySQL modernas.

Atributos de Conexão

Bun automaticamente envia informações do cliente para MySQL para melhor monitoramento:

ts
// Estes atributos são enviados automaticamente:
// _client_name: "Bun"
// _client_version: <versão do bun>
// Você pode ver estes no performance_schema.session_connect_attrs do MySQL

Manipulação de Tipos

Tipos do MySQL são automaticamente convertidos para tipos JavaScript:

Tipo MySQLTipo JavaScriptNotas
INT, TINYINT, MEDIUMINTnumberDentro do range seguro de inteiros
BIGINTstring, number ou BigIntSe o valor cabe em i32/u32 será number caso contrário string ou BigInt Baseado na opção bigint
DECIMAL, NUMERICstringPara preservar precisão
FLOAT, DOUBLEnumber
DATEDateObjeto Date JavaScript
DATETIME, TIMESTAMPDateCom manipulação de timezone
TIMEnumberTotal de microssegundos
YEARnumber
CHAR, VARCHAR, VARSTRING, STRINGstring
TINY TEXT, MEDIUM TEXT, TEXT, LONG TEXTstring
TINY BLOB, MEDIUM BLOB, BLOG, LONG BLOBstringTipos BLOB são alias para tipos TEXT
JSONobject/arrayAutomaticamente parseado
BIT(1)booleanBIT(1) no MySQL
GEOMETRYstringDados de geometria

Diferenças do PostgreSQL

Embora a API seja unificada, há algumas diferenças comportamentais:

  1. Placeholders de parâmetro: MySQL usa ? internamente mas Bun converte estilo $1, $2 automaticamente
  2. Cláusula RETURNING: MySQL não suporta RETURNING; use result.lastInsertRowid ou SELECT separado
  3. Tipos de array: MySQL não tem tipos de array nativos como PostgreSQL

Recursos Específicos do MySQL

Ainda não implementamos suporte a LOAD DATA INFILE

Recursos Específicos do PostgreSQL

Ainda não implementamos estes:

  • Suporte a COPY
  • Suporte a LISTEN
  • Suporte a NOTIFY

Também não implementamos alguns dos recursos mais incomuns como:

  • Autenticação GSSAPI
  • Suporte a SCRAM-SHA-256-PLUS
  • Tipos Point & PostGIS
  • Todos os tipos de array de inteiros multidimensionais (apenas alguns tipos são suportados)

Padrões Comuns & Melhores Práticas

Trabalhando com Result Sets do MySQL

ts
// Obtendo ID de insert após INSERT
const result = await mysql`INSERT INTO users (name) VALUES (${"Alice"})`;
console.log(result.lastInsertRowid); // LAST_INSERT_ID() do MySQL

// Lidando com linhas afetadas
const updated = await mysql`UPDATE users SET active = ${false} WHERE age < ${18}`;
console.log(updated.affectedRows); // Número de linhas atualizadas

// Usando funções específicas do MySQL
const now = await mysql`SELECT NOW() as current_time`;
const uuid = await mysql`SELECT UUID() as id`;

Tratamento de Erros do MySQL

ts
try {
  await mysql`INSERT INTO users (email) VALUES (${"duplicate@email.com"})`;
} catch (error) {
  if (error.code === "ER_DUP_ENTRY") {
    console.log("Entrada duplicada detectada");
  } else if (error.code === "ER_ACCESS_DENIED_ERROR") {
    console.log("Acesso negado");
  } else if (error.code === "ER_BAD_DB_ERROR") {
    console.log("Banco de dados não existe");
  }
  // Códigos de erro do MySQL são compatíveis com pacotes mysql/mysql2
}

Dicas de Performance para MySQL

  1. Use pooling de conexões: Defina tamanho de pool max apropriado baseado na sua carga de trabalho
  2. Habilite prepared statements: Eles são habilitados por padrão e melhoram performance
  3. Use transações para operações em massa: Agrupe queries relacionadas em transações
  4. Indexe apropriadamente: MySQL depende fortemente de índices para performance de query
  5. Use charset utf8mb4: É definido por padrão e manipula todos os caracteres Unicode

Perguntas Frequentes

Por que isso é `Bun.sql` e não `Bun.postgres`?
	O plano era adicionar mais drivers de banco de dados no futuro. Agora com suporte MySQL adicionado, esta API unificada suporta PostgreSQL, MySQL e SQLite.
Como sei qual adapter de banco de dados está sendo usado?
	O adapter é automaticamente detectado da string de conexão:

	- URLs começando com `mysql://` ou `mysql2://` usam MySQL
	- URLs correspondendo padrões SQLite (`:memory:`, `sqlite://`, `file://`) usam SQLite
	- Todo resto padrão é PostgreSQL

Stored procedures do MySQL são suportadas?">
	Sim, stored procedures são totalmente suportadas incluindo parâmetros OUT e múltiplos result sets:

	```ts
	// Chama stored procedure
	const results = await mysql`CALL GetUserStats(${userId}, @total_orders)`;

	// Obtém parâmetro OUT
	const outParam = await mysql`SELECT @total_orders as total`;
	```

Posso usar sintaxe SQL específica do MySQL?">
	Sim, você pode usar qualquer sintaxe específica do MySQL:

	```ts
	// Sintaxe específica do MySQL funciona bem
	await mysql`SET @user_id = ${userId}`;
	await mysql`SHOW TABLES`;
	await mysql`DESCRIBE users`;
	await mysql`EXPLAIN SELECT * FROM users WHERE id = ${id}`;
	```

Por que não apenas usar uma biblioteca existente?

Pacotes npm como postgres.js, pg e node-postgres podem ser usados no Bun também. São ótimas opções.

Duas razões:

  1. Achamos que é mais simples para desenvolvedores ter um driver de banco de dados built-in no Bun. O tempo que você gasta library shopping é tempo que poderia estar construindo seu app.
  2. Aproveitamos alguns internals do engine JavaScriptCore para torná-lo mais rápido para criar objetos que seriam difíceis de implementar em uma biblioteca

Créditos

Grandes agradecimentos a @porsager pelo postgres.js pela inspiração para a interface da API.

Bun by www.bunjs.com.cn edit