turbine-orm 0.29.0 → 0.31.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.
Files changed (48) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/cli/index.js +5 -0
  3. package/dist/cjs/cli/mcp.js +22 -92
  4. package/dist/cjs/client.js +47 -6
  5. package/dist/cjs/generate.js +71 -25
  6. package/dist/cjs/index.js +4 -1
  7. package/dist/cjs/introspect.js +350 -120
  8. package/dist/cjs/mssql.js +42 -136
  9. package/dist/cjs/mysql.js +16 -129
  10. package/dist/cjs/optional-peer-import.cjs +122 -0
  11. package/dist/cjs/powdb.js +579 -89
  12. package/dist/cjs/powql.js +56 -26
  13. package/dist/cjs/query/builder.js +601 -86
  14. package/dist/cjs/query/filters.js +80 -2
  15. package/dist/cjs/schema-metadata.js +316 -0
  16. package/dist/cjs/sqlite.js +8 -89
  17. package/dist/cli/index.d.ts +2 -0
  18. package/dist/cli/index.js +5 -0
  19. package/dist/cli/mcp.d.ts +18 -0
  20. package/dist/cli/mcp.js +22 -93
  21. package/dist/client.d.ts +19 -2
  22. package/dist/client.js +47 -6
  23. package/dist/generate.d.ts +16 -4
  24. package/dist/generate.js +71 -25
  25. package/dist/index.d.ts +2 -1
  26. package/dist/index.js +2 -0
  27. package/dist/introspect.d.ts +94 -1
  28. package/dist/introspect.js +345 -120
  29. package/dist/mssql.js +40 -104
  30. package/dist/mysql.js +14 -97
  31. package/dist/optional-peer-import.cjs +89 -0
  32. package/dist/optional-peer-import.d.cts +53 -0
  33. package/dist/powdb.d.ts +118 -23
  34. package/dist/powdb.js +574 -88
  35. package/dist/powql.d.ts +6 -0
  36. package/dist/powql.js +58 -28
  37. package/dist/query/builder.d.ts +145 -8
  38. package/dist/query/builder.js +602 -87
  39. package/dist/query/deferred.d.ts +7 -2
  40. package/dist/query/filters.d.ts +46 -1
  41. package/dist/query/filters.js +76 -1
  42. package/dist/query/index.d.ts +1 -1
  43. package/dist/query/types.d.ts +85 -11
  44. package/dist/schema-metadata.d.ts +77 -0
  45. package/dist/schema-metadata.js +313 -0
  46. package/dist/schema.d.ts +10 -0
  47. package/dist/sqlite.js +9 -90
  48. package/package.json +3 -3
package/dist/powql.d.ts CHANGED
@@ -156,6 +156,12 @@ export declare class PowqlInterface<T extends object = Record<string, unknown>>
156
156
  * it and the trailing `returning` reads it back — as is any non-string PK.
157
157
  */
158
158
  private applyPkDefault;
159
+ /**
160
+ * The table name as a PowQL type reference — backtick-quoted when it is a
161
+ * reserved word (e.g. a table named `order`). Used in every emitted
162
+ * statement; plain `this.table` stays in error messages.
163
+ */
164
+ private get qt();
159
165
  create(args: CreateArgs<T>): Promise<T>;
160
166
  createMany(args: CreateManyArgs<T>): Promise<T[]>;
161
167
  update(args: UpdateArgs<T>): Promise<T>;
package/dist/powql.js CHANGED
@@ -37,9 +37,9 @@
37
37
  import { randomUUID } from 'node:crypto';
38
38
  import { NotFoundError, TimeoutError, UnsupportedFeatureError, ValidationError } from './errors.js';
39
39
  import { executeNestedCreate, executeNestedUpdate, hasRelationFields, } from './nested-write.js';
40
- import { PowdbFloatParam, powqlColumnType, rowToEntity } from './powdb.js';
40
+ import { PowdbFloatParam, powqlColumnType, quotePowqlIdent, rowToEntity } from './powdb.js';
41
41
  import { escapeLike } from './query/utils.js';
42
- import { normalizeKeyColumns, } from './schema.js';
42
+ import { normalizeKeyColumns, snakeToCamel, } from './schema.js';
43
43
  /**
44
44
  * Max parent keys per relation-loader `in (…)` query. A `with` over a large
45
45
  * parent set is split into batches of this size so a single query never exceeds
@@ -106,7 +106,14 @@ export class PowqlInterface {
106
106
  }
107
107
  this.meta = meta;
108
108
  this.defaultLimit = options.defaultLimit;
109
- this.warnOnUnlimited = options.warnOnUnlimited !== false;
109
+ // Same per-table resolution as QueryInterface: an object map accepts BOTH
110
+ // the snake_case table name and the camelCase accessor as keys (snake_case
111
+ // wins on conflict); unlisted tables keep the default (warn on).
112
+ const warnOpt = options.warnOnUnlimited;
113
+ this.warnOnUnlimited =
114
+ typeof warnOpt === 'object' && warnOpt !== null
115
+ ? (warnOpt[table] ?? warnOpt[snakeToCamel(table)]) !== false
116
+ : warnOpt !== false;
110
117
  this.onQuery = options._onQuery;
111
118
  }
112
119
  // -------------------------------------------------------------------------
@@ -411,7 +418,7 @@ export class PowqlInterface {
411
418
  const chunk = targetPks.slice(i, i + MAX_RELATION_KEYS);
412
419
  const params = [];
413
420
  const ph = chunk.map((v) => this.param(v, params)).join(', ');
414
- const { rows } = await this.exec(`${through.table} filter .${targetJCol} in (${ph}) { .${sourceJCol} }`, params, timeout);
421
+ const { rows } = await this.exec(`${quotePowqlIdent(through.table)} filter .${targetJCol} in (${ph}) { .${sourceJCol} }`, params, timeout);
415
422
  for (const r of rows) {
416
423
  const v = r[sourceJCol];
417
424
  if (v != null)
@@ -563,7 +570,7 @@ export class PowqlInterface {
563
570
  }
564
571
  const limitClause = limit !== undefined ? ` limit ${this.param(limit, params)}` : '';
565
572
  const offsetClause = args.offset ? ` offset ${this.param(args.offset, params)}` : '';
566
- const powql = `${this.table}${distinct}${filter}${order}${limitClause}${offsetClause} ${this.projection(cols)}`;
573
+ const powql = `${this.qt}${distinct}${filter}${order}${limitClause}${offsetClause} ${this.projection(cols)}`;
567
574
  const { rows } = await this.exec(powql, params, args.timeout);
568
575
  return rows;
569
576
  }
@@ -712,7 +719,7 @@ export class PowqlInterface {
712
719
  const chunk = parentKeys.slice(i, i + MAX_RELATION_KEYS);
713
720
  const params = [];
714
721
  const placeholders = chunk.map((v) => this.param(v, params)).join(', ');
715
- const powql = `${through.table} filter .${sourceJCol} in (${placeholders}) { .${sourceJCol}, .${targetJCol} }`;
722
+ const powql = `${quotePowqlIdent(through.table)} filter .${sourceJCol} in (${placeholders}) { .${sourceJCol}, .${targetJCol} }`;
716
723
  const { rows } = await this.exec(powql, params, timeout);
717
724
  for (const row of rows) {
718
725
  const sv = String(row[sourceJCol]);
@@ -794,6 +801,14 @@ export class PowqlInterface {
794
801
  }
795
802
  return out;
796
803
  }
804
+ /**
805
+ * The table name as a PowQL type reference — backtick-quoted when it is a
806
+ * reserved word (e.g. a table named `order`). Used in every emitted
807
+ * statement; plain `this.table` stays in error messages.
808
+ */
809
+ get qt() {
810
+ return quotePowqlIdent(this.table);
811
+ }
797
812
  async create(args) {
798
813
  return this.withMiddleware('create', args, async () => {
799
814
  if (hasRelationFields(args.data, this.meta)) {
@@ -802,9 +817,11 @@ export class PowqlInterface {
802
817
  const data = this.applyPkDefault(args.data);
803
818
  const assigns = this.scalarData(data);
804
819
  const params = [];
805
- const body = assigns.map((a) => `${a.col.name} := ${this.writeRef(a.value, a.col, params)}`).join(', ');
820
+ const body = assigns
821
+ .map((a) => `${quotePowqlIdent(a.col.name)} := ${this.writeRef(a.value, a.col, params)}`)
822
+ .join(', ');
806
823
  // `returning` surfaces the inserted row (all columns, schema order) in one round-trip.
807
- const { rows } = await this.exec(`insert ${this.table} { ${body} } returning`, params, args.timeout);
824
+ const { rows } = await this.exec(`insert ${this.qt} { ${body} } returning`, params, args.timeout);
808
825
  const row = rows.length ? this.shape(rows)[0] : null;
809
826
  if (!row)
810
827
  throw new NotFoundError({ table: this.table, where: data });
@@ -819,10 +836,10 @@ export class PowqlInterface {
819
836
  const params = [];
820
837
  const tuples = inputs.map((d) => {
821
838
  const assigns = this.scalarData(d);
822
- return `{ ${assigns.map((a) => `${a.col.name} := ${this.writeRef(a.value, a.col, params)}`).join(', ')} }`;
839
+ return `{ ${assigns.map((a) => `${quotePowqlIdent(a.col.name)} := ${this.writeRef(a.value, a.col, params)}`).join(', ')} }`;
823
840
  });
824
841
  // Multi-row insert with `returning` hands back every inserted row in one round-trip.
825
- const { rows } = await this.exec(`insert ${this.table} ${tuples.join(', ')} returning`, params, args.timeout);
842
+ const { rows } = await this.exec(`insert ${this.qt} ${tuples.join(', ')} returning`, params, args.timeout);
826
843
  return this.shape(rows);
827
844
  });
828
845
  }
@@ -837,7 +854,7 @@ export class PowqlInterface {
837
854
  this.assertCompiledWhere(where, false, 'update');
838
855
  const setClause = this.buildUpdateAssignments(args.data, params);
839
856
  // `returning` hands back the post-update row(s); take the first (single-row contract).
840
- const { rows } = await this.exec(`${this.table} filter ${where} update { ${setClause} } returning`, params, args.timeout);
857
+ const { rows } = await this.exec(`${this.qt} filter ${where} update { ${setClause} } returning`, params, args.timeout);
841
858
  const row = rows.length ? this.shape(rows)[0] : null;
842
859
  if (!row)
843
860
  throw new NotFoundError({ table: this.table, where: args.where });
@@ -852,7 +869,7 @@ export class PowqlInterface {
852
869
  this.assertCompiledWhere(where, args.allowFullTableScan, 'updateMany');
853
870
  const setClause = this.buildUpdateAssignments(args.data, params);
854
871
  const filter = where ? ` filter ${where}` : '';
855
- const { rowCount } = await this.exec(`${this.table}${filter} update { ${setClause} }`, params, args.timeout);
872
+ const { rowCount } = await this.exec(`${this.qt}${filter} update { ${setClause} }`, params, args.timeout);
856
873
  return { count: rowCount };
857
874
  });
858
875
  }
@@ -867,7 +884,7 @@ export class PowqlInterface {
867
884
  }
868
885
  const colMeta = this.column(field);
869
886
  const ref = this.ref(field);
870
- const col = colMeta.name;
887
+ const col = quotePowqlIdent(colMeta.name);
871
888
  if (value !== null && typeof value === 'object' && !(value instanceof Date)) {
872
889
  const opObj = value;
873
890
  if ('set' in opObj)
@@ -925,21 +942,34 @@ export class PowqlInterface {
925
942
  // drifts from `powdbDialect`; falls back to the literal lowercase keywords.
926
943
  const d = this.options.dialect;
927
944
  const client = await this.pool.connect();
945
+ let began = false;
928
946
  try {
929
947
  await client.query(d?.beginStatement?.() ?? 'begin');
948
+ began = true;
930
949
  const { TransactionClient } = await import('./client.js');
931
950
  const tx = new TransactionClient(client, this.schema, this.middlewares, this.options);
932
951
  const ctx = { schema: this.schema, tx: tx };
933
- const result = await fn(ctx);
952
+ // Plant the single-writer re-entrancy marker for the implicit tx's
953
+ // subtree (same seam TurbineClient.$transaction uses) — user code that
954
+ // fires db.$transaction from inside (e.g. $use middleware around a
955
+ // nested-write child op) must fast-fail E017, not queue into deadlock.
956
+ const wrap = client
957
+ .wrapTransactionCallback;
958
+ const result = await (wrap ? wrap(() => fn(ctx)) : fn(ctx));
934
959
  await client.query(d?.commitStatement?.() ?? 'commit');
935
960
  return result;
936
961
  }
937
962
  catch (err) {
938
- try {
939
- await client.query(d?.rollbackStatement?.() ?? 'rollback');
940
- }
941
- catch {
942
- /* best-effort — the connection may be gone */
963
+ // Only roll back a transaction we actually opened — a failed BEGIN
964
+ // (queue timeout, re-entrancy E017) must not emit a stray ROLLBACK that
965
+ // could land inside another transaction on a shared engine handle.
966
+ if (began) {
967
+ try {
968
+ await client.query(d?.rollbackStatement?.() ?? 'rollback');
969
+ }
970
+ catch {
971
+ /* best-effort — the connection may be gone */
972
+ }
943
973
  }
944
974
  throw err;
945
975
  }
@@ -962,7 +992,7 @@ export class PowqlInterface {
962
992
  const where = this.buildWhere(resolvedWhere, params);
963
993
  this.assertCompiledWhere(where, false, 'delete');
964
994
  // `returning` hands back the deleted row(s) — no separate pre-image reselect needed.
965
- const { rows } = await this.exec(`${this.table} filter ${where} delete returning`, params, args.timeout);
995
+ const { rows } = await this.exec(`${this.qt} filter ${where} delete returning`, params, args.timeout);
966
996
  const row = rows.length ? this.shape(rows)[0] : null;
967
997
  if (!row)
968
998
  throw new NotFoundError({ table: this.table, where: args.where });
@@ -976,7 +1006,7 @@ export class PowqlInterface {
976
1006
  const where = this.buildWhere(resolvedWhere, params);
977
1007
  this.assertCompiledWhere(where, args.allowFullTableScan, 'deleteMany');
978
1008
  const filter = where ? ` filter ${where}` : '';
979
- const { rowCount } = await this.exec(`${this.table}${filter} delete`, params, args.timeout);
1009
+ const { rowCount } = await this.exec(`${this.qt}${filter} delete`, params, args.timeout);
980
1010
  return { count: rowCount };
981
1011
  });
982
1012
  }
@@ -991,14 +1021,14 @@ export class PowqlInterface {
991
1021
  }
992
1022
  const params = [];
993
1023
  const createBody = this.scalarData(createData)
994
- .map((a) => `${a.col.name} := ${this.writeRef(a.value, a.col, params)}`)
1024
+ .map((a) => `${quotePowqlIdent(a.col.name)} := ${this.writeRef(a.value, a.col, params)}`)
995
1025
  .join(', ');
996
1026
  const updateBody = this.buildUpdateAssignments(args.update, params);
997
1027
  // PowDB 0.7.0's `upsert` statement does NOT accept a trailing `returning`
998
1028
  // (verified: "unexpected trailing token … 'returning'"), because it is one
999
1029
  // atomic insert-or-update, not two branches. So upsert alone keeps the
1000
1030
  // reselect-by-PK fetch; create/update/delete all use `returning`.
1001
- await this.exec(`upsert ${this.table} on .${pkCol} { ${createBody} } on conflict { ${updateBody} }`, params, args.timeout);
1031
+ await this.exec(`upsert ${this.qt} on .${pkCol} { ${createBody} } on conflict { ${updateBody} }`, params, args.timeout);
1002
1032
  const pkField = this.meta.reverseColumnMap[pkCol] ?? pkCol;
1003
1033
  const row = await this.reselectByPk(createData[pkField], args.timeout);
1004
1034
  if (!row)
@@ -1043,7 +1073,7 @@ export class PowqlInterface {
1043
1073
  const resolvedWhere = await this.resolveRelationFilters(args.where, args.timeout);
1044
1074
  const where = this.buildWhere(resolvedWhere, params);
1045
1075
  const filter = where ? ` filter ${where}` : '';
1046
- const { rows } = await this.exec(`count(${this.table}${filter})`, params, args.timeout);
1076
+ const { rows } = await this.exec(`count(${this.qt}${filter})`, params, args.timeout);
1047
1077
  return Number((rows[0]?.value ?? rows[0]?.count ?? 0));
1048
1078
  });
1049
1079
  }
@@ -1063,12 +1093,12 @@ export class PowqlInterface {
1063
1093
  };
1064
1094
  if (args._count) {
1065
1095
  if (args._count === true) {
1066
- result._count = (await scalar(`count(${this.table}${filter})`)) ?? 0;
1096
+ result._count = (await scalar(`count(${this.qt}${filter})`)) ?? 0;
1067
1097
  }
1068
1098
  else {
1069
1099
  const counts = {};
1070
1100
  for (const field of Object.keys(args._count).filter((f) => args._count[f])) {
1071
- counts[field] = (await scalar(`count(${this.table}${filter} { ${this.ref(field)} })`)) ?? 0;
1101
+ counts[field] = (await scalar(`count(${this.qt}${filter} { ${this.ref(field)} })`)) ?? 0;
1072
1102
  }
1073
1103
  result._count = counts;
1074
1104
  }
@@ -1080,7 +1110,7 @@ export class PowqlInterface {
1080
1110
  const acc = {};
1081
1111
  for (const field of Object.keys(spec).filter((f) => spec[f])) {
1082
1112
  const powfn = fn.slice(1); // sum/avg/min/max
1083
- acc[field] = await scalar(`${powfn}(${this.table}${filter} { ${this.ref(field)} })`);
1113
+ acc[field] = await scalar(`${powfn}(${this.qt}${filter} { ${this.ref(field)} })`);
1084
1114
  }
1085
1115
  result[fn] = acc;
1086
1116
  }
@@ -1114,7 +1144,7 @@ export class PowqlInterface {
1114
1144
  }
1115
1145
  const having = this.buildHaving(args.having, params);
1116
1146
  const order = this.buildOrder(args.orderBy);
1117
- const powql = `${this.table}${filter} group ${groupKeys.join(', ')}${having}${order} { ${proj.join(', ')} }`;
1147
+ const powql = `${this.qt}${filter} group ${groupKeys.join(', ')}${having}${order} { ${proj.join(', ')} }`;
1118
1148
  const { rows } = await this.exec(powql, params, args.timeout);
1119
1149
  // Reshape: group keys → camel fields + coerced; aggregates → nested {_sum:{field}}.
1120
1150
  return rows.map((raw) => {
@@ -55,6 +55,12 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
55
55
  /** Pre-computed column type lookups (avoids linear scans per query) */
56
56
  private readonly columnPgTypeMap;
57
57
  private readonly columnArrayTypeMap;
58
+ /**
59
+ * Columns whose type lives in a DIFFERENT schema than the introspected one
60
+ * (ColumnMetadata.pgTypeSchema is recorded only in that case) — such columns
61
+ * must never receive this schema's `::"enum"` cast (see enumTypeForColumn).
62
+ */
63
+ private readonly crossSchemaTypeColumns;
58
64
  /** Tracks tables that have already triggered a deep-with warning (one-time) */
59
65
  private readonly deepWithWarned;
60
66
  /**
@@ -249,7 +255,10 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
249
255
  * loop calling `db.users.findMany()` thousands of times only logs once.
250
256
  * Suppressed when `defaultLimit` is configured (the caller has already
251
257
  * opted in to a bounded query) and when the user passed an explicit
252
- * `limit`, `take`, or `cursor`.
258
+ * `limit`, `take`, or `cursor`. A per-call `warnOnUnlimited` overrides the
259
+ * config-level setting in either direction (`false` silences a call that
260
+ * intentionally reads the full set; `true` forces the warning even when
261
+ * disabled in config).
253
262
  */
254
263
  private maybeWarnUnlimited;
255
264
  /**
@@ -427,17 +436,27 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
427
436
  */
428
437
  private collectRelationFilterParams;
429
438
  private collectRelFilterParams;
430
- /** Collect params from operator clauses. Mirrors buildOperatorClauses. */
439
+ /**
440
+ * Collect params from operator clauses. Mirrors buildOperatorClauses:
441
+ * {@link ColumnRef} values compile into the SQL text, so they push NOTHING -
442
+ * but they re-run the same validation (unknown ref / insensitive mode) so a
443
+ * warmed cache can never skip a check the build path enforces.
444
+ */
431
445
  private collectOperatorParams;
432
- /** Collect params from JSON filter. Mirrors buildJsonFilterClauses. */
446
+ /**
447
+ * Collect params from JSON filter. Mirrors buildJsonFilterClauses exactly:
448
+ * the `path` is bound at most once (its placeholder is shared by every
449
+ * extraction clause), then equals/contains/hasKey values, then the range
450
+ * comparison values in {@link JSON_RANGE_OPERATORS} order.
451
+ */
433
452
  private collectJsonFilterParams;
434
453
  /** Collect params from array filter. Mirrors buildArrayFilterClauses. */
435
454
  private collectArrayFilterParams;
436
455
  /**
437
- * Collect params for an orderBy clause. Only vector KNN ordering pushes a
438
- * param (the `$n::vector` query vector); plain direction ordering is
439
- * parameterless. Mirrors buildOrderBy's push order exactly so the cached-SQL
440
- * param re-collection stays in lockstep.
456
+ * Collect params for an orderBy clause. Vector KNN ordering pushes the
457
+ * `$n::vector` query vector and JSON-path ordering pushes its text[] path;
458
+ * plain direction ordering is parameterless. Mirrors buildOrderBy's push
459
+ * order exactly so the cached-SQL param re-collection stays in lockstep.
441
460
  */
442
461
  private collectOrderByParams;
443
462
  /**
@@ -545,6 +564,28 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
545
564
  * (relation targets, not just `this.table`).
546
565
  */
547
566
  private pgTypeForColumn;
567
+ /**
568
+ * The Postgres enum type name for a column, when the schema knows one.
569
+ *
570
+ * Introspection stores each column's `udt_name` in `pgTypes` and every
571
+ * database enum in `schema.enums` (typname → labels); a column whose type
572
+ * matches an enum key needs an explicit `::"EnumName"` cast on its write
573
+ * binds — bulk-insert forms like `UNNEST($1::text[])` otherwise type the
574
+ * value as text and Postgres refuses the implicit text→enum coercion
575
+ * ("column X is of type Y but expression is of type text").
576
+ *
577
+ * Postgres-only by construction: gated on the active dialect being
578
+ * `postgresql` AND on `schema.enums` having entries (only PG introspection
579
+ * produces them — `defineSchema` and the other engines leave it empty), so
580
+ * SQLite/MySQL/MSSQL/PowDB output is byte-identical.
581
+ */
582
+ private enumTypeForColumn;
583
+ /**
584
+ * `::"EnumName"` cast suffix for a write-bind placeholder on an enum
585
+ * column; `''` for every other column, so non-enum SQL stays byte-identical.
586
+ * The type name is an introspected identifier and is quoted via the dialect.
587
+ */
588
+ private enumCastSuffix;
548
589
  /**
549
590
  * Equality-fallthrough guard shared by every SQL-build path AND every
550
591
  * cache-hit param-collect path. A plain object literal that matched no known
@@ -575,9 +616,28 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
575
616
  * differently-shaped wheres would share one cached SQL string.
576
617
  */
577
618
  private fingerprintAliasWhere;
619
+ /**
620
+ * Validate a `{ col }` column reference against its table and return the
621
+ * resolved snake_case column name. Shared by the SQL-build path
622
+ * ({@link buildOperatorClauses}) and the cache-hit param-collect path
623
+ * (`collectOperatorParams`) so both always throw identically: a warmed
624
+ * cache can never skip the check.
625
+ */
626
+ private resolveColumnRef;
627
+ /**
628
+ * Compile a `{ col }` reference to its quoted, prefix-matched SQL identifier.
629
+ * NO param is bound: the referenced column is part of the SQL text (and of
630
+ * the where fingerprint, see {@link fingerprintOperatorShape}).
631
+ */
632
+ private columnRefSql;
578
633
  /**
579
634
  * Build SQL clauses for a single operator object on a column.
580
635
  * Each operator key becomes its own clause, all ANDed together.
636
+ *
637
+ * `equals`/`not`/`gt`/`gte`/`lt`/`lte` also accept a {@link ColumnRef}
638
+ * (`{ col: 'otherField' }`) which compiles to a column-to-column comparison
639
+ * against `refCtx`: no param bound, so `collectOperatorParams` mirrors by
640
+ * pushing nothing and the referenced name lives in the fingerprint.
581
641
  */
582
642
  private buildOperatorClauses;
583
643
  /**
@@ -611,6 +671,33 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
611
671
  * {@link UnsupportedFeatureError} (E017) instead of broken SQL.
612
672
  */
613
673
  private nullsSuffix;
674
+ /**
675
+ * Resolve an orderBy key to its snake_case column via the table's columnMap
676
+ * (camelToSnake fallback), throwing the SAME unknown-field E003 the top-level
677
+ * where path uses. Shared by top-level JSON-path ordering and every nested
678
+ * relation orderBy path so nested orderBy accepts exactly what top-level
679
+ * accepts (the 0.30.x bug: nested orderBy skipped the columnMap and rejected
680
+ * camelCase-named DB columns like "sortOrder").
681
+ */
682
+ private resolveOrderByColumn;
683
+ /**
684
+ * Validate a {@link JsonPathOrderBy} entry: column must exist AND be
685
+ * json/jsonb, path must be a non-empty array of keys/indexes: and return
686
+ * the resolved column. Shared by the SQL-build path
687
+ * ({@link buildJsonPathOrderEntry}) and the cache-hit param-collect mirrors
688
+ * so both always throw identically.
689
+ */
690
+ private validateJsonPathOrderBy;
691
+ /**
692
+ * Compile one {@link JsonPathOrderBy} entry:
693
+ * `("col" #>> $n::text[])::numeric ASC`: the numeric cast only with
694
+ * `type: 'numeric'` (default is text comparison), the extraction routed
695
+ * through the dialect's JSON hook exactly like the JSON where-filters, the
696
+ * path bound as ONE text[] param (mirrored by the order-param collectors).
697
+ * `prefix` scopes the column (`''` top-level, `t0.` inside a relation
698
+ * subquery).
699
+ */
700
+ private buildJsonPathOrderEntry;
614
701
  /**
615
702
  * Compile a relation ordering term. For a to-many relation the only allowed
616
703
  * key is `_count`, which becomes a correlated `COUNT(*)` subquery. For a
@@ -619,8 +706,37 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
619
706
  *
620
707
  * Validation: relation must exist (E005); to-many only allows `_count`, and
621
708
  * to-one only allows real target columns (E003).
709
+ *
710
+ * `ctx` generalizes the term beyond the root table: inside a relation
711
+ * subquery's orderBy the relations live on the TARGET table's metadata and
712
+ * the correlation parent is the relation's alias, not `this.table`.
622
713
  */
623
714
  private buildRelationOrderBy;
715
+ /**
716
+ * Compile the ORDER BY terms of a relation `with` clause against the
717
+ * relation's table alias. One unified path for every relation shape
718
+ * (hasMany / manyToMany / belongsTo / hasOne) supporting exactly what the
719
+ * top-level orderBy accepts at this level:
720
+ *
721
+ * - scalar columns via columnMap resolution (camelToSnake fallback) with
722
+ * {@link OrderBySpec} nulls placement,
723
+ * - {@link JsonPathOrderBy} entries (path bound as one text[] param),
724
+ * - relation ordering on the TARGET's relations (`_count` for to-many, a
725
+ * target column for to-one), correlated to the relation alias,
726
+ * - vector KNN ordering stays top-level-only (E003, same as before).
727
+ *
728
+ * Param pushes (JSON paths, relation-order global filters) MUST be mirrored,
729
+ * in the same order, by {@link collectRelationOrderParams}.
730
+ */
731
+ private buildRelationOrderClause;
732
+ /**
733
+ * Param-collect mirror of {@link buildRelationOrderClause}: JSON-path
734
+ * entries push their path (one text[] param each); relation-order entries
735
+ * mirror {@link collectOrderByParams}' relation branch (count / to-one
736
+ * global-filter params); scalar entries push nothing but re-run the same
737
+ * column validation so a warmed cache can never skip it.
738
+ */
739
+ private collectRelationOrderParams;
624
740
  /**
625
741
  * Build a correlated `(SELECT COUNT(*) …)` scalar subquery for a to-many
626
742
  * relation, correlated to `parentRef`. hasMany counts child rows via the FK;
@@ -899,11 +1015,32 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
899
1015
  * E.g. '_text' → 'text', '_int4' → 'integer'
900
1016
  */
901
1017
  private getArrayElementType;
1018
+ /**
1019
+ * Validate and enumerate the range comparisons (`gt`/`gte`/`lt`/`lte`) on a
1020
+ * JSON filter, in the fixed {@link JSON_RANGE_OPERATORS} order. Shared by
1021
+ * the SQL-build path ({@link buildJsonFilterClauses}) and the cache-hit
1022
+ * param-collect path ({@link collectJsonFilterParams}) so both always agree
1023
+ * on which params are pushed — and both throw identically for invalid
1024
+ * shapes, so a warmed cache can never skip validation.
1025
+ */
1026
+ private jsonRangeEntries;
902
1027
  /**
903
1028
  * Build SQL clauses for JSONB filter operators on a column.
904
- * Supports: path, equals, contains, hasKey.
1029
+ * Supports: path, equals, contains, hasKey, gt, gte, lt, lte.
1030
+ *
1031
+ * The `path` param is bound at most once and its placeholder is shared by
1032
+ * every clause that extracts it (equals + range ops), so the param list
1033
+ * stays byte-identical to {@link collectJsonFilterParams}.
905
1034
  */
906
1035
  private buildJsonFilterClauses;
1036
+ /**
1037
+ * Cast an extracted JSON path text value to a numeric type for range
1038
+ * comparison. PostgreSQL uses `(expr)::numeric` (exact — the right way to
1039
+ * compare JSON numbers, and `::float` would lose precision on big ints);
1040
+ * other dialects route through {@link Dialect.castAggregate} (SQLite/MySQL/
1041
+ * SQL Server have no `::` operator) as a float cast.
1042
+ */
1043
+ private castJsonNumeric;
907
1044
  /**
908
1045
  * Build SQL clauses for Array filter operators on a column.
909
1046
  * Supports: has, hasEvery, hasSome, isEmpty.