prisma-sql 1.44.0 → 1.46.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
@@ -482,20 +533,21 @@ function isEmptyWhere(where) {
482
533
  if (!isNotNullish(where)) return true;
483
534
  return Object.keys(where).length === 0;
484
535
  }
536
+ function sqlPreview(sql) {
537
+ const s = String(sql);
538
+ if (s.length <= 160) return s;
539
+ return `${s.slice(0, 160)}...`;
540
+ }
485
541
  function validateSelectQuery(sql) {
486
542
  if (!hasValidContent(sql)) {
487
543
  throw new Error("CRITICAL: Generated empty SQL query");
488
544
  }
489
545
  if (!hasRequiredKeywords(sql)) {
490
- throw new Error(
491
- `CRITICAL: Invalid SQL structure. SQL: ${sql.substring(0, 100)}...`
492
- );
546
+ throw new Error(`CRITICAL: Invalid SQL structure. SQL: ${sqlPreview(sql)}`);
493
547
  }
494
548
  }
495
- function sqlPreview(sql) {
496
- return `${sql.substring(0, 100)}...`;
497
- }
498
- function parseDollarNumber(sql, start, n) {
549
+ function parseDollarNumber(sql, start) {
550
+ const n = sql.length;
499
551
  let i = start;
500
552
  let num = 0;
501
553
  let hasDigit = false;
@@ -506,14 +558,14 @@ function parseDollarNumber(sql, start, n) {
506
558
  num = num * 10 + (c - 48);
507
559
  i++;
508
560
  }
509
- if (!hasDigit || num <= 0) return { next: i, num: 0, ok: false };
510
- return { next: i, num, ok: true };
561
+ if (!hasDigit || num <= 0) return { next: i, num: 0 };
562
+ return { next: i, num };
511
563
  }
512
564
  function scanDollarPlaceholders(sql, markUpTo) {
513
565
  const seen = new Uint8Array(markUpTo + 1);
514
- let count = 0;
515
566
  let min = Number.POSITIVE_INFINITY;
516
567
  let max = 0;
568
+ let sawAny = false;
517
569
  const n = sql.length;
518
570
  let i = 0;
519
571
  while (i < n) {
@@ -521,17 +573,21 @@ function scanDollarPlaceholders(sql, markUpTo) {
521
573
  i++;
522
574
  continue;
523
575
  }
524
- const { next, num, ok } = parseDollarNumber(sql, i + 1, n);
525
- i = next;
526
- if (!ok) continue;
527
- count++;
576
+ const parsed = parseDollarNumber(sql, i + 1);
577
+ i = parsed.next;
578
+ const num = parsed.num;
579
+ if (num === 0) continue;
580
+ sawAny = true;
528
581
  if (num < min) min = num;
529
582
  if (num > max) max = num;
530
583
  if (num <= markUpTo) seen[num] = 1;
531
584
  }
532
- return { count, min, max, seen };
585
+ if (!sawAny) {
586
+ return { min: 0, max: 0, seen, sawAny: false };
587
+ }
588
+ return { min, max, seen, sawAny: true };
533
589
  }
534
- function assertNoGaps(scan, rangeMin, rangeMax, sql) {
590
+ function assertNoGapsDollar(scan, rangeMin, rangeMax, sql) {
535
591
  for (let k = rangeMin; k <= rangeMax; k++) {
536
592
  if (scan.seen[k] !== 1) {
537
593
  throw new Error(
@@ -542,174 +598,75 @@ function assertNoGaps(scan, rangeMin, rangeMax, sql) {
542
598
  }
543
599
  function validateParamConsistency(sql, params) {
544
600
  const paramLen = params.length;
545
- if (paramLen === 0) {
546
- if (sql.indexOf("$") === -1) return;
547
- }
548
601
  const scan = scanDollarPlaceholders(sql, paramLen);
549
- if (scan.count === 0) {
550
- if (paramLen !== 0) {
602
+ if (paramLen === 0) {
603
+ if (scan.sawAny) {
551
604
  throw new Error(
552
- `CRITICAL: Parameter mismatch - SQL has no placeholders but ${paramLen} params provided.`
605
+ `CRITICAL: SQL contains placeholders but params is empty. SQL: ${sqlPreview(sql)}`
553
606
  );
554
607
  }
555
608
  return;
556
609
  }
557
- if (scan.max !== paramLen) {
610
+ if (!scan.sawAny) {
558
611
  throw new Error(
559
- `CRITICAL: Parameter mismatch - SQL max placeholder is $${scan.max} but ${paramLen} params provided. This will cause SQL execution to fail. SQL: ${sqlPreview(sql)}`
612
+ `CRITICAL: SQL is missing placeholders ($1..$${paramLen}) but params has length ${paramLen}. SQL: ${sqlPreview(sql)}`
560
613
  );
561
614
  }
562
- assertNoGaps(scan, 1, scan.max, sql);
563
- }
564
- function needsQuoting(id) {
565
- if (!isNonEmptyString(id)) return true;
566
- const isKeyword = SQL_KEYWORDS.has(id.toLowerCase());
567
- if (isKeyword) return true;
568
- const isValidIdentifier = REGEX_CACHE.VALID_IDENTIFIER.test(id);
569
- return !isValidIdentifier;
570
- }
571
- function validateParamConsistencyFragment(sql, params) {
572
- const paramLen = params.length;
573
- const scan = scanDollarPlaceholders(sql, paramLen);
574
- if (scan.max === 0) return;
575
- if (scan.max > paramLen) {
615
+ if (scan.min !== 1) {
576
616
  throw new Error(
577
- `CRITICAL: Parameter mismatch - SQL references $${scan.max} but only ${paramLen} params provided. SQL: ${sqlPreview(sql)}`
617
+ `CRITICAL: Placeholder range must start at $1, got min=$${scan.min}. SQL: ${sqlPreview(sql)}`
578
618
  );
579
619
  }
580
- assertNoGaps(scan, scan.min, scan.max, sql);
581
- }
582
- function assertOrThrow(condition, message) {
583
- if (!condition) throw new Error(message);
584
- }
585
- function dialectPlaceholderPrefix(dialect) {
586
- return dialect === "sqlite" ? "?" : "$";
587
- }
588
- function parseSqlitePlaceholderIndices(sql) {
589
- const re = /\?(?:(\d+))?/g;
590
- const indices = [];
591
- let anonCount = 0;
592
- let sawNumbered = false;
593
- let sawAnonymous = false;
594
- for (const m of sql.matchAll(re)) {
595
- const n = m[1];
596
- if (n) {
597
- sawNumbered = true;
598
- indices.push(parseInt(n, 10));
599
- } else {
600
- sawAnonymous = true;
601
- anonCount += 1;
602
- indices.push(anonCount);
603
- }
604
- }
605
- return { indices, sawNumbered, sawAnonymous };
606
- }
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
- function maxIndex(indices) {
622
- return indices.length > 0 ? Math.max(...indices) : 0;
623
- }
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);
638
- for (let i = 1; i <= max; i++) {
639
- assertOrThrow(
640
- placeholders.has(i),
641
- `CRITICAL: Missing SQL placeholder ${prefix}${i} - placeholders must be sequential 1..${max}.`
620
+ if (scan.max !== paramLen) {
621
+ throw new Error(
622
+ `CRITICAL: Placeholder max must match params length. max=$${scan.max}, params=${paramLen}. SQL: ${sqlPreview(sql)}`
642
623
  );
643
624
  }
625
+ assertNoGapsDollar(scan, 1, paramLen, sql);
644
626
  }
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) {
652
- assertOrThrow(
653
- !mappingIndices.has(index),
654
- `CRITICAL: Duplicate ParamMapping index ${index} - each placeholder index must map to exactly one ParamMap.`
655
- );
656
- mappingIndices.add(index);
657
- }
658
- function ensureMappingIndexExistsInSql(placeholders, index) {
659
- assertOrThrow(
660
- placeholders.has(index),
661
- `CRITICAL: ParamMapping index ${index} not found in SQL placeholders.`
662
- );
663
- }
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
- );
627
+ function countQuestionMarkPlaceholders(sql) {
628
+ const s = String(sql);
629
+ let count = 0;
630
+ for (let i = 0; i < s.length; i++) {
631
+ if (s.charCodeAt(i) === 63) count++;
632
+ }
633
+ return count;
673
634
  }
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.`
635
+ function validateQuestionMarkConsistency(sql, params) {
636
+ const expected = params.length;
637
+ const found = countQuestionMarkPlaceholders(sql);
638
+ if (expected !== found) {
639
+ throw new Error(
640
+ `CRITICAL: Parameter mismatch - expected ${expected} '?' placeholders, found ${found}. SQL: ${sqlPreview(sql)}`
680
641
  );
681
642
  }
682
643
  }
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);
644
+ function validateParamConsistencyByDialect(sql, params, dialect) {
645
+ if (dialect === "postgres") {
646
+ validateParamConsistency(sql, params);
647
+ return;
690
648
  }
691
- ensureMappingsCoverAllIndices(mappingIndices, max, dialect);
692
- }
693
- function validateSqlPositions(sql, mappings, dialect) {
694
- const { indices, sawNumbered, sawAnonymous } = getPlaceholderIndices(
695
- sql,
696
- dialect
697
- );
698
649
  if (dialect === "sqlite") {
699
- ensureNoMixedSqlitePlaceholders(sawNumbered, sawAnonymous);
650
+ validateQuestionMarkConsistency(sql, params);
651
+ return;
652
+ }
653
+ if (dialect === "mysql" || dialect === "mariadb") {
654
+ validateQuestionMarkConsistency(sql, params);
655
+ return;
700
656
  }
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);
657
+ validateParamConsistency(sql, params);
658
+ }
659
+ function needsQuoting(identifier) {
660
+ const s = String(identifier);
661
+ if (!REGEX_CACHE.VALID_IDENTIFIER.test(s)) return true;
662
+ const lowered = s.toLowerCase();
663
+ if (SQL_KEYWORDS.has(lowered)) return true;
664
+ return false;
707
665
  }
708
666
 
709
667
  // src/builder/shared/sql-utils.ts
710
- var NUL = String.fromCharCode(0);
711
668
  function containsControlChars(s) {
712
- return s.includes(NUL) || s.includes("\n") || s.includes("\r");
669
+ return /[\u0000-\u001F\u007F]/.test(s);
713
670
  }
714
671
  function assertNoControlChars(label, s) {
715
672
  if (containsControlChars(s)) {
@@ -928,16 +885,46 @@ function buildTableReference(schemaName, tableName, dialect) {
928
885
  return `"${safeSchema}"."${safeTable}"`;
929
886
  }
930
887
  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");
888
+ if (typeof alias !== "string") {
889
+ throw new Error(`Invalid alias: expected string, got ${typeof alias}`);
890
+ }
891
+ const a = alias.trim();
892
+ if (a.length === 0) {
893
+ throw new Error("Invalid alias: required and cannot be empty");
894
+ }
895
+ if (a !== alias) {
896
+ throw new Error("Invalid alias: leading/trailing whitespace");
934
897
  }
935
- if (containsControlChars(a) || a.includes(";")) {
936
- throw new Error(`alias contains unsafe characters: ${JSON.stringify(a)}`);
898
+ if (/[\u0000-\u001F\u007F]/.test(a)) {
899
+ throw new Error(
900
+ "Invalid alias: contains unsafe characters (control characters)"
901
+ );
902
+ }
903
+ if (a.includes('"') || a.includes("'") || a.includes("`")) {
904
+ throw new Error("Invalid alias: contains unsafe characters (quotes)");
905
+ }
906
+ if (a.includes(";")) {
907
+ throw new Error("Invalid alias: contains unsafe characters (semicolon)");
908
+ }
909
+ if (a.includes("--") || a.includes("/*") || a.includes("*/")) {
910
+ throw new Error(
911
+ "Invalid alias: contains unsafe characters (SQL comment tokens)"
912
+ );
913
+ }
914
+ if (/\s/.test(a)) {
915
+ throw new Error(
916
+ "Invalid alias: must be a simple identifier without whitespace"
917
+ );
918
+ }
919
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(a)) {
920
+ throw new Error(
921
+ `Invalid alias: must be a simple identifier (alphanumeric with underscores): "${alias}"`
922
+ );
937
923
  }
938
- if (!/^[A-Za-z_]\w*$/.test(a)) {
924
+ const lowered = a.toLowerCase();
925
+ if (ALIAS_FORBIDDEN_KEYWORDS.has(lowered)) {
939
926
  throw new Error(
940
- `alias must be a simple identifier, got: ${JSON.stringify(a)}`
927
+ `Invalid alias: '${alias}' is a SQL keyword that would break query parsing. Forbidden aliases: ${[...ALIAS_FORBIDDEN_KEYWORDS].join(", ")}`
941
928
  );
942
929
  }
943
930
  }
@@ -979,7 +966,9 @@ function isValidRelationField(field) {
979
966
  if (fk.length === 0) return false;
980
967
  const refsRaw = field.references;
981
968
  const refs = normalizeKeyList(refsRaw);
982
- if (refs.length === 0) return false;
969
+ if (refs.length === 0) {
970
+ return fk.length === 1;
971
+ }
983
972
  if (refs.length !== fk.length) return false;
984
973
  return true;
985
974
  }
@@ -994,6 +983,8 @@ function getReferenceFieldNames(field, foreignKeyCount) {
994
983
  return refs;
995
984
  }
996
985
  function joinCondition(field, parentModel, childModel, parentAlias, childAlias) {
986
+ assertSafeAlias(parentAlias);
987
+ assertSafeAlias(childAlias);
997
988
  const fkFields = normalizeKeyList(field.foreignKey);
998
989
  if (fkFields.length === 0) {
999
990
  throw createError(
@@ -1143,6 +1134,66 @@ function normalizeOrderByInput(orderBy, parseValue) {
1143
1134
  throw new Error("orderBy must be an object or array of objects");
1144
1135
  }
1145
1136
 
1137
+ // src/builder/shared/order-by-determinism.ts
1138
+ function modelHasScalarId(model) {
1139
+ if (!model) return false;
1140
+ return getScalarFieldSet(model).has("id");
1141
+ }
1142
+ function hasIdTiebreaker(orderBy, parse) {
1143
+ if (!isNotNullish(orderBy)) return false;
1144
+ const normalized = normalizeOrderByInput(orderBy, parse);
1145
+ return normalized.some(
1146
+ (obj) => Object.prototype.hasOwnProperty.call(obj, "id")
1147
+ );
1148
+ }
1149
+ function addIdTiebreaker(orderBy) {
1150
+ if (Array.isArray(orderBy)) return [...orderBy, { id: "asc" }];
1151
+ return [orderBy, { id: "asc" }];
1152
+ }
1153
+ function ensureDeterministicOrderByInput(args) {
1154
+ const { orderBy, model, parseValue } = args;
1155
+ if (!modelHasScalarId(model)) return orderBy;
1156
+ if (!isNotNullish(orderBy)) {
1157
+ return { id: "asc" };
1158
+ }
1159
+ if (hasIdTiebreaker(orderBy, parseValue)) return orderBy;
1160
+ return addIdTiebreaker(orderBy);
1161
+ }
1162
+
1163
+ // src/builder/shared/validators/field-assertions.ts
1164
+ var NUMERIC_TYPES = /* @__PURE__ */ new Set(["Int", "Float", "Decimal", "BigInt"]);
1165
+ function assertScalarField(model, fieldName, context) {
1166
+ const field = getFieldInfo(model, fieldName);
1167
+ if (!field) {
1168
+ throw createError(
1169
+ `${context} references unknown field '${fieldName}' on model ${model.name}`,
1170
+ {
1171
+ field: fieldName,
1172
+ modelName: model.name,
1173
+ availableFields: model.fields.map((f) => f.name)
1174
+ }
1175
+ );
1176
+ }
1177
+ if (field.isRelation) {
1178
+ throw createError(
1179
+ `${context} does not support relation field '${fieldName}'`,
1180
+ { field: fieldName, modelName: model.name }
1181
+ );
1182
+ }
1183
+ return field;
1184
+ }
1185
+ function assertNumericField(model, fieldName, context) {
1186
+ const field = assertScalarField(model, fieldName, context);
1187
+ const baseType = field.type.replace(/\[\]|\?/g, "");
1188
+ if (!NUMERIC_TYPES.has(baseType)) {
1189
+ throw createError(
1190
+ `${context} requires numeric field, got '${field.type}'`,
1191
+ { field: fieldName, modelName: model.name }
1192
+ );
1193
+ }
1194
+ return field;
1195
+ }
1196
+
1146
1197
  // src/builder/pagination.ts
1147
1198
  var MAX_LIMIT_OFFSET = 2147483647;
1148
1199
  function parseDirectionRaw(raw, errorLabel) {
@@ -1183,30 +1234,31 @@ function parseOrderByValue(v, fieldName) {
1183
1234
  assertAllowedOrderByKeys(obj, fieldName);
1184
1235
  return { direction, nulls };
1185
1236
  }
1186
- function normalizeFiniteInteger(name, v) {
1187
- if (typeof v !== "number" || !Number.isFinite(v) || !Number.isInteger(v)) {
1188
- throw new Error(`${name} must be an integer`);
1189
- }
1190
- return v;
1191
- }
1192
1237
  function normalizeNonNegativeInt(name, v) {
1193
1238
  if (isDynamicParameter(v)) return v;
1194
- const n = normalizeFiniteInteger(name, v);
1195
- if (n < 0) {
1196
- throw new Error(`${name} must be >= 0`);
1197
- }
1198
- if (n > MAX_LIMIT_OFFSET) {
1199
- throw new Error(`${name} must be <= ${MAX_LIMIT_OFFSET}`);
1200
- }
1201
- return n;
1239
+ const result = normalizeIntLike(name, v, {
1240
+ min: 0,
1241
+ max: MAX_LIMIT_OFFSET,
1242
+ allowZero: true
1243
+ });
1244
+ if (result === void 0)
1245
+ throw new Error(`${name} normalization returned undefined`);
1246
+ return result;
1247
+ }
1248
+ function normalizeIntAllowNegative(name, v) {
1249
+ if (isDynamicParameter(v)) return v;
1250
+ const result = normalizeIntLike(name, v, {
1251
+ min: Number.MIN_SAFE_INTEGER,
1252
+ max: MAX_LIMIT_OFFSET,
1253
+ allowZero: true
1254
+ });
1255
+ if (result === void 0)
1256
+ throw new Error(`${name} normalization returned undefined`);
1257
+ return result;
1202
1258
  }
1203
1259
  function hasNonNullishProp(v, key) {
1204
1260
  return isPlainObject(v) && key in v && isNotNullish(v[key]);
1205
1261
  }
1206
- function normalizeIntegerOrDynamic(name, v) {
1207
- if (isDynamicParameter(v)) return v;
1208
- return normalizeFiniteInteger(name, v);
1209
- }
1210
1262
  function readSkipTake(relArgs) {
1211
1263
  const hasSkip = hasNonNullishProp(relArgs, "skip");
1212
1264
  const hasTake = hasNonNullishProp(relArgs, "take");
@@ -1220,7 +1272,7 @@ function readSkipTake(relArgs) {
1220
1272
  }
1221
1273
  const obj = relArgs;
1222
1274
  const skipVal = hasSkip ? normalizeNonNegativeInt("skip", obj.skip) : void 0;
1223
- const takeVal = hasTake ? normalizeIntegerOrDynamic("take", obj.take) : void 0;
1275
+ const takeVal = hasTake ? normalizeIntAllowNegative("take", obj.take) : void 0;
1224
1276
  return { hasSkip, hasTake, skipVal, takeVal };
1225
1277
  }
1226
1278
  function buildOrderByFragment(entries, alias, dialect, model) {
@@ -1246,9 +1298,7 @@ function buildOrderByFragment(entries, alias, dialect, model) {
1246
1298
  return out.join(SQL_SEPARATORS.ORDER_BY);
1247
1299
  }
1248
1300
  function defaultNullsFor(dialect, direction) {
1249
- if (dialect === "postgres") {
1250
- return direction === "asc" ? "last" : "first";
1251
- }
1301
+ if (dialect === "postgres") return direction === "asc" ? "last" : "first";
1252
1302
  return direction === "asc" ? "first" : "last";
1253
1303
  }
1254
1304
  function ensureCursorFieldsInOrder(orderEntries, cursorEntries) {
@@ -1265,13 +1315,12 @@ function ensureCursorFieldsInOrder(orderEntries, cursorEntries) {
1265
1315
  }
1266
1316
  function buildCursorFilterParts(cursor, cursorAlias, params, model) {
1267
1317
  const entries = Object.entries(cursor);
1268
- if (entries.length === 0) {
1318
+ if (entries.length === 0)
1269
1319
  throw new Error("cursor must have at least one field");
1270
- }
1271
1320
  const placeholdersByField = /* @__PURE__ */ new Map();
1272
1321
  const parts = [];
1273
1322
  for (const [field, value] of entries) {
1274
- const c = `${cursorAlias}.${quote(field)}`;
1323
+ const c = `${cursorAlias}.${quoteColumn(model, field)}`;
1275
1324
  if (value === null) {
1276
1325
  parts.push(`${c} IS NULL`);
1277
1326
  continue;
@@ -1285,13 +1334,6 @@ function buildCursorFilterParts(cursor, cursorAlias, params, model) {
1285
1334
  placeholdersByField
1286
1335
  };
1287
1336
  }
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
1337
  function buildCursorEqualityExpr(columnExpr, valueExpr) {
1296
1338
  return `((${valueExpr} IS NULL AND ${columnExpr} IS NULL) OR (${valueExpr} IS NOT NULL AND ${columnExpr} = ${valueExpr}))`;
1297
1339
  }
@@ -1328,26 +1370,70 @@ function buildOrderEntries(orderBy) {
1328
1370
  if (typeof value === "string") {
1329
1371
  entries.push({ field, direction: value });
1330
1372
  } else {
1331
- entries.push({
1332
- field,
1333
- direction: value.sort,
1334
- nulls: value.nulls
1335
- });
1373
+ entries.push({ field, direction: value.direction, nulls: value.nulls });
1336
1374
  }
1337
1375
  }
1338
1376
  }
1339
1377
  return entries;
1340
1378
  }
1379
+ function buildCursorCteSelectList(cursorEntries, orderEntries, model) {
1380
+ const seen = /* @__PURE__ */ new Set();
1381
+ const ordered = [];
1382
+ for (const [f] of cursorEntries) {
1383
+ if (!seen.has(f)) {
1384
+ seen.add(f);
1385
+ ordered.push(f);
1386
+ }
1387
+ }
1388
+ for (const e of orderEntries) {
1389
+ if (!seen.has(e.field)) {
1390
+ seen.add(e.field);
1391
+ ordered.push(e.field);
1392
+ }
1393
+ }
1394
+ if (ordered.length === 0) throw new Error("cursor cte select list is empty");
1395
+ return ordered.map((f) => quoteColumn(model, f)).join(SQL_SEPARATORS.FIELD_LIST);
1396
+ }
1397
+ function truncateIdent(name, maxLen) {
1398
+ const s = String(name);
1399
+ if (s.length <= maxLen) return s;
1400
+ return s.slice(0, maxLen);
1401
+ }
1402
+ function buildCursorNames(outerAlias) {
1403
+ const maxLen = 63;
1404
+ const base = outerAlias.toLowerCase();
1405
+ const cteName = truncateIdent(`__tp_cursor_${base}`, maxLen);
1406
+ const srcAlias = truncateIdent(`__tp_cursor_src_${base}`, maxLen);
1407
+ if (cteName === outerAlias || srcAlias === outerAlias) {
1408
+ return {
1409
+ cteName: truncateIdent(`__tp_cursor_${base}_x`, maxLen),
1410
+ srcAlias: truncateIdent(`__tp_cursor_src_${base}_x`, maxLen)
1411
+ };
1412
+ }
1413
+ return { cteName, srcAlias };
1414
+ }
1415
+ function assertCursorAndOrderFieldsScalar(model, cursor, orderEntries) {
1416
+ if (!model) return;
1417
+ for (const k of Object.keys(cursor)) assertScalarField(model, k, "cursor");
1418
+ for (const e of orderEntries) assertScalarField(model, e.field, "orderBy");
1419
+ }
1341
1420
  function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect, model) {
1342
1421
  var _a;
1422
+ assertSafeTableRef(tableName);
1423
+ assertSafeAlias(alias);
1343
1424
  const d = dialect != null ? dialect : getGlobalDialect();
1344
1425
  const cursorEntries = Object.entries(cursor);
1345
- if (cursorEntries.length === 0) {
1426
+ if (cursorEntries.length === 0)
1346
1427
  throw new Error("cursor must have at least one field");
1347
- }
1348
- const cursorAlias = "__tp_cursor_src";
1349
- const { whereSql: cursorWhereSql, placeholdersByField } = buildCursorFilterParts(cursor, cursorAlias, params);
1350
- let orderEntries = buildOrderEntries(orderBy);
1428
+ const { cteName, srcAlias } = buildCursorNames(alias);
1429
+ assertSafeAlias(cteName);
1430
+ assertSafeAlias(srcAlias);
1431
+ const deterministicOrderBy = ensureDeterministicOrderByInput({
1432
+ orderBy,
1433
+ model,
1434
+ parseValue: parseOrderByValue
1435
+ });
1436
+ let orderEntries = buildOrderEntries(deterministicOrderBy);
1351
1437
  if (orderEntries.length === 0) {
1352
1438
  orderEntries = cursorEntries.map(([field]) => ({
1353
1439
  field,
@@ -1356,11 +1442,23 @@ function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect
1356
1442
  } else {
1357
1443
  orderEntries = ensureCursorFieldsInOrder(orderEntries, cursorEntries);
1358
1444
  }
1359
- const existsExpr = buildCursorRowExistsExpr(
1360
- tableName,
1361
- cursorAlias,
1362
- cursorWhereSql
1445
+ assertCursorAndOrderFieldsScalar(model, cursor, orderEntries);
1446
+ const { whereSql: cursorWhereSql, placeholdersByField } = buildCursorFilterParts(cursor, srcAlias, params, model);
1447
+ const cursorOrderBy = orderEntries.map(
1448
+ (e) => `${srcAlias}.${quoteColumn(model, e.field)} ${e.direction.toUpperCase()}`
1449
+ ).join(", ");
1450
+ const selectList = buildCursorCteSelectList(
1451
+ cursorEntries,
1452
+ orderEntries,
1453
+ model
1363
1454
  );
1455
+ const cte = `${cteName} AS (
1456
+ SELECT ${selectList} FROM ${tableName} ${srcAlias}
1457
+ WHERE ${cursorWhereSql}
1458
+ ORDER BY ${cursorOrderBy}
1459
+ LIMIT 1
1460
+ )`;
1461
+ const existsExpr = `EXISTS (SELECT 1 FROM ${cteName})`;
1364
1462
  const outerCursorMatch = buildOuterCursorMatch(
1365
1463
  cursor,
1366
1464
  alias,
@@ -1368,34 +1466,29 @@ function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect
1368
1466
  params,
1369
1467
  model
1370
1468
  );
1469
+ const getValueExpr = (field) => `(SELECT ${quoteColumn(model, field)} FROM ${cteName})`;
1371
1470
  const orClauses = [];
1372
1471
  for (let level = 0; level < orderEntries.length; level++) {
1373
1472
  const andParts = [];
1374
1473
  for (let i = 0; i < level; i++) {
1375
1474
  const e2 = orderEntries[i];
1376
1475
  const c2 = col(alias, e2.field, model);
1377
- const v2 = cursorValueExpr(
1378
- tableName,
1379
- cursorAlias,
1380
- cursorWhereSql,
1381
- e2.field);
1476
+ const v2 = getValueExpr(e2.field);
1382
1477
  andParts.push(buildCursorEqualityExpr(c2, v2));
1383
1478
  }
1384
1479
  const e = orderEntries[level];
1385
1480
  const c = col(alias, e.field, model);
1386
- const v = cursorValueExpr(
1387
- tableName,
1388
- cursorAlias,
1389
- cursorWhereSql,
1390
- e.field);
1481
+ const v = getValueExpr(e.field);
1391
1482
  const nulls = (_a = e.nulls) != null ? _a : defaultNullsFor(d, e.direction);
1392
1483
  andParts.push(buildCursorInequalityExpr(c, e.direction, nulls, v));
1393
1484
  orClauses.push(`(${andParts.join(SQL_SEPARATORS.CONDITION_AND)})`);
1394
1485
  }
1395
1486
  const exclusive = orClauses.join(SQL_SEPARATORS.CONDITION_OR);
1396
- return `(${existsExpr} ${SQL_SEPARATORS.CONDITION_AND} ((${exclusive})${SQL_SEPARATORS.CONDITION_OR}(${outerCursorMatch})))`;
1487
+ const condition = `(${existsExpr} ${SQL_SEPARATORS.CONDITION_AND} ((${exclusive})${SQL_SEPARATORS.CONDITION_OR}(${outerCursorMatch})))`;
1488
+ return { cte, condition };
1397
1489
  }
1398
1490
  function buildOrderBy(orderBy, alias, dialect, model) {
1491
+ assertSafeAlias(alias);
1399
1492
  const entries = buildOrderEntries(orderBy);
1400
1493
  if (entries.length === 0) return "";
1401
1494
  const d = dialect != null ? dialect : getGlobalDialect();
@@ -1417,9 +1510,7 @@ function normalizeTakeLike(v) {
1417
1510
  max: MAX_LIMIT_OFFSET,
1418
1511
  allowZero: true
1419
1512
  });
1420
- if (typeof n === "number") {
1421
- if (n === 0) return 0;
1422
- }
1513
+ if (typeof n === "number" && n === 0) return 0;
1423
1514
  return n;
1424
1515
  }
1425
1516
  function normalizeSkipLike(v) {
@@ -1495,6 +1586,11 @@ function buildScalarOperator(expr, op, val, params, mode, fieldType, dialect) {
1495
1586
  }
1496
1587
  return handleInOperator(expr, op, val, params, dialect);
1497
1588
  }
1589
+ if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && !isNotNullish(dialect)) {
1590
+ throw createError(`Insensitive equals requires a SQL dialect`, {
1591
+ operator: op
1592
+ });
1593
+ }
1498
1594
  return handleComparisonOperator(expr, op, val, params);
1499
1595
  }
1500
1596
  function handleNullValue(expr, op) {
@@ -1510,6 +1606,28 @@ function normalizeMode(v) {
1510
1606
  function handleNotOperator(expr, val, params, outerMode, fieldType, dialect) {
1511
1607
  const innerMode = normalizeMode(val.mode);
1512
1608
  const effectiveMode = innerMode != null ? innerMode : outerMode;
1609
+ const entries = Object.entries(val).filter(
1610
+ ([k, v]) => k !== "mode" && v !== void 0
1611
+ );
1612
+ if (entries.length === 0) return "";
1613
+ if (!isNotNullish(dialect)) {
1614
+ const clauses = [];
1615
+ for (const [subOp, subVal] of entries) {
1616
+ const sub = buildScalarOperator(
1617
+ expr,
1618
+ subOp,
1619
+ subVal,
1620
+ params,
1621
+ effectiveMode,
1622
+ fieldType,
1623
+ void 0
1624
+ );
1625
+ if (sub && sub.trim().length > 0) clauses.push(`(${sub})`);
1626
+ }
1627
+ if (clauses.length === 0) return "";
1628
+ if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
1629
+ return `${SQL_TEMPLATES.NOT} (${clauses.join(` ${SQL_TEMPLATES.AND} `)})`;
1630
+ }
1513
1631
  return buildNotComposite(
1514
1632
  expr,
1515
1633
  val,
@@ -1724,6 +1842,7 @@ function handleArrayIsEmpty(expr, val, dialect) {
1724
1842
 
1725
1843
  // src/builder/where/operators-json.ts
1726
1844
  var SAFE_JSON_PATH_SEGMENT = /^[a-zA-Z_]\w*$/;
1845
+ var MAX_PATH_SEGMENT_LENGTH = 255;
1727
1846
  function validateJsonPathSegments(segments) {
1728
1847
  for (const segment of segments) {
1729
1848
  if (typeof segment !== "string") {
@@ -1732,6 +1851,12 @@ function validateJsonPathSegments(segments) {
1732
1851
  value: segment
1733
1852
  });
1734
1853
  }
1854
+ if (segment.length > MAX_PATH_SEGMENT_LENGTH) {
1855
+ throw createError(
1856
+ `JSON path segment too long: max ${MAX_PATH_SEGMENT_LENGTH} characters`,
1857
+ { operator: Ops.PATH, value: `[${segment.length} chars]` }
1858
+ );
1859
+ }
1735
1860
  if (!SAFE_JSON_PATH_SEGMENT.test(segment)) {
1736
1861
  throw createError(
1737
1862
  `Invalid JSON path segment: '${segment}'. Must be alphanumeric with underscores, starting with letter or underscore.`,
@@ -1820,6 +1945,9 @@ function handleJsonWildcard(expr, op, val, params, wildcards, dialect) {
1820
1945
 
1821
1946
  // src/builder/where/relations.ts
1822
1947
  var NO_JOINS = Object.freeze([]);
1948
+ function freezeJoins(items) {
1949
+ return Object.freeze([...items]);
1950
+ }
1823
1951
  function isListRelation(fieldType) {
1824
1952
  return typeof fieldType === "string" && fieldType.endsWith("[]");
1825
1953
  }
@@ -1882,7 +2010,7 @@ function buildListRelationFilters(args) {
1882
2010
  const whereClause = `${relAlias}.${quote(checkField.name)} IS NULL`;
1883
2011
  return Object.freeze({
1884
2012
  clause: whereClause,
1885
- joins: [leftJoinSql]
2013
+ joins: freezeJoins([leftJoinSql])
1886
2014
  });
1887
2015
  }
1888
2016
  }
@@ -2087,7 +2215,7 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
2087
2215
  Ops.HAS_EVERY,
2088
2216
  Ops.IS_EMPTY
2089
2217
  ]);
2090
- const JSON_OPS = /* @__PURE__ */ new Set([
2218
+ const JSON_OPS2 = /* @__PURE__ */ new Set([
2091
2219
  Ops.PATH,
2092
2220
  Ops.STRING_CONTAINS,
2093
2221
  Ops.STRING_STARTS_WITH,
@@ -2104,7 +2232,7 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
2104
2232
  modelName
2105
2233
  });
2106
2234
  }
2107
- const isJsonOp = JSON_OPS.has(op);
2235
+ const isJsonOp = JSON_OPS2.has(op);
2108
2236
  const isFieldJson = isJsonType(fieldType);
2109
2237
  const jsonOpMismatch = isJsonOp && !isFieldJson;
2110
2238
  if (jsonOpMismatch) {
@@ -2118,6 +2246,14 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
2118
2246
  }
2119
2247
 
2120
2248
  // src/builder/where/builder.ts
2249
+ var MAX_QUERY_DEPTH = 50;
2250
+ var EMPTY_JOINS = Object.freeze([]);
2251
+ var JSON_OPS = /* @__PURE__ */ new Set([
2252
+ Ops.PATH,
2253
+ Ops.STRING_CONTAINS,
2254
+ Ops.STRING_STARTS_WITH,
2255
+ Ops.STRING_ENDS_WITH
2256
+ ]);
2121
2257
  var WhereBuilder = class {
2122
2258
  build(where, ctx) {
2123
2259
  if (!isPlainObject(where)) {
@@ -2129,8 +2265,6 @@ var WhereBuilder = class {
2129
2265
  return buildWhereInternal(where, ctx, this);
2130
2266
  }
2131
2267
  };
2132
- var MAX_QUERY_DEPTH = 50;
2133
- var EMPTY_JOINS = Object.freeze([]);
2134
2268
  var whereBuilderInstance = new WhereBuilder();
2135
2269
  function freezeResult(clause, joins = EMPTY_JOINS) {
2136
2270
  return Object.freeze({ clause, joins });
@@ -2307,16 +2441,8 @@ function buildOperator(expr, op, val, ctx, mode, fieldType) {
2307
2441
  if (fieldType && isArrayType(fieldType)) {
2308
2442
  return buildArrayOperator(expr, op, val, ctx.params, fieldType, ctx.dialect);
2309
2443
  }
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
- }
2444
+ if (fieldType && isJsonType(fieldType) && JSON_OPS.has(op)) {
2445
+ return buildJsonOperator(expr, op, val, ctx.params, ctx.dialect);
2320
2446
  }
2321
2447
  return buildScalarOperator(
2322
2448
  expr,
@@ -2337,7 +2463,7 @@ function toSafeSqlIdentifier(input) {
2337
2463
  const base = startsOk ? cleaned : `_${cleaned}`;
2338
2464
  const fallback = base.length > 0 ? base : "_t";
2339
2465
  const lowered = fallback.toLowerCase();
2340
- return SQL_KEYWORDS.has(lowered) ? `_${lowered}` : lowered;
2466
+ return ALIAS_FORBIDDEN_KEYWORDS.has(lowered) ? `_${lowered}` : lowered;
2341
2467
  }
2342
2468
  function createAliasGenerator(maxAliases = 1e4) {
2343
2469
  let counter = 0;
@@ -2547,6 +2673,7 @@ function toPublicResult(clause, joins, params) {
2547
2673
  // src/builder/where.ts
2548
2674
  function buildWhereClause(where, options) {
2549
2675
  var _a, _b, _c, _d, _e;
2676
+ assertSafeAlias(options.alias);
2550
2677
  const dialect = options.dialect || getGlobalDialect();
2551
2678
  const params = (_a = options.params) != null ? _a : createParamStore();
2552
2679
  const ctx = {
@@ -2562,22 +2689,6 @@ function buildWhereClause(where, options) {
2562
2689
  };
2563
2690
  const result = whereBuilderInstance.build(where, ctx);
2564
2691
  const publicResult = toPublicResult(result.clause, result.joins, params);
2565
- if (!options.isSubquery) {
2566
- const nums = [...publicResult.clause.matchAll(/\$(\d+)/g)].map(
2567
- (m) => parseInt(m[1], 10)
2568
- );
2569
- if (nums.length > 0) {
2570
- const min = Math.min(...nums);
2571
- if (min === 1) {
2572
- validateParamConsistency(publicResult.clause, publicResult.params);
2573
- } else {
2574
- validateParamConsistencyFragment(
2575
- publicResult.clause,
2576
- publicResult.params
2577
- );
2578
- }
2579
- }
2580
- }
2581
2692
  return publicResult;
2582
2693
  }
2583
2694
 
@@ -2715,6 +2826,9 @@ function buildRelationSelect(relArgs, relModel, relAlias) {
2715
2826
  }
2716
2827
 
2717
2828
  // src/builder/select/includes.ts
2829
+ var MAX_INCLUDE_DEPTH = 10;
2830
+ var MAX_TOTAL_SUBQUERIES = 100;
2831
+ var MAX_TOTAL_INCLUDES = 50;
2718
2832
  function getRelationTableReference(relModel, dialect) {
2719
2833
  return buildTableReference(
2720
2834
  SQL_TEMPLATES.PUBLIC_SCHEMA,
@@ -2760,107 +2874,23 @@ function relationEntriesFromArgs(args, model) {
2760
2874
  pushFrom(args.select);
2761
2875
  return out;
2762
2876
  }
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
2877
  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;
2878
+ if (!isNotNullish(orderBy)) return;
2879
+ const scalarSet = getScalarFieldSet(model);
2880
+ const normalized = normalizeOrderByInput(orderBy, parseOrderByValue);
2881
+ for (const item of normalized) {
2882
+ const entries = Object.entries(item);
2883
+ if (entries.length !== 1) {
2884
+ throw new Error("orderBy array entries must have exactly one field");
2856
2885
  }
2857
- if (isPlainObject(v)) {
2858
- validateOrderByObject(fieldName, v);
2859
- continue;
2886
+ const fieldName = String(entries[0][0]).trim();
2887
+ if (fieldName.length === 0)
2888
+ throw new Error("orderBy field name cannot be empty");
2889
+ if (!scalarSet.has(fieldName)) {
2890
+ throw new Error(
2891
+ `orderBy references unknown or non-scalar field '${fieldName}' on model ${model.name}`
2892
+ );
2860
2893
  }
2861
- throw new Error(
2862
- `orderBy for '${fieldName}' must be 'asc' | 'desc' or { sort, nulls? }`
2863
- );
2864
2894
  }
2865
2895
  }
2866
2896
  function appendLimitOffset(sql, dialect, params, takeVal, skipVal, scope) {
@@ -2889,7 +2919,10 @@ function readWhereInput(relArgs) {
2889
2919
  function readOrderByInput(relArgs) {
2890
2920
  if (!isPlainObject(relArgs)) return { hasOrderBy: false, orderBy: void 0 };
2891
2921
  if (!("orderBy" in relArgs)) return { hasOrderBy: false, orderBy: void 0 };
2892
- return { hasOrderBy: true, orderBy: relArgs.orderBy };
2922
+ return {
2923
+ hasOrderBy: true,
2924
+ orderBy: relArgs.orderBy
2925
+ };
2893
2926
  }
2894
2927
  function extractRelationPaginationConfig(relArgs) {
2895
2928
  const { hasOrderBy, orderBy: rawOrderByInput } = readOrderByInput(relArgs);
@@ -2911,44 +2944,25 @@ function extractRelationPaginationConfig(relArgs) {
2911
2944
  function maybeReverseNegativeTake(takeVal, hasOrderBy, orderByInput) {
2912
2945
  if (typeof takeVal !== "number") return { takeVal, orderByInput };
2913
2946
  if (takeVal >= 0) return { takeVal, orderByInput };
2914
- if (!hasOrderBy) {
2947
+ if (!hasOrderBy)
2915
2948
  throw new Error("Negative take requires orderBy for deterministic results");
2916
- }
2917
2949
  return {
2918
2950
  takeVal: Math.abs(takeVal),
2919
2951
  orderByInput: reverseOrderByInput(orderByInput)
2920
2952
  };
2921
2953
  }
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);
2940
- }
2941
- return orderByInput;
2954
+ function finalizeOrderByForInclude(args) {
2955
+ if (args.hasOrderBy && isNotNullish(args.orderByInput)) {
2956
+ validateOrderByForModel(args.relModel, args.orderByInput);
2942
2957
  }
2943
- if (!hasOrderBy) {
2944
- return modelHasScalarId(relModel) ? { id: "asc" } : orderByInput;
2958
+ if (!args.hasPagination) {
2959
+ return args.orderByInput;
2945
2960
  }
2946
- if (isNotNullish(orderByInput)) {
2947
- validateOrderByForModel(relModel, orderByInput);
2948
- }
2949
- if (!modelHasScalarId(relModel)) return orderByInput;
2950
- if (hasIdTiebreaker(orderByInput)) return orderByInput;
2951
- return addIdTiebreaker(orderByInput);
2961
+ return ensureDeterministicOrderByInput({
2962
+ orderBy: args.hasOrderBy ? args.orderByInput : void 0,
2963
+ model: args.relModel,
2964
+ parseValue: parseOrderByValue
2965
+ });
2952
2966
  }
2953
2967
  function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
2954
2968
  let relSelect = buildRelationSelect(relArgs, relModel, relAlias);
@@ -2959,7 +2973,10 @@ function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
2959
2973
  relAlias,
2960
2974
  ctx.aliasGen,
2961
2975
  ctx.params,
2962
- ctx.dialect
2976
+ ctx.dialect,
2977
+ ctx.visitPath || [],
2978
+ (ctx.depth || 0) + 1,
2979
+ ctx.stats
2963
2980
  ) : [];
2964
2981
  if (isNonEmptyArray(nestedIncludes)) {
2965
2982
  const emptyJson = ctx.dialect === "postgres" ? `'[]'::json` : `json('[]')`;
@@ -3046,11 +3063,7 @@ function buildListIncludeSpec(args) {
3046
3063
  joinPredicate: args.joinPredicate,
3047
3064
  whereClause: args.whereClause
3048
3065
  });
3049
- return Object.freeze({
3050
- name: args.relName,
3051
- sql: sql2,
3052
- isOneToOne: false
3053
- });
3066
+ return Object.freeze({ name: args.relName, sql: sql2, isOneToOne: false });
3054
3067
  }
3055
3068
  const rowAlias = args.ctx.aliasGen.next(`${args.relName}_row`);
3056
3069
  let base = buildBaseSql({
@@ -3061,9 +3074,7 @@ function buildListIncludeSpec(args) {
3061
3074
  joinPredicate: args.joinPredicate,
3062
3075
  whereClause: args.whereClause
3063
3076
  });
3064
- if (args.orderBySql) {
3065
- base += ` ${SQL_TEMPLATES.ORDER_BY} ${args.orderBySql}`;
3066
- }
3077
+ if (args.orderBySql) base += ` ${SQL_TEMPLATES.ORDER_BY} ${args.orderBySql}`;
3067
3078
  base = appendLimitOffset(
3068
3079
  base,
3069
3080
  args.ctx.dialect,
@@ -3074,11 +3085,7 @@ function buildListIncludeSpec(args) {
3074
3085
  );
3075
3086
  const selectExpr = jsonAgg("row", args.ctx.dialect);
3076
3087
  const sql = `${SQL_TEMPLATES.SELECT} ${selectExpr} ${SQL_TEMPLATES.FROM} (${base}) ${rowAlias}`;
3077
- return Object.freeze({
3078
- name: args.relName,
3079
- sql,
3080
- isOneToOne: false
3081
- });
3088
+ return Object.freeze({ name: args.relName, sql, isOneToOne: false });
3082
3089
  }
3083
3090
  function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3084
3091
  const relTable = getRelationTableReference(relModel, ctx.dialect);
@@ -3109,12 +3116,12 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3109
3116
  paginationConfig.orderBy
3110
3117
  );
3111
3118
  const hasPagination = paginationConfig.hasSkip || paginationConfig.hasTake;
3112
- const finalOrderByInput = ensureDeterministicOrderBy(
3119
+ const finalOrderByInput = finalizeOrderByForInclude({
3113
3120
  relModel,
3114
- paginationConfig.hasOrderBy,
3115
- adjusted.orderByInput,
3121
+ hasOrderBy: paginationConfig.hasOrderBy,
3122
+ orderByInput: adjusted.orderByInput,
3116
3123
  hasPagination
3117
- );
3124
+ });
3118
3125
  const orderBySql = buildOrderBySql(
3119
3126
  finalOrderByInput,
3120
3127
  relAlias,
@@ -3135,11 +3142,7 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3135
3142
  skipVal: paginationConfig.skipVal,
3136
3143
  ctx
3137
3144
  });
3138
- return Object.freeze({
3139
- name: relName,
3140
- sql,
3141
- isOneToOne: true
3142
- });
3145
+ return Object.freeze({ name: relName, sql, isOneToOne: true });
3143
3146
  }
3144
3147
  return buildListIncludeSpec({
3145
3148
  relName,
@@ -3155,32 +3158,69 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3155
3158
  ctx
3156
3159
  });
3157
3160
  }
3158
- function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, params, dialect) {
3161
+ function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, params, dialect, visitPath = [], depth = 0, stats) {
3162
+ if (!stats) stats = { totalIncludes: 0, totalSubqueries: 0, maxDepth: 0 };
3163
+ if (depth > MAX_INCLUDE_DEPTH) {
3164
+ throw new Error(
3165
+ `Maximum include depth of ${MAX_INCLUDE_DEPTH} exceeded. Path: ${visitPath.join(" -> ")}. Deep includes cause exponential SQL complexity and performance issues.`
3166
+ );
3167
+ }
3168
+ stats.maxDepth = Math.max(stats.maxDepth, depth);
3159
3169
  const includes = [];
3160
3170
  const entries = relationEntriesFromArgs(args, model);
3161
3171
  for (const [relName, relArgs] of entries) {
3162
3172
  if (relArgs === false) continue;
3173
+ stats.totalIncludes++;
3174
+ if (stats.totalIncludes > MAX_TOTAL_INCLUDES) {
3175
+ throw new Error(
3176
+ `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.`
3177
+ );
3178
+ }
3179
+ stats.totalSubqueries++;
3180
+ if (stats.totalSubqueries > MAX_TOTAL_SUBQUERIES) {
3181
+ throw new Error(
3182
+ `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.`
3183
+ );
3184
+ }
3163
3185
  const resolved = resolveRelationOrThrow(model, schemas, relName);
3164
- const include = buildSingleInclude(
3165
- relName,
3166
- relArgs,
3167
- resolved.field,
3168
- resolved.relModel,
3169
- {
3186
+ const relationPath = `${model.name}.${relName}`;
3187
+ const currentPath = [...visitPath, relationPath];
3188
+ if (visitPath.includes(relationPath)) {
3189
+ throw new Error(
3190
+ `Circular include detected: ${currentPath.join(" -> ")}. Relation '${relationPath}' creates an infinite loop.`
3191
+ );
3192
+ }
3193
+ const modelOccurrences = currentPath.filter(
3194
+ (p) => p.startsWith(`${resolved.relModel.name}.`)
3195
+ ).length;
3196
+ if (modelOccurrences > 2) {
3197
+ throw new Error(
3198
+ `Include too deeply nested: model '${resolved.relModel.name}' appears ${modelOccurrences} times in path: ${currentPath.join(" -> ")}`
3199
+ );
3200
+ }
3201
+ includes.push(
3202
+ buildSingleInclude(relName, relArgs, resolved.field, resolved.relModel, {
3170
3203
  model,
3171
3204
  schemas,
3172
3205
  parentAlias,
3173
3206
  aliasGen,
3174
3207
  dialect,
3175
- params
3176
- }
3208
+ params,
3209
+ visitPath: currentPath,
3210
+ depth: depth + 1,
3211
+ stats
3212
+ })
3177
3213
  );
3178
- includes.push(include);
3179
3214
  }
3180
3215
  return includes;
3181
3216
  }
3182
3217
  function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
3183
3218
  const aliasGen = createAliasGenerator();
3219
+ const stats = {
3220
+ totalIncludes: 0,
3221
+ totalSubqueries: 0,
3222
+ maxDepth: 0
3223
+ };
3184
3224
  return buildIncludeSqlInternal(
3185
3225
  args,
3186
3226
  model,
@@ -3188,7 +3228,10 @@ function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
3188
3228
  parentAlias,
3189
3229
  aliasGen,
3190
3230
  params,
3191
- dialect
3231
+ dialect,
3232
+ [],
3233
+ 0,
3234
+ stats
3192
3235
  );
3193
3236
  }
3194
3237
  function resolveCountRelationOrThrow(relName, model, schemas) {
@@ -3199,10 +3242,14 @@ function resolveCountRelationOrThrow(relName, model, schemas) {
3199
3242
  );
3200
3243
  }
3201
3244
  const field = model.fields.find((f) => f.name === relName);
3202
- if (!field) {
3245
+ if (!field)
3203
3246
  throw new Error(
3204
3247
  `_count.${relName} references unknown relation on model ${model.name}`
3205
3248
  );
3249
+ if (!isValidRelationField(field)) {
3250
+ throw new Error(
3251
+ `_count.${relName} has invalid relation metadata on model ${model.name}`
3252
+ );
3206
3253
  }
3207
3254
  const relModel = schemas.find((m) => m.name === field.relatedModel);
3208
3255
  if (!relModel) {
@@ -3212,31 +3259,78 @@ function resolveCountRelationOrThrow(relName, model, schemas) {
3212
3259
  }
3213
3260
  return { field, relModel };
3214
3261
  }
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])}`;
3262
+ function defaultReferencesForCount(fkCount) {
3263
+ if (fkCount === 1) return ["id"];
3264
+ throw new Error(
3265
+ "Relation count for composite keys requires explicit references matching foreignKey length"
3266
+ );
3219
3267
  }
3220
- function leftJoinOnForCount(field, parentAlias, joinAlias) {
3268
+ function resolveCountKeyPairs(field) {
3221
3269
  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")}`;
3270
+ if (fkFields.length === 0)
3271
+ throw new Error("Relation count requires foreignKey");
3272
+ const refsRaw = field.references;
3273
+ const refs = normalizeKeyList(refsRaw);
3274
+ const refFields = refs.length > 0 ? refs : defaultReferencesForCount(fkFields.length);
3275
+ if (refFields.length !== fkFields.length) {
3276
+ throw new Error(
3277
+ "Relation count requires references count to match foreignKey count"
3278
+ );
3279
+ }
3280
+ const relKeyFields = field.isForeignKeyLocal ? refFields : fkFields;
3281
+ const parentKeyFields = field.isForeignKeyLocal ? fkFields : refFields;
3282
+ return { relKeyFields, parentKeyFields };
3283
+ }
3284
+ function aliasQualifiedColumn(alias, model, field) {
3285
+ return `${alias}.${quoteColumn(model, field)}`;
3286
+ }
3287
+ function subqueryForCount(args) {
3288
+ const selectKeys = args.relKeyFields.map(
3289
+ (f, i) => `${aliasQualifiedColumn(args.countAlias, args.relModel, f)} AS "__fk${i}"`
3290
+ ).join(SQL_SEPARATORS.FIELD_LIST);
3291
+ const groupByKeys = args.relKeyFields.map((f) => aliasQualifiedColumn(args.countAlias, args.relModel, f)).join(SQL_SEPARATORS.FIELD_LIST);
3292
+ const cntExpr = args.dialect === "postgres" ? "COUNT(*)::int AS __cnt" : "COUNT(*) AS __cnt";
3293
+ return `(SELECT ${selectKeys}${SQL_SEPARATORS.FIELD_LIST}${cntExpr} FROM ${args.relTable} ${args.countAlias} GROUP BY ${groupByKeys})`;
3224
3294
  }
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})`;
3295
+ function leftJoinOnForCount(args) {
3296
+ const parts = args.parentKeyFields.map(
3297
+ (f, i) => `${args.joinAlias}."__fk${i}" = ${aliasQualifiedColumn(args.parentAlias, args.parentModel, f)}`
3298
+ );
3299
+ return parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`;
3300
+ }
3301
+ function nextAliasAvoiding(aliasGen, base, forbidden) {
3302
+ let a = aliasGen.next(base);
3303
+ while (forbidden.has(a)) a = aliasGen.next(base);
3304
+ return a;
3227
3305
  }
3228
3306
  function buildCountJoinAndPair(args) {
3229
3307
  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,
3308
+ const { relKeyFields, parentKeyFields } = resolveCountKeyPairs(args.field);
3309
+ const forbidden = /* @__PURE__ */ new Set([args.parentAlias]);
3310
+ const countAlias = nextAliasAvoiding(
3311
+ args.aliasGen,
3312
+ `__tp_cnt_${args.relName}`,
3313
+ forbidden
3314
+ );
3315
+ forbidden.add(countAlias);
3316
+ const subquery = subqueryForCount({
3317
+ dialect: args.dialect,
3234
3318
  relTable,
3235
3319
  countAlias,
3236
- groupByCol
3320
+ relModel: args.relModel,
3321
+ relKeyFields
3322
+ });
3323
+ const joinAlias = nextAliasAvoiding(
3324
+ args.aliasGen,
3325
+ `__tp_cnt_j_${args.relName}`,
3326
+ forbidden
3237
3327
  );
3238
- const joinAlias = `__tp_cnt_j_${args.relName}`;
3239
- const leftJoinOn = leftJoinOnForCount(args.field, args.parentAlias, joinAlias);
3328
+ const leftJoinOn = leftJoinOnForCount({
3329
+ joinAlias,
3330
+ parentAlias: args.parentAlias,
3331
+ parentModel: args.parentModel,
3332
+ parentKeyFields
3333
+ });
3240
3334
  return {
3241
3335
  joinSql: `LEFT JOIN ${subquery} ${joinAlias} ON ${leftJoinOn}`,
3242
3336
  pairSql: `${sqlStringLiteral(args.relName)}, COALESCE(${joinAlias}.__cnt, 0)`
@@ -3245,6 +3339,7 @@ function buildCountJoinAndPair(args) {
3245
3339
  function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params, dialect) {
3246
3340
  const joins = [];
3247
3341
  const pairs = [];
3342
+ const aliasGen = createAliasGenerator();
3248
3343
  for (const [relName, shouldCount] of Object.entries(countSelect)) {
3249
3344
  if (!shouldCount) continue;
3250
3345
  const resolved = resolveCountRelationOrThrow(relName, model, schemas);
@@ -3252,29 +3347,33 @@ function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params
3252
3347
  relName,
3253
3348
  field: resolved.field,
3254
3349
  relModel: resolved.relModel,
3350
+ parentModel: model,
3255
3351
  parentAlias,
3256
- dialect
3352
+ dialect,
3353
+ aliasGen
3257
3354
  });
3258
3355
  joins.push(built.joinSql);
3259
3356
  pairs.push(built.pairSql);
3260
3357
  }
3261
- return {
3262
- joins,
3263
- jsonPairs: pairs.join(SQL_SEPARATORS.FIELD_LIST)
3264
- };
3358
+ return { joins, jsonPairs: pairs.join(SQL_SEPARATORS.FIELD_LIST) };
3265
3359
  }
3266
3360
 
3267
3361
  // src/builder/select/assembly.ts
3268
- var SIMPLE_SELECT_RE_CACHE = /* @__PURE__ */ new Map();
3269
- function normalizeFinalParams(params) {
3270
- return params.map(normalizeValue);
3271
- }
3362
+ var ALIAS_CAPTURE = "([A-Za-z_][A-Za-z0-9_]*)";
3363
+ var COLUMN_PART = '(?:"([^"]+)"|([a-z_][a-z0-9_]*))';
3364
+ var AS_PART = `(?:\\s+AS\\s+${COLUMN_PART})?`;
3365
+ var SIMPLE_COLUMN_PATTERN = `^${ALIAS_CAPTURE}\\.${COLUMN_PART}${AS_PART}$`;
3366
+ var SIMPLE_COLUMN_RE = new RegExp(SIMPLE_COLUMN_PATTERN, "i");
3272
3367
  function joinNonEmpty(parts, sep) {
3273
3368
  return parts.filter((s) => s.trim().length > 0).join(sep);
3274
3369
  }
3275
3370
  function buildWhereSql(conditions) {
3276
3371
  if (!isNonEmptyArray(conditions)) return "";
3277
- return ` ${SQL_TEMPLATES.WHERE} ${conditions.join(SQL_SEPARATORS.CONDITION_AND)}`;
3372
+ const parts = [
3373
+ SQL_TEMPLATES.WHERE,
3374
+ conditions.join(SQL_SEPARATORS.CONDITION_AND)
3375
+ ];
3376
+ return ` ${parts.join(" ")}`;
3278
3377
  }
3279
3378
  function buildJoinsSql(...joinGroups) {
3280
3379
  const all = [];
@@ -3289,37 +3388,43 @@ function buildSelectList(baseSelect, extraCols) {
3289
3388
  if (base && extra) return `${base}${SQL_SEPARATORS.FIELD_LIST}${extra}`;
3290
3389
  return base || extra;
3291
3390
  }
3292
- function finalizeSql(sql, params) {
3391
+ function finalizeSql(sql, params, dialect) {
3293
3392
  const snapshot = params.snapshot();
3294
3393
  validateSelectQuery(sql);
3295
- validateParamConsistency(sql, snapshot.params);
3394
+ validateParamConsistencyByDialect(
3395
+ sql,
3396
+ snapshot.params,
3397
+ dialect === "sqlite" ? "postgres" : dialect
3398
+ );
3296
3399
  return Object.freeze({
3297
3400
  sql,
3298
- params: normalizeFinalParams(snapshot.params),
3401
+ params: snapshot.params,
3299
3402
  paramMappings: Object.freeze(snapshot.mappings)
3300
3403
  });
3301
3404
  }
3302
- function parseSimpleScalarSelect(select, alias) {
3303
- var _a, _b;
3405
+ function parseSimpleScalarSelect(select, fromAlias) {
3406
+ var _a, _b, _c, _d;
3304
3407
  const raw = select.trim();
3305
3408
  if (raw.length === 0) return [];
3306
- let re = SIMPLE_SELECT_RE_CACHE.get(alias);
3307
- if (!re) {
3308
- const safeAlias2 = alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3309
- re = new RegExp(`^${safeAlias2}\\.(?:"([^"]+)"|([a-z_][a-z0-9_]*))$`, "i");
3310
- SIMPLE_SELECT_RE_CACHE.set(alias, re);
3311
- }
3312
3409
  const parts = raw.split(SQL_SEPARATORS.FIELD_LIST);
3313
3410
  const names = [];
3314
3411
  for (const part of parts) {
3315
3412
  const p = part.trim();
3316
- const m = p.match(re);
3413
+ const m = p.match(SIMPLE_COLUMN_RE);
3317
3414
  if (!m) {
3318
3415
  throw new Error(
3319
- `sqlite distinct emulation requires scalar select fields to be simple columns. Got: ${p}`
3416
+ `sqlite distinct emulation requires scalar select fields to be simple columns (optionally with AS). Got: ${p}`
3320
3417
  );
3321
3418
  }
3322
- const name = ((_b = (_a = m[1]) != null ? _a : m[2]) != null ? _b : "").trim();
3419
+ const actualAlias = m[1];
3420
+ if (actualAlias.toLowerCase() !== fromAlias.toLowerCase()) {
3421
+ throw new Error(
3422
+ `Expected alias '${fromAlias}', got '${actualAlias}' in: ${p}`
3423
+ );
3424
+ }
3425
+ const columnName = ((_b = (_a = m[2]) != null ? _a : m[3]) != null ? _b : "").trim();
3426
+ const outAlias = ((_d = (_c = m[4]) != null ? _c : m[5]) != null ? _d : "").trim();
3427
+ const name = outAlias.length > 0 ? outAlias : columnName;
3323
3428
  if (name.length === 0) {
3324
3429
  throw new Error(`Failed to parse selected column name from: ${p}`);
3325
3430
  }
@@ -3328,18 +3433,18 @@ function parseSimpleScalarSelect(select, alias) {
3328
3433
  return names;
3329
3434
  }
3330
3435
  function replaceOrderByAlias(orderBy, fromAlias, outerAlias) {
3331
- const needle = `${fromAlias}.`;
3332
- const replacement = `${outerAlias}.`;
3333
- return orderBy.split(needle).join(replacement);
3436
+ const src = String(fromAlias);
3437
+ if (src.length === 0) return orderBy;
3438
+ const escaped = src.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3439
+ const re = new RegExp(`\\b${escaped}\\.`, "gi");
3440
+ return orderBy.replace(re, `${outerAlias}.`);
3334
3441
  }
3335
3442
  function buildDistinctColumns(distinct, fromAlias, model) {
3336
3443
  return distinct.map((f) => col(fromAlias, f, model)).join(SQL_SEPARATORS.FIELD_LIST);
3337
3444
  }
3338
3445
  function buildOutputColumns(scalarNames, includeNames, hasCount) {
3339
3446
  const outputCols = [...scalarNames, ...includeNames];
3340
- if (hasCount) {
3341
- outputCols.push("_count");
3342
- }
3447
+ if (hasCount) outputCols.push("_count");
3343
3448
  const formatted = outputCols.map((n) => quote(n)).join(SQL_SEPARATORS.FIELD_LIST);
3344
3449
  if (!isNonEmptyString(formatted)) {
3345
3450
  throw new Error("distinct emulation requires at least one output column");
@@ -3348,9 +3453,10 @@ function buildOutputColumns(scalarNames, includeNames, hasCount) {
3348
3453
  }
3349
3454
  function buildWindowOrder(args) {
3350
3455
  const { baseOrder, idField, fromAlias, model } = args;
3456
+ const fromLower = String(fromAlias).toLowerCase();
3351
3457
  const orderFields = baseOrder.split(SQL_SEPARATORS.ORDER_BY).map((s) => s.trim().toLowerCase());
3352
3458
  const hasIdInOrder = orderFields.some(
3353
- (f) => f.startsWith(`${fromAlias}.id `) || f.startsWith(`${fromAlias}."id" `)
3459
+ (f) => f.startsWith(`${fromLower}.id `) || f.startsWith(`${fromLower}."id" `)
3354
3460
  );
3355
3461
  if (hasIdInOrder) return baseOrder;
3356
3462
  const idTiebreaker = idField ? `, ${col(fromAlias, "id", model)} ASC` : "";
@@ -3385,15 +3491,37 @@ function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
3385
3491
  const outerOrder = isNonEmptyString(orderBy) ? replaceOrderByAlias(orderBy, from.alias, `"__tp_distinct"`) : replaceOrderByAlias(fallbackOrder, from.alias, `"__tp_distinct"`);
3386
3492
  const joins = buildJoinsSql(whereJoins, countJoins);
3387
3493
  const conditions = [];
3388
- if (whereClause && whereClause !== "1=1") {
3389
- conditions.push(whereClause);
3390
- }
3494
+ if (whereClause && whereClause !== "1=1") conditions.push(whereClause);
3391
3495
  const whereSql = buildWhereSql(conditions);
3392
3496
  const innerSelectList = selectWithIncludes.trim();
3393
3497
  const innerComma = innerSelectList.length > 0 ? SQL_SEPARATORS.FIELD_LIST : "";
3394
- const inner = `${SQL_TEMPLATES.SELECT} ${innerSelectList}${innerComma}ROW_NUMBER() OVER (PARTITION BY ${distinctCols} ORDER BY ${windowOrder}) ${SQL_TEMPLATES.AS} "__tp_rn" ${SQL_TEMPLATES.FROM} ${from.table} ${from.alias}${joins}${whereSql}`;
3395
- const outer = `${SQL_TEMPLATES.SELECT} ${outerSelectCols} ${SQL_TEMPLATES.FROM} (${inner}) ${SQL_TEMPLATES.AS} "__tp_distinct" ${SQL_TEMPLATES.WHERE} "__tp_rn" = 1` + (isNonEmptyString(outerOrder) ? ` ${SQL_TEMPLATES.ORDER_BY} ${outerOrder}` : "");
3396
- return outer;
3498
+ const innerParts = [
3499
+ SQL_TEMPLATES.SELECT,
3500
+ innerSelectList + innerComma,
3501
+ `ROW_NUMBER() OVER (PARTITION BY ${distinctCols} ORDER BY ${windowOrder})`,
3502
+ SQL_TEMPLATES.AS,
3503
+ '"__tp_rn"',
3504
+ SQL_TEMPLATES.FROM,
3505
+ from.table,
3506
+ from.alias
3507
+ ];
3508
+ if (joins) innerParts.push(joins);
3509
+ if (whereSql) innerParts.push(whereSql);
3510
+ const inner = innerParts.filter(Boolean).join(" ");
3511
+ const outerParts = [
3512
+ SQL_TEMPLATES.SELECT,
3513
+ outerSelectCols,
3514
+ SQL_TEMPLATES.FROM,
3515
+ `(${inner})`,
3516
+ SQL_TEMPLATES.AS,
3517
+ '"__tp_distinct"',
3518
+ SQL_TEMPLATES.WHERE,
3519
+ '"__tp_rn" = 1'
3520
+ ];
3521
+ if (isNonEmptyString(outerOrder)) {
3522
+ outerParts.push(SQL_TEMPLATES.ORDER_BY, outerOrder);
3523
+ }
3524
+ return outerParts.filter(Boolean).join(" ");
3397
3525
  }
3398
3526
  function buildIncludeColumns(spec) {
3399
3527
  var _a, _b;
@@ -3521,6 +3649,7 @@ function constructFinalSql(spec) {
3521
3649
  orderBy,
3522
3650
  distinct,
3523
3651
  method,
3652
+ cursorCte,
3524
3653
  cursorClause,
3525
3654
  params,
3526
3655
  dialect,
@@ -3535,9 +3664,13 @@ function constructFinalSql(spec) {
3535
3664
  const spec2 = withCountJoins(spec, countJoins, whereJoins);
3536
3665
  let sql2 = buildSqliteDistinctQuery(spec2, selectWithIncludes).trim();
3537
3666
  sql2 = appendPagination(sql2, spec);
3538
- return finalizeSql(sql2, params);
3667
+ return finalizeSql(sql2, params, dialect);
3539
3668
  }
3540
- const parts = [SQL_TEMPLATES.SELECT];
3669
+ const parts = [];
3670
+ if (cursorCte) {
3671
+ parts.push(`WITH ${cursorCte}`);
3672
+ }
3673
+ parts.push(SQL_TEMPLATES.SELECT);
3541
3674
  const distinctOn = dialect === "postgres" ? buildPostgresDistinctOnClause(from.alias, distinct, model) : null;
3542
3675
  if (distinctOn) parts.push(distinctOn);
3543
3676
  const baseSelect = (select != null ? select : "").trim();
@@ -3553,7 +3686,7 @@ function constructFinalSql(spec) {
3553
3686
  if (isNonEmptyString(orderBy)) parts.push(SQL_TEMPLATES.ORDER_BY, orderBy);
3554
3687
  let sql = parts.join(" ").trim();
3555
3688
  sql = appendPagination(sql, spec);
3556
- return finalizeSql(sql, params);
3689
+ return finalizeSql(sql, params, dialect);
3557
3690
  }
3558
3691
 
3559
3692
  // src/builder/select.ts
@@ -3586,7 +3719,7 @@ function buildPostgresDistinctOrderBy(distinctFields, existing) {
3586
3719
  }
3587
3720
  return next;
3588
3721
  }
3589
- function applyPostgresDistinctOrderBy(args, _model) {
3722
+ function applyPostgresDistinctOrderBy(args) {
3590
3723
  const distinctFields = normalizeDistinctFields(args.distinct);
3591
3724
  if (distinctFields.length === 0) return args;
3592
3725
  if (!isNotNullish(args.orderBy)) return args;
@@ -3596,19 +3729,6 @@ function applyPostgresDistinctOrderBy(args, _model) {
3596
3729
  orderBy: buildPostgresDistinctOrderBy(distinctFields, existing)
3597
3730
  });
3598
3731
  }
3599
- function assertScalarFieldOnModel(model, fieldName, ctx) {
3600
- const f = model.fields.find((x) => x.name === fieldName);
3601
- if (!f) {
3602
- throw new Error(
3603
- `${ctx} references unknown field '${fieldName}' on model ${model.name}`
3604
- );
3605
- }
3606
- if (f.isRelation) {
3607
- throw new Error(
3608
- `${ctx} does not support relation field '${fieldName}' on model ${model.name}`
3609
- );
3610
- }
3611
- }
3612
3732
  function validateDistinct(model, distinct) {
3613
3733
  if (!isNotNullish(distinct) || !isNonEmptyArray(distinct)) return;
3614
3734
  const seen = /* @__PURE__ */ new Set();
@@ -3619,24 +3739,24 @@ function validateDistinct(model, distinct) {
3619
3739
  throw new Error(`distinct must not contain duplicates (field: '${f}')`);
3620
3740
  }
3621
3741
  seen.add(f);
3622
- assertScalarFieldOnModel(model, f, "distinct");
3742
+ assertScalarField(model, f, "distinct");
3623
3743
  }
3624
3744
  }
3625
- function validateOrderByValue(fieldName, v) {
3626
- parseOrderByValue(v, fieldName);
3627
- }
3628
3745
  function validateOrderBy(model, orderBy) {
3629
3746
  if (!isNotNullish(orderBy)) return;
3630
3747
  const items = normalizeOrderByInput2(orderBy);
3631
3748
  if (items.length === 0) return;
3632
3749
  for (const it of items) {
3633
3750
  const entries = Object.entries(it);
3751
+ if (entries.length !== 1) {
3752
+ throw new Error("orderBy array entries must have exactly one field");
3753
+ }
3634
3754
  const fieldName = String(entries[0][0]).trim();
3635
3755
  if (fieldName.length === 0) {
3636
3756
  throw new Error("orderBy field name cannot be empty");
3637
3757
  }
3638
- assertScalarFieldOnModel(model, fieldName, "orderBy");
3639
- validateOrderByValue(fieldName, entries[0][1]);
3758
+ assertScalarField(model, fieldName, "orderBy");
3759
+ parseOrderByValue(entries[0][1], fieldName);
3640
3760
  }
3641
3761
  }
3642
3762
  function validateCursor(model, cursor) {
@@ -3653,7 +3773,7 @@ function validateCursor(model, cursor) {
3653
3773
  if (f.length === 0) {
3654
3774
  throw new Error("cursor field name cannot be empty");
3655
3775
  }
3656
- assertScalarFieldOnModel(model, f, "cursor");
3776
+ assertScalarField(model, f, "cursor");
3657
3777
  }
3658
3778
  }
3659
3779
  function resolveDialect(dialect) {
@@ -3672,20 +3792,21 @@ function normalizeArgsForNegativeTake(method, args) {
3672
3792
  orderBy: reverseOrderByInput(args.orderBy)
3673
3793
  });
3674
3794
  }
3675
- function normalizeArgsForDialect(dialect, args, model) {
3795
+ function normalizeArgsForDialect(dialect, args) {
3676
3796
  if (dialect !== "postgres") return args;
3677
3797
  return applyPostgresDistinctOrderBy(args);
3678
3798
  }
3679
3799
  function buildCursorClauseIfAny(input) {
3680
- const { cursor, orderBy, tableName, alias, params, dialect } = input;
3681
- if (!isNotNullish(cursor)) return void 0;
3800
+ const { cursor, orderBy, tableName, alias, params, dialect, model } = input;
3801
+ if (!isNotNullish(cursor)) return {};
3682
3802
  return buildCursorCondition(
3683
3803
  cursor,
3684
3804
  orderBy,
3685
3805
  tableName,
3686
3806
  alias,
3687
3807
  params,
3688
- dialect
3808
+ dialect,
3809
+ model
3689
3810
  );
3690
3811
  }
3691
3812
  function buildSelectSpec(input) {
@@ -3724,14 +3845,20 @@ function buildSelectSpec(input) {
3724
3845
  params,
3725
3846
  dialect
3726
3847
  );
3727
- const cursorClause = buildCursorClauseIfAny({
3848
+ const cursorResult = buildCursorClauseIfAny({
3728
3849
  cursor,
3729
3850
  orderBy: normalizedArgs.orderBy,
3730
3851
  tableName,
3731
3852
  alias,
3732
3853
  params,
3733
- dialect
3854
+ dialect,
3855
+ model
3734
3856
  });
3857
+ if (dialect === "sqlite" && isNonEmptyArray(normalizedArgs.distinct) && cursorResult.condition) {
3858
+ throw new Error(
3859
+ "Cursor pagination with distinct is not supported in SQLite due to window function limitations. Use findMany with skip/take instead, or remove distinct."
3860
+ );
3861
+ }
3735
3862
  return {
3736
3863
  select: selectFields,
3737
3864
  includes,
@@ -3742,7 +3869,8 @@ function buildSelectSpec(input) {
3742
3869
  pagination: { take, skip },
3743
3870
  distinct: normalizedArgs.distinct,
3744
3871
  method,
3745
- cursorClause,
3872
+ cursorCte: cursorResult.cte,
3873
+ cursorClause: cursorResult.condition,
3746
3874
  params,
3747
3875
  dialect,
3748
3876
  model,
@@ -3756,9 +3884,7 @@ function buildSelectSql(input) {
3756
3884
  assertSafeTableRef(from.tableName);
3757
3885
  const dialectToUse = resolveDialect(dialect);
3758
3886
  const argsForSql = normalizeArgsForNegativeTake(method, args);
3759
- const normalizedArgs = normalizeArgsForDialect(
3760
- dialectToUse,
3761
- argsForSql);
3887
+ const normalizedArgs = normalizeArgsForDialect(dialectToUse, argsForSql);
3762
3888
  validateDistinct(model, normalizedArgs.distinct);
3763
3889
  validateOrderBy(model, normalizedArgs.orderBy);
3764
3890
  validateCursor(model, normalizedArgs.cursor);
@@ -3774,8 +3900,21 @@ function buildSelectSql(input) {
3774
3900
  });
3775
3901
  return constructFinalSql(spec);
3776
3902
  }
3777
- var MODEL_FIELD_CACHE = /* @__PURE__ */ new WeakMap();
3778
- var NUMERIC_TYPES = /* @__PURE__ */ new Set(["Int", "Float", "Decimal", "BigInt"]);
3903
+
3904
+ // src/builder/shared/comparison-builder.ts
3905
+ function buildComparisons(expr, filter, params, dialect, builder, excludeKeys = /* @__PURE__ */ new Set(["mode"])) {
3906
+ const out = [];
3907
+ for (const [op, val] of Object.entries(filter)) {
3908
+ if (excludeKeys.has(op) || val === void 0) continue;
3909
+ const built = builder(expr, op, val, params, dialect);
3910
+ if (built && built.trim().length > 0) {
3911
+ out.push(built);
3912
+ }
3913
+ }
3914
+ return out;
3915
+ }
3916
+
3917
+ // src/builder/aggregates.ts
3779
3918
  var AGGREGATES = [
3780
3919
  ["_sum", "SUM"],
3781
3920
  ["_avg", "AVG"],
@@ -3790,22 +3929,32 @@ var COMPARISON_OPS = {
3790
3929
  [Ops.LT]: "<",
3791
3930
  [Ops.LTE]: "<="
3792
3931
  };
3793
- function normalizeFinalParams2(params) {
3794
- return params.map(normalizeValue);
3795
- }
3796
- function getModelFieldMap(model) {
3797
- const cached = MODEL_FIELD_CACHE.get(model);
3798
- if (cached) return cached;
3799
- const m = /* @__PURE__ */ new Map();
3800
- for (const f of model.fields) {
3801
- m.set(f.name, { name: f.name, type: f.type, isRelation: !!f.isRelation });
3802
- }
3803
- MODEL_FIELD_CACHE.set(model, m);
3804
- return m;
3805
- }
3932
+ var HAVING_ALLOWED_OPS = /* @__PURE__ */ new Set([
3933
+ Ops.EQUALS,
3934
+ Ops.NOT,
3935
+ Ops.GT,
3936
+ Ops.GTE,
3937
+ Ops.LT,
3938
+ Ops.LTE,
3939
+ Ops.IN,
3940
+ Ops.NOT_IN
3941
+ ]);
3806
3942
  function isTruthySelection(v) {
3807
3943
  return v === true;
3808
3944
  }
3945
+ function isLogicalKey(key) {
3946
+ return key === LogicalOps.AND || key === LogicalOps.OR || key === LogicalOps.NOT;
3947
+ }
3948
+ function isAggregateKey(key) {
3949
+ return key === "_count" || key === "_sum" || key === "_avg" || key === "_min" || key === "_max";
3950
+ }
3951
+ function assertHavingOp(op) {
3952
+ if (!HAVING_ALLOWED_OPS.has(op)) {
3953
+ throw new Error(
3954
+ `Unsupported HAVING operator '${op}'. Allowed: ${[...HAVING_ALLOWED_OPS].join(", ")}`
3955
+ );
3956
+ }
3957
+ }
3809
3958
  function aggExprForField(aggKey, field, alias, model) {
3810
3959
  if (aggKey === "_count") {
3811
3960
  return field === "_all" ? `COUNT(*)` : `COUNT(${col(alias, field, model)})`;
@@ -3839,32 +3988,9 @@ function normalizeLogicalValue2(operator, value) {
3839
3988
  }
3840
3989
  return out;
3841
3990
  }
3842
- if (isPlainObject(value)) {
3843
- return [value];
3844
- }
3991
+ if (isPlainObject(value)) return [value];
3845
3992
  throw new Error(`${operator} must be an object or array of objects in HAVING`);
3846
3993
  }
3847
- function assertScalarField2(model, fieldName, ctx) {
3848
- const m = getModelFieldMap(model);
3849
- const field = m.get(fieldName);
3850
- if (!field) {
3851
- throw new Error(
3852
- `${ctx} references unknown field '${fieldName}' on model ${model.name}. Available fields: ${model.fields.map((f) => f.name).join(", ")}`
3853
- );
3854
- }
3855
- if (field.isRelation) {
3856
- throw new Error(`${ctx} does not support relation field '${fieldName}'`);
3857
- }
3858
- return { name: field.name, type: field.type };
3859
- }
3860
- function assertAggregateFieldType(aggKey, fieldType, fieldName, modelName) {
3861
- const baseType = fieldType.replace(/\[\]|\?/g, "");
3862
- if ((aggKey === "_sum" || aggKey === "_avg") && !NUMERIC_TYPES.has(baseType)) {
3863
- throw new Error(
3864
- `Cannot use ${aggKey} on non-numeric field '${fieldName}' (type: ${fieldType}) on model ${modelName}`
3865
- );
3866
- }
3867
- }
3868
3994
  function buildNullComparison(expr, op) {
3869
3995
  if (op === Ops.EQUALS) return `${expr} ${SQL_TEMPLATES.IS_NULL}`;
3870
3996
  if (op === Ops.NOT) return `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
@@ -3891,6 +4017,7 @@ function buildBinaryComparison(expr, op, val, params) {
3891
4017
  return `${expr} ${sqlOp} ${placeholder}`;
3892
4018
  }
3893
4019
  function buildSimpleComparison(expr, op, val, params, dialect) {
4020
+ assertHavingOp(op);
3894
4021
  if (val === null) return buildNullComparison(expr, op);
3895
4022
  if (op === Ops.NOT && isPlainObject(val)) {
3896
4023
  return buildNotComposite(
@@ -3907,12 +4034,6 @@ function buildSimpleComparison(expr, op, val, params, dialect) {
3907
4034
  }
3908
4035
  return buildBinaryComparison(expr, op, val, params);
3909
4036
  }
3910
- function isLogicalKey(key) {
3911
- return key === LogicalOps.AND || key === LogicalOps.OR || key === LogicalOps.NOT;
3912
- }
3913
- function isAggregateKey(key) {
3914
- return key === "_count" || key === "_sum" || key === "_avg" || key === "_min" || key === "_max";
3915
- }
3916
4037
  function negateClauses(subClauses) {
3917
4038
  if (subClauses.length === 1) return `${SQL_TEMPLATES.NOT} ${subClauses[0]}`;
3918
4039
  return `${SQL_TEMPLATES.NOT} (${subClauses.join(SQL_SEPARATORS.CONDITION_AND)})`;
@@ -3921,16 +4042,75 @@ function combineLogical(key, subClauses) {
3921
4042
  if (key === LogicalOps.NOT) return negateClauses(subClauses);
3922
4043
  return subClauses.join(` ${key} `);
3923
4044
  }
4045
+ function buildHavingNode(node, alias, params, dialect, model) {
4046
+ const clauses = [];
4047
+ const entries = Object.entries(node);
4048
+ for (const [key, value] of entries) {
4049
+ const built = buildHavingEntry(key, value, alias, params, dialect, model);
4050
+ for (const c of built) {
4051
+ if (c && c.trim().length > 0) clauses.push(c);
4052
+ }
4053
+ }
4054
+ return clauses.join(SQL_SEPARATORS.CONDITION_AND);
4055
+ }
3924
4056
  function buildLogicalClause2(key, value, alias, params, dialect, model) {
3925
4057
  const items = normalizeLogicalValue2(key, value);
3926
4058
  const subClauses = [];
3927
4059
  for (const it of items) {
3928
4060
  const c = buildHavingNode(it, alias, params, dialect, model);
3929
- if (c && c !== "") subClauses.push(`(${c})`);
4061
+ if (c && c.trim().length > 0) subClauses.push(`(${c})`);
3930
4062
  }
3931
4063
  if (subClauses.length === 0) return "";
3932
4064
  return combineLogical(key, subClauses);
3933
4065
  }
4066
+ function assertHavingAggTarget(aggKey, field, model) {
4067
+ if (field === "_all") {
4068
+ if (aggKey !== "_count")
4069
+ throw new Error(`HAVING '${aggKey}' does not support '_all'`);
4070
+ return;
4071
+ }
4072
+ if (aggKey === "_sum" || aggKey === "_avg") {
4073
+ assertNumericField(model, field, "HAVING");
4074
+ } else {
4075
+ assertScalarField(model, field, "HAVING");
4076
+ }
4077
+ }
4078
+ function buildHavingOpsForExpr(expr, filter, params, dialect) {
4079
+ return buildComparisons(expr, filter, params, dialect, buildSimpleComparison);
4080
+ }
4081
+ function buildHavingForAggregateFirstShape(aggKey, target, alias, params, dialect, model) {
4082
+ if (!isPlainObject(target)) {
4083
+ throw new Error(`HAVING '${aggKey}' must be an object`);
4084
+ }
4085
+ const out = [];
4086
+ for (const [field, filter] of Object.entries(target)) {
4087
+ assertHavingAggTarget(aggKey, field, model);
4088
+ if (!isPlainObject(filter) || Object.keys(filter).length === 0) continue;
4089
+ const expr = aggExprForField(aggKey, field, alias, model);
4090
+ out.push(...buildHavingOpsForExpr(expr, filter, params, dialect));
4091
+ }
4092
+ return out;
4093
+ }
4094
+ function buildHavingForFieldFirstShape(fieldName, target, alias, params, dialect, model) {
4095
+ if (!isPlainObject(target)) {
4096
+ throw new Error(`HAVING '${fieldName}' must be an object`);
4097
+ }
4098
+ assertScalarField(model, fieldName, "HAVING");
4099
+ const out = [];
4100
+ const obj = target;
4101
+ const keys = ["_count", "_sum", "_avg", "_min", "_max"];
4102
+ for (const aggKey of keys) {
4103
+ const aggFilter = obj[aggKey];
4104
+ if (!isPlainObject(aggFilter)) continue;
4105
+ if (Object.keys(aggFilter).length === 0) continue;
4106
+ if (aggKey === "_sum" || aggKey === "_avg") {
4107
+ assertNumericField(model, fieldName, "HAVING");
4108
+ }
4109
+ const expr = aggExprForField(aggKey, fieldName, alias, model);
4110
+ out.push(...buildHavingOpsForExpr(expr, aggFilter, params, dialect));
4111
+ }
4112
+ return out;
4113
+ }
3934
4114
  function buildHavingEntry(key, value, alias, params, dialect, model) {
3935
4115
  if (isLogicalKey(key)) {
3936
4116
  const logical = buildLogicalClause2(
@@ -3962,71 +4142,10 @@ function buildHavingEntry(key, value, alias, params, dialect, model) {
3962
4142
  model
3963
4143
  );
3964
4144
  }
3965
- function buildHavingNode(node, alias, params, dialect, model) {
3966
- const clauses = [];
3967
- for (const [key, value] of Object.entries(node)) {
3968
- const built = buildHavingEntry(key, value, alias, params, dialect, model);
3969
- for (const c of built) {
3970
- if (c && c.trim().length > 0) clauses.push(c);
3971
- }
3972
- }
3973
- return clauses.join(SQL_SEPARATORS.CONDITION_AND);
3974
- }
3975
- function assertHavingAggTarget(aggKey, field, model) {
3976
- if (field === "_all") {
3977
- if (aggKey !== "_count") {
3978
- throw new Error(`HAVING '${aggKey}' does not support '_all'`);
3979
- }
3980
- return;
3981
- }
3982
- const f = assertScalarField2(model, field, "HAVING");
3983
- assertAggregateFieldType(aggKey, f.type, f.name, model.name);
3984
- }
3985
- function buildHavingOpsForExpr(expr, filter, params, dialect) {
3986
- const out = [];
3987
- for (const [op, val] of Object.entries(filter)) {
3988
- if (op === "mode") continue;
3989
- const built = buildSimpleComparison(expr, op, val, params, dialect);
3990
- if (built && built.trim().length > 0) out.push(built);
3991
- }
3992
- return out;
3993
- }
3994
- function buildHavingForAggregateFirstShape(aggKey, target, alias, params, dialect, model) {
3995
- if (!isPlainObject(target)) return [];
3996
- const out = [];
3997
- for (const [field, filter] of Object.entries(target)) {
3998
- assertHavingAggTarget(aggKey, field, model);
3999
- if (!isPlainObject(filter) || Object.keys(filter).length === 0) continue;
4000
- const expr = aggExprForField(aggKey, field, alias, model);
4001
- out.push(...buildHavingOpsForExpr(expr, filter, params, dialect));
4002
- }
4003
- return out;
4004
- }
4005
- function buildHavingForFieldFirstShape(fieldName, target, alias, params, dialect, model) {
4006
- if (!isPlainObject(target)) return [];
4007
- const field = assertScalarField2(model, fieldName, "HAVING");
4008
- const out = [];
4009
- const obj = target;
4010
- const keys = ["_count", "_sum", "_avg", "_min", "_max"];
4011
- for (const aggKey of keys) {
4012
- const aggFilter = obj[aggKey];
4013
- if (!isPlainObject(aggFilter)) continue;
4014
- assertAggregateFieldType(aggKey, field.type, field.name, model.name);
4015
- const entries = Object.entries(aggFilter);
4016
- if (entries.length === 0) continue;
4017
- const expr = aggExprForField(aggKey, fieldName, alias, model);
4018
- for (const [op, val] of entries) {
4019
- if (op === "mode") continue;
4020
- const built = buildSimpleComparison(expr, op, val, params, dialect);
4021
- if (built && built.trim().length > 0) out.push(built);
4022
- }
4023
- }
4024
- return out;
4025
- }
4026
4145
  function buildHavingClause(having, alias, params, model, dialect) {
4027
4146
  if (!isNotNullish(having)) return "";
4028
4147
  const d = dialect != null ? dialect : getGlobalDialect();
4029
- if (!isPlainObject(having)) return "";
4148
+ if (!isPlainObject(having)) throw new Error("having must be an object");
4030
4149
  return buildHavingNode(having, alias, params, d, model);
4031
4150
  }
4032
4151
  function normalizeCountArg(v) {
@@ -4040,26 +4159,13 @@ function pushCountAllField(fields) {
4040
4159
  `${SQL_TEMPLATES.COUNT_ALL} ${SQL_TEMPLATES.AS} ${quote("_count._all")}`
4041
4160
  );
4042
4161
  }
4043
- function assertCountableScalarField(fieldMap, model, fieldName) {
4044
- const field = fieldMap.get(fieldName);
4045
- if (!field) {
4046
- throw new Error(
4047
- `Field '${fieldName}' does not exist on model ${model.name}`
4048
- );
4049
- }
4050
- if (field.isRelation) {
4051
- throw new Error(
4052
- `Cannot use _count on relation field '${fieldName}' on model ${model.name}`
4053
- );
4054
- }
4055
- }
4056
4162
  function pushCountField(fields, alias, fieldName, model) {
4057
4163
  const outAlias = `_count.${fieldName}`;
4058
4164
  fields.push(
4059
4165
  `COUNT(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4060
4166
  );
4061
4167
  }
4062
- function addCountFields(fields, countArg, alias, model, fieldMap) {
4168
+ function addCountFields(fields, countArg, alias, model) {
4063
4169
  if (!isNotNullish(countArg)) return;
4064
4170
  if (countArg === true) {
4065
4171
  pushCountAllField(fields);
@@ -4073,7 +4179,7 @@ function addCountFields(fields, countArg, alias, model, fieldMap) {
4073
4179
  ([f, v]) => f !== "_all" && isTruthySelection(v)
4074
4180
  );
4075
4181
  for (const [f] of selected) {
4076
- assertCountableScalarField(fieldMap, model, f);
4182
+ assertScalarField(model, f, "_count");
4077
4183
  pushCountField(fields, alias, f, model);
4078
4184
  }
4079
4185
  }
@@ -4081,19 +4187,12 @@ function getAggregateSelectionObject(args, agg) {
4081
4187
  const obj = args[agg];
4082
4188
  return isPlainObject(obj) ? obj : void 0;
4083
4189
  }
4084
- function assertAggregatableScalarField(fieldMap, model, agg, fieldName) {
4085
- const field = fieldMap.get(fieldName);
4086
- if (!field) {
4087
- throw new Error(
4088
- `Field '${fieldName}' does not exist on model ${model.name}`
4089
- );
4090
- }
4091
- if (field.isRelation) {
4092
- throw new Error(
4093
- `Cannot use ${agg} on relation field '${fieldName}' on model ${model.name}`
4094
- );
4190
+ function assertAggregatableScalarField(model, agg, fieldName) {
4191
+ if (agg === "_sum" || agg === "_avg") {
4192
+ assertNumericField(model, fieldName, agg);
4193
+ } else {
4194
+ assertScalarField(model, fieldName, agg);
4095
4195
  }
4096
- return field;
4097
4196
  }
4098
4197
  function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
4099
4198
  const outAlias = `${agg}.${fieldName}`;
@@ -4101,7 +4200,7 @@ function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
4101
4200
  `${aggFn}(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4102
4201
  );
4103
4202
  }
4104
- function addAggregateFields(fields, args, alias, model, fieldMap) {
4203
+ function addAggregateFields(fields, args, alias, model) {
4105
4204
  for (const [agg, aggFn] of AGGREGATES) {
4106
4205
  const obj = getAggregateSelectionObject(args, agg);
4107
4206
  if (!obj) continue;
@@ -4109,23 +4208,16 @@ function addAggregateFields(fields, args, alias, model, fieldMap) {
4109
4208
  if (fieldName === "_all")
4110
4209
  throw new Error(`'${agg}' does not support '_all'`);
4111
4210
  if (!isTruthySelection(selection)) continue;
4112
- const field = assertAggregatableScalarField(
4113
- fieldMap,
4114
- model,
4115
- agg,
4116
- fieldName
4117
- );
4118
- assertAggregateFieldType(agg, field.type, fieldName, model.name);
4211
+ assertAggregatableScalarField(model, agg, fieldName);
4119
4212
  pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model);
4120
4213
  }
4121
4214
  }
4122
4215
  }
4123
4216
  function buildAggregateFields(args, alias, model) {
4124
4217
  const fields = [];
4125
- const fieldMap = getModelFieldMap(model);
4126
4218
  const countArg = normalizeCountArg(args._count);
4127
- addCountFields(fields, countArg, alias, model, fieldMap);
4128
- addAggregateFields(fields, args, alias, model, fieldMap);
4219
+ addCountFields(fields, countArg, alias, model);
4220
+ addAggregateFields(fields, args, alias, model);
4129
4221
  return fields;
4130
4222
  }
4131
4223
  function buildAggregateSql(args, whereResult, tableName, alias, model) {
@@ -4149,7 +4241,7 @@ function buildAggregateSql(args, whereResult, tableName, alias, model) {
4149
4241
  validateParamConsistency(sql, whereResult.params);
4150
4242
  return Object.freeze({
4151
4243
  sql,
4152
- params: Object.freeze(normalizeFinalParams2([...whereResult.params])),
4244
+ params: Object.freeze([...whereResult.params]),
4153
4245
  paramMappings: Object.freeze([...whereResult.paramMappings])
4154
4246
  });
4155
4247
  }
@@ -4162,32 +4254,22 @@ function assertGroupByBy(args, model) {
4162
4254
  if (bySet.size !== byFields.length) {
4163
4255
  throw new Error("buildGroupBySql: by must not contain duplicates");
4164
4256
  }
4165
- const modelFieldMap = getModelFieldMap(model);
4166
4257
  for (const f of byFields) {
4167
- const field = modelFieldMap.get(f);
4168
- if (!field) {
4169
- throw new Error(
4170
- `groupBy.by references unknown field '${f}' on model ${model.name}`
4171
- );
4172
- }
4173
- if (field.isRelation) {
4174
- throw new Error(
4175
- `groupBy.by does not support relation field '${f}' on model ${model.name}`
4176
- );
4177
- }
4258
+ assertScalarField(model, f, "groupBy.by");
4178
4259
  }
4179
4260
  return byFields;
4180
4261
  }
4181
4262
  function buildGroupBySelectParts(args, alias, model, byFields) {
4182
4263
  const groupCols = byFields.map((f) => col(alias, f, model));
4264
+ const selectCols = byFields.map((f) => colWithAlias(alias, f, model));
4183
4265
  const groupFields = groupCols.join(SQL_SEPARATORS.FIELD_LIST);
4184
4266
  const aggFields = buildAggregateFields(args, alias, model);
4185
- const selectFields = isNonEmptyArray(aggFields) ? groupCols.concat(aggFields).join(SQL_SEPARATORS.FIELD_LIST) : groupCols.join(SQL_SEPARATORS.FIELD_LIST);
4267
+ const selectFields = isNonEmptyArray(aggFields) ? selectCols.concat(aggFields).join(SQL_SEPARATORS.FIELD_LIST) : selectCols.join(SQL_SEPARATORS.FIELD_LIST);
4186
4268
  return { groupCols, groupFields, selectFields };
4187
4269
  }
4188
4270
  function buildGroupByHaving(args, alias, params, model, dialect) {
4189
4271
  if (!isNotNullish(args.having)) return "";
4190
- if (!isPlainObject(args.having)) return "";
4272
+ if (!isPlainObject(args.having)) throw new Error("having must be an object");
4191
4273
  const h = buildHavingClause(args.having, alias, params, model, dialect);
4192
4274
  if (!h || h.trim().length === 0) return "";
4193
4275
  return `${SQL_TEMPLATES.HAVING} ${h}`;
@@ -4220,64 +4302,60 @@ function buildGroupBySql(args, whereResult, tableName, alias, model, dialect) {
4220
4302
  const snapshot = params.snapshot();
4221
4303
  validateSelectQuery(sql);
4222
4304
  validateParamConsistency(sql, [...whereResult.params, ...snapshot.params]);
4223
- const mergedParams = [...whereResult.params, ...snapshot.params];
4224
4305
  return Object.freeze({
4225
4306
  sql,
4226
- params: Object.freeze(normalizeFinalParams2(mergedParams)),
4307
+ params: Object.freeze([...whereResult.params, ...snapshot.params]),
4227
4308
  paramMappings: Object.freeze([
4228
4309
  ...whereResult.paramMappings,
4229
4310
  ...snapshot.mappings
4230
4311
  ])
4231
4312
  });
4232
4313
  }
4233
- function buildCountSql(whereResult, tableName, alias, skip, dialect) {
4314
+ function buildCountSql(whereResult, tableName, alias, skip, _dialect) {
4234
4315
  assertSafeAlias(alias);
4235
4316
  assertSafeTableRef(tableName);
4236
- const d = dialect != null ? dialect : getGlobalDialect();
4317
+ if (skip !== void 0 && skip !== null) {
4318
+ if (isDynamicParameter(skip)) {
4319
+ throw new Error(
4320
+ "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."
4321
+ );
4322
+ }
4323
+ if (typeof skip === "string") {
4324
+ const s = skip.trim();
4325
+ if (s.length > 0) {
4326
+ const n = Number(s);
4327
+ if (Number.isFinite(n) && Number.isInteger(n) && n > 0) {
4328
+ throw new Error(
4329
+ "count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
4330
+ );
4331
+ }
4332
+ }
4333
+ }
4334
+ if (typeof skip === "number" && Number.isFinite(skip) && Number.isInteger(skip) && skip > 0) {
4335
+ throw new Error(
4336
+ "count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
4337
+ );
4338
+ }
4339
+ }
4237
4340
  const whereClause = isValidWhereClause(whereResult.clause) ? `${SQL_TEMPLATES.WHERE} ${whereResult.clause}` : "";
4238
- const params = createParamStore(whereResult.nextParamIndex);
4239
- const baseSubSelect = [
4240
- SQL_TEMPLATES.SELECT,
4241
- "1",
4242
- SQL_TEMPLATES.FROM,
4243
- tableName,
4244
- alias,
4245
- whereClause
4246
- ].filter((x) => x && String(x).trim().length > 0).join(" ").trim();
4247
- const normalizedSkip = normalizeSkipLike(skip);
4248
- const subSelect = applyCountSkip(baseSubSelect, normalizedSkip, params, d);
4249
4341
  const sql = [
4250
4342
  SQL_TEMPLATES.SELECT,
4251
4343
  SQL_TEMPLATES.COUNT_ALL,
4252
4344
  SQL_TEMPLATES.AS,
4253
4345
  quote("_count._all"),
4254
4346
  SQL_TEMPLATES.FROM,
4255
- `(${subSelect})`,
4256
- SQL_TEMPLATES.AS,
4257
- `"sub"`
4347
+ tableName,
4348
+ alias,
4349
+ whereClause
4258
4350
  ].filter((x) => x && String(x).trim().length > 0).join(" ").trim();
4259
4351
  validateSelectQuery(sql);
4260
- const snapshot = params.snapshot();
4261
- const mergedParams = [...whereResult.params, ...snapshot.params];
4262
- validateParamConsistency(sql, mergedParams);
4352
+ validateParamConsistency(sql, whereResult.params);
4263
4353
  return Object.freeze({
4264
4354
  sql,
4265
- params: Object.freeze(normalizeFinalParams2(mergedParams)),
4266
- paramMappings: Object.freeze([
4267
- ...whereResult.paramMappings,
4268
- ...snapshot.mappings
4269
- ])
4355
+ params: Object.freeze([...whereResult.params]),
4356
+ paramMappings: Object.freeze([...whereResult.paramMappings])
4270
4357
  });
4271
4358
  }
4272
- function applyCountSkip(subSelect, normalizedSkip, params, dialect) {
4273
- const shouldApply = isDynamicParameter(normalizedSkip) || typeof normalizedSkip === "number" && normalizedSkip > 0;
4274
- if (!shouldApply) return subSelect;
4275
- const placeholder = addAutoScoped(params, normalizedSkip, "count.skip");
4276
- if (dialect === "sqlite") {
4277
- return `${subSelect} ${SQL_TEMPLATES.LIMIT} -1 ${SQL_TEMPLATES.OFFSET} ${placeholder}`;
4278
- }
4279
- return `${subSelect} ${SQL_TEMPLATES.OFFSET} ${placeholder}`;
4280
- }
4281
4359
  function safeAlias(input) {
4282
4360
  const raw = String(input).toLowerCase();
4283
4361
  const cleaned = raw.replace(/[^a-z0-9_]/g, "_");
@@ -4427,8 +4505,12 @@ function buildAndNormalizeSql(args) {
4427
4505
  );
4428
4506
  }
4429
4507
  function finalizeDirective(args) {
4430
- const { directive, normalizedSql, normalizedMappings } = args;
4431
- validateSqlPositions(normalizedSql, normalizedMappings, getGlobalDialect());
4508
+ const { directive, normalizedSql, normalizedMappings, dialect } = args;
4509
+ const params = normalizedMappings.map((m) => {
4510
+ var _a;
4511
+ return (_a = m.value) != null ? _a : void 0;
4512
+ });
4513
+ validateParamConsistencyByDialect(normalizedSql, params, dialect);
4432
4514
  const { staticParams, dynamicKeys } = buildParamsFromMappings(normalizedMappings);
4433
4515
  return {
4434
4516
  method: directive.method,
@@ -4465,7 +4547,8 @@ function generateSQL(directive) {
4465
4547
  return finalizeDirective({
4466
4548
  directive,
4467
4549
  normalizedSql: normalized.sql,
4468
- normalizedMappings: normalized.paramMappings
4550
+ normalizedMappings: normalized.paramMappings,
4551
+ dialect
4469
4552
  });
4470
4553
  }
4471
4554
 
@@ -4842,9 +4925,7 @@ function buildSQLFull(model, models, method, args, dialect) {
4842
4925
  whereResult,
4843
4926
  tableName,
4844
4927
  alias,
4845
- args.skip,
4846
- dialect
4847
- );
4928
+ args.skip);
4848
4929
  break;
4849
4930
  default:
4850
4931
  result = buildSelectSql({