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