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