turbine-orm 0.21.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -771,9 +771,9 @@ When Turbine receives an external pool, `db.disconnect()` is a no-op: the caller
771
771
 
772
772
  ## Database engines
773
773
 
774
- Turbine is **Postgres-first** — `import { TurbineClient } from 'turbine-orm'` targets PostgreSQL, and the safety bundle above is built around it. When you need another database, the same typed API runs on **SQLite**, **MySQL 8**, and **SQL Server** through subpath exports. Multi-engine is *additive*, not a pivot: pick the engine that fits, keep the same `findMany` / `with` / `where` API.
774
+ Turbine is **Postgres-first** — `import { TurbineClient } from 'turbine-orm'` targets PostgreSQL, and the safety bundle above is built around it. When you need another database, the same typed API runs on **SQLite**, **MySQL 8**, and **SQL Server** through subpath exports — plus **PowDB**, a single-node embedded database with its own query language (PowQL). Multi-engine is *additive*, not a pivot: pick the engine that fits, keep the same `findMany` / `with` / `where` API.
775
775
 
776
- The root install stays one dependency (`pg`). Each engine's driver is its own concern: SQLite needs nothing (Node's built-in `node:sqlite`), MySQL and SQL Server use **optional peer dependencies** you install only if you use them.
776
+ Two engines run **in-process** (no server): **SQLite** (always — there is no SQLite wire protocol) and **PowDB**, which uniquely runs *both* in-process (embedded) *and* over a network client against the same data. The root install stays one dependency (`pg`). Each engine's driver is its own concern: SQLite needs nothing (Node's built-in `node:sqlite`), while MySQL, SQL Server, and PowDB use **optional peer dependencies** you install only if you use them.
777
777
 
778
778
  ```bash
779
779
  # SQLite — zero extra deps (Node >= 22.5, built-in node:sqlite)
@@ -784,6 +784,10 @@ npm install turbine-orm mysql2
784
784
 
785
785
  # SQL Server 2016+ — optional peer
786
786
  npm install turbine-orm mssql
787
+
788
+ # PowDB — optional peer; embedded (in-process) or networked transport
789
+ npm install turbine-orm @zvndev/powdb-embedded # in-process
790
+ npm install turbine-orm @zvndev/powdb-client # networked
787
791
  ```
788
792
 
789
793
  Each engine ships a factory that returns the same `TurbineClient`:
@@ -813,6 +817,17 @@ import { SCHEMA } from './generated/turbine/metadata.js';
813
817
  const db = await turbineMssql('mssql://sa:Passw0rd!@localhost:1433/app', SCHEMA);
814
818
  ```
815
819
 
820
+ ```ts
821
+ // PowDB — async; embedded (in-process) or networked. Schema is code-defined (no introspection).
822
+ import { turbinePowDB } from 'turbine-orm/powdb';
823
+ import { schema } from './schema.js'; // defineSchema({...})
824
+
825
+ // Embedded, in-process — syncMode 'normal' makes writes beat SQLite:
826
+ const db = await turbinePowDB({ embedded: './data', syncMode: 'normal' }, schema);
827
+ // …or networked against a running powdb-server:
828
+ // const db = await turbinePowDB('powdb://127.0.0.1:7070', schema);
829
+ ```
830
+
816
831
  ### Capability matrix
817
832
 
818
833
  Everything is honest about what ports and what doesn't. Features marked **PG-only** throw a typed `UnsupportedFeatureError` (`TURBINE_E017`) on other engines rather than silently degrading.
@@ -831,6 +846,8 @@ Everything is honest about what ports and what doesn't. Features marked **PG-onl
831
846
 
832
847
  **Engine notes:** SQLite uses `RETURNING` (≥ 3.35) just like Postgres. MySQL has no `RETURNING`, so writes re-`SELECT` the affected row and **`createMany` returns `[]`** (the rows ARE inserted — re-query if you need them). SQL Server returns rows via `OUTPUT`/`MERGE`; `DISTINCT ON` is Postgres-only. Only Postgres streams via a true cursor (constant memory); the other engines' `findManyStream` materializes the result then yields it in batches. Optimistic locking throws `OptimisticLockError` on all engines (on MySQL the conflict is detected from the version-checked UPDATE's affected-row count). The `turbine` CLI (`generate`, `migrate`) is currently PostgreSQL-only — point the engine factories at a hand-written or programmatically introspected `SCHEMA`.
833
848
 
849
+ **PowDB** speaks its own non-SQL query language (PowQL), so it sits outside the SQL matrix above. It uses `reselect` writes with client-assigned UUID PKs, N+1 relation loaders (no `json_agg`), single-writer transactions (no nesting), and `defineSchema` (no introspection). Embedded `syncMode: 'normal'` writes beat SQLite; the networked transport runs the same data over a socket. many-to-many, nested writes, composite keys, cursor streaming, and the Postgres-only trio throw `UnsupportedFeatureError`. Full details: **[turbineorm.dev/engines#powdb](https://turbineorm.dev/engines#powdb)**.
850
+
834
851
  Full setup, signatures, and the complete support matrix: **[turbineorm.dev/engines](https://turbineorm.dev/engines)**.
835
852
 
836
853
  ## Configuration
@@ -120,7 +120,9 @@ class TransactionClient {
120
120
  // We use a proxy pool that routes queries through the transaction client
121
121
  const txPool = this.createTxPool();
122
122
  const txOpts = { ...this.queryOptions, _txScoped: true };
123
- qi = new index_js_1.QueryInterface(txPool, name, this.schema, this.middlewares, txOpts);
123
+ qi = txOpts.queryInterfaceFactory
124
+ ? txOpts.queryInterfaceFactory(txPool, name, this.schema, this.middlewares, txOpts)
125
+ : new index_js_1.QueryInterface(txPool, name, this.schema, this.middlewares, txOpts);
124
126
  this.tableCache.set(name, qi);
125
127
  }
126
128
  return qi;
@@ -262,6 +264,11 @@ class TurbineClient {
262
264
  preparedStatements: envDisablePrepared ? false : (config.preparedStatements ?? !config.pool),
263
265
  sqlCache: config.sqlCache ?? true,
264
266
  dialect: config.dialect,
267
+ // Non-SQL backends (PowDB) inject a factory that builds their own query
268
+ // interface (PowqlInterface) instead of the SQL QueryInterface. SQL engines
269
+ // never set this, so `table()` keeps constructing `new QueryInterface`.
270
+ queryInterfaceFactory: config
271
+ .queryInterfaceFactory,
265
272
  _onQuery: (event) => {
266
273
  if (this.queryListeners.size === 0)
267
274
  return;
@@ -413,7 +420,9 @@ class TurbineClient {
413
420
  table(name) {
414
421
  let qi = this.tableCache.get(name);
415
422
  if (!qi) {
416
- qi = new index_js_1.QueryInterface(this.pool, name, this.schema, this.middlewares, this.queryOptions);
423
+ qi = this.queryOptions?.queryInterfaceFactory
424
+ ? this.queryOptions.queryInterfaceFactory(this.pool, name, this.schema, this.middlewares, this.queryOptions)
425
+ : new index_js_1.QueryInterface(this.pool, name, this.schema, this.middlewares, this.queryOptions);
417
426
  this.tableCache.set(name, qi);
418
427
  }
419
428
  return qi;
@@ -508,6 +508,10 @@ function serializeColumn(col) {
508
508
  `arrayType: '${escSQ(col.arrayType ?? col.pgArrayType)}'`,
509
509
  `pgArrayType: '${escSQ(col.pgArrayType)}'`,
510
510
  ];
511
+ // Emit isGenerated only when set (server-generated serial/identity), so the
512
+ // output stays byte-identical for the common client-default columns.
513
+ if (col.isGenerated)
514
+ parts.push(`isGenerated: true`);
511
515
  if (col.maxLength !== undefined)
512
516
  parts.push(`maxLength: ${col.maxLength}`);
513
517
  return `{ ${parts.join(', ')} }`;
@@ -35,6 +35,7 @@ const SQL_COLUMNS = `
35
35
  data_type,
36
36
  is_nullable,
37
37
  column_default,
38
+ is_identity,
38
39
  ordinal_position,
39
40
  character_maximum_length
40
41
  FROM information_schema.columns
@@ -170,6 +171,11 @@ async function introspectPostgresCatalog(options) {
170
171
  (0, schema_js_1.pgTypeToTs)(isArray ? dialectType : baseType, isNullable),
171
172
  nullable: isNullable,
172
173
  hasDefault: row.column_default !== null,
174
+ // Server-generated = a sequence default (serial/BIGSERIAL → nextval(…))
175
+ // or an IDENTITY column. Distinct from a client-side default expression
176
+ // (gen_random_uuid(), now()), which Turbine must still synthesize.
177
+ isGenerated: (typeof row.column_default === 'string' && row.column_default.includes('nextval(')) ||
178
+ row.is_identity === 'YES',
173
179
  isArray,
174
180
  arrayType,
175
181
  pgArrayType: arrayType,