prisma-sql 1.44.0 → 1.45.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/dist/index.cjs CHANGED
@@ -49,20 +49,13 @@ var SQL_SEPARATORS = Object.freeze({
49
49
  CONDITION_OR: " OR ",
50
50
  ORDER_BY: ", "
51
51
  });
52
- var SQL_RESERVED_WORDS = /* @__PURE__ */ new Set([
52
+ var ALIAS_FORBIDDEN_KEYWORDS = /* @__PURE__ */ new Set([
53
53
  "select",
54
54
  "from",
55
55
  "where",
56
- "and",
57
- "or",
58
- "not",
59
- "in",
60
- "like",
61
- "between",
56
+ "having",
62
57
  "order",
63
- "by",
64
58
  "group",
65
- "having",
66
59
  "limit",
67
60
  "offset",
68
61
  "join",
@@ -70,14 +63,42 @@ var SQL_RESERVED_WORDS = /* @__PURE__ */ new Set([
70
63
  "left",
71
64
  "right",
72
65
  "outer",
73
- "on",
66
+ "cross",
67
+ "full",
68
+ "and",
69
+ "or",
70
+ "not",
71
+ "by",
74
72
  "as",
73
+ "on",
74
+ "union",
75
+ "intersect",
76
+ "except",
77
+ "case",
78
+ "when",
79
+ "then",
80
+ "else",
81
+ "end"
82
+ ]);
83
+ var SQL_KEYWORDS = /* @__PURE__ */ new Set([
84
+ ...ALIAS_FORBIDDEN_KEYWORDS,
85
+ "user",
86
+ "users",
75
87
  "table",
76
88
  "column",
77
89
  "index",
78
- "user",
79
- "users",
80
90
  "values",
91
+ "in",
92
+ "like",
93
+ "between",
94
+ "is",
95
+ "exists",
96
+ "null",
97
+ "true",
98
+ "false",
99
+ "all",
100
+ "any",
101
+ "some",
81
102
  "update",
82
103
  "insert",
83
104
  "delete",
@@ -88,25 +109,9 @@ var SQL_RESERVED_WORDS = /* @__PURE__ */ new Set([
88
109
  "grant",
89
110
  "revoke",
90
111
  "exec",
91
- "execute",
92
- "union",
93
- "intersect",
94
- "except",
95
- "case",
96
- "when",
97
- "then",
98
- "else",
99
- "end",
100
- "null",
101
- "true",
102
- "false",
103
- "is",
104
- "exists",
105
- "all",
106
- "any",
107
- "some"
112
+ "execute"
108
113
  ]);
109
- var SQL_KEYWORDS = SQL_RESERVED_WORDS;
114
+ var SQL_RESERVED_WORDS = SQL_KEYWORDS;
110
115
  var DEFAULT_WHERE_CLAUSE = "1=1";
111
116
  var SPECIAL_FIELDS = Object.freeze({
112
117
  ID: "id"
@@ -185,12 +190,48 @@ var LIMITS = Object.freeze({
185
190
  });
186
191
 
187
192
  // src/utils/normalize-value.ts
188
- function normalizeValue(value) {
193
+ var MAX_DEPTH = 20;
194
+ function normalizeValue(value, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
195
+ if (depth > MAX_DEPTH) {
196
+ throw new Error(`Max normalization depth exceeded (${MAX_DEPTH} levels)`);
197
+ }
189
198
  if (value instanceof Date) {
199
+ const t = value.getTime();
200
+ if (!Number.isFinite(t)) {
201
+ throw new Error("Invalid Date value in SQL params");
202
+ }
190
203
  return value.toISOString();
191
204
  }
205
+ if (typeof value === "bigint") {
206
+ return value.toString();
207
+ }
192
208
  if (Array.isArray(value)) {
193
- return value.map(normalizeValue);
209
+ const arrRef = value;
210
+ if (seen.has(arrRef)) {
211
+ throw new Error("Circular reference in SQL params");
212
+ }
213
+ seen.add(arrRef);
214
+ const out = value.map((v) => normalizeValue(v, seen, depth + 1));
215
+ seen.delete(arrRef);
216
+ return out;
217
+ }
218
+ if (value && typeof value === "object") {
219
+ if (value instanceof Uint8Array) return value;
220
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) return value;
221
+ const proto = Object.getPrototypeOf(value);
222
+ const isPlain = proto === Object.prototype || proto === null;
223
+ if (!isPlain) return value;
224
+ const obj = value;
225
+ if (seen.has(obj)) {
226
+ throw new Error("Circular reference in SQL params");
227
+ }
228
+ seen.add(obj);
229
+ const out = {};
230
+ for (const [k, v] of Object.entries(obj)) {
231
+ out[k] = normalizeValue(v, seen, depth + 1);
232
+ }
233
+ seen.delete(obj);
234
+ return out;
194
235
  }
195
236
  return value;
196
237
  }
@@ -371,7 +412,7 @@ function prepareArrayParam(value, dialect) {
371
412
  throw new Error("prepareArrayParam requires array value");
372
413
  }
373
414
  if (dialect === "postgres") {
374
- return value.map(normalizeValue);
415
+ return value.map((v) => normalizeValue(v));
375
416
  }
376
417
  return JSON.stringify(value);
377
418
  }
@@ -443,36 +484,46 @@ function createError(message, ctx, code = "VALIDATION_ERROR") {
443
484
  }
444
485
 
445
486
  // src/builder/shared/model-field-cache.ts
446
- var SCALAR_SET_CACHE = /* @__PURE__ */ new WeakMap();
447
- var RELATION_SET_CACHE = /* @__PURE__ */ new WeakMap();
487
+ var MODEL_CACHE = /* @__PURE__ */ new WeakMap();
488
+ function ensureFullCache(model) {
489
+ let cache = MODEL_CACHE.get(model);
490
+ if (!cache) {
491
+ const fieldInfo = /* @__PURE__ */ new Map();
492
+ const scalarFields = /* @__PURE__ */ new Set();
493
+ const relationFields = /* @__PURE__ */ new Set();
494
+ const columnMap = /* @__PURE__ */ new Map();
495
+ for (const f of model.fields) {
496
+ const info = {
497
+ name: f.name,
498
+ dbName: f.dbName || f.name,
499
+ type: f.type,
500
+ isRelation: !!f.isRelation,
501
+ isRequired: !!f.isRequired
502
+ };
503
+ fieldInfo.set(f.name, info);
504
+ if (info.isRelation) {
505
+ relationFields.add(f.name);
506
+ } else {
507
+ scalarFields.add(f.name);
508
+ columnMap.set(f.name, info.dbName);
509
+ }
510
+ }
511
+ cache = { fieldInfo, scalarFields, relationFields, columnMap };
512
+ MODEL_CACHE.set(model, cache);
513
+ }
514
+ return cache;
515
+ }
516
+ function getFieldInfo(model, fieldName) {
517
+ return ensureFullCache(model).fieldInfo.get(fieldName);
518
+ }
448
519
  function getScalarFieldSet(model) {
449
- const cached = SCALAR_SET_CACHE.get(model);
450
- if (cached) return cached;
451
- const s = /* @__PURE__ */ new Set();
452
- for (const f of model.fields) if (!f.isRelation) s.add(f.name);
453
- SCALAR_SET_CACHE.set(model, s);
454
- return s;
520
+ return ensureFullCache(model).scalarFields;
455
521
  }
456
522
  function getRelationFieldSet(model) {
457
- const cached = RELATION_SET_CACHE.get(model);
458
- if (cached) return cached;
459
- const s = /* @__PURE__ */ new Set();
460
- for (const f of model.fields) if (f.isRelation) s.add(f.name);
461
- RELATION_SET_CACHE.set(model, s);
462
- return s;
463
- }
464
- var COLUMN_MAP_CACHE = /* @__PURE__ */ new WeakMap();
523
+ return ensureFullCache(model).relationFields;
524
+ }
465
525
  function getColumnMap(model) {
466
- const cached = COLUMN_MAP_CACHE.get(model);
467
- if (cached) return cached;
468
- const map = /* @__PURE__ */ new Map();
469
- for (const f of model.fields) {
470
- if (!f.isRelation) {
471
- map.set(f.name, f.dbName || f.name);
472
- }
473
- }
474
- COLUMN_MAP_CACHE.set(model, map);
475
- return map;
526
+ return ensureFullCache(model).columnMap;
476
527
  }
477
528
 
478
529
  // src/builder/shared/validators/sql-validators.ts
@@ -532,7 +583,7 @@ function scanDollarPlaceholders(sql, markUpTo) {
532
583
  }
533
584
  return { count, min, max, seen };
534
585
  }
535
- function assertNoGaps(scan, rangeMin, rangeMax, sql) {
586
+ function assertNoGapsDollar(scan, rangeMin, rangeMax, sql) {
536
587
  for (let k = rangeMin; k <= rangeMax; k++) {
537
588
  if (scan.seen[k] !== 1) {
538
589
  throw new Error(
@@ -560,7 +611,7 @@ function validateParamConsistency(sql, params) {
560
611
  `CRITICAL: Parameter mismatch - SQL max placeholder is $${scan.max} but ${paramLen} params provided. This will cause SQL execution to fail. SQL: ${sqlPreview(sql)}`
561
612
  );
562
613
  }
563
- assertNoGaps(scan, 1, scan.max, sql);
614
+ assertNoGapsDollar(scan, 1, scan.max, sql);
564
615
  }
565
616
  function needsQuoting(id) {
566
617
  if (!isNonEmptyString(id)) return true;
@@ -578,14 +629,11 @@ function validateParamConsistencyFragment(sql, params) {
578
629
  `CRITICAL: Parameter mismatch - SQL references $${scan.max} but only ${paramLen} params provided. SQL: ${sqlPreview(sql)}`
579
630
  );
580
631
  }
581
- assertNoGaps(scan, scan.min, scan.max, sql);
632
+ assertNoGapsDollar(scan, scan.min, scan.max, sql);
582
633
  }
583
634
  function assertOrThrow(condition, message) {
584
635
  if (!condition) throw new Error(message);
585
636
  }
586
- function dialectPlaceholderPrefix(dialect) {
587
- return dialect === "sqlite" ? "?" : "$";
588
- }
589
637
  function parseSqlitePlaceholderIndices(sql) {
590
638
  const re = /\?(?:(\d+))?/g;
591
639
  const indices = [];
@@ -605,112 +653,70 @@ function parseSqlitePlaceholderIndices(sql) {
605
653
  }
606
654
  return { indices, sawNumbered, sawAnonymous };
607
655
  }
608
- function parseDollarPlaceholderIndices(sql) {
609
- const re = /\$(\d+)/g;
610
- const indices = [];
611
- for (const m of sql.matchAll(re)) indices.push(parseInt(m[1], 10));
612
- return indices;
613
- }
614
- function getPlaceholderIndices(sql, dialect) {
615
- if (dialect === "sqlite") return parseSqlitePlaceholderIndices(sql);
616
- return {
617
- indices: parseDollarPlaceholderIndices(sql),
618
- sawNumbered: false,
619
- sawAnonymous: false
620
- };
621
- }
622
656
  function maxIndex(indices) {
623
657
  return indices.length > 0 ? Math.max(...indices) : 0;
624
658
  }
625
- function ensureNoMixedSqlitePlaceholders(sawNumbered, sawAnonymous) {
626
- assertOrThrow(
627
- !(sawNumbered && sawAnonymous),
628
- `CRITICAL: Mixed sqlite placeholders ('?' and '?NNN') are not supported.`
629
- );
630
- }
631
- function ensurePlaceholderMaxMatchesMappingsLength(max, mappingsLength, dialect) {
632
- assertOrThrow(
633
- max === mappingsLength,
634
- `CRITICAL: SQL placeholder max mismatch - max is ${dialectPlaceholderPrefix(dialect)}${max}, but mappings length is ${mappingsLength}.`
635
- );
636
- }
637
- function ensureSequentialPlaceholders(placeholders, max, dialect) {
638
- const prefix = dialectPlaceholderPrefix(dialect);
659
+ function ensureSequentialIndices(seen, max, prefix) {
639
660
  for (let i = 1; i <= max; i++) {
640
661
  assertOrThrow(
641
- placeholders.has(i),
662
+ seen.has(i),
642
663
  `CRITICAL: Missing SQL placeholder ${prefix}${i} - placeholders must be sequential 1..${max}.`
643
664
  );
644
665
  }
645
666
  }
646
- function validateMappingIndex(mapping, max) {
647
- assertOrThrow(
648
- Number.isInteger(mapping.index) && mapping.index >= 1 && mapping.index <= max,
649
- `CRITICAL: ParamMapping index ${mapping.index} out of range 1..${max}.`
650
- );
651
- }
652
- function ensureUniqueMappingIndex(mappingIndices, index, dialect) {
667
+ function validateSqlitePlaceholders(sql, params) {
668
+ const paramLen = params.length;
669
+ const { indices, sawNumbered, sawAnonymous } = parseSqlitePlaceholderIndices(sql);
670
+ if (indices.length === 0) {
671
+ if (paramLen !== 0) {
672
+ throw new Error(
673
+ `CRITICAL: Parameter mismatch - SQL has no sqlite placeholders but ${paramLen} params provided. SQL: ${sqlPreview(sql)}`
674
+ );
675
+ }
676
+ return;
677
+ }
653
678
  assertOrThrow(
654
- !mappingIndices.has(index),
655
- `CRITICAL: Duplicate ParamMapping index ${index} - each placeholder index must map to exactly one ParamMap.`
679
+ !(sawNumbered && sawAnonymous),
680
+ `CRITICAL: Mixed sqlite placeholders ('?' and '?NNN') are not supported.`
656
681
  );
657
- mappingIndices.add(index);
658
- }
659
- function ensureMappingIndexExistsInSql(placeholders, index) {
682
+ const max = maxIndex(indices);
660
683
  assertOrThrow(
661
- placeholders.has(index),
662
- `CRITICAL: ParamMapping index ${index} not found in SQL placeholders.`
684
+ max === paramLen,
685
+ `CRITICAL: SQL placeholder max mismatch - max is ?${max}, but params length is ${paramLen}. SQL: ${sqlPreview(sql)}`
663
686
  );
687
+ const set = new Set(indices);
688
+ ensureSequentialIndices(set, max, "?");
664
689
  }
665
- function validateMappingValueShape(mapping) {
666
- assertOrThrow(
667
- !(mapping.dynamicName !== void 0 && mapping.value !== void 0),
668
- `CRITICAL: ParamMap ${mapping.index} has both dynamicName and value`
669
- );
670
- assertOrThrow(
671
- !(mapping.dynamicName === void 0 && mapping.value === void 0),
672
- `CRITICAL: ParamMap ${mapping.index} has neither dynamicName nor value`
673
- );
690
+ function validateDollarPlaceholders(sql, params) {
691
+ validateParamConsistency(sql, params);
674
692
  }
675
- function ensureMappingsCoverAllIndices(mappingIndices, max, dialect) {
676
- const prefix = dialectPlaceholderPrefix(dialect);
677
- for (let i = 1; i <= max; i++) {
678
- assertOrThrow(
679
- mappingIndices.has(i),
680
- `CRITICAL: Missing ParamMap for placeholder ${prefix}${i} - mappings must cover 1..${max} with no gaps.`
681
- );
682
- }
693
+ function detectPlaceholderStyle(sql) {
694
+ const hasDollar = /\$\d+/.test(sql);
695
+ const hasSqliteQ = /\?(?:\d+)?/.test(sql);
696
+ return { hasDollar, hasSqliteQ };
683
697
  }
684
- function validateMappingsAgainstPlaceholders(mappings, placeholders, max, dialect) {
685
- const mappingIndices = /* @__PURE__ */ new Set();
686
- for (const mapping of mappings) {
687
- validateMappingIndex(mapping, max);
688
- ensureUniqueMappingIndex(mappingIndices, mapping.index);
689
- ensureMappingIndexExistsInSql(placeholders, mapping.index);
690
- validateMappingValueShape(mapping);
698
+ function validateParamConsistencyByDialect(sql, params, dialect) {
699
+ const { hasDollar, hasSqliteQ } = detectPlaceholderStyle(sql);
700
+ if (dialect !== "sqlite") {
701
+ if (hasSqliteQ && !hasDollar) {
702
+ throw new Error(
703
+ `CRITICAL: Non-sqlite dialect query contains sqlite '?' placeholders. SQL: ${sqlPreview(sql)}`
704
+ );
705
+ }
706
+ return validateDollarPlaceholders(sql, params);
691
707
  }
692
- ensureMappingsCoverAllIndices(mappingIndices, max, dialect);
693
- }
694
- function validateSqlPositions(sql, mappings, dialect) {
695
- const { indices, sawNumbered, sawAnonymous } = getPlaceholderIndices(
696
- sql,
697
- dialect
698
- );
699
- if (dialect === "sqlite") {
700
- ensureNoMixedSqlitePlaceholders(sawNumbered, sawAnonymous);
708
+ if (hasDollar && hasSqliteQ) {
709
+ throw new Error(
710
+ `CRITICAL: Mixed placeholder styles ($N and ?/ ?NNN) are not supported. SQL: ${sqlPreview(sql)}`
711
+ );
701
712
  }
702
- const placeholders = new Set(indices);
703
- if (placeholders.size === 0 && mappings.length === 0) return;
704
- const max = maxIndex(indices);
705
- ensurePlaceholderMaxMatchesMappingsLength(max, mappings.length, dialect);
706
- ensureSequentialPlaceholders(placeholders, max, dialect);
707
- validateMappingsAgainstPlaceholders(mappings, placeholders, max, dialect);
713
+ if (hasSqliteQ) return validateSqlitePlaceholders(sql, params);
714
+ return validateDollarPlaceholders(sql, params);
708
715
  }
709
716
 
710
717
  // src/builder/shared/sql-utils.ts
711
- var NUL = String.fromCharCode(0);
712
718
  function containsControlChars(s) {
713
- return s.includes(NUL) || s.includes("\n") || s.includes("\r");
719
+ return /[\u0000-\u001F\u007F]/.test(s);
714
720
  }
715
721
  function assertNoControlChars(label, s) {
716
722
  if (containsControlChars(s)) {
@@ -929,16 +935,46 @@ function buildTableReference(schemaName, tableName, dialect) {
929
935
  return `"${safeSchema}"."${safeTable}"`;
930
936
  }
931
937
  function assertSafeAlias(alias) {
932
- const a = String(alias);
933
- if (a.trim().length === 0) {
934
- throw new Error("alias is required and cannot be empty");
938
+ if (typeof alias !== "string") {
939
+ throw new Error(`Invalid alias: expected string, got ${typeof alias}`);
935
940
  }
936
- if (containsControlChars(a) || a.includes(";")) {
937
- throw new Error(`alias contains unsafe characters: ${JSON.stringify(a)}`);
941
+ const a = alias.trim();
942
+ if (a.length === 0) {
943
+ throw new Error("Invalid alias: required and cannot be empty");
938
944
  }
939
- if (!/^[A-Za-z_]\w*$/.test(a)) {
945
+ if (a !== alias) {
946
+ throw new Error("Invalid alias: leading/trailing whitespace");
947
+ }
948
+ if (/[\u0000-\u001F\u007F]/.test(a)) {
949
+ throw new Error(
950
+ "Invalid alias: contains unsafe characters (control characters)"
951
+ );
952
+ }
953
+ if (a.includes('"') || a.includes("'") || a.includes("`")) {
954
+ throw new Error("Invalid alias: contains unsafe characters (quotes)");
955
+ }
956
+ if (a.includes(";")) {
957
+ throw new Error("Invalid alias: contains unsafe characters (semicolon)");
958
+ }
959
+ if (a.includes("--") || a.includes("/*") || a.includes("*/")) {
960
+ throw new Error(
961
+ "Invalid alias: contains unsafe characters (SQL comment tokens)"
962
+ );
963
+ }
964
+ if (/\s/.test(a)) {
965
+ throw new Error(
966
+ "Invalid alias: must be a simple identifier without whitespace"
967
+ );
968
+ }
969
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(a)) {
970
+ throw new Error(
971
+ `Invalid alias: must be a simple identifier (alphanumeric with underscores): "${alias}"`
972
+ );
973
+ }
974
+ const lowered = a.toLowerCase();
975
+ if (ALIAS_FORBIDDEN_KEYWORDS.has(lowered)) {
940
976
  throw new Error(
941
- `alias must be a simple identifier, got: ${JSON.stringify(a)}`
977
+ `Invalid alias: '${alias}' is a SQL keyword that would break query parsing. Forbidden aliases: ${[...ALIAS_FORBIDDEN_KEYWORDS].join(", ")}`
942
978
  );
943
979
  }
944
980
  }
@@ -980,7 +1016,9 @@ function isValidRelationField(field) {
980
1016
  if (fk.length === 0) return false;
981
1017
  const refsRaw = field.references;
982
1018
  const refs = normalizeKeyList(refsRaw);
983
- if (refs.length === 0) return false;
1019
+ if (refs.length === 0) {
1020
+ return fk.length === 1;
1021
+ }
984
1022
  if (refs.length !== fk.length) return false;
985
1023
  return true;
986
1024
  }
@@ -995,6 +1033,8 @@ function getReferenceFieldNames(field, foreignKeyCount) {
995
1033
  return refs;
996
1034
  }
997
1035
  function joinCondition(field, parentModel, childModel, parentAlias, childAlias) {
1036
+ assertSafeAlias(parentAlias);
1037
+ assertSafeAlias(childAlias);
998
1038
  const fkFields = normalizeKeyList(field.foreignKey);
999
1039
  if (fkFields.length === 0) {
1000
1040
  throw createError(
@@ -1144,6 +1184,32 @@ function normalizeOrderByInput(orderBy, parseValue) {
1144
1184
  throw new Error("orderBy must be an object or array of objects");
1145
1185
  }
1146
1186
 
1187
+ // src/builder/shared/order-by-determinism.ts
1188
+ function modelHasScalarId(model) {
1189
+ if (!model) return false;
1190
+ return getScalarFieldSet(model).has("id");
1191
+ }
1192
+ function hasIdTiebreaker(orderBy, parse) {
1193
+ if (!isNotNullish(orderBy)) return false;
1194
+ const normalized = normalizeOrderByInput(orderBy, parse);
1195
+ return normalized.some(
1196
+ (obj) => Object.prototype.hasOwnProperty.call(obj, "id")
1197
+ );
1198
+ }
1199
+ function addIdTiebreaker(orderBy) {
1200
+ if (Array.isArray(orderBy)) return [...orderBy, { id: "asc" }];
1201
+ return [orderBy, { id: "asc" }];
1202
+ }
1203
+ function ensureDeterministicOrderByInput(args) {
1204
+ const { orderBy, model, parseValue } = args;
1205
+ if (!modelHasScalarId(model)) return orderBy;
1206
+ if (!isNotNullish(orderBy)) {
1207
+ return { id: "asc" };
1208
+ }
1209
+ if (hasIdTiebreaker(orderBy, parseValue)) return orderBy;
1210
+ return addIdTiebreaker(orderBy);
1211
+ }
1212
+
1147
1213
  // src/builder/pagination.ts
1148
1214
  var MAX_LIMIT_OFFSET = 2147483647;
1149
1215
  function parseDirectionRaw(raw, errorLabel) {
@@ -1206,7 +1272,15 @@ function hasNonNullishProp(v, key) {
1206
1272
  }
1207
1273
  function normalizeIntegerOrDynamic(name, v) {
1208
1274
  if (schemaParser.isDynamicParameter(v)) return v;
1209
- return normalizeFiniteInteger(name, v);
1275
+ const result = normalizeIntLike(name, v, {
1276
+ min: Number.MIN_SAFE_INTEGER,
1277
+ max: MAX_LIMIT_OFFSET,
1278
+ allowZero: true
1279
+ });
1280
+ if (result === void 0) {
1281
+ throw new Error(`${name} normalization returned undefined`);
1282
+ }
1283
+ return result;
1210
1284
  }
1211
1285
  function readSkipTake(relArgs) {
1212
1286
  const hasSkip = hasNonNullishProp(relArgs, "skip");
@@ -1272,7 +1346,7 @@ function buildCursorFilterParts(cursor, cursorAlias, params, model) {
1272
1346
  const placeholdersByField = /* @__PURE__ */ new Map();
1273
1347
  const parts = [];
1274
1348
  for (const [field, value] of entries) {
1275
- const c = `${cursorAlias}.${quote(field)}`;
1349
+ const c = `${cursorAlias}.${quoteColumn(model, field)}`;
1276
1350
  if (value === null) {
1277
1351
  parts.push(`${c} IS NULL`);
1278
1352
  continue;
@@ -1286,13 +1360,6 @@ function buildCursorFilterParts(cursor, cursorAlias, params, model) {
1286
1360
  placeholdersByField
1287
1361
  };
1288
1362
  }
1289
- function cursorValueExpr(tableName, cursorAlias, cursorWhereSql, field, model) {
1290
- const colName = quote(field);
1291
- return `(SELECT ${cursorAlias}.${colName} ${SQL_TEMPLATES.FROM} ${tableName} ${cursorAlias} ${SQL_TEMPLATES.WHERE} ${cursorWhereSql} ${SQL_TEMPLATES.LIMIT} 1)`;
1292
- }
1293
- function buildCursorRowExistsExpr(tableName, cursorAlias, cursorWhereSql) {
1294
- return `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${tableName} ${cursorAlias} ${SQL_TEMPLATES.WHERE} ${cursorWhereSql} ${SQL_TEMPLATES.LIMIT} 1)`;
1295
- }
1296
1363
  function buildCursorEqualityExpr(columnExpr, valueExpr) {
1297
1364
  return `((${valueExpr} IS NULL AND ${columnExpr} IS NULL) OR (${valueExpr} IS NOT NULL AND ${columnExpr} = ${valueExpr}))`;
1298
1365
  }
@@ -1331,7 +1398,7 @@ function buildOrderEntries(orderBy) {
1331
1398
  } else {
1332
1399
  entries.push({
1333
1400
  field,
1334
- direction: value.sort,
1401
+ direction: value.direction,
1335
1402
  nulls: value.nulls
1336
1403
  });
1337
1404
  }
@@ -1339,16 +1406,53 @@ function buildOrderEntries(orderBy) {
1339
1406
  }
1340
1407
  return entries;
1341
1408
  }
1409
+ function buildCursorCteSelectList(cursorEntries, orderEntries, model) {
1410
+ const set = /* @__PURE__ */ new Set();
1411
+ for (const [f] of cursorEntries) set.add(f);
1412
+ for (const e of orderEntries) set.add(e.field);
1413
+ const cols = [...set].map((f) => quoteColumn(model, f));
1414
+ if (cols.length === 0) {
1415
+ throw new Error("cursor cte select list is empty");
1416
+ }
1417
+ return cols.join(SQL_SEPARATORS.FIELD_LIST);
1418
+ }
1419
+ function truncateIdent(name, maxLen) {
1420
+ const s = String(name);
1421
+ if (s.length <= maxLen) return s;
1422
+ return s.slice(0, maxLen);
1423
+ }
1424
+ function buildCursorNames(outerAlias) {
1425
+ const maxLen = 63;
1426
+ const base = outerAlias.toLowerCase();
1427
+ const cteName = truncateIdent(`__tp_cursor_${base}`, maxLen);
1428
+ const srcAlias = truncateIdent(`__tp_cursor_src_${base}`, maxLen);
1429
+ if (cteName === outerAlias || srcAlias === outerAlias) {
1430
+ return {
1431
+ cteName: truncateIdent(`__tp_cursor_${base}_x`, maxLen),
1432
+ srcAlias: truncateIdent(`__tp_cursor_src_${base}_x`, maxLen)
1433
+ };
1434
+ }
1435
+ return { cteName, srcAlias };
1436
+ }
1342
1437
  function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect, model) {
1343
1438
  var _a;
1439
+ assertSafeTableRef(tableName);
1440
+ assertSafeAlias(alias);
1344
1441
  const d = dialect != null ? dialect : getGlobalDialect();
1345
1442
  const cursorEntries = Object.entries(cursor);
1346
1443
  if (cursorEntries.length === 0) {
1347
1444
  throw new Error("cursor must have at least one field");
1348
1445
  }
1349
- const cursorAlias = "__tp_cursor_src";
1350
- const { whereSql: cursorWhereSql, placeholdersByField } = buildCursorFilterParts(cursor, cursorAlias, params);
1351
- let orderEntries = buildOrderEntries(orderBy);
1446
+ const { cteName, srcAlias } = buildCursorNames(alias);
1447
+ assertSafeAlias(cteName);
1448
+ assertSafeAlias(srcAlias);
1449
+ const { whereSql: cursorWhereSql, placeholdersByField } = buildCursorFilterParts(cursor, srcAlias, params, model);
1450
+ const deterministicOrderBy = ensureDeterministicOrderByInput({
1451
+ orderBy,
1452
+ model,
1453
+ parseValue: parseOrderByValue
1454
+ });
1455
+ let orderEntries = buildOrderEntries(deterministicOrderBy);
1352
1456
  if (orderEntries.length === 0) {
1353
1457
  orderEntries = cursorEntries.map(([field]) => ({
1354
1458
  field,
@@ -1357,11 +1461,21 @@ function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect
1357
1461
  } else {
1358
1462
  orderEntries = ensureCursorFieldsInOrder(orderEntries, cursorEntries);
1359
1463
  }
1360
- const existsExpr = buildCursorRowExistsExpr(
1361
- tableName,
1362
- cursorAlias,
1363
- cursorWhereSql
1464
+ const cursorOrderBy = orderEntries.map(
1465
+ (e) => `${srcAlias}.${quoteColumn(model, e.field)} ${e.direction.toUpperCase()}`
1466
+ ).join(", ");
1467
+ const selectList = buildCursorCteSelectList(
1468
+ cursorEntries,
1469
+ orderEntries,
1470
+ model
1364
1471
  );
1472
+ const cte = `${cteName} AS (
1473
+ SELECT ${selectList} FROM ${tableName} ${srcAlias}
1474
+ WHERE ${cursorWhereSql}
1475
+ ORDER BY ${cursorOrderBy}
1476
+ LIMIT 1
1477
+ )`;
1478
+ const existsExpr = `EXISTS (SELECT 1 FROM ${cteName})`;
1365
1479
  const outerCursorMatch = buildOuterCursorMatch(
1366
1480
  cursor,
1367
1481
  alias,
@@ -1369,34 +1483,31 @@ function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect
1369
1483
  params,
1370
1484
  model
1371
1485
  );
1486
+ const getValueExpr = (field) => {
1487
+ return `(SELECT ${quoteColumn(model, field)} FROM ${cteName})`;
1488
+ };
1372
1489
  const orClauses = [];
1373
1490
  for (let level = 0; level < orderEntries.length; level++) {
1374
1491
  const andParts = [];
1375
1492
  for (let i = 0; i < level; i++) {
1376
1493
  const e2 = orderEntries[i];
1377
1494
  const c2 = col(alias, e2.field, model);
1378
- const v2 = cursorValueExpr(
1379
- tableName,
1380
- cursorAlias,
1381
- cursorWhereSql,
1382
- e2.field);
1495
+ const v2 = getValueExpr(e2.field);
1383
1496
  andParts.push(buildCursorEqualityExpr(c2, v2));
1384
1497
  }
1385
1498
  const e = orderEntries[level];
1386
1499
  const c = col(alias, e.field, model);
1387
- const v = cursorValueExpr(
1388
- tableName,
1389
- cursorAlias,
1390
- cursorWhereSql,
1391
- e.field);
1500
+ const v = getValueExpr(e.field);
1392
1501
  const nulls = (_a = e.nulls) != null ? _a : defaultNullsFor(d, e.direction);
1393
1502
  andParts.push(buildCursorInequalityExpr(c, e.direction, nulls, v));
1394
1503
  orClauses.push(`(${andParts.join(SQL_SEPARATORS.CONDITION_AND)})`);
1395
1504
  }
1396
1505
  const exclusive = orClauses.join(SQL_SEPARATORS.CONDITION_OR);
1397
- return `(${existsExpr} ${SQL_SEPARATORS.CONDITION_AND} ((${exclusive})${SQL_SEPARATORS.CONDITION_OR}(${outerCursorMatch})))`;
1506
+ const condition = `(${existsExpr} ${SQL_SEPARATORS.CONDITION_AND} ((${exclusive})${SQL_SEPARATORS.CONDITION_OR}(${outerCursorMatch})))`;
1507
+ return { cte, condition };
1398
1508
  }
1399
1509
  function buildOrderBy(orderBy, alias, dialect, model) {
1510
+ assertSafeAlias(alias);
1400
1511
  const entries = buildOrderEntries(orderBy);
1401
1512
  if (entries.length === 0) return "";
1402
1513
  const d = dialect != null ? dialect : getGlobalDialect();
@@ -1496,6 +1607,11 @@ function buildScalarOperator(expr, op, val, params, mode, fieldType, dialect) {
1496
1607
  }
1497
1608
  return handleInOperator(expr, op, val, params, dialect);
1498
1609
  }
1610
+ if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && !isNotNullish(dialect)) {
1611
+ throw createError(`Insensitive equals requires a SQL dialect`, {
1612
+ operator: op
1613
+ });
1614
+ }
1499
1615
  return handleComparisonOperator(expr, op, val, params);
1500
1616
  }
1501
1617
  function handleNullValue(expr, op) {
@@ -1511,6 +1627,28 @@ function normalizeMode(v) {
1511
1627
  function handleNotOperator(expr, val, params, outerMode, fieldType, dialect) {
1512
1628
  const innerMode = normalizeMode(val.mode);
1513
1629
  const effectiveMode = innerMode != null ? innerMode : outerMode;
1630
+ const entries = Object.entries(val).filter(
1631
+ ([k, v]) => k !== "mode" && v !== void 0
1632
+ );
1633
+ if (entries.length === 0) return "";
1634
+ if (!isNotNullish(dialect)) {
1635
+ const clauses = [];
1636
+ for (const [subOp, subVal] of entries) {
1637
+ const sub = buildScalarOperator(
1638
+ expr,
1639
+ subOp,
1640
+ subVal,
1641
+ params,
1642
+ effectiveMode,
1643
+ fieldType,
1644
+ void 0
1645
+ );
1646
+ if (sub && sub.trim().length > 0) clauses.push(`(${sub})`);
1647
+ }
1648
+ if (clauses.length === 0) return "";
1649
+ if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
1650
+ return `${SQL_TEMPLATES.NOT} (${clauses.join(` ${SQL_TEMPLATES.AND} `)})`;
1651
+ }
1514
1652
  return buildNotComposite(
1515
1653
  expr,
1516
1654
  val,
@@ -1725,6 +1863,7 @@ function handleArrayIsEmpty(expr, val, dialect) {
1725
1863
 
1726
1864
  // src/builder/where/operators-json.ts
1727
1865
  var SAFE_JSON_PATH_SEGMENT = /^[a-zA-Z_]\w*$/;
1866
+ var MAX_PATH_SEGMENT_LENGTH = 255;
1728
1867
  function validateJsonPathSegments(segments) {
1729
1868
  for (const segment of segments) {
1730
1869
  if (typeof segment !== "string") {
@@ -1733,6 +1872,12 @@ function validateJsonPathSegments(segments) {
1733
1872
  value: segment
1734
1873
  });
1735
1874
  }
1875
+ if (segment.length > MAX_PATH_SEGMENT_LENGTH) {
1876
+ throw createError(
1877
+ `JSON path segment too long: max ${MAX_PATH_SEGMENT_LENGTH} characters`,
1878
+ { operator: Ops.PATH, value: `[${segment.length} chars]` }
1879
+ );
1880
+ }
1736
1881
  if (!SAFE_JSON_PATH_SEGMENT.test(segment)) {
1737
1882
  throw createError(
1738
1883
  `Invalid JSON path segment: '${segment}'. Must be alphanumeric with underscores, starting with letter or underscore.`,
@@ -1821,6 +1966,9 @@ function handleJsonWildcard(expr, op, val, params, wildcards, dialect) {
1821
1966
 
1822
1967
  // src/builder/where/relations.ts
1823
1968
  var NO_JOINS = Object.freeze([]);
1969
+ function freezeJoins(items) {
1970
+ return Object.freeze([...items]);
1971
+ }
1824
1972
  function isListRelation(fieldType) {
1825
1973
  return typeof fieldType === "string" && fieldType.endsWith("[]");
1826
1974
  }
@@ -1883,7 +2031,7 @@ function buildListRelationFilters(args) {
1883
2031
  const whereClause = `${relAlias}.${quote(checkField.name)} IS NULL`;
1884
2032
  return Object.freeze({
1885
2033
  clause: whereClause,
1886
- joins: [leftJoinSql]
2034
+ joins: freezeJoins([leftJoinSql])
1887
2035
  });
1888
2036
  }
1889
2037
  }
@@ -2088,7 +2236,7 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
2088
2236
  Ops.HAS_EVERY,
2089
2237
  Ops.IS_EMPTY
2090
2238
  ]);
2091
- const JSON_OPS = /* @__PURE__ */ new Set([
2239
+ const JSON_OPS2 = /* @__PURE__ */ new Set([
2092
2240
  Ops.PATH,
2093
2241
  Ops.STRING_CONTAINS,
2094
2242
  Ops.STRING_STARTS_WITH,
@@ -2105,7 +2253,7 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
2105
2253
  modelName
2106
2254
  });
2107
2255
  }
2108
- const isJsonOp = JSON_OPS.has(op);
2256
+ const isJsonOp = JSON_OPS2.has(op);
2109
2257
  const isFieldJson = isJsonType(fieldType);
2110
2258
  const jsonOpMismatch = isJsonOp && !isFieldJson;
2111
2259
  if (jsonOpMismatch) {
@@ -2119,6 +2267,14 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
2119
2267
  }
2120
2268
 
2121
2269
  // src/builder/where/builder.ts
2270
+ var MAX_QUERY_DEPTH = 50;
2271
+ var EMPTY_JOINS = Object.freeze([]);
2272
+ var JSON_OPS = /* @__PURE__ */ new Set([
2273
+ Ops.PATH,
2274
+ Ops.STRING_CONTAINS,
2275
+ Ops.STRING_STARTS_WITH,
2276
+ Ops.STRING_ENDS_WITH
2277
+ ]);
2122
2278
  var WhereBuilder = class {
2123
2279
  build(where, ctx) {
2124
2280
  if (!isPlainObject(where)) {
@@ -2130,8 +2286,6 @@ var WhereBuilder = class {
2130
2286
  return buildWhereInternal(where, ctx, this);
2131
2287
  }
2132
2288
  };
2133
- var MAX_QUERY_DEPTH = 50;
2134
- var EMPTY_JOINS = Object.freeze([]);
2135
2289
  var whereBuilderInstance = new WhereBuilder();
2136
2290
  function freezeResult(clause, joins = EMPTY_JOINS) {
2137
2291
  return Object.freeze({ clause, joins });
@@ -2308,16 +2462,8 @@ function buildOperator(expr, op, val, ctx, mode, fieldType) {
2308
2462
  if (fieldType && isArrayType(fieldType)) {
2309
2463
  return buildArrayOperator(expr, op, val, ctx.params, fieldType, ctx.dialect);
2310
2464
  }
2311
- if (fieldType && isJsonType(fieldType)) {
2312
- const JSON_OPS = /* @__PURE__ */ new Set([
2313
- Ops.PATH,
2314
- Ops.STRING_CONTAINS,
2315
- Ops.STRING_STARTS_WITH,
2316
- Ops.STRING_ENDS_WITH
2317
- ]);
2318
- if (JSON_OPS.has(op)) {
2319
- return buildJsonOperator(expr, op, val, ctx.params, ctx.dialect);
2320
- }
2465
+ if (fieldType && isJsonType(fieldType) && JSON_OPS.has(op)) {
2466
+ return buildJsonOperator(expr, op, val, ctx.params, ctx.dialect);
2321
2467
  }
2322
2468
  return buildScalarOperator(
2323
2469
  expr,
@@ -2338,7 +2484,7 @@ function toSafeSqlIdentifier(input) {
2338
2484
  const base = startsOk ? cleaned : `_${cleaned}`;
2339
2485
  const fallback = base.length > 0 ? base : "_t";
2340
2486
  const lowered = fallback.toLowerCase();
2341
- return SQL_KEYWORDS.has(lowered) ? `_${lowered}` : lowered;
2487
+ return ALIAS_FORBIDDEN_KEYWORDS.has(lowered) ? `_${lowered}` : lowered;
2342
2488
  }
2343
2489
  function createAliasGenerator(maxAliases = 1e4) {
2344
2490
  let counter = 0;
@@ -2548,6 +2694,7 @@ function toPublicResult(clause, joins, params) {
2548
2694
  // src/builder/where.ts
2549
2695
  function buildWhereClause(where, options) {
2550
2696
  var _a, _b, _c, _d, _e;
2697
+ assertSafeAlias(options.alias);
2551
2698
  const dialect = options.dialect || getGlobalDialect();
2552
2699
  const params = (_a = options.params) != null ? _a : createParamStore();
2553
2700
  const ctx = {
@@ -2716,6 +2863,9 @@ function buildRelationSelect(relArgs, relModel, relAlias) {
2716
2863
  }
2717
2864
 
2718
2865
  // src/builder/select/includes.ts
2866
+ var MAX_INCLUDE_DEPTH = 10;
2867
+ var MAX_TOTAL_SUBQUERIES = 100;
2868
+ var MAX_TOTAL_INCLUDES = 50;
2719
2869
  function getRelationTableReference(relModel, dialect) {
2720
2870
  return buildTableReference(
2721
2871
  SQL_TEMPLATES.PUBLIC_SCHEMA,
@@ -2761,107 +2911,24 @@ function relationEntriesFromArgs(args, model) {
2761
2911
  pushFrom(args.select);
2762
2912
  return out;
2763
2913
  }
2764
- function assertScalarField(model, fieldName) {
2765
- const f = model.fields.find((x) => x.name === fieldName);
2766
- if (!f) {
2767
- throw new Error(
2768
- `orderBy references unknown field '${fieldName}' on model ${model.name}`
2769
- );
2770
- }
2771
- if (f.isRelation) {
2772
- throw new Error(
2773
- `orderBy does not support relation field '${fieldName}' on model ${model.name}`
2774
- );
2775
- }
2776
- }
2777
- function validateOrderByDirection(fieldName, v) {
2778
- const s = String(v).toLowerCase();
2779
- if (s !== "asc" && s !== "desc") {
2780
- throw new Error(
2781
- `Invalid orderBy direction for '${fieldName}': ${String(v)}`
2782
- );
2783
- }
2784
- }
2785
- function validateOrderByObject(fieldName, v) {
2786
- if (!("sort" in v)) {
2787
- throw new Error(
2788
- `orderBy for '${fieldName}' must be 'asc' | 'desc' or { sort, nulls? }`
2789
- );
2790
- }
2791
- validateOrderByDirection(fieldName, v.sort);
2792
- if ("nulls" in v && isNotNullish(v.nulls)) {
2793
- const n = String(v.nulls).toLowerCase();
2794
- if (n !== "first" && n !== "last") {
2795
- throw new Error(
2796
- `Invalid orderBy.nulls for '${fieldName}': ${String(v.nulls)}`
2797
- );
2798
- }
2799
- }
2800
- const allowed = /* @__PURE__ */ new Set(["sort", "nulls"]);
2801
- for (const k of Object.keys(v)) {
2802
- if (!allowed.has(k)) {
2803
- throw new Error(`Unsupported orderBy key '${k}' for field '${fieldName}'`);
2804
- }
2805
- }
2806
- }
2807
- function normalizeOrderByFieldName(name) {
2808
- const fieldName = String(name).trim();
2809
- if (fieldName.length === 0) {
2810
- throw new Error("orderBy field name cannot be empty");
2811
- }
2812
- return fieldName;
2813
- }
2814
- function requirePlainObjectForOrderByEntry(v) {
2815
- if (typeof v !== "object" || v === null || Array.isArray(v)) {
2816
- throw new Error("orderBy array entries must be objects");
2817
- }
2818
- return v;
2819
- }
2820
- function parseSingleFieldOrderByObject(obj) {
2821
- const entries = Object.entries(obj);
2822
- if (entries.length !== 1) {
2823
- throw new Error("orderBy array entries must have exactly one field");
2824
- }
2825
- const fieldName = normalizeOrderByFieldName(entries[0][0]);
2826
- return [fieldName, entries[0][1]];
2827
- }
2828
- function parseOrderByArray(orderBy) {
2829
- return orderBy.map(
2830
- (item) => parseSingleFieldOrderByObject(requirePlainObjectForOrderByEntry(item))
2831
- );
2832
- }
2833
- function parseOrderByObject(orderBy) {
2834
- const out = [];
2835
- for (const [k, v] of Object.entries(orderBy)) {
2836
- out.push([normalizeOrderByFieldName(k), v]);
2837
- }
2838
- return out;
2839
- }
2840
- function getOrderByEntries(orderBy) {
2841
- if (!isNotNullish(orderBy)) return [];
2842
- if (Array.isArray(orderBy)) {
2843
- return parseOrderByArray(orderBy);
2844
- }
2845
- if (typeof orderBy === "object" && orderBy !== null) {
2846
- return parseOrderByObject(orderBy);
2847
- }
2848
- throw new Error("orderBy must be an object or array of objects");
2849
- }
2850
2914
  function validateOrderByForModel(model, orderBy) {
2851
- const entries = getOrderByEntries(orderBy);
2852
- for (const [fieldName, v] of entries) {
2853
- assertScalarField(model, fieldName);
2854
- if (typeof v === "string") {
2855
- validateOrderByDirection(fieldName, v);
2856
- continue;
2915
+ if (!isNotNullish(orderBy)) return;
2916
+ const scalarSet = getScalarFieldSet(model);
2917
+ const normalized = normalizeOrderByInput(orderBy, parseOrderByValue);
2918
+ for (const item of normalized) {
2919
+ const entries = Object.entries(item);
2920
+ if (entries.length !== 1) {
2921
+ throw new Error("orderBy array entries must have exactly one field");
2857
2922
  }
2858
- if (isPlainObject(v)) {
2859
- validateOrderByObject(fieldName, v);
2860
- continue;
2923
+ const fieldName = String(entries[0][0]).trim();
2924
+ if (fieldName.length === 0) {
2925
+ throw new Error("orderBy field name cannot be empty");
2926
+ }
2927
+ if (!scalarSet.has(fieldName)) {
2928
+ throw new Error(
2929
+ `orderBy references unknown or non-scalar field '${fieldName}' on model ${model.name}`
2930
+ );
2861
2931
  }
2862
- throw new Error(
2863
- `orderBy for '${fieldName}' must be 'asc' | 'desc' or { sort, nulls? }`
2864
- );
2865
2932
  }
2866
2933
  }
2867
2934
  function appendLimitOffset(sql, dialect, params, takeVal, skipVal, scope) {
@@ -2890,7 +2957,10 @@ function readWhereInput(relArgs) {
2890
2957
  function readOrderByInput(relArgs) {
2891
2958
  if (!isPlainObject(relArgs)) return { hasOrderBy: false, orderBy: void 0 };
2892
2959
  if (!("orderBy" in relArgs)) return { hasOrderBy: false, orderBy: void 0 };
2893
- return { hasOrderBy: true, orderBy: relArgs.orderBy };
2960
+ return {
2961
+ hasOrderBy: true,
2962
+ orderBy: relArgs.orderBy
2963
+ };
2894
2964
  }
2895
2965
  function extractRelationPaginationConfig(relArgs) {
2896
2966
  const { hasOrderBy, orderBy: rawOrderByInput } = readOrderByInput(relArgs);
@@ -2920,36 +2990,28 @@ function maybeReverseNegativeTake(takeVal, hasOrderBy, orderByInput) {
2920
2990
  orderByInput: reverseOrderByInput(orderByInput)
2921
2991
  };
2922
2992
  }
2923
- function hasIdTiebreaker(orderByInput) {
2924
- const entries = Array.isArray(orderByInput) ? orderByInput : [orderByInput];
2925
- return entries.some(
2926
- (entry) => isPlainObject(entry) ? Object.prototype.hasOwnProperty.call(entry, "id") : false
2927
- );
2928
- }
2929
- function modelHasScalarId(relModel) {
2930
- const idField = relModel.fields.find((f) => f.name === "id");
2931
- return Boolean(idField && !idField.isRelation);
2932
- }
2933
- function addIdTiebreaker(orderByInput) {
2934
- if (Array.isArray(orderByInput)) return [...orderByInput, { id: "asc" }];
2935
- return [orderByInput, { id: "asc" }];
2936
- }
2937
- function ensureDeterministicOrderBy(relModel, hasOrderBy, orderByInput, hasPagination) {
2938
- if (!hasPagination) {
2939
- if (hasOrderBy && isNotNullish(orderByInput)) {
2940
- validateOrderByForModel(relModel, orderByInput);
2993
+ function ensureDeterministicOrderByForInclude(args) {
2994
+ if (!args.hasPagination) {
2995
+ if (args.hasOrderBy && isNotNullish(args.orderByInput)) {
2996
+ validateOrderByForModel(args.relModel, args.orderByInput);
2941
2997
  }
2942
- return orderByInput;
2998
+ return args.orderByInput;
2943
2999
  }
2944
- if (!hasOrderBy) {
2945
- return modelHasScalarId(relModel) ? { id: "asc" } : orderByInput;
3000
+ if (!args.hasOrderBy) {
3001
+ return ensureDeterministicOrderByInput({
3002
+ orderBy: void 0,
3003
+ model: args.relModel,
3004
+ parseValue: parseOrderByValue
3005
+ });
2946
3006
  }
2947
- if (isNotNullish(orderByInput)) {
2948
- validateOrderByForModel(relModel, orderByInput);
3007
+ if (isNotNullish(args.orderByInput)) {
3008
+ validateOrderByForModel(args.relModel, args.orderByInput);
2949
3009
  }
2950
- if (!modelHasScalarId(relModel)) return orderByInput;
2951
- if (hasIdTiebreaker(orderByInput)) return orderByInput;
2952
- return addIdTiebreaker(orderByInput);
3010
+ return ensureDeterministicOrderByInput({
3011
+ orderBy: args.orderByInput,
3012
+ model: args.relModel,
3013
+ parseValue: parseOrderByValue
3014
+ });
2953
3015
  }
2954
3016
  function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
2955
3017
  let relSelect = buildRelationSelect(relArgs, relModel, relAlias);
@@ -2960,7 +3022,10 @@ function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
2960
3022
  relAlias,
2961
3023
  ctx.aliasGen,
2962
3024
  ctx.params,
2963
- ctx.dialect
3025
+ ctx.dialect,
3026
+ ctx.visitPath || [],
3027
+ (ctx.depth || 0) + 1,
3028
+ ctx.stats
2964
3029
  ) : [];
2965
3030
  if (isNonEmptyArray(nestedIncludes)) {
2966
3031
  const emptyJson = ctx.dialect === "postgres" ? `'[]'::json` : `json('[]')`;
@@ -3110,12 +3175,12 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3110
3175
  paginationConfig.orderBy
3111
3176
  );
3112
3177
  const hasPagination = paginationConfig.hasSkip || paginationConfig.hasTake;
3113
- const finalOrderByInput = ensureDeterministicOrderBy(
3178
+ const finalOrderByInput = ensureDeterministicOrderByForInclude({
3114
3179
  relModel,
3115
- paginationConfig.hasOrderBy,
3116
- adjusted.orderByInput,
3180
+ hasOrderBy: paginationConfig.hasOrderBy,
3181
+ orderByInput: adjusted.orderByInput,
3117
3182
  hasPagination
3118
- );
3183
+ });
3119
3184
  const orderBySql = buildOrderBySql(
3120
3185
  finalOrderByInput,
3121
3186
  relAlias,
@@ -3156,12 +3221,48 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3156
3221
  ctx
3157
3222
  });
3158
3223
  }
3159
- function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, params, dialect) {
3224
+ function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, params, dialect, visitPath = [], depth = 0, stats) {
3225
+ if (!stats) {
3226
+ stats = { totalIncludes: 0, totalSubqueries: 0, maxDepth: 0 };
3227
+ }
3228
+ if (depth > MAX_INCLUDE_DEPTH) {
3229
+ throw new Error(
3230
+ `Maximum include depth of ${MAX_INCLUDE_DEPTH} exceeded. Path: ${visitPath.join(" -> ")}. Deep includes cause exponential SQL complexity and performance issues.`
3231
+ );
3232
+ }
3233
+ stats.maxDepth = Math.max(stats.maxDepth, depth);
3160
3234
  const includes = [];
3161
3235
  const entries = relationEntriesFromArgs(args, model);
3162
3236
  for (const [relName, relArgs] of entries) {
3163
3237
  if (relArgs === false) continue;
3238
+ stats.totalIncludes++;
3239
+ if (stats.totalIncludes > MAX_TOTAL_INCLUDES) {
3240
+ throw new Error(
3241
+ `Maximum total includes (${MAX_TOTAL_INCLUDES}) exceeded. Current: ${stats.totalIncludes} includes. Query has ${stats.maxDepth} levels deep. Simplify your query structure or use multiple queries.`
3242
+ );
3243
+ }
3244
+ stats.totalSubqueries++;
3245
+ if (stats.totalSubqueries > MAX_TOTAL_SUBQUERIES) {
3246
+ throw new Error(
3247
+ `Query complexity limit exceeded: ${stats.totalSubqueries} subqueries generated. Maximum allowed: ${MAX_TOTAL_SUBQUERIES}. This indicates exponential include nesting. Stats: depth=${stats.maxDepth}, includes=${stats.totalIncludes}. Path: ${visitPath.join(" -> ")}. Simplify your include structure or split into multiple queries.`
3248
+ );
3249
+ }
3164
3250
  const resolved = resolveRelationOrThrow(model, schemas, relName);
3251
+ const relationPath = `${model.name}.${relName}`;
3252
+ const currentPath = [...visitPath, relationPath];
3253
+ if (visitPath.includes(relationPath)) {
3254
+ throw new Error(
3255
+ `Circular include detected: ${currentPath.join(" -> ")}. Relation '${relationPath}' creates an infinite loop.`
3256
+ );
3257
+ }
3258
+ const modelOccurrences = currentPath.filter(
3259
+ (p) => p.startsWith(`${resolved.relModel.name}.`)
3260
+ ).length;
3261
+ if (modelOccurrences > 2) {
3262
+ throw new Error(
3263
+ `Include too deeply nested: model '${resolved.relModel.name}' appears ${modelOccurrences} times in path: ${currentPath.join(" -> ")}`
3264
+ );
3265
+ }
3165
3266
  const include = buildSingleInclude(
3166
3267
  relName,
3167
3268
  relArgs,
@@ -3173,7 +3274,10 @@ function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, pa
3173
3274
  parentAlias,
3174
3275
  aliasGen,
3175
3276
  dialect,
3176
- params
3277
+ params,
3278
+ visitPath: currentPath,
3279
+ depth: depth + 1,
3280
+ stats
3177
3281
  }
3178
3282
  );
3179
3283
  includes.push(include);
@@ -3182,6 +3286,11 @@ function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, pa
3182
3286
  }
3183
3287
  function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
3184
3288
  const aliasGen = createAliasGenerator();
3289
+ const stats = {
3290
+ totalIncludes: 0,
3291
+ totalSubqueries: 0,
3292
+ maxDepth: 0
3293
+ };
3185
3294
  return buildIncludeSqlInternal(
3186
3295
  args,
3187
3296
  model,
@@ -3189,7 +3298,10 @@ function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
3189
3298
  parentAlias,
3190
3299
  aliasGen,
3191
3300
  params,
3192
- dialect
3301
+ dialect,
3302
+ [],
3303
+ 0,
3304
+ stats
3193
3305
  );
3194
3306
  }
3195
3307
  function resolveCountRelationOrThrow(relName, model, schemas) {
@@ -3205,6 +3317,11 @@ function resolveCountRelationOrThrow(relName, model, schemas) {
3205
3317
  `_count.${relName} references unknown relation on model ${model.name}`
3206
3318
  );
3207
3319
  }
3320
+ if (!isValidRelationField(field)) {
3321
+ throw new Error(
3322
+ `_count.${relName} has invalid relation metadata on model ${model.name}`
3323
+ );
3324
+ }
3208
3325
  const relModel = schemas.find((m) => m.name === field.relatedModel);
3209
3326
  if (!relModel) {
3210
3327
  throw new Error(
@@ -3213,31 +3330,81 @@ function resolveCountRelationOrThrow(relName, model, schemas) {
3213
3330
  }
3214
3331
  return { field, relModel };
3215
3332
  }
3216
- function groupByColForCount(field, countAlias) {
3217
- const fkFields = normalizeKeyList(field.foreignKey);
3218
- const refFields = normalizeKeyList(field.references);
3219
- return field.isForeignKeyLocal ? `${countAlias}.${quote(refFields[0] || "id")}` : `${countAlias}.${quote(fkFields[0])}`;
3333
+ function defaultReferencesForCount(fkCount) {
3334
+ if (fkCount === 1) return ["id"];
3335
+ throw new Error(
3336
+ "Relation count for composite keys requires explicit references matching foreignKey length"
3337
+ );
3220
3338
  }
3221
- function leftJoinOnForCount(field, parentAlias, joinAlias) {
3339
+ function resolveCountKeyPairs(field) {
3222
3340
  const fkFields = normalizeKeyList(field.foreignKey);
3223
- const refFields = normalizeKeyList(field.references);
3224
- return field.isForeignKeyLocal ? `${joinAlias}.__fk = ${parentAlias}.${quote(fkFields[0])}` : `${joinAlias}.__fk = ${parentAlias}.${quote(refFields[0] || "id")}`;
3341
+ if (fkFields.length === 0) {
3342
+ throw new Error("Relation count requires foreignKey");
3343
+ }
3344
+ const refsRaw = field.references;
3345
+ const refs = normalizeKeyList(refsRaw);
3346
+ const refFields = refs.length > 0 ? refs : defaultReferencesForCount(fkFields.length);
3347
+ if (refFields.length !== fkFields.length) {
3348
+ throw new Error(
3349
+ "Relation count requires references count to match foreignKey count"
3350
+ );
3351
+ }
3352
+ const relKeyFields = field.isForeignKeyLocal ? refFields : fkFields;
3353
+ const parentKeyFields = field.isForeignKeyLocal ? fkFields : refFields;
3354
+ return { relKeyFields, parentKeyFields };
3355
+ }
3356
+ function aliasQualifiedColumn(alias, model, field) {
3357
+ return `${alias}.${quoteColumn(model, field)}`;
3358
+ }
3359
+ function subqueryForCount(args) {
3360
+ const selectKeys = args.relKeyFields.map(
3361
+ (f, i) => `${aliasQualifiedColumn(args.countAlias, args.relModel, f)} AS "__fk${i}"`
3362
+ ).join(SQL_SEPARATORS.FIELD_LIST);
3363
+ const groupByKeys = args.relKeyFields.map((f) => aliasQualifiedColumn(args.countAlias, args.relModel, f)).join(SQL_SEPARATORS.FIELD_LIST);
3364
+ const cntExpr = args.dialect === "postgres" ? "COUNT(*)::int AS __cnt" : "COUNT(*) AS __cnt";
3365
+ return `(SELECT ${selectKeys}${SQL_SEPARATORS.FIELD_LIST}${cntExpr} FROM ${args.relTable} ${args.countAlias} GROUP BY ${groupByKeys})`;
3366
+ }
3367
+ function leftJoinOnForCount(args) {
3368
+ const parts = args.parentKeyFields.map(
3369
+ (f, i) => `${args.joinAlias}."__fk${i}" = ${aliasQualifiedColumn(args.parentAlias, args.parentModel, f)}`
3370
+ );
3371
+ return parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`;
3225
3372
  }
3226
- function subqueryForCount(dialect, relTable, countAlias, groupByCol) {
3227
- return dialect === "postgres" ? `(SELECT ${groupByCol} AS __fk, COUNT(*)::int AS __cnt FROM ${relTable} ${countAlias} GROUP BY ${groupByCol})` : `(SELECT ${groupByCol} AS __fk, COUNT(*) AS __cnt FROM ${relTable} ${countAlias} GROUP BY ${groupByCol})`;
3373
+ function nextAliasAvoiding(aliasGen, base, forbidden) {
3374
+ let a = aliasGen.next(base);
3375
+ while (forbidden.has(a)) {
3376
+ a = aliasGen.next(base);
3377
+ }
3378
+ return a;
3228
3379
  }
3229
3380
  function buildCountJoinAndPair(args) {
3230
3381
  const relTable = getRelationTableReference(args.relModel, args.dialect);
3231
- const countAlias = `__tp_cnt_${args.relName}`;
3232
- const groupByCol = groupByColForCount(args.field, countAlias);
3233
- const subquery = subqueryForCount(
3234
- args.dialect,
3382
+ const { relKeyFields, parentKeyFields } = resolveCountKeyPairs(args.field);
3383
+ const forbidden = /* @__PURE__ */ new Set([args.parentAlias]);
3384
+ const countAlias = nextAliasAvoiding(
3385
+ args.aliasGen,
3386
+ `__tp_cnt_${args.relName}`,
3387
+ forbidden
3388
+ );
3389
+ forbidden.add(countAlias);
3390
+ const subquery = subqueryForCount({
3391
+ dialect: args.dialect,
3235
3392
  relTable,
3236
3393
  countAlias,
3237
- groupByCol
3394
+ relModel: args.relModel,
3395
+ relKeyFields
3396
+ });
3397
+ const joinAlias = nextAliasAvoiding(
3398
+ args.aliasGen,
3399
+ `__tp_cnt_j_${args.relName}`,
3400
+ forbidden
3238
3401
  );
3239
- const joinAlias = `__tp_cnt_j_${args.relName}`;
3240
- const leftJoinOn = leftJoinOnForCount(args.field, args.parentAlias, joinAlias);
3402
+ const leftJoinOn = leftJoinOnForCount({
3403
+ joinAlias,
3404
+ parentAlias: args.parentAlias,
3405
+ parentModel: args.parentModel,
3406
+ parentKeyFields
3407
+ });
3241
3408
  return {
3242
3409
  joinSql: `LEFT JOIN ${subquery} ${joinAlias} ON ${leftJoinOn}`,
3243
3410
  pairSql: `${sqlStringLiteral(args.relName)}, COALESCE(${joinAlias}.__cnt, 0)`
@@ -3246,6 +3413,7 @@ function buildCountJoinAndPair(args) {
3246
3413
  function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params, dialect) {
3247
3414
  const joins = [];
3248
3415
  const pairs = [];
3416
+ const aliasGen = createAliasGenerator();
3249
3417
  for (const [relName, shouldCount] of Object.entries(countSelect)) {
3250
3418
  if (!shouldCount) continue;
3251
3419
  const resolved = resolveCountRelationOrThrow(relName, model, schemas);
@@ -3253,8 +3421,10 @@ function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params
3253
3421
  relName,
3254
3422
  field: resolved.field,
3255
3423
  relModel: resolved.relModel,
3424
+ parentModel: model,
3256
3425
  parentAlias,
3257
- dialect
3426
+ dialect,
3427
+ aliasGen
3258
3428
  });
3259
3429
  joins.push(built.joinSql);
3260
3430
  pairs.push(built.pairSql);
@@ -3266,16 +3436,21 @@ function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params
3266
3436
  }
3267
3437
 
3268
3438
  // src/builder/select/assembly.ts
3269
- var SIMPLE_SELECT_RE_CACHE = /* @__PURE__ */ new Map();
3270
- function normalizeFinalParams(params) {
3271
- return params.map(normalizeValue);
3272
- }
3439
+ var ALIAS_CAPTURE = "([A-Za-z_][A-Za-z0-9_]*)";
3440
+ var COLUMN_PART = '(?:"([^"]+)"|([a-z_][a-z0-9_]*))';
3441
+ var AS_PART = `(?:\\s+AS\\s+${COLUMN_PART})?`;
3442
+ var SIMPLE_COLUMN_PATTERN = `^${ALIAS_CAPTURE}\\.${COLUMN_PART}${AS_PART}$`;
3443
+ var SIMPLE_COLUMN_RE = new RegExp(SIMPLE_COLUMN_PATTERN, "i");
3273
3444
  function joinNonEmpty(parts, sep) {
3274
3445
  return parts.filter((s) => s.trim().length > 0).join(sep);
3275
3446
  }
3276
3447
  function buildWhereSql(conditions) {
3277
3448
  if (!isNonEmptyArray(conditions)) return "";
3278
- return ` ${SQL_TEMPLATES.WHERE} ${conditions.join(SQL_SEPARATORS.CONDITION_AND)}`;
3449
+ const parts = [
3450
+ SQL_TEMPLATES.WHERE,
3451
+ conditions.join(SQL_SEPARATORS.CONDITION_AND)
3452
+ ];
3453
+ return ` ${parts.join(" ")}`;
3279
3454
  }
3280
3455
  function buildJoinsSql(...joinGroups) {
3281
3456
  const all = [];
@@ -3290,37 +3465,39 @@ function buildSelectList(baseSelect, extraCols) {
3290
3465
  if (base && extra) return `${base}${SQL_SEPARATORS.FIELD_LIST}${extra}`;
3291
3466
  return base || extra;
3292
3467
  }
3293
- function finalizeSql(sql, params) {
3468
+ function finalizeSql(sql, params, dialect) {
3294
3469
  const snapshot = params.snapshot();
3295
3470
  validateSelectQuery(sql);
3296
- validateParamConsistency(sql, snapshot.params);
3471
+ validateParamConsistencyByDialect(sql, snapshot.params, dialect);
3297
3472
  return Object.freeze({
3298
3473
  sql,
3299
- params: normalizeFinalParams(snapshot.params),
3474
+ params: snapshot.params,
3300
3475
  paramMappings: Object.freeze(snapshot.mappings)
3301
3476
  });
3302
3477
  }
3303
- function parseSimpleScalarSelect(select, alias) {
3304
- var _a, _b;
3478
+ function parseSimpleScalarSelect(select, fromAlias) {
3479
+ var _a, _b, _c, _d;
3305
3480
  const raw = select.trim();
3306
3481
  if (raw.length === 0) return [];
3307
- let re = SIMPLE_SELECT_RE_CACHE.get(alias);
3308
- if (!re) {
3309
- const safeAlias2 = alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3310
- re = new RegExp(`^${safeAlias2}\\.(?:"([^"]+)"|([a-z_][a-z0-9_]*))$`, "i");
3311
- SIMPLE_SELECT_RE_CACHE.set(alias, re);
3312
- }
3313
3482
  const parts = raw.split(SQL_SEPARATORS.FIELD_LIST);
3314
3483
  const names = [];
3315
3484
  for (const part of parts) {
3316
3485
  const p = part.trim();
3317
- const m = p.match(re);
3486
+ const m = p.match(SIMPLE_COLUMN_RE);
3318
3487
  if (!m) {
3319
3488
  throw new Error(
3320
- `sqlite distinct emulation requires scalar select fields to be simple columns. Got: ${p}`
3489
+ `sqlite distinct emulation requires scalar select fields to be simple columns (optionally with AS). Got: ${p}`
3321
3490
  );
3322
3491
  }
3323
- const name = ((_b = (_a = m[1]) != null ? _a : m[2]) != null ? _b : "").trim();
3492
+ const actualAlias = m[1];
3493
+ if (actualAlias.toLowerCase() !== fromAlias.toLowerCase()) {
3494
+ throw new Error(
3495
+ `Expected alias '${fromAlias}', got '${actualAlias}' in: ${p}`
3496
+ );
3497
+ }
3498
+ const columnName = ((_b = (_a = m[2]) != null ? _a : m[3]) != null ? _b : "").trim();
3499
+ const outAlias = ((_d = (_c = m[4]) != null ? _c : m[5]) != null ? _d : "").trim();
3500
+ const name = outAlias.length > 0 ? outAlias : columnName;
3324
3501
  if (name.length === 0) {
3325
3502
  throw new Error(`Failed to parse selected column name from: ${p}`);
3326
3503
  }
@@ -3329,18 +3506,18 @@ function parseSimpleScalarSelect(select, alias) {
3329
3506
  return names;
3330
3507
  }
3331
3508
  function replaceOrderByAlias(orderBy, fromAlias, outerAlias) {
3332
- const needle = `${fromAlias}.`;
3333
- const replacement = `${outerAlias}.`;
3334
- return orderBy.split(needle).join(replacement);
3509
+ const src = String(fromAlias);
3510
+ if (src.length === 0) return orderBy;
3511
+ const escaped = src.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3512
+ const re = new RegExp(`\\b${escaped}\\.`, "gi");
3513
+ return orderBy.replace(re, `${outerAlias}.`);
3335
3514
  }
3336
3515
  function buildDistinctColumns(distinct, fromAlias, model) {
3337
3516
  return distinct.map((f) => col(fromAlias, f, model)).join(SQL_SEPARATORS.FIELD_LIST);
3338
3517
  }
3339
3518
  function buildOutputColumns(scalarNames, includeNames, hasCount) {
3340
3519
  const outputCols = [...scalarNames, ...includeNames];
3341
- if (hasCount) {
3342
- outputCols.push("_count");
3343
- }
3520
+ if (hasCount) outputCols.push("_count");
3344
3521
  const formatted = outputCols.map((n) => quote(n)).join(SQL_SEPARATORS.FIELD_LIST);
3345
3522
  if (!isNonEmptyString(formatted)) {
3346
3523
  throw new Error("distinct emulation requires at least one output column");
@@ -3349,9 +3526,10 @@ function buildOutputColumns(scalarNames, includeNames, hasCount) {
3349
3526
  }
3350
3527
  function buildWindowOrder(args) {
3351
3528
  const { baseOrder, idField, fromAlias, model } = args;
3529
+ const fromLower = String(fromAlias).toLowerCase();
3352
3530
  const orderFields = baseOrder.split(SQL_SEPARATORS.ORDER_BY).map((s) => s.trim().toLowerCase());
3353
3531
  const hasIdInOrder = orderFields.some(
3354
- (f) => f.startsWith(`${fromAlias}.id `) || f.startsWith(`${fromAlias}."id" `)
3532
+ (f) => f.startsWith(`${fromLower}.id `) || f.startsWith(`${fromLower}."id" `)
3355
3533
  );
3356
3534
  if (hasIdInOrder) return baseOrder;
3357
3535
  const idTiebreaker = idField ? `, ${col(fromAlias, "id", model)} ASC` : "";
@@ -3386,15 +3564,37 @@ function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
3386
3564
  const outerOrder = isNonEmptyString(orderBy) ? replaceOrderByAlias(orderBy, from.alias, `"__tp_distinct"`) : replaceOrderByAlias(fallbackOrder, from.alias, `"__tp_distinct"`);
3387
3565
  const joins = buildJoinsSql(whereJoins, countJoins);
3388
3566
  const conditions = [];
3389
- if (whereClause && whereClause !== "1=1") {
3390
- conditions.push(whereClause);
3391
- }
3567
+ if (whereClause && whereClause !== "1=1") conditions.push(whereClause);
3392
3568
  const whereSql = buildWhereSql(conditions);
3393
3569
  const innerSelectList = selectWithIncludes.trim();
3394
3570
  const innerComma = innerSelectList.length > 0 ? SQL_SEPARATORS.FIELD_LIST : "";
3395
- const inner = `${SQL_TEMPLATES.SELECT} ${innerSelectList}${innerComma}ROW_NUMBER() OVER (PARTITION BY ${distinctCols} ORDER BY ${windowOrder}) ${SQL_TEMPLATES.AS} "__tp_rn" ${SQL_TEMPLATES.FROM} ${from.table} ${from.alias}${joins}${whereSql}`;
3396
- const outer = `${SQL_TEMPLATES.SELECT} ${outerSelectCols} ${SQL_TEMPLATES.FROM} (${inner}) ${SQL_TEMPLATES.AS} "__tp_distinct" ${SQL_TEMPLATES.WHERE} "__tp_rn" = 1` + (isNonEmptyString(outerOrder) ? ` ${SQL_TEMPLATES.ORDER_BY} ${outerOrder}` : "");
3397
- return outer;
3571
+ const innerParts = [
3572
+ SQL_TEMPLATES.SELECT,
3573
+ innerSelectList + innerComma,
3574
+ `ROW_NUMBER() OVER (PARTITION BY ${distinctCols} ORDER BY ${windowOrder})`,
3575
+ SQL_TEMPLATES.AS,
3576
+ '"__tp_rn"',
3577
+ SQL_TEMPLATES.FROM,
3578
+ from.table,
3579
+ from.alias
3580
+ ];
3581
+ if (joins) innerParts.push(joins);
3582
+ if (whereSql) innerParts.push(whereSql);
3583
+ const inner = innerParts.filter(Boolean).join(" ");
3584
+ const outerParts = [
3585
+ SQL_TEMPLATES.SELECT,
3586
+ outerSelectCols,
3587
+ SQL_TEMPLATES.FROM,
3588
+ `(${inner})`,
3589
+ SQL_TEMPLATES.AS,
3590
+ '"__tp_distinct"',
3591
+ SQL_TEMPLATES.WHERE,
3592
+ '"__tp_rn" = 1'
3593
+ ];
3594
+ if (isNonEmptyString(outerOrder)) {
3595
+ outerParts.push(SQL_TEMPLATES.ORDER_BY, outerOrder);
3596
+ }
3597
+ return outerParts.filter(Boolean).join(" ");
3398
3598
  }
3399
3599
  function buildIncludeColumns(spec) {
3400
3600
  var _a, _b;
@@ -3522,6 +3722,7 @@ function constructFinalSql(spec) {
3522
3722
  orderBy,
3523
3723
  distinct,
3524
3724
  method,
3725
+ cursorCte,
3525
3726
  cursorClause,
3526
3727
  params,
3527
3728
  dialect,
@@ -3536,9 +3737,13 @@ function constructFinalSql(spec) {
3536
3737
  const spec2 = withCountJoins(spec, countJoins, whereJoins);
3537
3738
  let sql2 = buildSqliteDistinctQuery(spec2, selectWithIncludes).trim();
3538
3739
  sql2 = appendPagination(sql2, spec);
3539
- return finalizeSql(sql2, params);
3740
+ return finalizeSql(sql2, params, dialect);
3540
3741
  }
3541
- const parts = [SQL_TEMPLATES.SELECT];
3742
+ const parts = [];
3743
+ if (cursorCte) {
3744
+ parts.push(`WITH ${cursorCte}`);
3745
+ }
3746
+ parts.push(SQL_TEMPLATES.SELECT);
3542
3747
  const distinctOn = dialect === "postgres" ? buildPostgresDistinctOnClause(from.alias, distinct, model) : null;
3543
3748
  if (distinctOn) parts.push(distinctOn);
3544
3749
  const baseSelect = (select != null ? select : "").trim();
@@ -3554,7 +3759,41 @@ function constructFinalSql(spec) {
3554
3759
  if (isNonEmptyString(orderBy)) parts.push(SQL_TEMPLATES.ORDER_BY, orderBy);
3555
3760
  let sql = parts.join(" ").trim();
3556
3761
  sql = appendPagination(sql, spec);
3557
- return finalizeSql(sql, params);
3762
+ return finalizeSql(sql, params, dialect);
3763
+ }
3764
+
3765
+ // src/builder/shared/validators/field-assertions.ts
3766
+ var NUMERIC_TYPES = /* @__PURE__ */ new Set(["Int", "Float", "Decimal", "BigInt"]);
3767
+ function assertScalarField(model, fieldName, context) {
3768
+ const field = getFieldInfo(model, fieldName);
3769
+ if (!field) {
3770
+ throw createError(
3771
+ `${context} references unknown field '${fieldName}' on model ${model.name}`,
3772
+ {
3773
+ field: fieldName,
3774
+ modelName: model.name,
3775
+ availableFields: model.fields.map((f) => f.name)
3776
+ }
3777
+ );
3778
+ }
3779
+ if (field.isRelation) {
3780
+ throw createError(
3781
+ `${context} does not support relation field '${fieldName}'`,
3782
+ { field: fieldName, modelName: model.name }
3783
+ );
3784
+ }
3785
+ return field;
3786
+ }
3787
+ function assertNumericField(model, fieldName, context) {
3788
+ const field = assertScalarField(model, fieldName, context);
3789
+ const baseType = field.type.replace(/\[\]|\?/g, "");
3790
+ if (!NUMERIC_TYPES.has(baseType)) {
3791
+ throw createError(
3792
+ `${context} requires numeric field, got '${field.type}'`,
3793
+ { field: fieldName, modelName: model.name }
3794
+ );
3795
+ }
3796
+ return field;
3558
3797
  }
3559
3798
 
3560
3799
  // src/builder/select.ts
@@ -3587,7 +3826,7 @@ function buildPostgresDistinctOrderBy(distinctFields, existing) {
3587
3826
  }
3588
3827
  return next;
3589
3828
  }
3590
- function applyPostgresDistinctOrderBy(args, _model) {
3829
+ function applyPostgresDistinctOrderBy(args) {
3591
3830
  const distinctFields = normalizeDistinctFields(args.distinct);
3592
3831
  if (distinctFields.length === 0) return args;
3593
3832
  if (!isNotNullish(args.orderBy)) return args;
@@ -3597,19 +3836,6 @@ function applyPostgresDistinctOrderBy(args, _model) {
3597
3836
  orderBy: buildPostgresDistinctOrderBy(distinctFields, existing)
3598
3837
  });
3599
3838
  }
3600
- function assertScalarFieldOnModel(model, fieldName, ctx) {
3601
- const f = model.fields.find((x) => x.name === fieldName);
3602
- if (!f) {
3603
- throw new Error(
3604
- `${ctx} references unknown field '${fieldName}' on model ${model.name}`
3605
- );
3606
- }
3607
- if (f.isRelation) {
3608
- throw new Error(
3609
- `${ctx} does not support relation field '${fieldName}' on model ${model.name}`
3610
- );
3611
- }
3612
- }
3613
3839
  function validateDistinct(model, distinct) {
3614
3840
  if (!isNotNullish(distinct) || !isNonEmptyArray(distinct)) return;
3615
3841
  const seen = /* @__PURE__ */ new Set();
@@ -3620,24 +3846,24 @@ function validateDistinct(model, distinct) {
3620
3846
  throw new Error(`distinct must not contain duplicates (field: '${f}')`);
3621
3847
  }
3622
3848
  seen.add(f);
3623
- assertScalarFieldOnModel(model, f, "distinct");
3849
+ assertScalarField(model, f, "distinct");
3624
3850
  }
3625
3851
  }
3626
- function validateOrderByValue(fieldName, v) {
3627
- parseOrderByValue(v, fieldName);
3628
- }
3629
3852
  function validateOrderBy(model, orderBy) {
3630
3853
  if (!isNotNullish(orderBy)) return;
3631
3854
  const items = normalizeOrderByInput2(orderBy);
3632
3855
  if (items.length === 0) return;
3633
3856
  for (const it of items) {
3634
3857
  const entries = Object.entries(it);
3858
+ if (entries.length !== 1) {
3859
+ throw new Error("orderBy array entries must have exactly one field");
3860
+ }
3635
3861
  const fieldName = String(entries[0][0]).trim();
3636
3862
  if (fieldName.length === 0) {
3637
3863
  throw new Error("orderBy field name cannot be empty");
3638
3864
  }
3639
- assertScalarFieldOnModel(model, fieldName, "orderBy");
3640
- validateOrderByValue(fieldName, entries[0][1]);
3865
+ assertScalarField(model, fieldName, "orderBy");
3866
+ parseOrderByValue(entries[0][1], fieldName);
3641
3867
  }
3642
3868
  }
3643
3869
  function validateCursor(model, cursor) {
@@ -3654,7 +3880,7 @@ function validateCursor(model, cursor) {
3654
3880
  if (f.length === 0) {
3655
3881
  throw new Error("cursor field name cannot be empty");
3656
3882
  }
3657
- assertScalarFieldOnModel(model, f, "cursor");
3883
+ assertScalarField(model, f, "cursor");
3658
3884
  }
3659
3885
  }
3660
3886
  function resolveDialect(dialect) {
@@ -3673,20 +3899,21 @@ function normalizeArgsForNegativeTake(method, args) {
3673
3899
  orderBy: reverseOrderByInput(args.orderBy)
3674
3900
  });
3675
3901
  }
3676
- function normalizeArgsForDialect(dialect, args, model) {
3902
+ function normalizeArgsForDialect(dialect, args) {
3677
3903
  if (dialect !== "postgres") return args;
3678
3904
  return applyPostgresDistinctOrderBy(args);
3679
3905
  }
3680
3906
  function buildCursorClauseIfAny(input) {
3681
- const { cursor, orderBy, tableName, alias, params, dialect } = input;
3682
- if (!isNotNullish(cursor)) return void 0;
3907
+ const { cursor, orderBy, tableName, alias, params, dialect, model } = input;
3908
+ if (!isNotNullish(cursor)) return {};
3683
3909
  return buildCursorCondition(
3684
3910
  cursor,
3685
3911
  orderBy,
3686
3912
  tableName,
3687
3913
  alias,
3688
3914
  params,
3689
- dialect
3915
+ dialect,
3916
+ model
3690
3917
  );
3691
3918
  }
3692
3919
  function buildSelectSpec(input) {
@@ -3725,14 +3952,20 @@ function buildSelectSpec(input) {
3725
3952
  params,
3726
3953
  dialect
3727
3954
  );
3728
- const cursorClause = buildCursorClauseIfAny({
3955
+ const cursorResult = buildCursorClauseIfAny({
3729
3956
  cursor,
3730
3957
  orderBy: normalizedArgs.orderBy,
3731
3958
  tableName,
3732
3959
  alias,
3733
3960
  params,
3734
- dialect
3961
+ dialect,
3962
+ model
3735
3963
  });
3964
+ if (dialect === "sqlite" && isNonEmptyArray(normalizedArgs.distinct) && cursorResult.condition) {
3965
+ throw new Error(
3966
+ "Cursor pagination with distinct is not supported in SQLite due to window function limitations. Use findMany with skip/take instead, or remove distinct."
3967
+ );
3968
+ }
3736
3969
  return {
3737
3970
  select: selectFields,
3738
3971
  includes,
@@ -3743,7 +3976,8 @@ function buildSelectSpec(input) {
3743
3976
  pagination: { take, skip },
3744
3977
  distinct: normalizedArgs.distinct,
3745
3978
  method,
3746
- cursorClause,
3979
+ cursorCte: cursorResult.cte,
3980
+ cursorClause: cursorResult.condition,
3747
3981
  params,
3748
3982
  dialect,
3749
3983
  model,
@@ -3757,9 +3991,7 @@ function buildSelectSql(input) {
3757
3991
  assertSafeTableRef(from.tableName);
3758
3992
  const dialectToUse = resolveDialect(dialect);
3759
3993
  const argsForSql = normalizeArgsForNegativeTake(method, args);
3760
- const normalizedArgs = normalizeArgsForDialect(
3761
- dialectToUse,
3762
- argsForSql);
3994
+ const normalizedArgs = normalizeArgsForDialect(dialectToUse, argsForSql);
3763
3995
  validateDistinct(model, normalizedArgs.distinct);
3764
3996
  validateOrderBy(model, normalizedArgs.orderBy);
3765
3997
  validateCursor(model, normalizedArgs.cursor);
@@ -3775,8 +4007,21 @@ function buildSelectSql(input) {
3775
4007
  });
3776
4008
  return constructFinalSql(spec);
3777
4009
  }
3778
- var MODEL_FIELD_CACHE = /* @__PURE__ */ new WeakMap();
3779
- var NUMERIC_TYPES = /* @__PURE__ */ new Set(["Int", "Float", "Decimal", "BigInt"]);
4010
+
4011
+ // src/builder/shared/comparison-builder.ts
4012
+ function buildComparisons(expr, filter, params, dialect, builder, excludeKeys = /* @__PURE__ */ new Set(["mode"])) {
4013
+ const out = [];
4014
+ for (const [op, val] of Object.entries(filter)) {
4015
+ if (excludeKeys.has(op) || val === void 0) continue;
4016
+ const built = builder(expr, op, val, params, dialect);
4017
+ if (built && built.trim().length > 0) {
4018
+ out.push(built);
4019
+ }
4020
+ }
4021
+ return out;
4022
+ }
4023
+
4024
+ // src/builder/aggregates.ts
3780
4025
  var AGGREGATES = [
3781
4026
  ["_sum", "SUM"],
3782
4027
  ["_avg", "AVG"],
@@ -3791,19 +4036,16 @@ var COMPARISON_OPS = {
3791
4036
  [Ops.LT]: "<",
3792
4037
  [Ops.LTE]: "<="
3793
4038
  };
3794
- function normalizeFinalParams2(params) {
3795
- return params.map(normalizeValue);
3796
- }
3797
- function getModelFieldMap(model) {
3798
- const cached = MODEL_FIELD_CACHE.get(model);
3799
- if (cached) return cached;
3800
- const m = /* @__PURE__ */ new Map();
3801
- for (const f of model.fields) {
3802
- m.set(f.name, { name: f.name, type: f.type, isRelation: !!f.isRelation });
3803
- }
3804
- MODEL_FIELD_CACHE.set(model, m);
3805
- return m;
3806
- }
4039
+ var HAVING_ALLOWED_OPS = /* @__PURE__ */ new Set([
4040
+ Ops.EQUALS,
4041
+ Ops.NOT,
4042
+ Ops.GT,
4043
+ Ops.GTE,
4044
+ Ops.LT,
4045
+ Ops.LTE,
4046
+ Ops.IN,
4047
+ Ops.NOT_IN
4048
+ ]);
3807
4049
  function isTruthySelection(v) {
3808
4050
  return v === true;
3809
4051
  }
@@ -3845,24 +4087,10 @@ function normalizeLogicalValue2(operator, value) {
3845
4087
  }
3846
4088
  throw new Error(`${operator} must be an object or array of objects in HAVING`);
3847
4089
  }
3848
- function assertScalarField2(model, fieldName, ctx) {
3849
- const m = getModelFieldMap(model);
3850
- const field = m.get(fieldName);
3851
- if (!field) {
4090
+ function assertHavingOp(op) {
4091
+ if (!HAVING_ALLOWED_OPS.has(op)) {
3852
4092
  throw new Error(
3853
- `${ctx} references unknown field '${fieldName}' on model ${model.name}. Available fields: ${model.fields.map((f) => f.name).join(", ")}`
3854
- );
3855
- }
3856
- if (field.isRelation) {
3857
- throw new Error(`${ctx} does not support relation field '${fieldName}'`);
3858
- }
3859
- return { name: field.name, type: field.type };
3860
- }
3861
- function assertAggregateFieldType(aggKey, fieldType, fieldName, modelName) {
3862
- const baseType = fieldType.replace(/\[\]|\?/g, "");
3863
- if ((aggKey === "_sum" || aggKey === "_avg") && !NUMERIC_TYPES.has(baseType)) {
3864
- throw new Error(
3865
- `Cannot use ${aggKey} on non-numeric field '${fieldName}' (type: ${fieldType}) on model ${modelName}`
4093
+ `Unsupported HAVING operator '${op}'. Allowed: ${[...HAVING_ALLOWED_OPS].join(", ")}`
3866
4094
  );
3867
4095
  }
3868
4096
  }
@@ -3892,6 +4120,7 @@ function buildBinaryComparison(expr, op, val, params) {
3892
4120
  return `${expr} ${sqlOp} ${placeholder}`;
3893
4121
  }
3894
4122
  function buildSimpleComparison(expr, op, val, params, dialect) {
4123
+ assertHavingOp(op);
3895
4124
  if (val === null) return buildNullComparison(expr, op);
3896
4125
  if (op === Ops.NOT && isPlainObject(val)) {
3897
4126
  return buildNotComposite(
@@ -3980,20 +4209,19 @@ function assertHavingAggTarget(aggKey, field, model) {
3980
4209
  }
3981
4210
  return;
3982
4211
  }
3983
- const f = assertScalarField2(model, field, "HAVING");
3984
- assertAggregateFieldType(aggKey, f.type, f.name, model.name);
4212
+ if (aggKey === "_sum" || aggKey === "_avg") {
4213
+ assertNumericField(model, field, "HAVING");
4214
+ } else {
4215
+ assertScalarField(model, field, "HAVING");
4216
+ }
3985
4217
  }
3986
4218
  function buildHavingOpsForExpr(expr, filter, params, dialect) {
3987
- const out = [];
3988
- for (const [op, val] of Object.entries(filter)) {
3989
- if (op === "mode") continue;
3990
- const built = buildSimpleComparison(expr, op, val, params, dialect);
3991
- if (built && built.trim().length > 0) out.push(built);
3992
- }
3993
- return out;
4219
+ return buildComparisons(expr, filter, params, dialect, buildSimpleComparison);
3994
4220
  }
3995
4221
  function buildHavingForAggregateFirstShape(aggKey, target, alias, params, dialect, model) {
3996
- if (!isPlainObject(target)) return [];
4222
+ if (!isPlainObject(target)) {
4223
+ throw new Error(`HAVING '${aggKey}' must be an object`);
4224
+ }
3997
4225
  const out = [];
3998
4226
  for (const [field, filter] of Object.entries(target)) {
3999
4227
  assertHavingAggTarget(aggKey, field, model);
@@ -4004,30 +4232,39 @@ function buildHavingForAggregateFirstShape(aggKey, target, alias, params, dialec
4004
4232
  return out;
4005
4233
  }
4006
4234
  function buildHavingForFieldFirstShape(fieldName, target, alias, params, dialect, model) {
4007
- if (!isPlainObject(target)) return [];
4008
- const field = assertScalarField2(model, fieldName, "HAVING");
4235
+ if (!isPlainObject(target)) {
4236
+ throw new Error(`HAVING '${fieldName}' must be an object`);
4237
+ }
4238
+ assertScalarField(model, fieldName, "HAVING");
4009
4239
  const out = [];
4010
4240
  const obj = target;
4011
4241
  const keys = ["_count", "_sum", "_avg", "_min", "_max"];
4012
4242
  for (const aggKey of keys) {
4013
4243
  const aggFilter = obj[aggKey];
4014
4244
  if (!isPlainObject(aggFilter)) continue;
4015
- assertAggregateFieldType(aggKey, field.type, field.name, model.name);
4245
+ if (aggKey === "_sum" || aggKey === "_avg") {
4246
+ assertNumericField(model, fieldName, "HAVING");
4247
+ }
4016
4248
  const entries = Object.entries(aggFilter);
4017
4249
  if (entries.length === 0) continue;
4018
4250
  const expr = aggExprForField(aggKey, fieldName, alias, model);
4019
- for (const [op, val] of entries) {
4020
- if (op === "mode") continue;
4021
- const built = buildSimpleComparison(expr, op, val, params, dialect);
4022
- if (built && built.trim().length > 0) out.push(built);
4023
- }
4251
+ const clauses = buildComparisons(
4252
+ expr,
4253
+ aggFilter,
4254
+ params,
4255
+ dialect,
4256
+ buildSimpleComparison
4257
+ );
4258
+ out.push(...clauses);
4024
4259
  }
4025
4260
  return out;
4026
4261
  }
4027
4262
  function buildHavingClause(having, alias, params, model, dialect) {
4028
4263
  if (!isNotNullish(having)) return "";
4029
4264
  const d = dialect != null ? dialect : getGlobalDialect();
4030
- if (!isPlainObject(having)) return "";
4265
+ if (!isPlainObject(having)) {
4266
+ throw new Error("having must be an object");
4267
+ }
4031
4268
  return buildHavingNode(having, alias, params, d, model);
4032
4269
  }
4033
4270
  function normalizeCountArg(v) {
@@ -4041,18 +4278,8 @@ function pushCountAllField(fields) {
4041
4278
  `${SQL_TEMPLATES.COUNT_ALL} ${SQL_TEMPLATES.AS} ${quote("_count._all")}`
4042
4279
  );
4043
4280
  }
4044
- function assertCountableScalarField(fieldMap, model, fieldName) {
4045
- const field = fieldMap.get(fieldName);
4046
- if (!field) {
4047
- throw new Error(
4048
- `Field '${fieldName}' does not exist on model ${model.name}`
4049
- );
4050
- }
4051
- if (field.isRelation) {
4052
- throw new Error(
4053
- `Cannot use _count on relation field '${fieldName}' on model ${model.name}`
4054
- );
4055
- }
4281
+ function assertCountableScalarField(model, fieldName) {
4282
+ assertScalarField(model, fieldName, "_count");
4056
4283
  }
4057
4284
  function pushCountField(fields, alias, fieldName, model) {
4058
4285
  const outAlias = `_count.${fieldName}`;
@@ -4060,7 +4287,7 @@ function pushCountField(fields, alias, fieldName, model) {
4060
4287
  `COUNT(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4061
4288
  );
4062
4289
  }
4063
- function addCountFields(fields, countArg, alias, model, fieldMap) {
4290
+ function addCountFields(fields, countArg, alias, model) {
4064
4291
  if (!isNotNullish(countArg)) return;
4065
4292
  if (countArg === true) {
4066
4293
  pushCountAllField(fields);
@@ -4074,7 +4301,7 @@ function addCountFields(fields, countArg, alias, model, fieldMap) {
4074
4301
  ([f, v]) => f !== "_all" && isTruthySelection(v)
4075
4302
  );
4076
4303
  for (const [f] of selected) {
4077
- assertCountableScalarField(fieldMap, model, f);
4304
+ assertCountableScalarField(model, f);
4078
4305
  pushCountField(fields, alias, f, model);
4079
4306
  }
4080
4307
  }
@@ -4082,19 +4309,12 @@ function getAggregateSelectionObject(args, agg) {
4082
4309
  const obj = args[agg];
4083
4310
  return isPlainObject(obj) ? obj : void 0;
4084
4311
  }
4085
- function assertAggregatableScalarField(fieldMap, model, agg, fieldName) {
4086
- const field = fieldMap.get(fieldName);
4087
- if (!field) {
4088
- throw new Error(
4089
- `Field '${fieldName}' does not exist on model ${model.name}`
4090
- );
4091
- }
4092
- if (field.isRelation) {
4093
- throw new Error(
4094
- `Cannot use ${agg} on relation field '${fieldName}' on model ${model.name}`
4095
- );
4312
+ function assertAggregatableScalarField(model, agg, fieldName) {
4313
+ if (agg === "_sum" || agg === "_avg") {
4314
+ assertNumericField(model, fieldName, agg);
4315
+ } else {
4316
+ assertScalarField(model, fieldName, agg);
4096
4317
  }
4097
- return field;
4098
4318
  }
4099
4319
  function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
4100
4320
  const outAlias = `${agg}.${fieldName}`;
@@ -4102,7 +4322,7 @@ function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
4102
4322
  `${aggFn}(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4103
4323
  );
4104
4324
  }
4105
- function addAggregateFields(fields, args, alias, model, fieldMap) {
4325
+ function addAggregateFields(fields, args, alias, model) {
4106
4326
  for (const [agg, aggFn] of AGGREGATES) {
4107
4327
  const obj = getAggregateSelectionObject(args, agg);
4108
4328
  if (!obj) continue;
@@ -4110,23 +4330,16 @@ function addAggregateFields(fields, args, alias, model, fieldMap) {
4110
4330
  if (fieldName === "_all")
4111
4331
  throw new Error(`'${agg}' does not support '_all'`);
4112
4332
  if (!isTruthySelection(selection)) continue;
4113
- const field = assertAggregatableScalarField(
4114
- fieldMap,
4115
- model,
4116
- agg,
4117
- fieldName
4118
- );
4119
- assertAggregateFieldType(agg, field.type, fieldName, model.name);
4333
+ assertAggregatableScalarField(model, agg, fieldName);
4120
4334
  pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model);
4121
4335
  }
4122
4336
  }
4123
4337
  }
4124
4338
  function buildAggregateFields(args, alias, model) {
4125
4339
  const fields = [];
4126
- const fieldMap = getModelFieldMap(model);
4127
4340
  const countArg = normalizeCountArg(args._count);
4128
- addCountFields(fields, countArg, alias, model, fieldMap);
4129
- addAggregateFields(fields, args, alias, model, fieldMap);
4341
+ addCountFields(fields, countArg, alias, model);
4342
+ addAggregateFields(fields, args, alias, model);
4130
4343
  return fields;
4131
4344
  }
4132
4345
  function buildAggregateSql(args, whereResult, tableName, alias, model) {
@@ -4150,7 +4363,7 @@ function buildAggregateSql(args, whereResult, tableName, alias, model) {
4150
4363
  validateParamConsistency(sql, whereResult.params);
4151
4364
  return Object.freeze({
4152
4365
  sql,
4153
- params: Object.freeze(normalizeFinalParams2([...whereResult.params])),
4366
+ params: Object.freeze([...whereResult.params]),
4154
4367
  paramMappings: Object.freeze([...whereResult.paramMappings])
4155
4368
  });
4156
4369
  }
@@ -4163,32 +4376,24 @@ function assertGroupByBy(args, model) {
4163
4376
  if (bySet.size !== byFields.length) {
4164
4377
  throw new Error("buildGroupBySql: by must not contain duplicates");
4165
4378
  }
4166
- const modelFieldMap = getModelFieldMap(model);
4167
4379
  for (const f of byFields) {
4168
- const field = modelFieldMap.get(f);
4169
- if (!field) {
4170
- throw new Error(
4171
- `groupBy.by references unknown field '${f}' on model ${model.name}`
4172
- );
4173
- }
4174
- if (field.isRelation) {
4175
- throw new Error(
4176
- `groupBy.by does not support relation field '${f}' on model ${model.name}`
4177
- );
4178
- }
4380
+ assertScalarField(model, f, "groupBy.by");
4179
4381
  }
4180
4382
  return byFields;
4181
4383
  }
4182
4384
  function buildGroupBySelectParts(args, alias, model, byFields) {
4183
4385
  const groupCols = byFields.map((f) => col(alias, f, model));
4386
+ const selectCols = byFields.map((f) => colWithAlias(alias, f, model));
4184
4387
  const groupFields = groupCols.join(SQL_SEPARATORS.FIELD_LIST);
4185
4388
  const aggFields = buildAggregateFields(args, alias, model);
4186
- const selectFields = isNonEmptyArray(aggFields) ? groupCols.concat(aggFields).join(SQL_SEPARATORS.FIELD_LIST) : groupCols.join(SQL_SEPARATORS.FIELD_LIST);
4389
+ const selectFields = isNonEmptyArray(aggFields) ? selectCols.concat(aggFields).join(SQL_SEPARATORS.FIELD_LIST) : selectCols.join(SQL_SEPARATORS.FIELD_LIST);
4187
4390
  return { groupCols, groupFields, selectFields };
4188
4391
  }
4189
4392
  function buildGroupByHaving(args, alias, params, model, dialect) {
4190
4393
  if (!isNotNullish(args.having)) return "";
4191
- if (!isPlainObject(args.having)) return "";
4394
+ if (!isPlainObject(args.having)) {
4395
+ throw new Error("having must be an object");
4396
+ }
4192
4397
  const h = buildHavingClause(args.having, alias, params, model, dialect);
4193
4398
  if (!h || h.trim().length === 0) return "";
4194
4399
  return `${SQL_TEMPLATES.HAVING} ${h}`;
@@ -4224,61 +4429,58 @@ function buildGroupBySql(args, whereResult, tableName, alias, model, dialect) {
4224
4429
  const mergedParams = [...whereResult.params, ...snapshot.params];
4225
4430
  return Object.freeze({
4226
4431
  sql,
4227
- params: Object.freeze(normalizeFinalParams2(mergedParams)),
4432
+ params: Object.freeze(mergedParams),
4228
4433
  paramMappings: Object.freeze([
4229
4434
  ...whereResult.paramMappings,
4230
4435
  ...snapshot.mappings
4231
4436
  ])
4232
4437
  });
4233
4438
  }
4234
- function buildCountSql(whereResult, tableName, alias, skip, dialect) {
4439
+ function buildCountSql(whereResult, tableName, alias, skip, _dialect) {
4235
4440
  assertSafeAlias(alias);
4236
4441
  assertSafeTableRef(tableName);
4237
- const d = dialect != null ? dialect : getGlobalDialect();
4442
+ if (skip !== void 0 && skip !== null) {
4443
+ if (schemaParser.isDynamicParameter(skip)) {
4444
+ throw new Error(
4445
+ "count() with skip is not supported because it produces nondeterministic results. Dynamic skip cannot be validated at build time. Use findMany().length or add explicit orderBy + cursor/skip logic in a deterministic query."
4446
+ );
4447
+ }
4448
+ if (typeof skip === "string") {
4449
+ const s = skip.trim();
4450
+ if (s.length > 0) {
4451
+ const n = Number(s);
4452
+ if (Number.isFinite(n) && Number.isInteger(n) && n > 0) {
4453
+ throw new Error(
4454
+ "count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
4455
+ );
4456
+ }
4457
+ }
4458
+ }
4459
+ if (typeof skip === "number" && Number.isFinite(skip) && Number.isInteger(skip) && skip > 0) {
4460
+ throw new Error(
4461
+ "count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
4462
+ );
4463
+ }
4464
+ }
4238
4465
  const whereClause = isValidWhereClause(whereResult.clause) ? `${SQL_TEMPLATES.WHERE} ${whereResult.clause}` : "";
4239
- const params = createParamStore(whereResult.nextParamIndex);
4240
- const baseSubSelect = [
4241
- SQL_TEMPLATES.SELECT,
4242
- "1",
4243
- SQL_TEMPLATES.FROM,
4244
- tableName,
4245
- alias,
4246
- whereClause
4247
- ].filter((x) => x && String(x).trim().length > 0).join(" ").trim();
4248
- const normalizedSkip = normalizeSkipLike(skip);
4249
- const subSelect = applyCountSkip(baseSubSelect, normalizedSkip, params, d);
4250
4466
  const sql = [
4251
4467
  SQL_TEMPLATES.SELECT,
4252
4468
  SQL_TEMPLATES.COUNT_ALL,
4253
4469
  SQL_TEMPLATES.AS,
4254
4470
  quote("_count._all"),
4255
4471
  SQL_TEMPLATES.FROM,
4256
- `(${subSelect})`,
4257
- SQL_TEMPLATES.AS,
4258
- `"sub"`
4472
+ tableName,
4473
+ alias,
4474
+ whereClause
4259
4475
  ].filter((x) => x && String(x).trim().length > 0).join(" ").trim();
4260
4476
  validateSelectQuery(sql);
4261
- const snapshot = params.snapshot();
4262
- const mergedParams = [...whereResult.params, ...snapshot.params];
4263
- validateParamConsistency(sql, mergedParams);
4477
+ validateParamConsistency(sql, whereResult.params);
4264
4478
  return Object.freeze({
4265
4479
  sql,
4266
- params: Object.freeze(normalizeFinalParams2(mergedParams)),
4267
- paramMappings: Object.freeze([
4268
- ...whereResult.paramMappings,
4269
- ...snapshot.mappings
4270
- ])
4480
+ params: Object.freeze([...whereResult.params]),
4481
+ paramMappings: Object.freeze([...whereResult.paramMappings])
4271
4482
  });
4272
4483
  }
4273
- function applyCountSkip(subSelect, normalizedSkip, params, dialect) {
4274
- const shouldApply = schemaParser.isDynamicParameter(normalizedSkip) || typeof normalizedSkip === "number" && normalizedSkip > 0;
4275
- if (!shouldApply) return subSelect;
4276
- const placeholder = addAutoScoped(params, normalizedSkip, "count.skip");
4277
- if (dialect === "sqlite") {
4278
- return `${subSelect} ${SQL_TEMPLATES.LIMIT} -1 ${SQL_TEMPLATES.OFFSET} ${placeholder}`;
4279
- }
4280
- return `${subSelect} ${SQL_TEMPLATES.OFFSET} ${placeholder}`;
4281
- }
4282
4484
  function safeAlias(input) {
4283
4485
  const raw = String(input).toLowerCase();
4284
4486
  const cleaned = raw.replace(/[^a-z0-9_]/g, "_");
@@ -4428,8 +4630,12 @@ function buildAndNormalizeSql(args) {
4428
4630
  );
4429
4631
  }
4430
4632
  function finalizeDirective(args) {
4431
- const { directive, normalizedSql, normalizedMappings } = args;
4432
- validateSqlPositions(normalizedSql, normalizedMappings, getGlobalDialect());
4633
+ const { directive, normalizedSql, normalizedMappings, dialect } = args;
4634
+ const params = normalizedMappings.map((m) => {
4635
+ var _a;
4636
+ return (_a = m.value) != null ? _a : void 0;
4637
+ });
4638
+ validateParamConsistencyByDialect(normalizedSql, params, dialect);
4433
4639
  const { staticParams, dynamicKeys } = buildParamsFromMappings(normalizedMappings);
4434
4640
  return {
4435
4641
  method: directive.method,
@@ -4466,7 +4672,8 @@ function generateSQL(directive) {
4466
4672
  return finalizeDirective({
4467
4673
  directive,
4468
4674
  normalizedSql: normalized.sql,
4469
- normalizedMappings: normalized.paramMappings
4675
+ normalizedMappings: normalized.paramMappings,
4676
+ dialect
4470
4677
  });
4471
4678
  }
4472
4679
 
@@ -4843,9 +5050,7 @@ function buildSQLFull(model, models, method, args, dialect) {
4843
5050
  whereResult,
4844
5051
  tableName,
4845
5052
  alias,
4846
- args.skip,
4847
- dialect
4848
- );
5053
+ args.skip);
4849
5054
  break;
4850
5055
  default:
4851
5056
  result = buildSelectSql({