Database Capabilities

The db.capabilities property exposes database feature support at runtime.

Use capabilities to write portable code that adapts to the underlying database.

#Usage

You can access capabilities directly on the database instance:

import { createDatabase } from "db0";
import sqlite from "db0/connectors/better-sqlite3";

const db = createDatabase(sqlite({}));

console.log(db.capabilities);
// { json: true, booleans: false, arrays: false, ... }

if (db.capabilities.booleans) {
  // Use a native boolean column
} else {
  // Fall back to an integer 0/1 column
}

Capabilities are derived from the connector's SQL dialect, with per-connector overrides where a specific backend differs from its dialect (see the table below).

#Available Flags

FlagDescription
jsonThe database has JSON support (a native type and/or JSON functions).
booleansThe database has a boolean column type (MySQL maps it to TINYINT(1)).
arraysThe database supports native array column types.
datesThe database supports native date/timestamp types.
uuidsThe database supports native UUID column types.
transactionsExplicit BEGIN/COMMIT through db.sql opens a transaction (see note below).

#Capabilities by Connector

Tip

See db-compat.onmax.me for a comprehensive database feature comparison.

ConnectorJSONBoolArrayDateUUIDTx
better-sqlite3
bun-sqlite
cloudflare-d1
cloudflare-hyperdrive-mysql
cloudflare-hyperdrive-postgresql
libsql-core
libsql-http
libsql-node
libsql-web
mysql2
neon
node-sqlite
pglite
planetscale
postgresql
sqlite3

Note

transactions describes whether BEGIN/COMMIT sent through db.sql actually open a transaction. It is a property of the driver's session model rather than of the SQL dialect, so several connectors report false even though their database engine supports transactions:

  • Cloudflare D1 rejects BEGIN/COMMIT outright; only implicit transactions via D1Database.batch() are available.
  • PlanetScale opens a new HTTP session per query, so consecutive statements never share a transaction. Use Client.transaction() on the underlying client.
  • libsql-http and libsql-web open and close a Hrana stream within a single request per query. Use client.transaction() on the underlying client.

libsql-node and libsql-core report true because they are commonly used against a local file, where a single connection is held open. Pointed at a remote libsql:/http: URL they behave like libsql-http, and this flag cannot detect that statically — reach for client.transaction() if you are unsure.

#Use Cases

#Conditional Feature Usage

You can adapt your queries to what the database supports:

import type { Database } from "db0";

function storeList(db: Database, items: string[]) {
  const value = db.capabilities.arrays
    ? `{${items.join(",")}}` // PostgreSQL array literal
    : JSON.stringify(items);
  return db.sql`INSERT INTO data (items) VALUES (${value})`;
}

Note

Values interpolated into db.sql must be primitives (string, number, boolean, null or undefined), so arrays and objects have to be serialized to a value the target database understands.

#Runtime Validation

You can validate that the database meets your application's requirements:

import type { Database } from "db0";

function initDatabase(db: Database) {
  if (!db.capabilities.transactions) {
    throw new Error("This application requires transaction support");
  }
}

#Feature Detection in Libraries

You can build database-agnostic libraries that adapt automatically:

import type { Database } from "db0";

export function createRepository(db: Database) {
  return {
    saveFlag(id: string, enabled: boolean) {
      const value = db.capabilities.booleans ? enabled : Number(enabled);
      return db.sql`UPDATE items SET enabled = ${value} WHERE id = ${id}`;
    },
  };
}

db0  tiny sql connector.