turbine-orm 0.90.2 → 0.91.0-next.a5cefb1

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.
Files changed (45) hide show
  1. package/README.md +3 -3
  2. package/dist/cjs/dialect.d.ts +26 -1
  3. package/dist/cjs/dialect.js +4 -0
  4. package/dist/cjs/index.d.ts +2 -2
  5. package/dist/cjs/mssql.js +2 -0
  6. package/dist/cjs/mysql.js +3 -0
  7. package/dist/cjs/powdb.js +2 -0
  8. package/dist/cjs/powql.d.ts +5 -2
  9. package/dist/cjs/powql.js +49 -28
  10. package/dist/cjs/prisma-compat.d.ts +2 -2
  11. package/dist/cjs/prisma-compat.js +8 -5
  12. package/dist/cjs/query/aggregates.js +74 -4
  13. package/dist/cjs/query/builder.d.ts +20 -7
  14. package/dist/cjs/query/builder.js +31 -22
  15. package/dist/cjs/query/expr.d.ts +47 -0
  16. package/dist/cjs/query/expr.js +129 -3
  17. package/dist/cjs/query/index.d.ts +1 -1
  18. package/dist/cjs/query/option-surface.js +3 -0
  19. package/dist/cjs/query/types.d.ts +107 -7
  20. package/dist/cjs/query/writes.d.ts +14 -2
  21. package/dist/cjs/query/writes.js +65 -18
  22. package/dist/cjs/sqlite.js +6 -0
  23. package/dist/dialect.d.ts +26 -1
  24. package/dist/dialect.js +4 -0
  25. package/dist/index.d.ts +2 -2
  26. package/dist/mssql.js +2 -0
  27. package/dist/mysql.js +3 -0
  28. package/dist/powdb.js +2 -0
  29. package/dist/powql.d.ts +5 -2
  30. package/dist/powql.js +49 -28
  31. package/dist/prisma-compat.d.ts +2 -2
  32. package/dist/prisma-compat.js +8 -5
  33. package/dist/query/aggregates.js +74 -4
  34. package/dist/query/builder.d.ts +20 -7
  35. package/dist/query/builder.js +31 -22
  36. package/dist/query/expr.d.ts +47 -0
  37. package/dist/query/expr.js +128 -3
  38. package/dist/query/index.d.ts +1 -1
  39. package/dist/query/option-surface.js +3 -0
  40. package/dist/query/types.d.ts +107 -7
  41. package/dist/query/writes.d.ts +14 -2
  42. package/dist/query/writes.js +63 -18
  43. package/dist/sqlite.js +6 -0
  44. package/package.json +1 -1
  45. package/skills/turbine-orm/SKILL.md +8 -0
@@ -1034,7 +1034,7 @@ export interface CreateArgs<T, R extends object = {}, S extends Record<string, b
1034
1034
  /** Query timeout in milliseconds. Rejects with an error if exceeded. */
1035
1035
  timeout?: number | undefined;
1036
1036
  }
1037
- export interface CreateManyArgs<T, D = OptionalInput<T>, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined> {
1037
+ export interface CreateManyArgs<T, D = OptionalInput<T>, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined, C extends boolean | undefined = true | undefined> {
1038
1038
  /** The rows. A generated client passes its `XCreate` as `D`. */
1039
1039
  data: D[];
1040
1040
  /** Skip rows that conflict on ANY unique constraint (ON CONFLICT DO NOTHING). */
@@ -1052,6 +1052,22 @@ export interface CreateManyArgs<T, D = OptionalInput<T>, S extends Record<string
1052
1052
  select?: FlagsArg<T, S>;
1053
1053
  /** Return the written rows without these fields. Same rules as `select`. */
1054
1054
  omit?: FlagsArg<T, O>;
1055
+ /**
1056
+ * `false` resolves to `{ count }`, the number of rows written, instead of the
1057
+ * rows. A plain insert then sends no `RETURNING` / `OUTPUT` at all; an
1058
+ * `onConflict` call returns only the key columns it needs to refuse two input
1059
+ * rows that write one table row. The count is the number of rows the call
1060
+ * would otherwise have returned: inserted plus updated, never a row
1061
+ * `skipDuplicates` or `ignore` skipped. Cannot be combined with `select` or
1062
+ * `omit` (E003). On MySQL, `skipDuplicates` with `false` is E017: a skipped
1063
+ * row there counts as written.
1064
+ *
1065
+ * Typed by `C`, which defaults to the rows form, so args typed ahead of the
1066
+ * call (`const args: CreateManyArgs<User> = ...`) still resolve to rows.
1067
+ * Pass `false` as `C` to type count-only args ahead of the call, or
1068
+ * `boolean` for a flag known only at run time (the result is then either).
1069
+ */
1070
+ returning?: C;
1055
1071
  /**
1056
1072
  * Opt out of configured {@link GlobalFilters} on the conflict-UPDATE of
1057
1073
  * `onConflict` (inserts are never filtered). See {@link SkipGlobalFilters}.
@@ -1060,6 +1076,16 @@ export interface CreateManyArgs<T, D = OptionalInput<T>, S extends Record<string
1060
1076
  /** Query timeout in milliseconds. Rejects with an error if exceeded. */
1061
1077
  timeout?: number | undefined;
1062
1078
  }
1079
+ /** What `createMany({ returning: false })` resolves to. */
1080
+ export interface CreateManyCount {
1081
+ /** Rows written: inserted plus updated. */
1082
+ count: number;
1083
+ }
1084
+ /**
1085
+ * `createMany`'s result for a `returning` flag `C`: the rows for `true` or no
1086
+ * flag, `{ count }` for `false`, either for a `boolean` known only at run time.
1087
+ */
1088
+ export type CreateManyResult<Row, C> = [C] extends [false] ? CreateManyCount : [C] extends [true | undefined] ? Row[] : Row[] | CreateManyCount;
1063
1089
  /**
1064
1090
  * The columns of the primary key, or of one non-partial unique constraint or
1065
1091
  * index, in any order. A `createMany` conflict target.
@@ -1098,7 +1124,7 @@ export type ConflictUpdateData<T> = {
1098
1124
  [K in keyof T]?: T[K] | UpdateOperatorInput<T[K]> | SqlExpr<T[K]> | ColumnRef | ExcludedRef | undefined;
1099
1125
  };
1100
1126
  /** The node kinds an update expression is built from (see {@link SqlExpr}). */
1101
- export type SqlExprKind = 'add' | 'sub' | 'mul' | 'div' | 'coalesce' | 'greatest' | 'least' | 'case' | 'now' | 'excluded';
1127
+ export type SqlExprKind = 'add' | 'sub' | 'mul' | 'div' | 'coalesce' | 'greatest' | 'least' | 'case' | 'now' | 'excluded' | 'secondsBetween';
1102
1128
  /** Carries the phantom result type of a {@link SqlExpr}. Type-level only. */
1103
1129
  declare const SQL_EXPR_RESULT: unique symbol;
1104
1130
  /**
@@ -1622,8 +1648,11 @@ export interface GroupByArgs<T, R extends object = {}> {
1622
1648
  _min?: GroupByAggregateSpec<T> | undefined;
1623
1649
  /** Maximum value of fields (or JSON paths) in each group */
1624
1650
  _max?: GroupByAggregateSpec<T> | undefined;
1625
- /** Percentiles of fields in each group (PostgreSQL only). See {@link PercentileSpec}. */
1626
- _percentile?: Partial<Record<keyof T & string, PercentileSpec>> | undefined;
1651
+ /**
1652
+ * Percentiles of fields, or of expressions, in each group (PostgreSQL only).
1653
+ * See {@link PercentileSpec} and {@link PercentileExprSpec}.
1654
+ */
1655
+ _percentile?: PercentileArgs<T> | undefined;
1627
1656
  /** Filter whole groups by their aggregate values (SQL HAVING). JSON-path aggregates key by their alias. */
1628
1657
  having?: HavingClause<T> | undefined;
1629
1658
  /**
@@ -1769,9 +1798,19 @@ export interface AggregateArgs<T, R extends object = {}> {
1769
1798
  * s._percentile.statusCode; // number | null
1770
1799
  * ```
1771
1800
  *
1772
- * See {@link PercentileSpec}.
1801
+ * A key that names no field computes the percentile of an expression,
1802
+ * given as `of`:
1803
+ *
1804
+ * ```ts
1805
+ * const s = await db.jobs.aggregate({
1806
+ * _percentile: { wait: { of: expr.secondsBetween(col('requestedAt'), col('readyAt')), p: 0.95 } },
1807
+ * });
1808
+ * s._percentile.wait; // number | null, in seconds
1809
+ * ```
1810
+ *
1811
+ * See {@link PercentileSpec} and {@link PercentileExprSpec}.
1773
1812
  */
1774
- _percentile?: Partial<Record<keyof T & string, PercentileSpec>> | undefined;
1813
+ _percentile?: PercentileArgs<T> | undefined;
1775
1814
  /**
1776
1815
  * Opt in to aggregating PII-tagged (`defineSchema` `pii: true`) columns.
1777
1816
  * `_min` / `_max` / `_percentile` return a STORED VALUE (or one that equals a
@@ -1836,11 +1875,72 @@ export type AggregateResultOf<T, A> = AggregateCountPart<A> & GroupBySumAvgPart<
1836
1875
  export interface PercentileSpec {
1837
1876
  p: number | readonly number[];
1838
1877
  method?: PercentileMethod | undefined;
1878
+ /** Only on a key that names no field: see {@link PercentileExprSpec}. */
1879
+ of?: undefined;
1880
+ }
1881
+ /**
1882
+ * One `_percentile` entry over an expression, under a key that names no field
1883
+ * (the key is only the result's name and never reaches the SQL).
1884
+ *
1885
+ * - `of`: a numeric expression over the row's own columns, built with
1886
+ * {@link expr}: arithmetic (`expr.add` / `sub` / `mul` / `div`),
1887
+ * `expr.coalesce` / `greatest` / `least` / `case`, and
1888
+ * `expr.secondsBetween(col('a'), col('b'))` for the seconds from one
1889
+ * timestamp column to another. Bound values must be numbers or bigints.
1890
+ * `col('x')` alone is refused: use the field key `x` instead.
1891
+ * - `p` and `method` as in {@link PercentileSpec}. The expression is ordered as
1892
+ * double precision, so the result is a `number` for both methods (an integer
1893
+ * beyond 2^53 loses exactness).
1894
+ *
1895
+ * Every column the expression reads as a value needs `includePii: UNSAFE`
1896
+ * when it is PII-tagged; columns read only by an `expr.case` arm's `when` do
1897
+ * not. Over zero rows the result is `null`.
1898
+ */
1899
+ export interface PercentileExprSpec {
1900
+ of: SqlExpr<number | bigint | string>;
1901
+ p: number | readonly number[];
1902
+ method?: PercentileMethod | undefined;
1839
1903
  }
1904
+ /**
1905
+ * The `_percentile` argument: field keys take a {@link PercentileSpec}; any
1906
+ * other key names an expression's result and takes a {@link PercentileExprSpec}.
1907
+ * On an untyped table (`keyof T` is `string`) any key takes either: the
1908
+ * `string` key is dropped from the field half, which would otherwise narrow
1909
+ * every key to a field spec.
1910
+ */
1911
+ export type PercentileArgs<T> = {
1912
+ [K in keyof T & string as string extends K ? never : K]?: PercentileSpec | undefined;
1913
+ } & {
1914
+ [name: string]: PercentileSpec | PercentileExprSpec | undefined;
1915
+ };
1916
+ /**
1917
+ * `_percentile` naming fields only. `aggregate` / `groupBy` take args of this
1918
+ * shape when their type is generic, where {@link PercentileKeysChecked} cannot
1919
+ * read the keys, so a wrapper generic over its args still compiles.
1920
+ */
1921
+ export type FieldPercentiles<T> = {
1922
+ _percentile?: {
1923
+ [K in keyof T & string]?: PercentileSpec | undefined;
1924
+ } | undefined;
1925
+ };
1926
+ /**
1927
+ * Applied to `aggregate` / `groupBy` arguments: every `_percentile` key that
1928
+ * names no field must carry `of`, so a misspelled field name is a compile
1929
+ * error rather than an expression percentile with no expression.
1930
+ */
1931
+ export type PercentileKeysChecked<T, A> = A extends {
1932
+ _percentile: infer S;
1933
+ } ? [S] extends [object] ? {
1934
+ _percentile: {
1935
+ [K in keyof S]: K extends keyof T ? S[K] : PercentileExprSpec | undefined;
1936
+ };
1937
+ } : unknown : unknown;
1840
1938
  /** `'cont'` interpolates (`percentile_cont`); `'disc'` returns a stored value (`percentile_disc`). */
1841
1939
  export type PercentileMethod = 'cont' | 'disc';
1842
1940
  /** One percentile value: the column's own type for `disc`; for `cont` a number, or an interval as the column reads. */
1843
- type PercentileScalar<T, K, Spec> = K extends keyof T ? Spec extends {
1941
+ type PercentileScalar<T, K, Spec> = Spec extends {
1942
+ of: object;
1943
+ } ? number : K extends keyof T ? Spec extends {
1844
1944
  method: 'disc';
1845
1945
  } ? NonNullable<T[K]> : [NonNullable<T[K]>] extends [number] ? number : number | NonNullable<T[K]> : unknown;
1846
1946
  /** A scalar `p` yields one value, an array `p` yields an array, `null` over zero rows. */
@@ -91,7 +91,7 @@ export declare function buildCreateMany<T extends object>(qi: BuilderCtx, args:
91
91
  * builder (`skipGlobalFilters`), which a concurrent call could change across
92
92
  * an await.
93
93
  */
94
- export declare function planCreateMany<T extends object>(qi: BuilderCtx, args: CreateManyArgs<T>, projection?: WriteProjection): {
94
+ export declare function planCreateMany<T extends object>(qi: BuilderCtx, args: CreateManyArgs<T>, projection?: WriteProjection, canSplit?: boolean): {
95
95
  chunks: DeferredQuery<T[]>[];
96
96
  rowsPerChunk: number;
97
97
  };
@@ -192,9 +192,21 @@ export declare function piiFields(_qi: BuilderCtx, meta: TableMetadata): string[
192
192
  * 34,500/s with `RETURNING *`.
193
193
  */
194
194
  export interface WriteProjection {
195
- /** Unquoted column names, in table order. Never empty. */
195
+ /**
196
+ * Unquoted column names, in table order. Empty only for {@link COUNT_ONLY}:
197
+ * a caller's projection always names at least one column.
198
+ */
196
199
  readonly columns: readonly string[];
197
200
  }
201
+ /**
202
+ * The projection of `createMany({ returning: false })`: no column. A plain
203
+ * insert then sends no RETURNING / OUTPUT and reports its row count; an
204
+ * onConflict call returns only its identity columns (see
205
+ * {@link conflictIdentity}), which the count is taken from.
206
+ */
207
+ export declare const COUNT_ONLY: WriteProjection;
208
+ /** True for {@link COUNT_ONLY} (or any projection naming no column). */
209
+ export declare function isCountOnly(projection: WriteProjection | undefined): boolean;
198
210
  /**
199
211
  * The write-SQL cache segment for a projection. EMPTY for the default shape, so
200
212
  * every existing key is byte-identical; a projected statement differs only in
@@ -828,12 +828,17 @@ export function buildCreateMany(qi, args, overBindLimit = 'refuse', projection)
828
828
  * builder (`skipGlobalFilters`), which a concurrent call could change across
829
829
  * an await.
830
830
  */
831
- export function planCreateMany(qi, args, projection) {
831
+ export function planCreateMany(qi, args, projection, canSplit = true) {
832
832
  const prepared = prepareCreateMany(qi, args, projection);
833
833
  if ('deferred' in prepared)
834
834
  return { chunks: [prepared.deferred], rowsPerChunk: args.data.length };
835
835
  const rows = prepared.rowValues;
836
- const size = rowsPerStatement(qi, prepared);
836
+ const { most, chunk } = statementSizes(qi, prepared);
837
+ // Rows that cannot be told apart across statements (refused below past one),
838
+ // and a call whose executor cannot run several statements in one
839
+ // transaction (`canSplit`), take the fewest statements the engine allows;
840
+ // the rest are split at the size the engine runs fastest.
841
+ const size = !canSplit || (prepared.conflict && !prepared.identity) ? most : chunk;
837
842
  if (rows.length <= size) {
838
843
  return { chunks: [emitCreateMany(qi, prepared, rows, 'allow', true)], rowsPerChunk: size };
839
844
  }
@@ -861,12 +866,19 @@ function prepareCreateMany(qi, args, projection) {
861
866
  // The data-independent half of onConflict is checked before the empty-data
862
867
  // shortcut, so a call is never valid with no rows and invalid with some.
863
868
  const conflict = args.onConflict === undefined ? null : resolveConflictPlan(qi, args);
869
+ const countOnly = isCountOnly(projection);
870
+ // Before the empty-data shortcut too, so the refusal never depends on the rows.
871
+ if (countOnly && args.skipDuplicates && qi.dialect.capabilities.skipDuplicatesCountsSkipped) {
872
+ throw new UnsupportedFeatureError('createMany({ skipDuplicates: true, returning: false })', qi.dialect.name, 'It counts a skipped row as written, so the count would include the rows skipped. Without ' +
873
+ 'returning: false the call writes the same rows and reports no count.');
874
+ }
864
875
  if (args.data.length === 0) {
865
876
  return {
866
877
  deferred: {
867
- sql: `SELECT * FROM ${qt} WHERE false`,
878
+ // `1 = 0`, not `false`: T-SQL has no boolean literal in a condition.
879
+ sql: `SELECT * FROM ${qt} WHERE 1 = 0`,
868
880
  params: [],
869
- transform: () => [],
881
+ transform: () => (countOnly ? { count: 0 } : []),
870
882
  tag: `${qi.table}.createMany`,
871
883
  },
872
884
  };
@@ -895,7 +907,9 @@ function prepareCreateMany(qi, args, projection) {
895
907
  deferred: {
896
908
  sql: buildDefaultValuesInsert(qi, args.data.length, args.skipDuplicates, projection),
897
909
  params: [],
898
- transform: (result) => result.rows.map((row) => parseWriteRow(qi, row, projection)),
910
+ transform: (result) => countOnly
911
+ ? countOf(result)
912
+ : result.rows.map((row) => parseWriteRow(qi, row, projection)),
899
913
  tag: `${qi.table}.createMany`,
900
914
  },
901
915
  };
@@ -973,32 +987,40 @@ function emitCreateMany(qi, prepared, rowValues, overBindLimit, first) {
973
987
  return {
974
988
  sql: built.sql,
975
989
  params: built.params,
976
- transform: (result) => result.rows.map((row) => parseWriteRow(qi, row, projection)),
990
+ transform: (result) => isCountOnly(projection)
991
+ ? countOf(result)
992
+ : result.rows.map((row) => parseWriteRow(qi, row, projection)),
977
993
  tag: `${qi.table}.createMany`,
978
994
  };
979
995
  }
980
996
  /**
981
- * Rows one `createMany` statement may carry: the most that keep its bind
982
- * parameters within the engine's limit and, on SQL Server, its rows within
983
- * the 1000 a plain `INSERT ... VALUES` accepts. The parameter cost is measured
984
- * from one-row and two-row builds, which separates the per-row parameters from
985
- * the ones bound once per statement (an onConflict update's values); the
986
- * PostgreSQL UNNEST form binds one array per column, so its rows never run out.
997
+ * Rows one `createMany` statement may carry. `most`: the most that keep its
998
+ * bind parameters within the engine's limit and, on SQL Server, its rows
999
+ * within the 1000 a plain `INSERT ... VALUES` accepts. `chunk`: the same under
1000
+ * the engine's `chunkBindParameters`, the size a call that runs as several
1001
+ * statements is split at. The parameter cost is measured from one-row and
1002
+ * two-row builds, which separates the per-row parameters from the ones bound
1003
+ * once per statement (an onConflict update's values); the PostgreSQL UNNEST
1004
+ * form binds one array per column, so its rows never run out.
987
1005
  */
988
- function rowsPerStatement(qi, prepared) {
1006
+ function statementSizes(qi, prepared) {
989
1007
  // An onConflict statement (SQL Server's MERGE) reads its rows as a derived
990
1008
  // table, which the plain INSERT ... VALUES row cap does not bind.
991
1009
  const cap = prepared.conflict ? Infinity : qi.dialect.capabilities.maxInsertRows;
992
- const limit = maxBindParameters(qi.dialect);
1010
+ const limit = maxBindParameters(qi.dialect) ?? Infinity;
1011
+ // A dialect written before the key existed has none, and splits at its limit.
1012
+ const preferred = qi.dialect.capabilities.chunkBindParameters;
1013
+ const chunkLimit = typeof preferred === 'number' && preferred < limit ? preferred : limit;
993
1014
  const rows = prepared.rowValues;
994
- if (limit === undefined || rows.length < 2)
995
- return cap;
1015
+ if (!Number.isFinite(chunkLimit) || rows.length < 2)
1016
+ return { most: cap, chunk: cap };
996
1017
  const one = emitCreateMany(qi, prepared, rows.slice(0, 1), 'allow', true).params.length;
997
1018
  const perRow = emitCreateMany(qi, prepared, rows.slice(0, 2), 'allow', true).params.length - one;
998
1019
  if (perRow <= 0)
999
- return cap;
1020
+ return { most: cap, chunk: cap };
1000
1021
  const fixed = Math.max(0, one - perRow);
1001
- return Math.max(1, Math.min(cap, Math.floor((limit - fixed) / perRow)));
1022
+ const fit = (bound) => Number.isFinite(bound) ? Math.max(1, Math.min(cap, Math.floor((bound - fixed) / perRow))) : cap;
1023
+ return { most: fit(limit), chunk: fit(chunkLimit) };
1002
1024
  }
1003
1025
  const ON_CONFLICT_KEYS = new Set(['target', 'update', 'where', 'ignore']);
1004
1026
  /**
@@ -1359,6 +1381,11 @@ function buildBulkUpsert(qi, conflict, insert, columns, overBindLimit, projectio
1359
1381
  transform: (result) => {
1360
1382
  if (identity)
1361
1383
  assertEachRowWrittenOnce(qi, conflict, identity, result.rows, first);
1384
+ // COUNT_ONLY: the rows back are the identity columns (or none at all when
1385
+ // the written rows cannot be identified), counted and never returned.
1386
+ if (isCountOnly(projection)) {
1387
+ return (identity ? { count: result.rows.length } : countOf(result));
1388
+ }
1362
1389
  const rows = result.rows.map((row) => parseWriteRow(qi, row, projection));
1363
1390
  if (identity && identity.added.length > 0) {
1364
1391
  for (const row of rows)
@@ -1989,6 +2016,21 @@ export function piiFields(_qi, meta) {
1989
2016
  }
1990
2017
  return out;
1991
2018
  }
2019
+ /**
2020
+ * The projection of `createMany({ returning: false })`: no column. A plain
2021
+ * insert then sends no RETURNING / OUTPUT and reports its row count; an
2022
+ * onConflict call returns only its identity columns (see
2023
+ * {@link conflictIdentity}), which the count is taken from.
2024
+ */
2025
+ export const COUNT_ONLY = Object.freeze({ columns: Object.freeze([]) });
2026
+ /** True for {@link COUNT_ONLY} (or any projection naming no column). */
2027
+ export function isCountOnly(projection) {
2028
+ return projection !== undefined && projection.columns.length === 0;
2029
+ }
2030
+ /** `{ count }` from a write that returned no rows to count: the engine's row count. */
2031
+ function countOf(result) {
2032
+ return { count: typeof result.rowCount === 'number' ? result.rowCount : result.rows.length };
2033
+ }
1992
2034
  /**
1993
2035
  * The write-SQL cache segment for a projection. EMPTY for the default shape, so
1994
2036
  * every existing key is byte-identical; a projected statement differs only in
@@ -2084,6 +2126,9 @@ export function writeReturningColumns(qi, projection) {
2084
2126
  */
2085
2127
  export function writeReselectSelection(qi, projection) {
2086
2128
  const cols = writeReturningColumns(qi, projection);
2129
+ // COUNT_ONLY: the re-read is counted, not returned, so it reads no column.
2130
+ if (cols !== '*' && cols.length === 0)
2131
+ return '1';
2087
2132
  return cols === '*' ? '*' : cols.join(', ');
2088
2133
  }
2089
2134
  /**
package/dist/sqlite.js CHANGED
@@ -905,12 +905,16 @@ export const sqliteDialect = {
905
905
  // The default SQLITE_MAX_VARIABLE_NUMBER.
906
906
  maxBindParameters: 32766,
907
907
  maxInsertRows: Number.POSITIVE_INFINITY,
908
+ // Measured: 40,000 rows insert 70 to 140 times faster in statements of 250
909
+ // parameters than of 32,766 (named-parameter lookup is a linear search).
910
+ chunkBindParameters: 250,
908
911
  upsertConflictTarget: 'explicit',
909
912
  setClauseOrder: 'snapshot',
910
913
  reselectLocksRows: false,
911
914
  integerDivision: '/',
912
915
  updateExpressions: true,
913
916
  bulkUpsertAppliesRepeatedKeys: true,
917
+ skipDuplicatesCountsSkipped: false,
914
918
  readOnlyTransactions: false,
915
919
  connectionParameters: false,
916
920
  backendCancel: false,
@@ -1121,6 +1125,8 @@ export const sqliteDialect = {
1121
1125
  (boolean ? 'Write true or false.' : 'Write a number, or a string holding one, such as "12.5".'));
1122
1126
  },
1123
1127
  buildReturningClause(selection = '*') {
1128
+ if (selection !== '*' && selection.length === 0)
1129
+ return '';
1124
1130
  return ` RETURNING ${selection === '*' ? '*' : selection.join(', ')}`;
1125
1131
  },
1126
1132
  buildInsertStatement(input) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turbine-orm",
3
- "version": "0.90.2",
3
+ "version": "0.91.0-next.a5cefb1",
4
4
  "description": "Postgres-native TypeScript ORM, runs on Neon, Vercel Postgres, Cloudflare, Supabase. Streaming cursors, typed errors, single-query nested relations. One dependency, no WASM engine",
5
5
  "type": "module",
6
6
  "//exports": "Each subpath declares its types PER CONDITION. A single shared top-level \"types\" resolves to the ESM declarations for `require` too, which is TS1479 (\"is an ES module ... cannot be require()d\") for any CJS consumer on moduleResolution node16/nodenext. The require condition points at dist/cjs, which ships its own {\"type\":\"commonjs\"} package.json, so those declarations are CJS declarations. Gated in CI by publint + @arethetypeswrong/cli + a real .cts consumer typecheck (see the package-types job in ci.yml).",
@@ -233,6 +233,12 @@ PostgreSQL only. `p` may be an array of fractions, and the result is then an
233
233
  array in the same order. The default `method: 'cont'` interpolates and is
234
234
  refused on a date column with `TURBINE_E003`; use `method: 'disc'` there.
235
235
 
236
+ A `_percentile` key that names no field takes `of`, an expression, and returns
237
+ a number under that key: `scoreGap: { of: expr.sub(col('rindScore'),
238
+ col('aromaScore')), p: 0.5 }`. `expr.secondsBetween(col('a'), col('b'))` is the
239
+ seconds between two timestamp columns, and a date column there is refused with
240
+ `TURBINE_E003`.
241
+
236
242
  ## groupBy and having
237
243
 
238
244
  `by` lists the grouping columns. The `having` shape is **column first, aggregate
@@ -293,6 +299,8 @@ refused with `TURBINE_E003`, and so is an expression in `create` data.
293
299
  target must be the primary key or one whole unique key; any other column list
294
300
  is refused with `TURBINE_E003`.
295
301
 
302
+ `createMany` with `returning: false` returns `{ count }` instead of the rows.
303
+
296
304
  ## Locking rows
297
305
 
298
306
  `lock: 'update'` on `findMany`, `findFirst` or `findUnique` locks the rows it