turbine-orm 0.11.0 → 0.12.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.
@@ -33,6 +33,28 @@ exports.postgresDialect = {
33
33
  const suffix = orderBy ? ` ${orderBy}` : '';
34
34
  return `COALESCE(json_agg(${jsonObjectExpr}${suffix}), ${this.emptyJsonArrayLiteral})`;
35
35
  },
36
+ buildReturningClause(selection = '*') {
37
+ return ` RETURNING ${selection}`;
38
+ },
39
+ buildInsertStatement(input) {
40
+ return `INSERT INTO ${input.table} (${input.columns.join(', ')}) VALUES (${input.valuePlaceholders.join(', ')})${this.buildReturningClause(input.returning)}`;
41
+ },
42
+ buildBulkInsertStatement(input) {
43
+ if (!input.columnArrayTypes || input.columnArrayTypes.length !== input.columns.length) {
44
+ throw new Error('PostgreSQL bulk insert requires one array type per column');
45
+ }
46
+ const columnArrays = input.columns.map((_, columnIndex) => input.rowValues.map((row) => row[columnIndex]));
47
+ const unnestArgs = input.columns.map((_, i) => `${this.paramPlaceholder(i + 1)}::${input.columnArrayTypes[i]}`);
48
+ let sql = `INSERT INTO ${input.table} (${input.columns.join(', ')}) SELECT * FROM UNNEST(${unnestArgs.join(', ')})`;
49
+ if (input.skipDuplicates)
50
+ sql += ' ON CONFLICT DO NOTHING';
51
+ return { sql: `${sql}${this.buildReturningClause(input.returning)}`, params: columnArrays };
52
+ },
53
+ buildUpsertStatement(input) {
54
+ return (`INSERT INTO ${input.table} (${input.insertColumns.join(', ')}) VALUES (${input.valuePlaceholders.join(', ')})` +
55
+ ` ON CONFLICT (${input.conflictColumns.join(', ')}) DO UPDATE SET ${input.updateSetClauses.join(', ')}` +
56
+ this.buildReturningClause(input.returning));
57
+ },
36
58
  buildInsensitiveLike(column, paramRef) {
37
59
  return `${column} ILIKE ${paramRef}`;
38
60
  },
@@ -736,7 +736,12 @@ class QueryInterface {
736
736
  const columns = entries.map(([k]) => this.toSqlColumn(k));
737
737
  const params = entries.map(([, v]) => v);
738
738
  const placeholders = entries.map((_, i) => `${this.p(i + 1)}`);
739
- const sql = `INSERT INTO ${this.q(this.table)} (${columns.join(', ')}) VALUES (${placeholders.join(', ')}) RETURNING *`;
739
+ const sql = this.dialect.buildInsertStatement({
740
+ table: this.q(this.table),
741
+ columns,
742
+ valuePlaceholders: placeholders,
743
+ returning: '*',
744
+ });
740
745
  return {
741
746
  sql,
742
747
  params,
@@ -776,27 +781,24 @@ class QueryInterface {
776
781
  }
777
782
  const keys = Object.keys(args.data[0]).filter((k) => args.data[0][k] !== undefined);
778
783
  const columns = keys.map((k) => this.toColumn(k));
779
- // Build column arrays for UNNEST
780
- const columnArrays = keys.map(() => []);
781
- for (const row of args.data) {
784
+ const rowValues = args.data.map((row) => {
782
785
  const record = row;
783
- keys.forEach((key, i) => {
784
- columnArrays[i].push(record[key]);
785
- });
786
- }
787
- // Use actual Postgres types for array casts
786
+ return keys.map((key) => record[key]);
787
+ });
788
+ // Use actual Postgres types for array casts in the default PostgreSQL dialect.
788
789
  const typeCasts = columns.map((col) => this.getColumnArrayType(col));
789
- const unnestArgs = columnArrays.map((_, i) => `${this.p(i + 1)}::${typeCasts[i]}`);
790
790
  const quotedColumns = columns.map((c) => this.q(c));
791
- let sql = `INSERT INTO ${qt} (${quotedColumns.join(', ')}) SELECT * FROM UNNEST(${unnestArgs.join(', ')})`;
792
- // skipDuplicates: add ON CONFLICT DO NOTHING
793
- if (args.skipDuplicates) {
794
- sql += ` ON CONFLICT DO NOTHING`;
795
- }
796
- sql += ` RETURNING *`;
791
+ const built = this.dialect.buildBulkInsertStatement({
792
+ table: qt,
793
+ columns: quotedColumns,
794
+ rowValues,
795
+ columnArrayTypes: typeCasts,
796
+ skipDuplicates: args.skipDuplicates,
797
+ returning: '*',
798
+ });
797
799
  return {
798
- sql,
799
- params: columnArrays,
800
+ sql: built.sql,
801
+ params: built.params,
800
802
  transform: (result) => result.rows.map((row) => this.parseRow(row, this.table)),
801
803
  tag: `${this.table}.createMany`,
802
804
  };
@@ -825,7 +827,7 @@ class QueryInterface {
825
827
  const whereClause = this.buildWhereClause(whereObj, freshParams);
826
828
  const whereSql = whereClause ? ` WHERE ${whereClause}` : '';
827
829
  this.assertMutationHasPredicate('update', whereSql, args.allowFullTableScan);
828
- return `UPDATE ${this.q(this.table)} SET ${setClauses.join(', ')}${whereSql} RETURNING *`;
830
+ return `UPDATE ${this.q(this.table)} SET ${setClauses.join(', ')}${whereSql}${this.dialect.buildReturningClause('*')}`;
829
831
  });
830
832
  // On cache hit, validate predicate
831
833
  if (whereFp === '') {
@@ -874,7 +876,7 @@ class QueryInterface {
874
876
  const clause = this.buildWhereClause(whereObj, freshParams);
875
877
  const whereSql = clause ? ` WHERE ${clause}` : '';
876
878
  this.assertMutationHasPredicate('delete', whereSql, args.allowFullTableScan);
877
- return `DELETE FROM ${this.q(this.table)}${whereSql} RETURNING *`;
879
+ return `DELETE FROM ${this.q(this.table)}${whereSql}${this.dialect.buildReturningClause('*')}`;
878
880
  });
879
881
  // On cache hit, still validate the predicate
880
882
  if (whereFp === '') {
@@ -928,9 +930,14 @@ class QueryInterface {
928
930
  });
929
931
  const updateParams = updateEntries.map(([, v]) => v);
930
932
  const params = [...createParams, ...updateParams];
931
- const sql = `INSERT INTO ${this.q(this.table)} (${columns.join(', ')}) VALUES (${placeholders.join(', ')})` +
932
- ` ON CONFLICT (${conflictColumns.join(', ')}) DO UPDATE SET ${setClauses.join(', ')}` +
933
- ` RETURNING *`;
933
+ const sql = this.dialect.buildUpsertStatement({
934
+ table: this.q(this.table),
935
+ insertColumns: columns,
936
+ valuePlaceholders: placeholders,
937
+ conflictColumns,
938
+ updateSetClauses: setClauses,
939
+ returning: '*',
940
+ });
934
941
  return {
935
942
  sql,
936
943
  params,
package/dist/dialect.d.ts CHANGED
@@ -7,6 +7,48 @@
7
7
  */
8
8
  import type { SchemaMetadata } from './schema.js';
9
9
  export type DialectName = 'postgresql' | 'mysql' | 'sqlite' | (string & {});
10
+ export interface InsertStatementInput {
11
+ /** SQL-ready quoted table name. */
12
+ table: string;
13
+ /** SQL-ready quoted insert columns. */
14
+ columns: string[];
15
+ /** SQL-ready parameter placeholders/expressions for VALUES. */
16
+ valuePlaceholders: string[];
17
+ /** Optional SQL-ready RETURNING selection. */
18
+ returning?: string;
19
+ }
20
+ export interface BulkInsertStatementInput {
21
+ /** SQL-ready quoted table name. */
22
+ table: string;
23
+ /** SQL-ready quoted insert columns. */
24
+ columns: string[];
25
+ /** Row-major values, one inner array per inserted row. */
26
+ rowValues: unknown[][];
27
+ /** Optional SQL-ready array casts for dialects that batch by column arrays (PostgreSQL UNNEST). */
28
+ columnArrayTypes?: string[];
29
+ /** Skip duplicate rows when supported by the dialect. */
30
+ skipDuplicates?: boolean;
31
+ /** Optional SQL-ready RETURNING selection. */
32
+ returning?: string;
33
+ }
34
+ export interface BuiltStatement {
35
+ sql: string;
36
+ params: unknown[];
37
+ }
38
+ export interface UpsertStatementInput {
39
+ /** SQL-ready quoted table name. */
40
+ table: string;
41
+ /** SQL-ready quoted insert columns. */
42
+ insertColumns: string[];
43
+ /** SQL-ready parameter placeholders/expressions for VALUES. */
44
+ valuePlaceholders: string[];
45
+ /** SQL-ready quoted conflict/unique columns. */
46
+ conflictColumns: string[];
47
+ /** SQL-ready update SET clauses. */
48
+ updateSetClauses: string[];
49
+ /** Optional SQL-ready RETURNING selection. */
50
+ returning?: string;
51
+ }
10
52
  export interface Dialect {
11
53
  /** Dialect identifier. */
12
54
  readonly name: DialectName;
@@ -26,6 +68,14 @@ export interface Dialect {
26
68
  buildJsonArrayAgg(jsonObjectExpr: string, orderBy?: string): string;
27
69
  /** Whether INSERT/UPDATE/DELETE support RETURNING rows. */
28
70
  readonly supportsReturning: boolean;
71
+ /** Build a dialect-specific RETURNING clause. Return an empty string when unsupported. */
72
+ buildReturningClause(selection?: string): string;
73
+ /** Build a single-row INSERT statement. Inputs are SQL-ready quoted fragments. */
74
+ buildInsertStatement(input: InsertStatementInput): string;
75
+ /** Build a multi-row bulk INSERT statement and its dialect-shaped params. */
76
+ buildBulkInsertStatement(input: BulkInsertStatementInput): BuiltStatement;
77
+ /** Build an upsert statement. Inputs are SQL-ready quoted fragments. */
78
+ buildUpsertStatement(input: UpsertStatementInput): string;
29
79
  /** Whether native ILIKE is supported. */
30
80
  readonly supportsILike: boolean;
31
81
  /** Build a case-insensitive LIKE equivalent. */
package/dist/dialect.js CHANGED
@@ -30,6 +30,28 @@ export const postgresDialect = {
30
30
  const suffix = orderBy ? ` ${orderBy}` : '';
31
31
  return `COALESCE(json_agg(${jsonObjectExpr}${suffix}), ${this.emptyJsonArrayLiteral})`;
32
32
  },
33
+ buildReturningClause(selection = '*') {
34
+ return ` RETURNING ${selection}`;
35
+ },
36
+ buildInsertStatement(input) {
37
+ return `INSERT INTO ${input.table} (${input.columns.join(', ')}) VALUES (${input.valuePlaceholders.join(', ')})${this.buildReturningClause(input.returning)}`;
38
+ },
39
+ buildBulkInsertStatement(input) {
40
+ if (!input.columnArrayTypes || input.columnArrayTypes.length !== input.columns.length) {
41
+ throw new Error('PostgreSQL bulk insert requires one array type per column');
42
+ }
43
+ const columnArrays = input.columns.map((_, columnIndex) => input.rowValues.map((row) => row[columnIndex]));
44
+ const unnestArgs = input.columns.map((_, i) => `${this.paramPlaceholder(i + 1)}::${input.columnArrayTypes[i]}`);
45
+ let sql = `INSERT INTO ${input.table} (${input.columns.join(', ')}) SELECT * FROM UNNEST(${unnestArgs.join(', ')})`;
46
+ if (input.skipDuplicates)
47
+ sql += ' ON CONFLICT DO NOTHING';
48
+ return { sql: `${sql}${this.buildReturningClause(input.returning)}`, params: columnArrays };
49
+ },
50
+ buildUpsertStatement(input) {
51
+ return (`INSERT INTO ${input.table} (${input.insertColumns.join(', ')}) VALUES (${input.valuePlaceholders.join(', ')})` +
52
+ ` ON CONFLICT (${input.conflictColumns.join(', ')}) DO UPDATE SET ${input.updateSetClauses.join(', ')}` +
53
+ this.buildReturningClause(input.returning));
54
+ },
33
55
  buildInsensitiveLike(column, paramRef) {
34
56
  return `${column} ILIKE ${paramRef}`;
35
57
  },
package/dist/index.d.ts CHANGED
@@ -35,7 +35,7 @@
35
35
  export type { DatabaseAdapter, IntrospectionOverrides } from './adapters/index.js';
36
36
  export { alloydb, cockroachdb, postgresql, timescale, yugabytedb } from './adapters/index.js';
37
37
  export { type Middleware, type MiddlewareNext, type MiddlewareParams, type PgCompatPool, type PgCompatPoolClient, type PgCompatQueryResult, TransactionClient, type TransactionOptions, TurbineClient, type TurbineConfig, } from './client.js';
38
- export type { Dialect, DialectIntrospector, DialectMigrator, DialectName, IntrospectOptions as DialectIntrospectOptions, } from './dialect.js';
38
+ export type { BuiltStatement, BulkInsertStatementInput, Dialect, DialectIntrospector, DialectMigrator, DialectName, InsertStatementInput, IntrospectOptions as DialectIntrospectOptions, UpsertStatementInput, } from './dialect.js';
39
39
  export { postgresDialect } from './dialect.js';
40
40
  export { CheckConstraintError, CircularRelationError, ConnectionError, DeadlockError, type ErrorMessageMode, ForeignKeyError, getErrorMessageMode, MigrationError, NotFoundError, NotNullViolationError, PipelineError, type PipelineResultSlot, RelationError, SerializationFailureError, setErrorMessageMode, TimeoutError, TurbineError, TurbineErrorCode, UniqueConstraintError, ValidationError, wrapPgError, } from './errors.js';
41
41
  export { type GenerateOptions, generate } from './generate.js';
@@ -733,7 +733,12 @@ export class QueryInterface {
733
733
  const columns = entries.map(([k]) => this.toSqlColumn(k));
734
734
  const params = entries.map(([, v]) => v);
735
735
  const placeholders = entries.map((_, i) => `${this.p(i + 1)}`);
736
- const sql = `INSERT INTO ${this.q(this.table)} (${columns.join(', ')}) VALUES (${placeholders.join(', ')}) RETURNING *`;
736
+ const sql = this.dialect.buildInsertStatement({
737
+ table: this.q(this.table),
738
+ columns,
739
+ valuePlaceholders: placeholders,
740
+ returning: '*',
741
+ });
737
742
  return {
738
743
  sql,
739
744
  params,
@@ -773,27 +778,24 @@ export class QueryInterface {
773
778
  }
774
779
  const keys = Object.keys(args.data[0]).filter((k) => args.data[0][k] !== undefined);
775
780
  const columns = keys.map((k) => this.toColumn(k));
776
- // Build column arrays for UNNEST
777
- const columnArrays = keys.map(() => []);
778
- for (const row of args.data) {
781
+ const rowValues = args.data.map((row) => {
779
782
  const record = row;
780
- keys.forEach((key, i) => {
781
- columnArrays[i].push(record[key]);
782
- });
783
- }
784
- // Use actual Postgres types for array casts
783
+ return keys.map((key) => record[key]);
784
+ });
785
+ // Use actual Postgres types for array casts in the default PostgreSQL dialect.
785
786
  const typeCasts = columns.map((col) => this.getColumnArrayType(col));
786
- const unnestArgs = columnArrays.map((_, i) => `${this.p(i + 1)}::${typeCasts[i]}`);
787
787
  const quotedColumns = columns.map((c) => this.q(c));
788
- let sql = `INSERT INTO ${qt} (${quotedColumns.join(', ')}) SELECT * FROM UNNEST(${unnestArgs.join(', ')})`;
789
- // skipDuplicates: add ON CONFLICT DO NOTHING
790
- if (args.skipDuplicates) {
791
- sql += ` ON CONFLICT DO NOTHING`;
792
- }
793
- sql += ` RETURNING *`;
788
+ const built = this.dialect.buildBulkInsertStatement({
789
+ table: qt,
790
+ columns: quotedColumns,
791
+ rowValues,
792
+ columnArrayTypes: typeCasts,
793
+ skipDuplicates: args.skipDuplicates,
794
+ returning: '*',
795
+ });
794
796
  return {
795
- sql,
796
- params: columnArrays,
797
+ sql: built.sql,
798
+ params: built.params,
797
799
  transform: (result) => result.rows.map((row) => this.parseRow(row, this.table)),
798
800
  tag: `${this.table}.createMany`,
799
801
  };
@@ -822,7 +824,7 @@ export class QueryInterface {
822
824
  const whereClause = this.buildWhereClause(whereObj, freshParams);
823
825
  const whereSql = whereClause ? ` WHERE ${whereClause}` : '';
824
826
  this.assertMutationHasPredicate('update', whereSql, args.allowFullTableScan);
825
- return `UPDATE ${this.q(this.table)} SET ${setClauses.join(', ')}${whereSql} RETURNING *`;
827
+ return `UPDATE ${this.q(this.table)} SET ${setClauses.join(', ')}${whereSql}${this.dialect.buildReturningClause('*')}`;
826
828
  });
827
829
  // On cache hit, validate predicate
828
830
  if (whereFp === '') {
@@ -871,7 +873,7 @@ export class QueryInterface {
871
873
  const clause = this.buildWhereClause(whereObj, freshParams);
872
874
  const whereSql = clause ? ` WHERE ${clause}` : '';
873
875
  this.assertMutationHasPredicate('delete', whereSql, args.allowFullTableScan);
874
- return `DELETE FROM ${this.q(this.table)}${whereSql} RETURNING *`;
876
+ return `DELETE FROM ${this.q(this.table)}${whereSql}${this.dialect.buildReturningClause('*')}`;
875
877
  });
876
878
  // On cache hit, still validate the predicate
877
879
  if (whereFp === '') {
@@ -925,9 +927,14 @@ export class QueryInterface {
925
927
  });
926
928
  const updateParams = updateEntries.map(([, v]) => v);
927
929
  const params = [...createParams, ...updateParams];
928
- const sql = `INSERT INTO ${this.q(this.table)} (${columns.join(', ')}) VALUES (${placeholders.join(', ')})` +
929
- ` ON CONFLICT (${conflictColumns.join(', ')}) DO UPDATE SET ${setClauses.join(', ')}` +
930
- ` RETURNING *`;
930
+ const sql = this.dialect.buildUpsertStatement({
931
+ table: this.q(this.table),
932
+ insertColumns: columns,
933
+ valuePlaceholders: placeholders,
934
+ conflictColumns,
935
+ updateSetClauses: setClauses,
936
+ returning: '*',
937
+ });
931
938
  return {
932
939
  sql,
933
940
  params,
@@ -6,7 +6,7 @@
6
6
  * former monolithic `import { … } from './query.js'`.
7
7
  */
8
8
  export type { AggregateArgs, AggregateResult, ArrayFilter, CountArgs, CreateArgs, CreateManyArgs, DeleteArgs, DeleteManyArgs, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GroupByArgs, JsonFilter, OrderDirection, RelationDescriptor, RelationFilter, TypedWithClause, UpdateArgs, UpdateInput, UpdateManyArgs, UpdateOperatorInput, UpsertArgs, WhereClause, WhereOperator, WhereValue, WithClause, WithOptions, WithResult, } from './types.js';
9
- export type { Dialect } from '../dialect.js';
9
+ export type { BuiltStatement, BulkInsertStatementInput, Dialect, InsertStatementInput, UpsertStatementInput, } from '../dialect.js';
10
10
  export { postgresDialect } from '../dialect.js';
11
11
  export type { SqlCacheEntry } from './utils.js';
12
12
  export { buildCorrelation, escapeLike, escSingleQuote, fnv1a64Hex, LRUCache, OPERATOR_KEYS, quoteIdent, sqlToPreparedName, } from './utils.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turbine-orm",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Postgres-native TypeScript ORM — runs on Neon, Vercel Postgres, Cloudflare, Supabase. Streaming cursors, typed errors, single-query nested relations. 1 dependency, ~110KB",
5
5
  "type": "module",
6
6
  "exports": {