prisma-sql 1.43.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}`);
940
+ }
941
+ const a = alias.trim();
942
+ if (a.length === 0) {
943
+ throw new Error("Invalid alias: required and cannot be empty");
935
944
  }
936
- if (containsControlChars(a) || a.includes(";")) {
937
- throw new Error(`alias contains unsafe characters: ${JSON.stringify(a)}`);
945
+ if (a !== alias) {
946
+ throw new Error("Invalid alias: leading/trailing whitespace");
938
947
  }
939
- if (!/^[A-Za-z_]\w*$/.test(a)) {
948
+ if (/[\u0000-\u001F\u007F]/.test(a)) {
940
949
  throw new Error(
941
- `alias must be a simple identifier, got: ${JSON.stringify(a)}`
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)) {
976
+ throw new Error(
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 };
3225
3355
  }
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})`;
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 ")})`;
3372
+ }
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,13 +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();
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");
3270
3444
  function joinNonEmpty(parts, sep) {
3271
3445
  return parts.filter((s) => s.trim().length > 0).join(sep);
3272
3446
  }
3273
3447
  function buildWhereSql(conditions) {
3274
3448
  if (!isNonEmptyArray(conditions)) return "";
3275
- 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(" ")}`;
3276
3454
  }
3277
3455
  function buildJoinsSql(...joinGroups) {
3278
3456
  const all = [];
@@ -3287,37 +3465,39 @@ function buildSelectList(baseSelect, extraCols) {
3287
3465
  if (base && extra) return `${base}${SQL_SEPARATORS.FIELD_LIST}${extra}`;
3288
3466
  return base || extra;
3289
3467
  }
3290
- function finalizeSql(sql, params) {
3468
+ function finalizeSql(sql, params, dialect) {
3291
3469
  const snapshot = params.snapshot();
3292
3470
  validateSelectQuery(sql);
3293
- validateParamConsistency(sql, snapshot.params);
3471
+ validateParamConsistencyByDialect(sql, snapshot.params, dialect);
3294
3472
  return Object.freeze({
3295
3473
  sql,
3296
3474
  params: snapshot.params,
3297
- paramMappings: snapshot.mappings
3475
+ paramMappings: Object.freeze(snapshot.mappings)
3298
3476
  });
3299
3477
  }
3300
- function parseSimpleScalarSelect(select, alias) {
3301
- var _a, _b;
3478
+ function parseSimpleScalarSelect(select, fromAlias) {
3479
+ var _a, _b, _c, _d;
3302
3480
  const raw = select.trim();
3303
3481
  if (raw.length === 0) return [];
3304
- let re = SIMPLE_SELECT_RE_CACHE.get(alias);
3305
- if (!re) {
3306
- const safeAlias2 = alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3307
- re = new RegExp(`^${safeAlias2}\\.(?:"([^"]+)"|([a-z_][a-z0-9_]*))$`, "i");
3308
- SIMPLE_SELECT_RE_CACHE.set(alias, re);
3309
- }
3310
3482
  const parts = raw.split(SQL_SEPARATORS.FIELD_LIST);
3311
3483
  const names = [];
3312
3484
  for (const part of parts) {
3313
3485
  const p = part.trim();
3314
- const m = p.match(re);
3486
+ const m = p.match(SIMPLE_COLUMN_RE);
3315
3487
  if (!m) {
3316
3488
  throw new Error(
3317
- `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}`
3490
+ );
3491
+ }
3492
+ const actualAlias = m[1];
3493
+ if (actualAlias.toLowerCase() !== fromAlias.toLowerCase()) {
3494
+ throw new Error(
3495
+ `Expected alias '${fromAlias}', got '${actualAlias}' in: ${p}`
3318
3496
  );
3319
3497
  }
3320
- const name = ((_b = (_a = m[1]) != null ? _a : m[2]) != null ? _b : "").trim();
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;
3321
3501
  if (name.length === 0) {
3322
3502
  throw new Error(`Failed to parse selected column name from: ${p}`);
3323
3503
  }
@@ -3326,18 +3506,18 @@ function parseSimpleScalarSelect(select, alias) {
3326
3506
  return names;
3327
3507
  }
3328
3508
  function replaceOrderByAlias(orderBy, fromAlias, outerAlias) {
3329
- const needle = `${fromAlias}.`;
3330
- const replacement = `${outerAlias}.`;
3331
- 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}.`);
3332
3514
  }
3333
3515
  function buildDistinctColumns(distinct, fromAlias, model) {
3334
3516
  return distinct.map((f) => col(fromAlias, f, model)).join(SQL_SEPARATORS.FIELD_LIST);
3335
3517
  }
3336
3518
  function buildOutputColumns(scalarNames, includeNames, hasCount) {
3337
3519
  const outputCols = [...scalarNames, ...includeNames];
3338
- if (hasCount) {
3339
- outputCols.push("_count");
3340
- }
3520
+ if (hasCount) outputCols.push("_count");
3341
3521
  const formatted = outputCols.map((n) => quote(n)).join(SQL_SEPARATORS.FIELD_LIST);
3342
3522
  if (!isNonEmptyString(formatted)) {
3343
3523
  throw new Error("distinct emulation requires at least one output column");
@@ -3346,9 +3526,10 @@ function buildOutputColumns(scalarNames, includeNames, hasCount) {
3346
3526
  }
3347
3527
  function buildWindowOrder(args) {
3348
3528
  const { baseOrder, idField, fromAlias, model } = args;
3529
+ const fromLower = String(fromAlias).toLowerCase();
3349
3530
  const orderFields = baseOrder.split(SQL_SEPARATORS.ORDER_BY).map((s) => s.trim().toLowerCase());
3350
3531
  const hasIdInOrder = orderFields.some(
3351
- (f) => f.startsWith(`${fromAlias}.id `) || f.startsWith(`${fromAlias}."id" `)
3532
+ (f) => f.startsWith(`${fromLower}.id `) || f.startsWith(`${fromLower}."id" `)
3352
3533
  );
3353
3534
  if (hasIdInOrder) return baseOrder;
3354
3535
  const idTiebreaker = idField ? `, ${col(fromAlias, "id", model)} ASC` : "";
@@ -3383,15 +3564,37 @@ function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
3383
3564
  const outerOrder = isNonEmptyString(orderBy) ? replaceOrderByAlias(orderBy, from.alias, `"__tp_distinct"`) : replaceOrderByAlias(fallbackOrder, from.alias, `"__tp_distinct"`);
3384
3565
  const joins = buildJoinsSql(whereJoins, countJoins);
3385
3566
  const conditions = [];
3386
- if (whereClause && whereClause !== "1=1") {
3387
- conditions.push(whereClause);
3388
- }
3567
+ if (whereClause && whereClause !== "1=1") conditions.push(whereClause);
3389
3568
  const whereSql = buildWhereSql(conditions);
3390
3569
  const innerSelectList = selectWithIncludes.trim();
3391
3570
  const innerComma = innerSelectList.length > 0 ? SQL_SEPARATORS.FIELD_LIST : "";
3392
- 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}`;
3393
- 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}` : "");
3394
- 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(" ");
3395
3598
  }
3396
3599
  function buildIncludeColumns(spec) {
3397
3600
  var _a, _b;
@@ -3519,6 +3722,7 @@ function constructFinalSql(spec) {
3519
3722
  orderBy,
3520
3723
  distinct,
3521
3724
  method,
3725
+ cursorCte,
3522
3726
  cursorClause,
3523
3727
  params,
3524
3728
  dialect,
@@ -3533,9 +3737,13 @@ function constructFinalSql(spec) {
3533
3737
  const spec2 = withCountJoins(spec, countJoins, whereJoins);
3534
3738
  let sql2 = buildSqliteDistinctQuery(spec2, selectWithIncludes).trim();
3535
3739
  sql2 = appendPagination(sql2, spec);
3536
- return finalizeSql(sql2, params);
3740
+ return finalizeSql(sql2, params, dialect);
3741
+ }
3742
+ const parts = [];
3743
+ if (cursorCte) {
3744
+ parts.push(`WITH ${cursorCte}`);
3537
3745
  }
3538
- const parts = [SQL_TEMPLATES.SELECT];
3746
+ parts.push(SQL_TEMPLATES.SELECT);
3539
3747
  const distinctOn = dialect === "postgres" ? buildPostgresDistinctOnClause(from.alias, distinct, model) : null;
3540
3748
  if (distinctOn) parts.push(distinctOn);
3541
3749
  const baseSelect = (select != null ? select : "").trim();
@@ -3551,7 +3759,41 @@ function constructFinalSql(spec) {
3551
3759
  if (isNonEmptyString(orderBy)) parts.push(SQL_TEMPLATES.ORDER_BY, orderBy);
3552
3760
  let sql = parts.join(" ").trim();
3553
3761
  sql = appendPagination(sql, spec);
3554
- 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;
3555
3797
  }
3556
3798
 
3557
3799
  // src/builder/select.ts
@@ -3584,7 +3826,7 @@ function buildPostgresDistinctOrderBy(distinctFields, existing) {
3584
3826
  }
3585
3827
  return next;
3586
3828
  }
3587
- function applyPostgresDistinctOrderBy(args, _model) {
3829
+ function applyPostgresDistinctOrderBy(args) {
3588
3830
  const distinctFields = normalizeDistinctFields(args.distinct);
3589
3831
  if (distinctFields.length === 0) return args;
3590
3832
  if (!isNotNullish(args.orderBy)) return args;
@@ -3594,19 +3836,6 @@ function applyPostgresDistinctOrderBy(args, _model) {
3594
3836
  orderBy: buildPostgresDistinctOrderBy(distinctFields, existing)
3595
3837
  });
3596
3838
  }
3597
- function assertScalarFieldOnModel(model, fieldName, ctx) {
3598
- const f = model.fields.find((x) => x.name === fieldName);
3599
- if (!f) {
3600
- throw new Error(
3601
- `${ctx} references unknown field '${fieldName}' on model ${model.name}`
3602
- );
3603
- }
3604
- if (f.isRelation) {
3605
- throw new Error(
3606
- `${ctx} does not support relation field '${fieldName}' on model ${model.name}`
3607
- );
3608
- }
3609
- }
3610
3839
  function validateDistinct(model, distinct) {
3611
3840
  if (!isNotNullish(distinct) || !isNonEmptyArray(distinct)) return;
3612
3841
  const seen = /* @__PURE__ */ new Set();
@@ -3617,24 +3846,24 @@ function validateDistinct(model, distinct) {
3617
3846
  throw new Error(`distinct must not contain duplicates (field: '${f}')`);
3618
3847
  }
3619
3848
  seen.add(f);
3620
- assertScalarFieldOnModel(model, f, "distinct");
3849
+ assertScalarField(model, f, "distinct");
3621
3850
  }
3622
3851
  }
3623
- function validateOrderByValue(fieldName, v) {
3624
- parseOrderByValue(v, fieldName);
3625
- }
3626
3852
  function validateOrderBy(model, orderBy) {
3627
3853
  if (!isNotNullish(orderBy)) return;
3628
3854
  const items = normalizeOrderByInput2(orderBy);
3629
3855
  if (items.length === 0) return;
3630
3856
  for (const it of items) {
3631
3857
  const entries = Object.entries(it);
3858
+ if (entries.length !== 1) {
3859
+ throw new Error("orderBy array entries must have exactly one field");
3860
+ }
3632
3861
  const fieldName = String(entries[0][0]).trim();
3633
3862
  if (fieldName.length === 0) {
3634
3863
  throw new Error("orderBy field name cannot be empty");
3635
3864
  }
3636
- assertScalarFieldOnModel(model, fieldName, "orderBy");
3637
- validateOrderByValue(fieldName, entries[0][1]);
3865
+ assertScalarField(model, fieldName, "orderBy");
3866
+ parseOrderByValue(entries[0][1], fieldName);
3638
3867
  }
3639
3868
  }
3640
3869
  function validateCursor(model, cursor) {
@@ -3651,7 +3880,7 @@ function validateCursor(model, cursor) {
3651
3880
  if (f.length === 0) {
3652
3881
  throw new Error("cursor field name cannot be empty");
3653
3882
  }
3654
- assertScalarFieldOnModel(model, f, "cursor");
3883
+ assertScalarField(model, f, "cursor");
3655
3884
  }
3656
3885
  }
3657
3886
  function resolveDialect(dialect) {
@@ -3670,20 +3899,21 @@ function normalizeArgsForNegativeTake(method, args) {
3670
3899
  orderBy: reverseOrderByInput(args.orderBy)
3671
3900
  });
3672
3901
  }
3673
- function normalizeArgsForDialect(dialect, args, model) {
3902
+ function normalizeArgsForDialect(dialect, args) {
3674
3903
  if (dialect !== "postgres") return args;
3675
3904
  return applyPostgresDistinctOrderBy(args);
3676
3905
  }
3677
3906
  function buildCursorClauseIfAny(input) {
3678
- const { cursor, orderBy, tableName, alias, params, dialect } = input;
3679
- if (!isNotNullish(cursor)) return void 0;
3907
+ const { cursor, orderBy, tableName, alias, params, dialect, model } = input;
3908
+ if (!isNotNullish(cursor)) return {};
3680
3909
  return buildCursorCondition(
3681
3910
  cursor,
3682
3911
  orderBy,
3683
3912
  tableName,
3684
3913
  alias,
3685
3914
  params,
3686
- dialect
3915
+ dialect,
3916
+ model
3687
3917
  );
3688
3918
  }
3689
3919
  function buildSelectSpec(input) {
@@ -3722,14 +3952,20 @@ function buildSelectSpec(input) {
3722
3952
  params,
3723
3953
  dialect
3724
3954
  );
3725
- const cursorClause = buildCursorClauseIfAny({
3955
+ const cursorResult = buildCursorClauseIfAny({
3726
3956
  cursor,
3727
3957
  orderBy: normalizedArgs.orderBy,
3728
3958
  tableName,
3729
3959
  alias,
3730
3960
  params,
3731
- dialect
3961
+ dialect,
3962
+ model
3732
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
+ }
3733
3969
  return {
3734
3970
  select: selectFields,
3735
3971
  includes,
@@ -3740,7 +3976,8 @@ function buildSelectSpec(input) {
3740
3976
  pagination: { take, skip },
3741
3977
  distinct: normalizedArgs.distinct,
3742
3978
  method,
3743
- cursorClause,
3979
+ cursorCte: cursorResult.cte,
3980
+ cursorClause: cursorResult.condition,
3744
3981
  params,
3745
3982
  dialect,
3746
3983
  model,
@@ -3754,9 +3991,7 @@ function buildSelectSql(input) {
3754
3991
  assertSafeTableRef(from.tableName);
3755
3992
  const dialectToUse = resolveDialect(dialect);
3756
3993
  const argsForSql = normalizeArgsForNegativeTake(method, args);
3757
- const normalizedArgs = normalizeArgsForDialect(
3758
- dialectToUse,
3759
- argsForSql);
3994
+ const normalizedArgs = normalizeArgsForDialect(dialectToUse, argsForSql);
3760
3995
  validateDistinct(model, normalizedArgs.distinct);
3761
3996
  validateOrderBy(model, normalizedArgs.orderBy);
3762
3997
  validateCursor(model, normalizedArgs.cursor);
@@ -3772,8 +4007,21 @@ function buildSelectSql(input) {
3772
4007
  });
3773
4008
  return constructFinalSql(spec);
3774
4009
  }
3775
- var MODEL_FIELD_CACHE = /* @__PURE__ */ new WeakMap();
3776
- 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
3777
4025
  var AGGREGATES = [
3778
4026
  ["_sum", "SUM"],
3779
4027
  ["_avg", "AVG"],
@@ -3788,16 +4036,16 @@ var COMPARISON_OPS = {
3788
4036
  [Ops.LT]: "<",
3789
4037
  [Ops.LTE]: "<="
3790
4038
  };
3791
- function getModelFieldMap(model) {
3792
- const cached = MODEL_FIELD_CACHE.get(model);
3793
- if (cached) return cached;
3794
- const m = /* @__PURE__ */ new Map();
3795
- for (const f of model.fields) {
3796
- m.set(f.name, { name: f.name, type: f.type, isRelation: !!f.isRelation });
3797
- }
3798
- MODEL_FIELD_CACHE.set(model, m);
3799
- return m;
3800
- }
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
+ ]);
3801
4049
  function isTruthySelection(v) {
3802
4050
  return v === true;
3803
4051
  }
@@ -3839,24 +4087,10 @@ function normalizeLogicalValue2(operator, value) {
3839
4087
  }
3840
4088
  throw new Error(`${operator} must be an object or array of objects in HAVING`);
3841
4089
  }
3842
- function assertScalarField2(model, fieldName, ctx) {
3843
- const m = getModelFieldMap(model);
3844
- const field = m.get(fieldName);
3845
- if (!field) {
4090
+ function assertHavingOp(op) {
4091
+ if (!HAVING_ALLOWED_OPS.has(op)) {
3846
4092
  throw new Error(
3847
- `${ctx} references unknown field '${fieldName}' on model ${model.name}. Available fields: ${model.fields.map((f) => f.name).join(", ")}`
3848
- );
3849
- }
3850
- if (field.isRelation) {
3851
- throw new Error(`${ctx} does not support relation field '${fieldName}'`);
3852
- }
3853
- return { name: field.name, type: field.type };
3854
- }
3855
- function assertAggregateFieldType(aggKey, fieldType, fieldName, modelName) {
3856
- const baseType = fieldType.replace(/\[\]|\?/g, "");
3857
- if ((aggKey === "_sum" || aggKey === "_avg") && !NUMERIC_TYPES.has(baseType)) {
3858
- throw new Error(
3859
- `Cannot use ${aggKey} on non-numeric field '${fieldName}' (type: ${fieldType}) on model ${modelName}`
4093
+ `Unsupported HAVING operator '${op}'. Allowed: ${[...HAVING_ALLOWED_OPS].join(", ")}`
3860
4094
  );
3861
4095
  }
3862
4096
  }
@@ -3886,6 +4120,7 @@ function buildBinaryComparison(expr, op, val, params) {
3886
4120
  return `${expr} ${sqlOp} ${placeholder}`;
3887
4121
  }
3888
4122
  function buildSimpleComparison(expr, op, val, params, dialect) {
4123
+ assertHavingOp(op);
3889
4124
  if (val === null) return buildNullComparison(expr, op);
3890
4125
  if (op === Ops.NOT && isPlainObject(val)) {
3891
4126
  return buildNotComposite(
@@ -3974,20 +4209,19 @@ function assertHavingAggTarget(aggKey, field, model) {
3974
4209
  }
3975
4210
  return;
3976
4211
  }
3977
- const f = assertScalarField2(model, field, "HAVING");
3978
- 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
+ }
3979
4217
  }
3980
4218
  function buildHavingOpsForExpr(expr, filter, params, dialect) {
3981
- const out = [];
3982
- for (const [op, val] of Object.entries(filter)) {
3983
- if (op === "mode") continue;
3984
- const built = buildSimpleComparison(expr, op, val, params, dialect);
3985
- if (built && built.trim().length > 0) out.push(built);
3986
- }
3987
- return out;
4219
+ return buildComparisons(expr, filter, params, dialect, buildSimpleComparison);
3988
4220
  }
3989
4221
  function buildHavingForAggregateFirstShape(aggKey, target, alias, params, dialect, model) {
3990
- if (!isPlainObject(target)) return [];
4222
+ if (!isPlainObject(target)) {
4223
+ throw new Error(`HAVING '${aggKey}' must be an object`);
4224
+ }
3991
4225
  const out = [];
3992
4226
  for (const [field, filter] of Object.entries(target)) {
3993
4227
  assertHavingAggTarget(aggKey, field, model);
@@ -3998,30 +4232,39 @@ function buildHavingForAggregateFirstShape(aggKey, target, alias, params, dialec
3998
4232
  return out;
3999
4233
  }
4000
4234
  function buildHavingForFieldFirstShape(fieldName, target, alias, params, dialect, model) {
4001
- if (!isPlainObject(target)) return [];
4002
- 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");
4003
4239
  const out = [];
4004
4240
  const obj = target;
4005
4241
  const keys = ["_count", "_sum", "_avg", "_min", "_max"];
4006
4242
  for (const aggKey of keys) {
4007
4243
  const aggFilter = obj[aggKey];
4008
4244
  if (!isPlainObject(aggFilter)) continue;
4009
- assertAggregateFieldType(aggKey, field.type, field.name, model.name);
4245
+ if (aggKey === "_sum" || aggKey === "_avg") {
4246
+ assertNumericField(model, fieldName, "HAVING");
4247
+ }
4010
4248
  const entries = Object.entries(aggFilter);
4011
4249
  if (entries.length === 0) continue;
4012
4250
  const expr = aggExprForField(aggKey, fieldName, alias, model);
4013
- for (const [op, val] of entries) {
4014
- if (op === "mode") continue;
4015
- const built = buildSimpleComparison(expr, op, val, params, dialect);
4016
- if (built && built.trim().length > 0) out.push(built);
4017
- }
4251
+ const clauses = buildComparisons(
4252
+ expr,
4253
+ aggFilter,
4254
+ params,
4255
+ dialect,
4256
+ buildSimpleComparison
4257
+ );
4258
+ out.push(...clauses);
4018
4259
  }
4019
4260
  return out;
4020
4261
  }
4021
4262
  function buildHavingClause(having, alias, params, model, dialect) {
4022
4263
  if (!isNotNullish(having)) return "";
4023
4264
  const d = dialect != null ? dialect : getGlobalDialect();
4024
- if (!isPlainObject(having)) return "";
4265
+ if (!isPlainObject(having)) {
4266
+ throw new Error("having must be an object");
4267
+ }
4025
4268
  return buildHavingNode(having, alias, params, d, model);
4026
4269
  }
4027
4270
  function normalizeCountArg(v) {
@@ -4035,18 +4278,8 @@ function pushCountAllField(fields) {
4035
4278
  `${SQL_TEMPLATES.COUNT_ALL} ${SQL_TEMPLATES.AS} ${quote("_count._all")}`
4036
4279
  );
4037
4280
  }
4038
- function assertCountableScalarField(fieldMap, model, fieldName) {
4039
- const field = fieldMap.get(fieldName);
4040
- if (!field) {
4041
- throw new Error(
4042
- `Field '${fieldName}' does not exist on model ${model.name}`
4043
- );
4044
- }
4045
- if (field.isRelation) {
4046
- throw new Error(
4047
- `Cannot use _count on relation field '${fieldName}' on model ${model.name}`
4048
- );
4049
- }
4281
+ function assertCountableScalarField(model, fieldName) {
4282
+ assertScalarField(model, fieldName, "_count");
4050
4283
  }
4051
4284
  function pushCountField(fields, alias, fieldName, model) {
4052
4285
  const outAlias = `_count.${fieldName}`;
@@ -4054,7 +4287,7 @@ function pushCountField(fields, alias, fieldName, model) {
4054
4287
  `COUNT(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4055
4288
  );
4056
4289
  }
4057
- function addCountFields(fields, countArg, alias, model, fieldMap) {
4290
+ function addCountFields(fields, countArg, alias, model) {
4058
4291
  if (!isNotNullish(countArg)) return;
4059
4292
  if (countArg === true) {
4060
4293
  pushCountAllField(fields);
@@ -4068,7 +4301,7 @@ function addCountFields(fields, countArg, alias, model, fieldMap) {
4068
4301
  ([f, v]) => f !== "_all" && isTruthySelection(v)
4069
4302
  );
4070
4303
  for (const [f] of selected) {
4071
- assertCountableScalarField(fieldMap, model, f);
4304
+ assertCountableScalarField(model, f);
4072
4305
  pushCountField(fields, alias, f, model);
4073
4306
  }
4074
4307
  }
@@ -4076,19 +4309,12 @@ function getAggregateSelectionObject(args, agg) {
4076
4309
  const obj = args[agg];
4077
4310
  return isPlainObject(obj) ? obj : void 0;
4078
4311
  }
4079
- function assertAggregatableScalarField(fieldMap, model, agg, fieldName) {
4080
- const field = fieldMap.get(fieldName);
4081
- if (!field) {
4082
- throw new Error(
4083
- `Field '${fieldName}' does not exist on model ${model.name}`
4084
- );
4085
- }
4086
- if (field.isRelation) {
4087
- throw new Error(
4088
- `Cannot use ${agg} on relation field '${fieldName}' on model ${model.name}`
4089
- );
4312
+ function assertAggregatableScalarField(model, agg, fieldName) {
4313
+ if (agg === "_sum" || agg === "_avg") {
4314
+ assertNumericField(model, fieldName, agg);
4315
+ } else {
4316
+ assertScalarField(model, fieldName, agg);
4090
4317
  }
4091
- return field;
4092
4318
  }
4093
4319
  function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
4094
4320
  const outAlias = `${agg}.${fieldName}`;
@@ -4096,7 +4322,7 @@ function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
4096
4322
  `${aggFn}(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4097
4323
  );
4098
4324
  }
4099
- function addAggregateFields(fields, args, alias, model, fieldMap) {
4325
+ function addAggregateFields(fields, args, alias, model) {
4100
4326
  for (const [agg, aggFn] of AGGREGATES) {
4101
4327
  const obj = getAggregateSelectionObject(args, agg);
4102
4328
  if (!obj) continue;
@@ -4104,23 +4330,16 @@ function addAggregateFields(fields, args, alias, model, fieldMap) {
4104
4330
  if (fieldName === "_all")
4105
4331
  throw new Error(`'${agg}' does not support '_all'`);
4106
4332
  if (!isTruthySelection(selection)) continue;
4107
- const field = assertAggregatableScalarField(
4108
- fieldMap,
4109
- model,
4110
- agg,
4111
- fieldName
4112
- );
4113
- assertAggregateFieldType(agg, field.type, fieldName, model.name);
4333
+ assertAggregatableScalarField(model, agg, fieldName);
4114
4334
  pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model);
4115
4335
  }
4116
4336
  }
4117
4337
  }
4118
4338
  function buildAggregateFields(args, alias, model) {
4119
4339
  const fields = [];
4120
- const fieldMap = getModelFieldMap(model);
4121
4340
  const countArg = normalizeCountArg(args._count);
4122
- addCountFields(fields, countArg, alias, model, fieldMap);
4123
- addAggregateFields(fields, args, alias, model, fieldMap);
4341
+ addCountFields(fields, countArg, alias, model);
4342
+ addAggregateFields(fields, args, alias, model);
4124
4343
  return fields;
4125
4344
  }
4126
4345
  function buildAggregateSql(args, whereResult, tableName, alias, model) {
@@ -4157,32 +4376,24 @@ function assertGroupByBy(args, model) {
4157
4376
  if (bySet.size !== byFields.length) {
4158
4377
  throw new Error("buildGroupBySql: by must not contain duplicates");
4159
4378
  }
4160
- const modelFieldMap = getModelFieldMap(model);
4161
4379
  for (const f of byFields) {
4162
- const field = modelFieldMap.get(f);
4163
- if (!field) {
4164
- throw new Error(
4165
- `groupBy.by references unknown field '${f}' on model ${model.name}`
4166
- );
4167
- }
4168
- if (field.isRelation) {
4169
- throw new Error(
4170
- `groupBy.by does not support relation field '${f}' on model ${model.name}`
4171
- );
4172
- }
4380
+ assertScalarField(model, f, "groupBy.by");
4173
4381
  }
4174
4382
  return byFields;
4175
4383
  }
4176
4384
  function buildGroupBySelectParts(args, alias, model, byFields) {
4177
4385
  const groupCols = byFields.map((f) => col(alias, f, model));
4386
+ const selectCols = byFields.map((f) => colWithAlias(alias, f, model));
4178
4387
  const groupFields = groupCols.join(SQL_SEPARATORS.FIELD_LIST);
4179
4388
  const aggFields = buildAggregateFields(args, alias, model);
4180
- 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);
4181
4390
  return { groupCols, groupFields, selectFields };
4182
4391
  }
4183
4392
  function buildGroupByHaving(args, alias, params, model, dialect) {
4184
4393
  if (!isNotNullish(args.having)) return "";
4185
- if (!isPlainObject(args.having)) return "";
4394
+ if (!isPlainObject(args.having)) {
4395
+ throw new Error("having must be an object");
4396
+ }
4186
4397
  const h = buildHavingClause(args.having, alias, params, model, dialect);
4187
4398
  if (!h || h.trim().length === 0) return "";
4188
4399
  return `${SQL_TEMPLATES.HAVING} ${h}`;
@@ -4215,63 +4426,61 @@ function buildGroupBySql(args, whereResult, tableName, alias, model, dialect) {
4215
4426
  const snapshot = params.snapshot();
4216
4427
  validateSelectQuery(sql);
4217
4428
  validateParamConsistency(sql, [...whereResult.params, ...snapshot.params]);
4429
+ const mergedParams = [...whereResult.params, ...snapshot.params];
4218
4430
  return Object.freeze({
4219
4431
  sql,
4220
- params: Object.freeze([...whereResult.params, ...snapshot.params]),
4432
+ params: Object.freeze(mergedParams),
4221
4433
  paramMappings: Object.freeze([
4222
4434
  ...whereResult.paramMappings,
4223
4435
  ...snapshot.mappings
4224
4436
  ])
4225
4437
  });
4226
4438
  }
4227
- function buildCountSql(whereResult, tableName, alias, skip, dialect) {
4439
+ function buildCountSql(whereResult, tableName, alias, skip, _dialect) {
4228
4440
  assertSafeAlias(alias);
4229
4441
  assertSafeTableRef(tableName);
4230
- 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
+ }
4231
4465
  const whereClause = isValidWhereClause(whereResult.clause) ? `${SQL_TEMPLATES.WHERE} ${whereResult.clause}` : "";
4232
- const params = createParamStore(whereResult.nextParamIndex);
4233
- const baseSubSelect = [
4234
- SQL_TEMPLATES.SELECT,
4235
- "1",
4236
- SQL_TEMPLATES.FROM,
4237
- tableName,
4238
- alias,
4239
- whereClause
4240
- ].filter((x) => x && String(x).trim().length > 0).join(" ").trim();
4241
- const normalizedSkip = normalizeSkipLike(skip);
4242
- const subSelect = applyCountSkip(baseSubSelect, normalizedSkip, params, d);
4243
4466
  const sql = [
4244
4467
  SQL_TEMPLATES.SELECT,
4245
4468
  SQL_TEMPLATES.COUNT_ALL,
4246
4469
  SQL_TEMPLATES.AS,
4247
4470
  quote("_count._all"),
4248
4471
  SQL_TEMPLATES.FROM,
4249
- `(${subSelect})`,
4250
- SQL_TEMPLATES.AS,
4251
- `"sub"`
4472
+ tableName,
4473
+ alias,
4474
+ whereClause
4252
4475
  ].filter((x) => x && String(x).trim().length > 0).join(" ").trim();
4253
4476
  validateSelectQuery(sql);
4254
- const snapshot = params.snapshot();
4255
- const mergedParams = [...whereResult.params, ...snapshot.params];
4256
- validateParamConsistency(sql, mergedParams);
4477
+ validateParamConsistency(sql, whereResult.params);
4257
4478
  return Object.freeze({
4258
4479
  sql,
4259
- params: Object.freeze(mergedParams),
4260
- paramMappings: Object.freeze([
4261
- ...whereResult.paramMappings,
4262
- ...snapshot.mappings
4263
- ])
4480
+ params: Object.freeze([...whereResult.params]),
4481
+ paramMappings: Object.freeze([...whereResult.paramMappings])
4264
4482
  });
4265
4483
  }
4266
- function applyCountSkip(subSelect, normalizedSkip, params, dialect) {
4267
- const shouldApply = schemaParser.isDynamicParameter(normalizedSkip) || typeof normalizedSkip === "number" && normalizedSkip > 0;
4268
- if (!shouldApply) return subSelect;
4269
- const placeholder = addAutoScoped(params, normalizedSkip, "count.skip");
4270
- if (dialect === "sqlite") {
4271
- return `${subSelect} ${SQL_TEMPLATES.LIMIT} -1 ${SQL_TEMPLATES.OFFSET} ${placeholder}`;
4272
- }
4273
- return `${subSelect} ${SQL_TEMPLATES.OFFSET} ${placeholder}`;
4274
- }
4275
4484
  function safeAlias(input) {
4276
4485
  const raw = String(input).toLowerCase();
4277
4486
  const cleaned = raw.replace(/[^a-z0-9_]/g, "_");
@@ -4421,8 +4630,12 @@ function buildAndNormalizeSql(args) {
4421
4630
  );
4422
4631
  }
4423
4632
  function finalizeDirective(args) {
4424
- const { directive, normalizedSql, normalizedMappings } = args;
4425
- 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);
4426
4639
  const { staticParams, dynamicKeys } = buildParamsFromMappings(normalizedMappings);
4427
4640
  return {
4428
4641
  method: directive.method,
@@ -4459,7 +4672,8 @@ function generateSQL(directive) {
4459
4672
  return finalizeDirective({
4460
4673
  directive,
4461
4674
  normalizedSql: normalized.sql,
4462
- normalizedMappings: normalized.paramMappings
4675
+ normalizedMappings: normalized.paramMappings,
4676
+ dialect
4463
4677
  });
4464
4678
  }
4465
4679
 
@@ -4836,9 +5050,7 @@ function buildSQLFull(model, models, method, args, dialect) {
4836
5050
  whereResult,
4837
5051
  tableName,
4838
5052
  alias,
4839
- args.skip,
4840
- dialect
4841
- );
5053
+ args.skip);
4842
5054
  break;
4843
5055
  default:
4844
5056
  result = buildSelectSql({