The turbine-orm/powdb module
turbine-orm/powdb runs the Turbine API on PowDB, a single-node database with its own query language, PowQL. Turbine generates PowQL instead of SQL, so findMany, with, where and create read the same as on PostgreSQL.
Install#
Install the driver for the transport you use. Both are optional peer dependencies.
npm install turbine-orm @zvndev/powdb-embedded # in-process, no server
npm install turbine-orm @zvndev/powdb-client # a running powdb-serverThe embedded addon ships binaries for macOS on Apple silicon and Linux (glibc) on x64 and arm64. On other platforms, use the networked client.
Connect#
import { schemaDefToMetadata } from 'turbine-orm';
import { powqlSchemaDDL, turbinePowDB } from 'turbine-orm/powdb';
import { schema } from './schema.js'; // defineSchema({ ... })
const SCHEMA = schemaDefToMetadata(schema);
// Embedded: opens a data directory in-process
const db = await turbinePowDB({ embedded: './data' }, SCHEMA);
// Networked: a running powdb-server
const remote = await turbinePowDB('powdb://127.0.0.1:7070', SCHEMA);
// Create the tables
for (const statement of powqlSchemaDDL(SCHEMA)) await db.rawUnsafe(statement);
const user = await db.table('users').findUnique({ where: { id: 1 }, with: { posts: true } });turbinePowDB(target, schema, options?) resolves to a TurbineClient. target is one of:
| Target | Transport |
|---|---|
{ embedded: dir, syncMode?, memoryLimit?, readonly? } | Embedded |
'powdb://host:port' | Networked |
{ host, port, ... } (PowdbConnOptions) | Networked |
A @zvndev/powdb-client Pool, or a PowdbPool | Networked, your pool |
The networked transport needs PowDB 0.7.0 or later and checks the server version on connect. Pass a data directory for embedded, not an open Database handle. disconnect() closes what the factory opened and leaves a pool you passed in open.
Options#
options accepts every client option except the connection settings and replicas, plus:
| Option | Default | What it does |
|---|---|---|
relationLoadStrategy | one statement on 0.18+ | 'batched' runs one lookup per relation. Below 0.18, 'join' uses server-side joins for eligible relations. |
connectionLimit | 10 | Pool size for a networked pool the factory builds. |
transactionQueueTimeoutMs | 30000 | How long a $transaction waits for the write lock before TimeoutError (TURBINE_E002). 0 waits forever. |
retryStaleReads | false | Networked only. Retry a first-statement read once when an idle socket has gone stale. Writes are never retried. |
readonly | false | Refuse writes locally with ReadOnlyError (TURBINE_E018). |
assumeEngineVersion | probed | Engine version for feature gating when it cannot be probed. |
transactionQueueTimeoutMs, retryStaleReads and readonly are ignored when you pass a PowdbPool: set them on its constructor.
The embedded target takes three more fields:
| Field | Default | What it does |
|---|---|---|
syncMode | 'full' | 'full' fsyncs every commit. 'normal' fsyncs off the commit path: faster writes, but an OS crash or power loss can lose the last interval. 'off' is for test data. |
memoryLimit | none | Per-query memory budget in bytes. |
readonly | false | Open read-only for snapshot serving. Cannot be combined with syncMode. |
Schema#
PowDB schemas are code-first: defineSchema, then schemaDefToMetadata. powqlSchemaDDL(SCHEMA) returns the PowQL statements that create the tables.
- Columns map to PowQL
str,int,float,boolandjson. ADateis stored as integer microseconds. - A generated integer primary key uses PowDB's
autoid. A defaulted string primary key gets a client-side UUID. - An enum column stores its label. A value that is not a label throws
ValidationError, andgt/ltcompare in declaration order. Ordering by an enum throwsTURBINE_E017. - Array and binary columns throw
ValidationErrorwhen the DDL is built. A composite index or composite unique key, other than the primary key, throwsTURBINE_E017.
To bootstrap from an existing database, introspectPowdbDatabase reads the live catalog:
import { capabilitiesFromVersion, introspectPowdbDatabase } from 'turbine-orm/powdb';
const meta = await introspectPowdbDatabase(
async (powql) => ({ rows: await db.rawUnsafe(powql) }),
{ capabilities: capabilitiesFromVersion('0.20.0') }, // your engine's version
);The primary key is a guess. Relations come only from declared entity links, and only when you pass capabilities for PowDB 0.19.1+. defineSchema remains the complete path.
What works#
findMany,findFirst,findUniqueand theOrThrowforms,count,aggregate,groupByandexplain().- Every write, including composite-key
upsert, atomic update operators andoptimisticLock. withfor every relation kind, many-to-many included.- Relation filters (
some,every,none,is,isNot) and nested writes increate/update. whereoperators with SQLNULLsemantics, includingNOT, andmode: 'insensitive'onin/notIn.- JSON path filters, ordering and grouping.
- Global filters, on relations and relation filters too.
Transactions#
PowDB has one writer and no savepoints.
- Concurrent
$transactioncalls queue and run one at a time, bounded bytransactionQueueTimeoutMs. - A nested
tx.$transactionthrowsTURBINE_E017. - Calling
db.$transactionfrom inside another transaction's callback throwsTURBINE_E017instead of deadlocking. Usetx.
Limits#
These throw UnsupportedFeatureError (TURBINE_E017):
- pgvector, LISTEN/NOTIFY, RLS
sessionContext, full-textsearch, array filters,_percentileanddistinct. findManyStream,findManyStreamBatchesand cursor pagination. Page withlimitandoffset.- Row locks (
lock),$transaction({ readOnly: true }), update expressions (expr), andcreateManywithonConflictorskipDuplicates. - JSON
contains, pathless JSONequals,containswith%or_in the value, andnulls: 'first'. - Comparisons, ordering, aggregates and arithmetic on a
numeric/decimalcolumn, which PowDB stores as text. - Composite-key relation filters, nested reads and many-to-many junctions.
col()column references in a filter, and nested writes insidecreateMany(usecreate).- A relation filter that matches more than 10,000 distinct keys.
createMany also requires every row to name the same fields, or it throws ValidationError.
Version-gated features#
Turbine checks the engine version on connect. Below a feature's minimum, the feature is refused with an error that names the version you need, rather than sent as PowQL the engine cannot parse.
| Feature | Minimum PowDB | On an older engine |
|---|---|---|
introspectPowdbDatabase | 0.10 | TURBINE_E017 |
json columns, JSON path filters, ordering and grouping | 0.12 | TURBINE_E017 |
| Doc-field indexes | 0.13 | TURBINE_E017 |
relationLoadStrategy: 'join' | 0.13 | TURBINE_E017 per query; a client default falls back to lookups |
| Read-only snapshots | 0.14 | ConnectionError |
One-statement with | 0.18 | Falls back to batched lookups, same results |
Relations from entity links in introspectPowdbDatabase | 0.19.1 | No relations |
Comparisons on a PowDB-native datetime column, per-field _count of a nullable column | 0.20 | TURBINE_E017 |
assumeEngineVersion overrides the probe. Setting it higher than the real engine replaces the clear TURBINE_E017 with a PowQL parse error.
Errors#
PowDB failures map to the typed errors: a unique violation is UniqueConstraintError, a lost or stale connection is ConnectionError (TURBINE_E004) with the original as .cause, and a timeout is TimeoutError. wrapPowdbError(err) applies the same mapping to errors from your own driver calls.
See also#
- Database Engines: the capability matrix and read-only snapshots.
- Writing a custom dialect: the SQL contract PowDB bypasses.
- Typed Errors: every error code.