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.
@@ -56,7 +56,7 @@ var require_package = __commonJS({
56
56
  "package.json"(exports$1, module) {
57
57
  module.exports = {
58
58
  name: "prisma-sql",
59
- version: "1.44.0",
59
+ version: "1.46.0",
60
60
  description: "Convert Prisma queries to optimized SQL with type safety. 2-7x faster than Prisma Client.",
61
61
  main: "dist/index.cjs",
62
62
  module: "dist/index.js",
@@ -159,20 +159,13 @@ var SQL_SEPARATORS = Object.freeze({
159
159
  CONDITION_OR: " OR ",
160
160
  ORDER_BY: ", "
161
161
  });
162
- var SQL_RESERVED_WORDS = /* @__PURE__ */ new Set([
162
+ var ALIAS_FORBIDDEN_KEYWORDS = /* @__PURE__ */ new Set([
163
163
  "select",
164
164
  "from",
165
165
  "where",
166
- "and",
167
- "or",
168
- "not",
169
- "in",
170
- "like",
171
- "between",
166
+ "having",
172
167
  "order",
173
- "by",
174
168
  "group",
175
- "having",
176
169
  "limit",
177
170
  "offset",
178
171
  "join",
@@ -180,14 +173,42 @@ var SQL_RESERVED_WORDS = /* @__PURE__ */ new Set([
180
173
  "left",
181
174
  "right",
182
175
  "outer",
183
- "on",
176
+ "cross",
177
+ "full",
178
+ "and",
179
+ "or",
180
+ "not",
181
+ "by",
184
182
  "as",
183
+ "on",
184
+ "union",
185
+ "intersect",
186
+ "except",
187
+ "case",
188
+ "when",
189
+ "then",
190
+ "else",
191
+ "end"
192
+ ]);
193
+ var SQL_KEYWORDS = /* @__PURE__ */ new Set([
194
+ ...ALIAS_FORBIDDEN_KEYWORDS,
195
+ "user",
196
+ "users",
185
197
  "table",
186
198
  "column",
187
199
  "index",
188
- "user",
189
- "users",
190
200
  "values",
201
+ "in",
202
+ "like",
203
+ "between",
204
+ "is",
205
+ "exists",
206
+ "null",
207
+ "true",
208
+ "false",
209
+ "all",
210
+ "any",
211
+ "some",
191
212
  "update",
192
213
  "insert",
193
214
  "delete",
@@ -198,25 +219,9 @@ var SQL_RESERVED_WORDS = /* @__PURE__ */ new Set([
198
219
  "grant",
199
220
  "revoke",
200
221
  "exec",
201
- "execute",
202
- "union",
203
- "intersect",
204
- "except",
205
- "case",
206
- "when",
207
- "then",
208
- "else",
209
- "end",
210
- "null",
211
- "true",
212
- "false",
213
- "is",
214
- "exists",
215
- "all",
216
- "any",
217
- "some"
222
+ "execute"
218
223
  ]);
219
- var SQL_KEYWORDS = SQL_RESERVED_WORDS;
224
+ var SQL_RESERVED_WORDS = SQL_KEYWORDS;
220
225
  var DEFAULT_WHERE_CLAUSE = "1=1";
221
226
  var SPECIAL_FIELDS = Object.freeze({
222
227
  ID: "id"
@@ -295,12 +300,48 @@ var LIMITS = Object.freeze({
295
300
  });
296
301
 
297
302
  // src/utils/normalize-value.ts
298
- function normalizeValue(value) {
303
+ var MAX_DEPTH = 20;
304
+ function normalizeValue(value, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
305
+ if (depth > MAX_DEPTH) {
306
+ throw new Error(`Max normalization depth exceeded (${MAX_DEPTH} levels)`);
307
+ }
299
308
  if (value instanceof Date) {
309
+ const t = value.getTime();
310
+ if (!Number.isFinite(t)) {
311
+ throw new Error("Invalid Date value in SQL params");
312
+ }
300
313
  return value.toISOString();
301
314
  }
315
+ if (typeof value === "bigint") {
316
+ return value.toString();
317
+ }
302
318
  if (Array.isArray(value)) {
303
- return value.map(normalizeValue);
319
+ const arrRef = value;
320
+ if (seen.has(arrRef)) {
321
+ throw new Error("Circular reference in SQL params");
322
+ }
323
+ seen.add(arrRef);
324
+ const out = value.map((v) => normalizeValue(v, seen, depth + 1));
325
+ seen.delete(arrRef);
326
+ return out;
327
+ }
328
+ if (value && typeof value === "object") {
329
+ if (value instanceof Uint8Array) return value;
330
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) return value;
331
+ const proto = Object.getPrototypeOf(value);
332
+ const isPlain = proto === Object.prototype || proto === null;
333
+ if (!isPlain) return value;
334
+ const obj = value;
335
+ if (seen.has(obj)) {
336
+ throw new Error("Circular reference in SQL params");
337
+ }
338
+ seen.add(obj);
339
+ const out = {};
340
+ for (const [k, v] of Object.entries(obj)) {
341
+ out[k] = normalizeValue(v, seen, depth + 1);
342
+ }
343
+ seen.delete(obj);
344
+ return out;
304
345
  }
305
346
  return value;
306
347
  }
@@ -481,7 +522,7 @@ function prepareArrayParam(value, dialect) {
481
522
  throw new Error("prepareArrayParam requires array value");
482
523
  }
483
524
  if (dialect === "postgres") {
484
- return value.map(normalizeValue);
525
+ return value.map((v) => normalizeValue(v));
485
526
  }
486
527
  return JSON.stringify(value);
487
528
  }
@@ -553,36 +594,46 @@ function createError(message, ctx, code = "VALIDATION_ERROR") {
553
594
  }
554
595
 
555
596
  // src/builder/shared/model-field-cache.ts
556
- var SCALAR_SET_CACHE = /* @__PURE__ */ new WeakMap();
557
- var RELATION_SET_CACHE = /* @__PURE__ */ new WeakMap();
597
+ var MODEL_CACHE = /* @__PURE__ */ new WeakMap();
598
+ function ensureFullCache(model) {
599
+ let cache = MODEL_CACHE.get(model);
600
+ if (!cache) {
601
+ const fieldInfo = /* @__PURE__ */ new Map();
602
+ const scalarFields = /* @__PURE__ */ new Set();
603
+ const relationFields = /* @__PURE__ */ new Set();
604
+ const columnMap = /* @__PURE__ */ new Map();
605
+ for (const f of model.fields) {
606
+ const info = {
607
+ name: f.name,
608
+ dbName: f.dbName || f.name,
609
+ type: f.type,
610
+ isRelation: !!f.isRelation,
611
+ isRequired: !!f.isRequired
612
+ };
613
+ fieldInfo.set(f.name, info);
614
+ if (info.isRelation) {
615
+ relationFields.add(f.name);
616
+ } else {
617
+ scalarFields.add(f.name);
618
+ columnMap.set(f.name, info.dbName);
619
+ }
620
+ }
621
+ cache = { fieldInfo, scalarFields, relationFields, columnMap };
622
+ MODEL_CACHE.set(model, cache);
623
+ }
624
+ return cache;
625
+ }
626
+ function getFieldInfo(model, fieldName) {
627
+ return ensureFullCache(model).fieldInfo.get(fieldName);
628
+ }
558
629
  function getScalarFieldSet(model) {
559
- const cached = SCALAR_SET_CACHE.get(model);
560
- if (cached) return cached;
561
- const s = /* @__PURE__ */ new Set();
562
- for (const f of model.fields) if (!f.isRelation) s.add(f.name);
563
- SCALAR_SET_CACHE.set(model, s);
564
- return s;
630
+ return ensureFullCache(model).scalarFields;
565
631
  }
566
632
  function getRelationFieldSet(model) {
567
- const cached = RELATION_SET_CACHE.get(model);
568
- if (cached) return cached;
569
- const s = /* @__PURE__ */ new Set();
570
- for (const f of model.fields) if (f.isRelation) s.add(f.name);
571
- RELATION_SET_CACHE.set(model, s);
572
- return s;
573
- }
574
- var COLUMN_MAP_CACHE = /* @__PURE__ */ new WeakMap();
633
+ return ensureFullCache(model).relationFields;
634
+ }
575
635
  function getColumnMap(model) {
576
- const cached = COLUMN_MAP_CACHE.get(model);
577
- if (cached) return cached;
578
- const map = /* @__PURE__ */ new Map();
579
- for (const f of model.fields) {
580
- if (!f.isRelation) {
581
- map.set(f.name, f.dbName || f.name);
582
- }
583
- }
584
- COLUMN_MAP_CACHE.set(model, map);
585
- return map;
636
+ return ensureFullCache(model).columnMap;
586
637
  }
587
638
 
588
639
  // src/builder/shared/validators/sql-validators.ts
@@ -593,20 +644,21 @@ function isEmptyWhere(where) {
593
644
  if (!isNotNullish(where)) return true;
594
645
  return Object.keys(where).length === 0;
595
646
  }
647
+ function sqlPreview(sql) {
648
+ const s = String(sql);
649
+ if (s.length <= 160) return s;
650
+ return `${s.slice(0, 160)}...`;
651
+ }
596
652
  function validateSelectQuery(sql) {
597
653
  if (!hasValidContent(sql)) {
598
654
  throw new Error("CRITICAL: Generated empty SQL query");
599
655
  }
600
656
  if (!hasRequiredKeywords(sql)) {
601
- throw new Error(
602
- `CRITICAL: Invalid SQL structure. SQL: ${sql.substring(0, 100)}...`
603
- );
657
+ throw new Error(`CRITICAL: Invalid SQL structure. SQL: ${sqlPreview(sql)}`);
604
658
  }
605
659
  }
606
- function sqlPreview(sql) {
607
- return `${sql.substring(0, 100)}...`;
608
- }
609
- function parseDollarNumber(sql, start, n) {
660
+ function parseDollarNumber(sql, start) {
661
+ const n = sql.length;
610
662
  let i = start;
611
663
  let num = 0;
612
664
  let hasDigit = false;
@@ -617,14 +669,14 @@ function parseDollarNumber(sql, start, n) {
617
669
  num = num * 10 + (c - 48);
618
670
  i++;
619
671
  }
620
- if (!hasDigit || num <= 0) return { next: i, num: 0, ok: false };
621
- return { next: i, num, ok: true };
672
+ if (!hasDigit || num <= 0) return { next: i, num: 0 };
673
+ return { next: i, num };
622
674
  }
623
675
  function scanDollarPlaceholders(sql, markUpTo) {
624
676
  const seen = new Uint8Array(markUpTo + 1);
625
- let count = 0;
626
677
  let min = Number.POSITIVE_INFINITY;
627
678
  let max = 0;
679
+ let sawAny = false;
628
680
  const n = sql.length;
629
681
  let i = 0;
630
682
  while (i < n) {
@@ -632,17 +684,21 @@ function scanDollarPlaceholders(sql, markUpTo) {
632
684
  i++;
633
685
  continue;
634
686
  }
635
- const { next, num, ok } = parseDollarNumber(sql, i + 1, n);
636
- i = next;
637
- if (!ok) continue;
638
- count++;
687
+ const parsed = parseDollarNumber(sql, i + 1);
688
+ i = parsed.next;
689
+ const num = parsed.num;
690
+ if (num === 0) continue;
691
+ sawAny = true;
639
692
  if (num < min) min = num;
640
693
  if (num > max) max = num;
641
694
  if (num <= markUpTo) seen[num] = 1;
642
695
  }
643
- return { count, min, max, seen };
696
+ if (!sawAny) {
697
+ return { min: 0, max: 0, seen, sawAny: false };
698
+ }
699
+ return { min, max, seen, sawAny: true };
644
700
  }
645
- function assertNoGaps(scan, rangeMin, rangeMax, sql) {
701
+ function assertNoGapsDollar(scan, rangeMin, rangeMax, sql) {
646
702
  for (let k = rangeMin; k <= rangeMax; k++) {
647
703
  if (scan.seen[k] !== 1) {
648
704
  throw new Error(
@@ -653,174 +709,75 @@ function assertNoGaps(scan, rangeMin, rangeMax, sql) {
653
709
  }
654
710
  function validateParamConsistency(sql, params) {
655
711
  const paramLen = params.length;
656
- if (paramLen === 0) {
657
- if (sql.indexOf("$") === -1) return;
658
- }
659
712
  const scan = scanDollarPlaceholders(sql, paramLen);
660
- if (scan.count === 0) {
661
- if (paramLen !== 0) {
713
+ if (paramLen === 0) {
714
+ if (scan.sawAny) {
662
715
  throw new Error(
663
- `CRITICAL: Parameter mismatch - SQL has no placeholders but ${paramLen} params provided.`
716
+ `CRITICAL: SQL contains placeholders but params is empty. SQL: ${sqlPreview(sql)}`
664
717
  );
665
718
  }
666
719
  return;
667
720
  }
668
- if (scan.max !== paramLen) {
721
+ if (!scan.sawAny) {
669
722
  throw new Error(
670
- `CRITICAL: Parameter mismatch - SQL max placeholder is $${scan.max} but ${paramLen} params provided. This will cause SQL execution to fail. SQL: ${sqlPreview(sql)}`
723
+ `CRITICAL: SQL is missing placeholders ($1..$${paramLen}) but params has length ${paramLen}. SQL: ${sqlPreview(sql)}`
671
724
  );
672
725
  }
673
- assertNoGaps(scan, 1, scan.max, sql);
674
- }
675
- function needsQuoting(id) {
676
- if (!isNonEmptyString(id)) return true;
677
- const isKeyword = SQL_KEYWORDS.has(id.toLowerCase());
678
- if (isKeyword) return true;
679
- const isValidIdentifier = REGEX_CACHE.VALID_IDENTIFIER.test(id);
680
- return !isValidIdentifier;
681
- }
682
- function validateParamConsistencyFragment(sql, params) {
683
- const paramLen = params.length;
684
- const scan = scanDollarPlaceholders(sql, paramLen);
685
- if (scan.max === 0) return;
686
- if (scan.max > paramLen) {
726
+ if (scan.min !== 1) {
687
727
  throw new Error(
688
- `CRITICAL: Parameter mismatch - SQL references $${scan.max} but only ${paramLen} params provided. SQL: ${sqlPreview(sql)}`
728
+ `CRITICAL: Placeholder range must start at $1, got min=$${scan.min}. SQL: ${sqlPreview(sql)}`
689
729
  );
690
730
  }
691
- assertNoGaps(scan, scan.min, scan.max, sql);
692
- }
693
- function assertOrThrow(condition, message) {
694
- if (!condition) throw new Error(message);
695
- }
696
- function dialectPlaceholderPrefix(dialect) {
697
- return dialect === "sqlite" ? "?" : "$";
698
- }
699
- function parseSqlitePlaceholderIndices(sql) {
700
- const re = /\?(?:(\d+))?/g;
701
- const indices = [];
702
- let anonCount = 0;
703
- let sawNumbered = false;
704
- let sawAnonymous = false;
705
- for (const m of sql.matchAll(re)) {
706
- const n = m[1];
707
- if (n) {
708
- sawNumbered = true;
709
- indices.push(parseInt(n, 10));
710
- } else {
711
- sawAnonymous = true;
712
- anonCount += 1;
713
- indices.push(anonCount);
714
- }
715
- }
716
- return { indices, sawNumbered, sawAnonymous };
717
- }
718
- function parseDollarPlaceholderIndices(sql) {
719
- const re = /\$(\d+)/g;
720
- const indices = [];
721
- for (const m of sql.matchAll(re)) indices.push(parseInt(m[1], 10));
722
- return indices;
723
- }
724
- function getPlaceholderIndices(sql, dialect) {
725
- if (dialect === "sqlite") return parseSqlitePlaceholderIndices(sql);
726
- return {
727
- indices: parseDollarPlaceholderIndices(sql),
728
- sawNumbered: false,
729
- sawAnonymous: false
730
- };
731
- }
732
- function maxIndex(indices) {
733
- return indices.length > 0 ? Math.max(...indices) : 0;
734
- }
735
- function ensureNoMixedSqlitePlaceholders(sawNumbered, sawAnonymous) {
736
- assertOrThrow(
737
- !(sawNumbered && sawAnonymous),
738
- `CRITICAL: Mixed sqlite placeholders ('?' and '?NNN') are not supported.`
739
- );
740
- }
741
- function ensurePlaceholderMaxMatchesMappingsLength(max, mappingsLength, dialect) {
742
- assertOrThrow(
743
- max === mappingsLength,
744
- `CRITICAL: SQL placeholder max mismatch - max is ${dialectPlaceholderPrefix(dialect)}${max}, but mappings length is ${mappingsLength}.`
745
- );
746
- }
747
- function ensureSequentialPlaceholders(placeholders, max, dialect) {
748
- const prefix = dialectPlaceholderPrefix(dialect);
749
- for (let i = 1; i <= max; i++) {
750
- assertOrThrow(
751
- placeholders.has(i),
752
- `CRITICAL: Missing SQL placeholder ${prefix}${i} - placeholders must be sequential 1..${max}.`
731
+ if (scan.max !== paramLen) {
732
+ throw new Error(
733
+ `CRITICAL: Placeholder max must match params length. max=$${scan.max}, params=${paramLen}. SQL: ${sqlPreview(sql)}`
753
734
  );
754
735
  }
736
+ assertNoGapsDollar(scan, 1, paramLen, sql);
755
737
  }
756
- function validateMappingIndex(mapping, max) {
757
- assertOrThrow(
758
- Number.isInteger(mapping.index) && mapping.index >= 1 && mapping.index <= max,
759
- `CRITICAL: ParamMapping index ${mapping.index} out of range 1..${max}.`
760
- );
761
- }
762
- function ensureUniqueMappingIndex(mappingIndices, index, dialect) {
763
- assertOrThrow(
764
- !mappingIndices.has(index),
765
- `CRITICAL: Duplicate ParamMapping index ${index} - each placeholder index must map to exactly one ParamMap.`
766
- );
767
- mappingIndices.add(index);
768
- }
769
- function ensureMappingIndexExistsInSql(placeholders, index) {
770
- assertOrThrow(
771
- placeholders.has(index),
772
- `CRITICAL: ParamMapping index ${index} not found in SQL placeholders.`
773
- );
774
- }
775
- function validateMappingValueShape(mapping) {
776
- assertOrThrow(
777
- !(mapping.dynamicName !== void 0 && mapping.value !== void 0),
778
- `CRITICAL: ParamMap ${mapping.index} has both dynamicName and value`
779
- );
780
- assertOrThrow(
781
- !(mapping.dynamicName === void 0 && mapping.value === void 0),
782
- `CRITICAL: ParamMap ${mapping.index} has neither dynamicName nor value`
783
- );
738
+ function countQuestionMarkPlaceholders(sql) {
739
+ const s = String(sql);
740
+ let count = 0;
741
+ for (let i = 0; i < s.length; i++) {
742
+ if (s.charCodeAt(i) === 63) count++;
743
+ }
744
+ return count;
784
745
  }
785
- function ensureMappingsCoverAllIndices(mappingIndices, max, dialect) {
786
- const prefix = dialectPlaceholderPrefix(dialect);
787
- for (let i = 1; i <= max; i++) {
788
- assertOrThrow(
789
- mappingIndices.has(i),
790
- `CRITICAL: Missing ParamMap for placeholder ${prefix}${i} - mappings must cover 1..${max} with no gaps.`
746
+ function validateQuestionMarkConsistency(sql, params) {
747
+ const expected = params.length;
748
+ const found = countQuestionMarkPlaceholders(sql);
749
+ if (expected !== found) {
750
+ throw new Error(
751
+ `CRITICAL: Parameter mismatch - expected ${expected} '?' placeholders, found ${found}. SQL: ${sqlPreview(sql)}`
791
752
  );
792
753
  }
793
754
  }
794
- function validateMappingsAgainstPlaceholders(mappings, placeholders, max, dialect) {
795
- const mappingIndices = /* @__PURE__ */ new Set();
796
- for (const mapping of mappings) {
797
- validateMappingIndex(mapping, max);
798
- ensureUniqueMappingIndex(mappingIndices, mapping.index);
799
- ensureMappingIndexExistsInSql(placeholders, mapping.index);
800
- validateMappingValueShape(mapping);
755
+ function validateParamConsistencyByDialect(sql, params, dialect) {
756
+ if (dialect === "postgres") {
757
+ validateParamConsistency(sql, params);
758
+ return;
801
759
  }
802
- ensureMappingsCoverAllIndices(mappingIndices, max, dialect);
803
- }
804
- function validateSqlPositions(sql, mappings, dialect) {
805
- const { indices, sawNumbered, sawAnonymous } = getPlaceholderIndices(
806
- sql,
807
- dialect
808
- );
809
760
  if (dialect === "sqlite") {
810
- ensureNoMixedSqlitePlaceholders(sawNumbered, sawAnonymous);
761
+ validateQuestionMarkConsistency(sql, params);
762
+ return;
763
+ }
764
+ if (dialect === "mysql" || dialect === "mariadb") {
765
+ validateQuestionMarkConsistency(sql, params);
766
+ return;
811
767
  }
812
- const placeholders = new Set(indices);
813
- if (placeholders.size === 0 && mappings.length === 0) return;
814
- const max = maxIndex(indices);
815
- ensurePlaceholderMaxMatchesMappingsLength(max, mappings.length, dialect);
816
- ensureSequentialPlaceholders(placeholders, max, dialect);
817
- validateMappingsAgainstPlaceholders(mappings, placeholders, max, dialect);
768
+ validateParamConsistency(sql, params);
769
+ }
770
+ function needsQuoting(identifier) {
771
+ const s = String(identifier);
772
+ if (!REGEX_CACHE.VALID_IDENTIFIER.test(s)) return true;
773
+ const lowered = s.toLowerCase();
774
+ if (SQL_KEYWORDS.has(lowered)) return true;
775
+ return false;
818
776
  }
819
777
 
820
778
  // src/builder/shared/sql-utils.ts
821
- var NUL = String.fromCharCode(0);
822
779
  function containsControlChars(s) {
823
- return s.includes(NUL) || s.includes("\n") || s.includes("\r");
780
+ return /[\u0000-\u001F\u007F]/.test(s);
824
781
  }
825
782
  function assertNoControlChars(label, s) {
826
783
  if (containsControlChars(s)) {
@@ -1039,16 +996,46 @@ function buildTableReference(schemaName, tableName, dialect) {
1039
996
  return `"${safeSchema}"."${safeTable}"`;
1040
997
  }
1041
998
  function assertSafeAlias(alias) {
1042
- const a = String(alias);
1043
- if (a.trim().length === 0) {
1044
- throw new Error("alias is required and cannot be empty");
999
+ if (typeof alias !== "string") {
1000
+ throw new Error(`Invalid alias: expected string, got ${typeof alias}`);
1001
+ }
1002
+ const a = alias.trim();
1003
+ if (a.length === 0) {
1004
+ throw new Error("Invalid alias: required and cannot be empty");
1005
+ }
1006
+ if (a !== alias) {
1007
+ throw new Error("Invalid alias: leading/trailing whitespace");
1045
1008
  }
1046
- if (containsControlChars(a) || a.includes(";")) {
1047
- throw new Error(`alias contains unsafe characters: ${JSON.stringify(a)}`);
1009
+ if (/[\u0000-\u001F\u007F]/.test(a)) {
1010
+ throw new Error(
1011
+ "Invalid alias: contains unsafe characters (control characters)"
1012
+ );
1013
+ }
1014
+ if (a.includes('"') || a.includes("'") || a.includes("`")) {
1015
+ throw new Error("Invalid alias: contains unsafe characters (quotes)");
1016
+ }
1017
+ if (a.includes(";")) {
1018
+ throw new Error("Invalid alias: contains unsafe characters (semicolon)");
1019
+ }
1020
+ if (a.includes("--") || a.includes("/*") || a.includes("*/")) {
1021
+ throw new Error(
1022
+ "Invalid alias: contains unsafe characters (SQL comment tokens)"
1023
+ );
1024
+ }
1025
+ if (/\s/.test(a)) {
1026
+ throw new Error(
1027
+ "Invalid alias: must be a simple identifier without whitespace"
1028
+ );
1029
+ }
1030
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(a)) {
1031
+ throw new Error(
1032
+ `Invalid alias: must be a simple identifier (alphanumeric with underscores): "${alias}"`
1033
+ );
1048
1034
  }
1049
- if (!/^[A-Za-z_]\w*$/.test(a)) {
1035
+ const lowered = a.toLowerCase();
1036
+ if (ALIAS_FORBIDDEN_KEYWORDS.has(lowered)) {
1050
1037
  throw new Error(
1051
- `alias must be a simple identifier, got: ${JSON.stringify(a)}`
1038
+ `Invalid alias: '${alias}' is a SQL keyword that would break query parsing. Forbidden aliases: ${[...ALIAS_FORBIDDEN_KEYWORDS].join(", ")}`
1052
1039
  );
1053
1040
  }
1054
1041
  }
@@ -1090,7 +1077,9 @@ function isValidRelationField(field) {
1090
1077
  if (fk.length === 0) return false;
1091
1078
  const refsRaw = field.references;
1092
1079
  const refs = normalizeKeyList(refsRaw);
1093
- if (refs.length === 0) return false;
1080
+ if (refs.length === 0) {
1081
+ return fk.length === 1;
1082
+ }
1094
1083
  if (refs.length !== fk.length) return false;
1095
1084
  return true;
1096
1085
  }
@@ -1105,6 +1094,8 @@ function getReferenceFieldNames(field, foreignKeyCount) {
1105
1094
  return refs;
1106
1095
  }
1107
1096
  function joinCondition(field, parentModel, childModel, parentAlias, childAlias) {
1097
+ assertSafeAlias(parentAlias);
1098
+ assertSafeAlias(childAlias);
1108
1099
  const fkFields = normalizeKeyList(field.foreignKey);
1109
1100
  if (fkFields.length === 0) {
1110
1101
  throw createError(
@@ -1254,6 +1245,66 @@ function normalizeOrderByInput(orderBy, parseValue) {
1254
1245
  throw new Error("orderBy must be an object or array of objects");
1255
1246
  }
1256
1247
 
1248
+ // src/builder/shared/order-by-determinism.ts
1249
+ function modelHasScalarId(model) {
1250
+ if (!model) return false;
1251
+ return getScalarFieldSet(model).has("id");
1252
+ }
1253
+ function hasIdTiebreaker(orderBy, parse) {
1254
+ if (!isNotNullish(orderBy)) return false;
1255
+ const normalized = normalizeOrderByInput(orderBy, parse);
1256
+ return normalized.some(
1257
+ (obj) => Object.prototype.hasOwnProperty.call(obj, "id")
1258
+ );
1259
+ }
1260
+ function addIdTiebreaker(orderBy) {
1261
+ if (Array.isArray(orderBy)) return [...orderBy, { id: "asc" }];
1262
+ return [orderBy, { id: "asc" }];
1263
+ }
1264
+ function ensureDeterministicOrderByInput(args) {
1265
+ const { orderBy, model, parseValue } = args;
1266
+ if (!modelHasScalarId(model)) return orderBy;
1267
+ if (!isNotNullish(orderBy)) {
1268
+ return { id: "asc" };
1269
+ }
1270
+ if (hasIdTiebreaker(orderBy, parseValue)) return orderBy;
1271
+ return addIdTiebreaker(orderBy);
1272
+ }
1273
+
1274
+ // src/builder/shared/validators/field-assertions.ts
1275
+ var NUMERIC_TYPES = /* @__PURE__ */ new Set(["Int", "Float", "Decimal", "BigInt"]);
1276
+ function assertScalarField(model, fieldName, context) {
1277
+ const field = getFieldInfo(model, fieldName);
1278
+ if (!field) {
1279
+ throw createError(
1280
+ `${context} references unknown field '${fieldName}' on model ${model.name}`,
1281
+ {
1282
+ field: fieldName,
1283
+ modelName: model.name,
1284
+ availableFields: model.fields.map((f) => f.name)
1285
+ }
1286
+ );
1287
+ }
1288
+ if (field.isRelation) {
1289
+ throw createError(
1290
+ `${context} does not support relation field '${fieldName}'`,
1291
+ { field: fieldName, modelName: model.name }
1292
+ );
1293
+ }
1294
+ return field;
1295
+ }
1296
+ function assertNumericField(model, fieldName, context) {
1297
+ const field = assertScalarField(model, fieldName, context);
1298
+ const baseType = field.type.replace(/\[\]|\?/g, "");
1299
+ if (!NUMERIC_TYPES.has(baseType)) {
1300
+ throw createError(
1301
+ `${context} requires numeric field, got '${field.type}'`,
1302
+ { field: fieldName, modelName: model.name }
1303
+ );
1304
+ }
1305
+ return field;
1306
+ }
1307
+
1257
1308
  // src/builder/pagination.ts
1258
1309
  var MAX_LIMIT_OFFSET = 2147483647;
1259
1310
  function parseDirectionRaw(raw, errorLabel) {
@@ -1294,30 +1345,31 @@ function parseOrderByValue(v, fieldName) {
1294
1345
  assertAllowedOrderByKeys(obj, fieldName);
1295
1346
  return { direction, nulls };
1296
1347
  }
1297
- function normalizeFiniteInteger(name, v) {
1298
- if (typeof v !== "number" || !Number.isFinite(v) || !Number.isInteger(v)) {
1299
- throw new Error(`${name} must be an integer`);
1300
- }
1301
- return v;
1302
- }
1303
1348
  function normalizeNonNegativeInt(name, v) {
1304
1349
  if (schemaParser.isDynamicParameter(v)) return v;
1305
- const n = normalizeFiniteInteger(name, v);
1306
- if (n < 0) {
1307
- throw new Error(`${name} must be >= 0`);
1308
- }
1309
- if (n > MAX_LIMIT_OFFSET) {
1310
- throw new Error(`${name} must be <= ${MAX_LIMIT_OFFSET}`);
1311
- }
1312
- return n;
1350
+ const result = normalizeIntLike(name, v, {
1351
+ min: 0,
1352
+ max: MAX_LIMIT_OFFSET,
1353
+ allowZero: true
1354
+ });
1355
+ if (result === void 0)
1356
+ throw new Error(`${name} normalization returned undefined`);
1357
+ return result;
1358
+ }
1359
+ function normalizeIntAllowNegative(name, v) {
1360
+ if (schemaParser.isDynamicParameter(v)) return v;
1361
+ const result = normalizeIntLike(name, v, {
1362
+ min: Number.MIN_SAFE_INTEGER,
1363
+ max: MAX_LIMIT_OFFSET,
1364
+ allowZero: true
1365
+ });
1366
+ if (result === void 0)
1367
+ throw new Error(`${name} normalization returned undefined`);
1368
+ return result;
1313
1369
  }
1314
1370
  function hasNonNullishProp(v, key) {
1315
1371
  return isPlainObject(v) && key in v && isNotNullish(v[key]);
1316
1372
  }
1317
- function normalizeIntegerOrDynamic(name, v) {
1318
- if (schemaParser.isDynamicParameter(v)) return v;
1319
- return normalizeFiniteInteger(name, v);
1320
- }
1321
1373
  function readSkipTake(relArgs) {
1322
1374
  const hasSkip = hasNonNullishProp(relArgs, "skip");
1323
1375
  const hasTake = hasNonNullishProp(relArgs, "take");
@@ -1331,7 +1383,7 @@ function readSkipTake(relArgs) {
1331
1383
  }
1332
1384
  const obj = relArgs;
1333
1385
  const skipVal = hasSkip ? normalizeNonNegativeInt("skip", obj.skip) : void 0;
1334
- const takeVal = hasTake ? normalizeIntegerOrDynamic("take", obj.take) : void 0;
1386
+ const takeVal = hasTake ? normalizeIntAllowNegative("take", obj.take) : void 0;
1335
1387
  return { hasSkip, hasTake, skipVal, takeVal };
1336
1388
  }
1337
1389
  function buildOrderByFragment(entries, alias, dialect, model) {
@@ -1357,9 +1409,7 @@ function buildOrderByFragment(entries, alias, dialect, model) {
1357
1409
  return out.join(SQL_SEPARATORS.ORDER_BY);
1358
1410
  }
1359
1411
  function defaultNullsFor(dialect, direction) {
1360
- if (dialect === "postgres") {
1361
- return direction === "asc" ? "last" : "first";
1362
- }
1412
+ if (dialect === "postgres") return direction === "asc" ? "last" : "first";
1363
1413
  return direction === "asc" ? "first" : "last";
1364
1414
  }
1365
1415
  function ensureCursorFieldsInOrder(orderEntries, cursorEntries) {
@@ -1376,13 +1426,12 @@ function ensureCursorFieldsInOrder(orderEntries, cursorEntries) {
1376
1426
  }
1377
1427
  function buildCursorFilterParts(cursor, cursorAlias, params, model) {
1378
1428
  const entries = Object.entries(cursor);
1379
- if (entries.length === 0) {
1429
+ if (entries.length === 0)
1380
1430
  throw new Error("cursor must have at least one field");
1381
- }
1382
1431
  const placeholdersByField = /* @__PURE__ */ new Map();
1383
1432
  const parts = [];
1384
1433
  for (const [field, value] of entries) {
1385
- const c = `${cursorAlias}.${quote(field)}`;
1434
+ const c = `${cursorAlias}.${quoteColumn(model, field)}`;
1386
1435
  if (value === null) {
1387
1436
  parts.push(`${c} IS NULL`);
1388
1437
  continue;
@@ -1396,13 +1445,6 @@ function buildCursorFilterParts(cursor, cursorAlias, params, model) {
1396
1445
  placeholdersByField
1397
1446
  };
1398
1447
  }
1399
- function cursorValueExpr(tableName, cursorAlias, cursorWhereSql, field, model) {
1400
- const colName = quote(field);
1401
- return `(SELECT ${cursorAlias}.${colName} ${SQL_TEMPLATES.FROM} ${tableName} ${cursorAlias} ${SQL_TEMPLATES.WHERE} ${cursorWhereSql} ${SQL_TEMPLATES.LIMIT} 1)`;
1402
- }
1403
- function buildCursorRowExistsExpr(tableName, cursorAlias, cursorWhereSql) {
1404
- return `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${tableName} ${cursorAlias} ${SQL_TEMPLATES.WHERE} ${cursorWhereSql} ${SQL_TEMPLATES.LIMIT} 1)`;
1405
- }
1406
1448
  function buildCursorEqualityExpr(columnExpr, valueExpr) {
1407
1449
  return `((${valueExpr} IS NULL AND ${columnExpr} IS NULL) OR (${valueExpr} IS NOT NULL AND ${columnExpr} = ${valueExpr}))`;
1408
1450
  }
@@ -1439,26 +1481,70 @@ function buildOrderEntries(orderBy) {
1439
1481
  if (typeof value === "string") {
1440
1482
  entries.push({ field, direction: value });
1441
1483
  } else {
1442
- entries.push({
1443
- field,
1444
- direction: value.sort,
1445
- nulls: value.nulls
1446
- });
1484
+ entries.push({ field, direction: value.direction, nulls: value.nulls });
1447
1485
  }
1448
1486
  }
1449
1487
  }
1450
1488
  return entries;
1451
1489
  }
1490
+ function buildCursorCteSelectList(cursorEntries, orderEntries, model) {
1491
+ const seen = /* @__PURE__ */ new Set();
1492
+ const ordered = [];
1493
+ for (const [f] of cursorEntries) {
1494
+ if (!seen.has(f)) {
1495
+ seen.add(f);
1496
+ ordered.push(f);
1497
+ }
1498
+ }
1499
+ for (const e of orderEntries) {
1500
+ if (!seen.has(e.field)) {
1501
+ seen.add(e.field);
1502
+ ordered.push(e.field);
1503
+ }
1504
+ }
1505
+ if (ordered.length === 0) throw new Error("cursor cte select list is empty");
1506
+ return ordered.map((f) => quoteColumn(model, f)).join(SQL_SEPARATORS.FIELD_LIST);
1507
+ }
1508
+ function truncateIdent(name, maxLen) {
1509
+ const s = String(name);
1510
+ if (s.length <= maxLen) return s;
1511
+ return s.slice(0, maxLen);
1512
+ }
1513
+ function buildCursorNames(outerAlias) {
1514
+ const maxLen = 63;
1515
+ const base = outerAlias.toLowerCase();
1516
+ const cteName = truncateIdent(`__tp_cursor_${base}`, maxLen);
1517
+ const srcAlias = truncateIdent(`__tp_cursor_src_${base}`, maxLen);
1518
+ if (cteName === outerAlias || srcAlias === outerAlias) {
1519
+ return {
1520
+ cteName: truncateIdent(`__tp_cursor_${base}_x`, maxLen),
1521
+ srcAlias: truncateIdent(`__tp_cursor_src_${base}_x`, maxLen)
1522
+ };
1523
+ }
1524
+ return { cteName, srcAlias };
1525
+ }
1526
+ function assertCursorAndOrderFieldsScalar(model, cursor, orderEntries) {
1527
+ if (!model) return;
1528
+ for (const k of Object.keys(cursor)) assertScalarField(model, k, "cursor");
1529
+ for (const e of orderEntries) assertScalarField(model, e.field, "orderBy");
1530
+ }
1452
1531
  function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect, model) {
1453
1532
  var _a;
1533
+ assertSafeTableRef(tableName);
1534
+ assertSafeAlias(alias);
1454
1535
  const d = dialect != null ? dialect : getGlobalDialect();
1455
1536
  const cursorEntries = Object.entries(cursor);
1456
- if (cursorEntries.length === 0) {
1537
+ if (cursorEntries.length === 0)
1457
1538
  throw new Error("cursor must have at least one field");
1458
- }
1459
- const cursorAlias = "__tp_cursor_src";
1460
- const { whereSql: cursorWhereSql, placeholdersByField } = buildCursorFilterParts(cursor, cursorAlias, params);
1461
- let orderEntries = buildOrderEntries(orderBy);
1539
+ const { cteName, srcAlias } = buildCursorNames(alias);
1540
+ assertSafeAlias(cteName);
1541
+ assertSafeAlias(srcAlias);
1542
+ const deterministicOrderBy = ensureDeterministicOrderByInput({
1543
+ orderBy,
1544
+ model,
1545
+ parseValue: parseOrderByValue
1546
+ });
1547
+ let orderEntries = buildOrderEntries(deterministicOrderBy);
1462
1548
  if (orderEntries.length === 0) {
1463
1549
  orderEntries = cursorEntries.map(([field]) => ({
1464
1550
  field,
@@ -1467,11 +1553,23 @@ function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect
1467
1553
  } else {
1468
1554
  orderEntries = ensureCursorFieldsInOrder(orderEntries, cursorEntries);
1469
1555
  }
1470
- const existsExpr = buildCursorRowExistsExpr(
1471
- tableName,
1472
- cursorAlias,
1473
- cursorWhereSql
1556
+ assertCursorAndOrderFieldsScalar(model, cursor, orderEntries);
1557
+ const { whereSql: cursorWhereSql, placeholdersByField } = buildCursorFilterParts(cursor, srcAlias, params, model);
1558
+ const cursorOrderBy = orderEntries.map(
1559
+ (e) => `${srcAlias}.${quoteColumn(model, e.field)} ${e.direction.toUpperCase()}`
1560
+ ).join(", ");
1561
+ const selectList = buildCursorCteSelectList(
1562
+ cursorEntries,
1563
+ orderEntries,
1564
+ model
1474
1565
  );
1566
+ const cte = `${cteName} AS (
1567
+ SELECT ${selectList} FROM ${tableName} ${srcAlias}
1568
+ WHERE ${cursorWhereSql}
1569
+ ORDER BY ${cursorOrderBy}
1570
+ LIMIT 1
1571
+ )`;
1572
+ const existsExpr = `EXISTS (SELECT 1 FROM ${cteName})`;
1475
1573
  const outerCursorMatch = buildOuterCursorMatch(
1476
1574
  cursor,
1477
1575
  alias,
@@ -1479,34 +1577,29 @@ function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect
1479
1577
  params,
1480
1578
  model
1481
1579
  );
1580
+ const getValueExpr = (field) => `(SELECT ${quoteColumn(model, field)} FROM ${cteName})`;
1482
1581
  const orClauses = [];
1483
1582
  for (let level = 0; level < orderEntries.length; level++) {
1484
1583
  const andParts = [];
1485
1584
  for (let i = 0; i < level; i++) {
1486
1585
  const e2 = orderEntries[i];
1487
1586
  const c2 = col(alias, e2.field, model);
1488
- const v2 = cursorValueExpr(
1489
- tableName,
1490
- cursorAlias,
1491
- cursorWhereSql,
1492
- e2.field);
1587
+ const v2 = getValueExpr(e2.field);
1493
1588
  andParts.push(buildCursorEqualityExpr(c2, v2));
1494
1589
  }
1495
1590
  const e = orderEntries[level];
1496
1591
  const c = col(alias, e.field, model);
1497
- const v = cursorValueExpr(
1498
- tableName,
1499
- cursorAlias,
1500
- cursorWhereSql,
1501
- e.field);
1592
+ const v = getValueExpr(e.field);
1502
1593
  const nulls = (_a = e.nulls) != null ? _a : defaultNullsFor(d, e.direction);
1503
1594
  andParts.push(buildCursorInequalityExpr(c, e.direction, nulls, v));
1504
1595
  orClauses.push(`(${andParts.join(SQL_SEPARATORS.CONDITION_AND)})`);
1505
1596
  }
1506
1597
  const exclusive = orClauses.join(SQL_SEPARATORS.CONDITION_OR);
1507
- return `(${existsExpr} ${SQL_SEPARATORS.CONDITION_AND} ((${exclusive})${SQL_SEPARATORS.CONDITION_OR}(${outerCursorMatch})))`;
1598
+ const condition = `(${existsExpr} ${SQL_SEPARATORS.CONDITION_AND} ((${exclusive})${SQL_SEPARATORS.CONDITION_OR}(${outerCursorMatch})))`;
1599
+ return { cte, condition };
1508
1600
  }
1509
1601
  function buildOrderBy(orderBy, alias, dialect, model) {
1602
+ assertSafeAlias(alias);
1510
1603
  const entries = buildOrderEntries(orderBy);
1511
1604
  if (entries.length === 0) return "";
1512
1605
  const d = dialect != null ? dialect : getGlobalDialect();
@@ -1528,9 +1621,7 @@ function normalizeTakeLike(v) {
1528
1621
  max: MAX_LIMIT_OFFSET,
1529
1622
  allowZero: true
1530
1623
  });
1531
- if (typeof n === "number") {
1532
- if (n === 0) return 0;
1533
- }
1624
+ if (typeof n === "number" && n === 0) return 0;
1534
1625
  return n;
1535
1626
  }
1536
1627
  function normalizeSkipLike(v) {
@@ -1606,6 +1697,11 @@ function buildScalarOperator(expr, op, val, params, mode, fieldType, dialect) {
1606
1697
  }
1607
1698
  return handleInOperator(expr, op, val, params, dialect);
1608
1699
  }
1700
+ if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && !isNotNullish(dialect)) {
1701
+ throw createError(`Insensitive equals requires a SQL dialect`, {
1702
+ operator: op
1703
+ });
1704
+ }
1609
1705
  return handleComparisonOperator(expr, op, val, params);
1610
1706
  }
1611
1707
  function handleNullValue(expr, op) {
@@ -1621,6 +1717,28 @@ function normalizeMode(v) {
1621
1717
  function handleNotOperator(expr, val, params, outerMode, fieldType, dialect) {
1622
1718
  const innerMode = normalizeMode(val.mode);
1623
1719
  const effectiveMode = innerMode != null ? innerMode : outerMode;
1720
+ const entries = Object.entries(val).filter(
1721
+ ([k, v]) => k !== "mode" && v !== void 0
1722
+ );
1723
+ if (entries.length === 0) return "";
1724
+ if (!isNotNullish(dialect)) {
1725
+ const clauses = [];
1726
+ for (const [subOp, subVal] of entries) {
1727
+ const sub = buildScalarOperator(
1728
+ expr,
1729
+ subOp,
1730
+ subVal,
1731
+ params,
1732
+ effectiveMode,
1733
+ fieldType,
1734
+ void 0
1735
+ );
1736
+ if (sub && sub.trim().length > 0) clauses.push(`(${sub})`);
1737
+ }
1738
+ if (clauses.length === 0) return "";
1739
+ if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
1740
+ return `${SQL_TEMPLATES.NOT} (${clauses.join(` ${SQL_TEMPLATES.AND} `)})`;
1741
+ }
1624
1742
  return buildNotComposite(
1625
1743
  expr,
1626
1744
  val,
@@ -1835,6 +1953,7 @@ function handleArrayIsEmpty(expr, val, dialect) {
1835
1953
 
1836
1954
  // src/builder/where/operators-json.ts
1837
1955
  var SAFE_JSON_PATH_SEGMENT = /^[a-zA-Z_]\w*$/;
1956
+ var MAX_PATH_SEGMENT_LENGTH = 255;
1838
1957
  function validateJsonPathSegments(segments) {
1839
1958
  for (const segment of segments) {
1840
1959
  if (typeof segment !== "string") {
@@ -1843,6 +1962,12 @@ function validateJsonPathSegments(segments) {
1843
1962
  value: segment
1844
1963
  });
1845
1964
  }
1965
+ if (segment.length > MAX_PATH_SEGMENT_LENGTH) {
1966
+ throw createError(
1967
+ `JSON path segment too long: max ${MAX_PATH_SEGMENT_LENGTH} characters`,
1968
+ { operator: Ops.PATH, value: `[${segment.length} chars]` }
1969
+ );
1970
+ }
1846
1971
  if (!SAFE_JSON_PATH_SEGMENT.test(segment)) {
1847
1972
  throw createError(
1848
1973
  `Invalid JSON path segment: '${segment}'. Must be alphanumeric with underscores, starting with letter or underscore.`,
@@ -1931,6 +2056,9 @@ function handleJsonWildcard(expr, op, val, params, wildcards, dialect) {
1931
2056
 
1932
2057
  // src/builder/where/relations.ts
1933
2058
  var NO_JOINS = Object.freeze([]);
2059
+ function freezeJoins(items) {
2060
+ return Object.freeze([...items]);
2061
+ }
1934
2062
  function isListRelation(fieldType) {
1935
2063
  return typeof fieldType === "string" && fieldType.endsWith("[]");
1936
2064
  }
@@ -1993,7 +2121,7 @@ function buildListRelationFilters(args) {
1993
2121
  const whereClause = `${relAlias}.${quote(checkField.name)} IS NULL`;
1994
2122
  return Object.freeze({
1995
2123
  clause: whereClause,
1996
- joins: [leftJoinSql]
2124
+ joins: freezeJoins([leftJoinSql])
1997
2125
  });
1998
2126
  }
1999
2127
  }
@@ -2198,7 +2326,7 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
2198
2326
  Ops.HAS_EVERY,
2199
2327
  Ops.IS_EMPTY
2200
2328
  ]);
2201
- const JSON_OPS = /* @__PURE__ */ new Set([
2329
+ const JSON_OPS2 = /* @__PURE__ */ new Set([
2202
2330
  Ops.PATH,
2203
2331
  Ops.STRING_CONTAINS,
2204
2332
  Ops.STRING_STARTS_WITH,
@@ -2215,7 +2343,7 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
2215
2343
  modelName
2216
2344
  });
2217
2345
  }
2218
- const isJsonOp = JSON_OPS.has(op);
2346
+ const isJsonOp = JSON_OPS2.has(op);
2219
2347
  const isFieldJson = isJsonType(fieldType);
2220
2348
  const jsonOpMismatch = isJsonOp && !isFieldJson;
2221
2349
  if (jsonOpMismatch) {
@@ -2229,6 +2357,14 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
2229
2357
  }
2230
2358
 
2231
2359
  // src/builder/where/builder.ts
2360
+ var MAX_QUERY_DEPTH = 50;
2361
+ var EMPTY_JOINS = Object.freeze([]);
2362
+ var JSON_OPS = /* @__PURE__ */ new Set([
2363
+ Ops.PATH,
2364
+ Ops.STRING_CONTAINS,
2365
+ Ops.STRING_STARTS_WITH,
2366
+ Ops.STRING_ENDS_WITH
2367
+ ]);
2232
2368
  var WhereBuilder = class {
2233
2369
  build(where, ctx) {
2234
2370
  if (!isPlainObject(where)) {
@@ -2240,8 +2376,6 @@ var WhereBuilder = class {
2240
2376
  return buildWhereInternal(where, ctx, this);
2241
2377
  }
2242
2378
  };
2243
- var MAX_QUERY_DEPTH = 50;
2244
- var EMPTY_JOINS = Object.freeze([]);
2245
2379
  var whereBuilderInstance = new WhereBuilder();
2246
2380
  function freezeResult(clause, joins = EMPTY_JOINS) {
2247
2381
  return Object.freeze({ clause, joins });
@@ -2418,16 +2552,8 @@ function buildOperator(expr, op, val, ctx, mode, fieldType) {
2418
2552
  if (fieldType && isArrayType(fieldType)) {
2419
2553
  return buildArrayOperator(expr, op, val, ctx.params, fieldType, ctx.dialect);
2420
2554
  }
2421
- if (fieldType && isJsonType(fieldType)) {
2422
- const JSON_OPS = /* @__PURE__ */ new Set([
2423
- Ops.PATH,
2424
- Ops.STRING_CONTAINS,
2425
- Ops.STRING_STARTS_WITH,
2426
- Ops.STRING_ENDS_WITH
2427
- ]);
2428
- if (JSON_OPS.has(op)) {
2429
- return buildJsonOperator(expr, op, val, ctx.params, ctx.dialect);
2430
- }
2555
+ if (fieldType && isJsonType(fieldType) && JSON_OPS.has(op)) {
2556
+ return buildJsonOperator(expr, op, val, ctx.params, ctx.dialect);
2431
2557
  }
2432
2558
  return buildScalarOperator(
2433
2559
  expr,
@@ -2448,7 +2574,7 @@ function toSafeSqlIdentifier(input) {
2448
2574
  const base = startsOk ? cleaned : `_${cleaned}`;
2449
2575
  const fallback = base.length > 0 ? base : "_t";
2450
2576
  const lowered = fallback.toLowerCase();
2451
- return SQL_KEYWORDS.has(lowered) ? `_${lowered}` : lowered;
2577
+ return ALIAS_FORBIDDEN_KEYWORDS.has(lowered) ? `_${lowered}` : lowered;
2452
2578
  }
2453
2579
  function createAliasGenerator(maxAliases = 1e4) {
2454
2580
  let counter = 0;
@@ -2658,6 +2784,7 @@ function toPublicResult(clause, joins, params) {
2658
2784
  // src/builder/where.ts
2659
2785
  function buildWhereClause(where, options) {
2660
2786
  var _a, _b, _c, _d, _e;
2787
+ assertSafeAlias(options.alias);
2661
2788
  const dialect = options.dialect || getGlobalDialect();
2662
2789
  const params = (_a = options.params) != null ? _a : createParamStore();
2663
2790
  const ctx = {
@@ -2673,22 +2800,6 @@ function buildWhereClause(where, options) {
2673
2800
  };
2674
2801
  const result = whereBuilderInstance.build(where, ctx);
2675
2802
  const publicResult = toPublicResult(result.clause, result.joins, params);
2676
- if (!options.isSubquery) {
2677
- const nums = [...publicResult.clause.matchAll(/\$(\d+)/g)].map(
2678
- (m) => parseInt(m[1], 10)
2679
- );
2680
- if (nums.length > 0) {
2681
- const min = Math.min(...nums);
2682
- if (min === 1) {
2683
- validateParamConsistency(publicResult.clause, publicResult.params);
2684
- } else {
2685
- validateParamConsistencyFragment(
2686
- publicResult.clause,
2687
- publicResult.params
2688
- );
2689
- }
2690
- }
2691
- }
2692
2803
  return publicResult;
2693
2804
  }
2694
2805
 
@@ -2826,6 +2937,9 @@ function buildRelationSelect(relArgs, relModel, relAlias) {
2826
2937
  }
2827
2938
 
2828
2939
  // src/builder/select/includes.ts
2940
+ var MAX_INCLUDE_DEPTH = 10;
2941
+ var MAX_TOTAL_SUBQUERIES = 100;
2942
+ var MAX_TOTAL_INCLUDES = 50;
2829
2943
  function getRelationTableReference(relModel, dialect) {
2830
2944
  return buildTableReference(
2831
2945
  SQL_TEMPLATES.PUBLIC_SCHEMA,
@@ -2871,107 +2985,23 @@ function relationEntriesFromArgs(args, model) {
2871
2985
  pushFrom(args.select);
2872
2986
  return out;
2873
2987
  }
2874
- function assertScalarField(model, fieldName) {
2875
- const f = model.fields.find((x) => x.name === fieldName);
2876
- if (!f) {
2877
- throw new Error(
2878
- `orderBy references unknown field '${fieldName}' on model ${model.name}`
2879
- );
2880
- }
2881
- if (f.isRelation) {
2882
- throw new Error(
2883
- `orderBy does not support relation field '${fieldName}' on model ${model.name}`
2884
- );
2885
- }
2886
- }
2887
- function validateOrderByDirection(fieldName, v) {
2888
- const s = String(v).toLowerCase();
2889
- if (s !== "asc" && s !== "desc") {
2890
- throw new Error(
2891
- `Invalid orderBy direction for '${fieldName}': ${String(v)}`
2892
- );
2893
- }
2894
- }
2895
- function validateOrderByObject(fieldName, v) {
2896
- if (!("sort" in v)) {
2897
- throw new Error(
2898
- `orderBy for '${fieldName}' must be 'asc' | 'desc' or { sort, nulls? }`
2899
- );
2900
- }
2901
- validateOrderByDirection(fieldName, v.sort);
2902
- if ("nulls" in v && isNotNullish(v.nulls)) {
2903
- const n = String(v.nulls).toLowerCase();
2904
- if (n !== "first" && n !== "last") {
2905
- throw new Error(
2906
- `Invalid orderBy.nulls for '${fieldName}': ${String(v.nulls)}`
2907
- );
2908
- }
2909
- }
2910
- const allowed = /* @__PURE__ */ new Set(["sort", "nulls"]);
2911
- for (const k of Object.keys(v)) {
2912
- if (!allowed.has(k)) {
2913
- throw new Error(`Unsupported orderBy key '${k}' for field '${fieldName}'`);
2914
- }
2915
- }
2916
- }
2917
- function normalizeOrderByFieldName(name) {
2918
- const fieldName = String(name).trim();
2919
- if (fieldName.length === 0) {
2920
- throw new Error("orderBy field name cannot be empty");
2921
- }
2922
- return fieldName;
2923
- }
2924
- function requirePlainObjectForOrderByEntry(v) {
2925
- if (typeof v !== "object" || v === null || Array.isArray(v)) {
2926
- throw new Error("orderBy array entries must be objects");
2927
- }
2928
- return v;
2929
- }
2930
- function parseSingleFieldOrderByObject(obj) {
2931
- const entries = Object.entries(obj);
2932
- if (entries.length !== 1) {
2933
- throw new Error("orderBy array entries must have exactly one field");
2934
- }
2935
- const fieldName = normalizeOrderByFieldName(entries[0][0]);
2936
- return [fieldName, entries[0][1]];
2937
- }
2938
- function parseOrderByArray(orderBy) {
2939
- return orderBy.map(
2940
- (item) => parseSingleFieldOrderByObject(requirePlainObjectForOrderByEntry(item))
2941
- );
2942
- }
2943
- function parseOrderByObject(orderBy) {
2944
- const out = [];
2945
- for (const [k, v] of Object.entries(orderBy)) {
2946
- out.push([normalizeOrderByFieldName(k), v]);
2947
- }
2948
- return out;
2949
- }
2950
- function getOrderByEntries(orderBy) {
2951
- if (!isNotNullish(orderBy)) return [];
2952
- if (Array.isArray(orderBy)) {
2953
- return parseOrderByArray(orderBy);
2954
- }
2955
- if (typeof orderBy === "object" && orderBy !== null) {
2956
- return parseOrderByObject(orderBy);
2957
- }
2958
- throw new Error("orderBy must be an object or array of objects");
2959
- }
2960
2988
  function validateOrderByForModel(model, orderBy) {
2961
- const entries = getOrderByEntries(orderBy);
2962
- for (const [fieldName, v] of entries) {
2963
- assertScalarField(model, fieldName);
2964
- if (typeof v === "string") {
2965
- validateOrderByDirection(fieldName, v);
2966
- continue;
2989
+ if (!isNotNullish(orderBy)) return;
2990
+ const scalarSet = getScalarFieldSet(model);
2991
+ const normalized = normalizeOrderByInput(orderBy, parseOrderByValue);
2992
+ for (const item of normalized) {
2993
+ const entries = Object.entries(item);
2994
+ if (entries.length !== 1) {
2995
+ throw new Error("orderBy array entries must have exactly one field");
2967
2996
  }
2968
- if (isPlainObject(v)) {
2969
- validateOrderByObject(fieldName, v);
2970
- continue;
2997
+ const fieldName = String(entries[0][0]).trim();
2998
+ if (fieldName.length === 0)
2999
+ throw new Error("orderBy field name cannot be empty");
3000
+ if (!scalarSet.has(fieldName)) {
3001
+ throw new Error(
3002
+ `orderBy references unknown or non-scalar field '${fieldName}' on model ${model.name}`
3003
+ );
2971
3004
  }
2972
- throw new Error(
2973
- `orderBy for '${fieldName}' must be 'asc' | 'desc' or { sort, nulls? }`
2974
- );
2975
3005
  }
2976
3006
  }
2977
3007
  function appendLimitOffset(sql, dialect, params, takeVal, skipVal, scope) {
@@ -3000,7 +3030,10 @@ function readWhereInput(relArgs) {
3000
3030
  function readOrderByInput(relArgs) {
3001
3031
  if (!isPlainObject(relArgs)) return { hasOrderBy: false, orderBy: void 0 };
3002
3032
  if (!("orderBy" in relArgs)) return { hasOrderBy: false, orderBy: void 0 };
3003
- return { hasOrderBy: true, orderBy: relArgs.orderBy };
3033
+ return {
3034
+ hasOrderBy: true,
3035
+ orderBy: relArgs.orderBy
3036
+ };
3004
3037
  }
3005
3038
  function extractRelationPaginationConfig(relArgs) {
3006
3039
  const { hasOrderBy, orderBy: rawOrderByInput } = readOrderByInput(relArgs);
@@ -3022,44 +3055,25 @@ function extractRelationPaginationConfig(relArgs) {
3022
3055
  function maybeReverseNegativeTake(takeVal, hasOrderBy, orderByInput) {
3023
3056
  if (typeof takeVal !== "number") return { takeVal, orderByInput };
3024
3057
  if (takeVal >= 0) return { takeVal, orderByInput };
3025
- if (!hasOrderBy) {
3058
+ if (!hasOrderBy)
3026
3059
  throw new Error("Negative take requires orderBy for deterministic results");
3027
- }
3028
3060
  return {
3029
3061
  takeVal: Math.abs(takeVal),
3030
3062
  orderByInput: reverseOrderByInput(orderByInput)
3031
3063
  };
3032
3064
  }
3033
- function hasIdTiebreaker(orderByInput) {
3034
- const entries = Array.isArray(orderByInput) ? orderByInput : [orderByInput];
3035
- return entries.some(
3036
- (entry) => isPlainObject(entry) ? Object.prototype.hasOwnProperty.call(entry, "id") : false
3037
- );
3038
- }
3039
- function modelHasScalarId(relModel) {
3040
- const idField = relModel.fields.find((f) => f.name === "id");
3041
- return Boolean(idField && !idField.isRelation);
3042
- }
3043
- function addIdTiebreaker(orderByInput) {
3044
- if (Array.isArray(orderByInput)) return [...orderByInput, { id: "asc" }];
3045
- return [orderByInput, { id: "asc" }];
3046
- }
3047
- function ensureDeterministicOrderBy(relModel, hasOrderBy, orderByInput, hasPagination) {
3048
- if (!hasPagination) {
3049
- if (hasOrderBy && isNotNullish(orderByInput)) {
3050
- validateOrderByForModel(relModel, orderByInput);
3051
- }
3052
- return orderByInput;
3053
- }
3054
- if (!hasOrderBy) {
3055
- return modelHasScalarId(relModel) ? { id: "asc" } : orderByInput;
3065
+ function finalizeOrderByForInclude(args) {
3066
+ if (args.hasOrderBy && isNotNullish(args.orderByInput)) {
3067
+ validateOrderByForModel(args.relModel, args.orderByInput);
3056
3068
  }
3057
- if (isNotNullish(orderByInput)) {
3058
- validateOrderByForModel(relModel, orderByInput);
3069
+ if (!args.hasPagination) {
3070
+ return args.orderByInput;
3059
3071
  }
3060
- if (!modelHasScalarId(relModel)) return orderByInput;
3061
- if (hasIdTiebreaker(orderByInput)) return orderByInput;
3062
- return addIdTiebreaker(orderByInput);
3072
+ return ensureDeterministicOrderByInput({
3073
+ orderBy: args.hasOrderBy ? args.orderByInput : void 0,
3074
+ model: args.relModel,
3075
+ parseValue: parseOrderByValue
3076
+ });
3063
3077
  }
3064
3078
  function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
3065
3079
  let relSelect = buildRelationSelect(relArgs, relModel, relAlias);
@@ -3070,7 +3084,10 @@ function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
3070
3084
  relAlias,
3071
3085
  ctx.aliasGen,
3072
3086
  ctx.params,
3073
- ctx.dialect
3087
+ ctx.dialect,
3088
+ ctx.visitPath || [],
3089
+ (ctx.depth || 0) + 1,
3090
+ ctx.stats
3074
3091
  ) : [];
3075
3092
  if (isNonEmptyArray(nestedIncludes)) {
3076
3093
  const emptyJson = ctx.dialect === "postgres" ? `'[]'::json` : `json('[]')`;
@@ -3157,11 +3174,7 @@ function buildListIncludeSpec(args) {
3157
3174
  joinPredicate: args.joinPredicate,
3158
3175
  whereClause: args.whereClause
3159
3176
  });
3160
- return Object.freeze({
3161
- name: args.relName,
3162
- sql: sql2,
3163
- isOneToOne: false
3164
- });
3177
+ return Object.freeze({ name: args.relName, sql: sql2, isOneToOne: false });
3165
3178
  }
3166
3179
  const rowAlias = args.ctx.aliasGen.next(`${args.relName}_row`);
3167
3180
  let base = buildBaseSql({
@@ -3172,9 +3185,7 @@ function buildListIncludeSpec(args) {
3172
3185
  joinPredicate: args.joinPredicate,
3173
3186
  whereClause: args.whereClause
3174
3187
  });
3175
- if (args.orderBySql) {
3176
- base += ` ${SQL_TEMPLATES.ORDER_BY} ${args.orderBySql}`;
3177
- }
3188
+ if (args.orderBySql) base += ` ${SQL_TEMPLATES.ORDER_BY} ${args.orderBySql}`;
3178
3189
  base = appendLimitOffset(
3179
3190
  base,
3180
3191
  args.ctx.dialect,
@@ -3185,11 +3196,7 @@ function buildListIncludeSpec(args) {
3185
3196
  );
3186
3197
  const selectExpr = jsonAgg("row", args.ctx.dialect);
3187
3198
  const sql = `${SQL_TEMPLATES.SELECT} ${selectExpr} ${SQL_TEMPLATES.FROM} (${base}) ${rowAlias}`;
3188
- return Object.freeze({
3189
- name: args.relName,
3190
- sql,
3191
- isOneToOne: false
3192
- });
3199
+ return Object.freeze({ name: args.relName, sql, isOneToOne: false });
3193
3200
  }
3194
3201
  function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3195
3202
  const relTable = getRelationTableReference(relModel, ctx.dialect);
@@ -3220,12 +3227,12 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3220
3227
  paginationConfig.orderBy
3221
3228
  );
3222
3229
  const hasPagination = paginationConfig.hasSkip || paginationConfig.hasTake;
3223
- const finalOrderByInput = ensureDeterministicOrderBy(
3230
+ const finalOrderByInput = finalizeOrderByForInclude({
3224
3231
  relModel,
3225
- paginationConfig.hasOrderBy,
3226
- adjusted.orderByInput,
3232
+ hasOrderBy: paginationConfig.hasOrderBy,
3233
+ orderByInput: adjusted.orderByInput,
3227
3234
  hasPagination
3228
- );
3235
+ });
3229
3236
  const orderBySql = buildOrderBySql(
3230
3237
  finalOrderByInput,
3231
3238
  relAlias,
@@ -3246,11 +3253,7 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3246
3253
  skipVal: paginationConfig.skipVal,
3247
3254
  ctx
3248
3255
  });
3249
- return Object.freeze({
3250
- name: relName,
3251
- sql,
3252
- isOneToOne: true
3253
- });
3256
+ return Object.freeze({ name: relName, sql, isOneToOne: true });
3254
3257
  }
3255
3258
  return buildListIncludeSpec({
3256
3259
  relName,
@@ -3266,32 +3269,69 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3266
3269
  ctx
3267
3270
  });
3268
3271
  }
3269
- function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, params, dialect) {
3272
+ function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, params, dialect, visitPath = [], depth = 0, stats) {
3273
+ if (!stats) stats = { totalIncludes: 0, totalSubqueries: 0, maxDepth: 0 };
3274
+ if (depth > MAX_INCLUDE_DEPTH) {
3275
+ throw new Error(
3276
+ `Maximum include depth of ${MAX_INCLUDE_DEPTH} exceeded. Path: ${visitPath.join(" -> ")}. Deep includes cause exponential SQL complexity and performance issues.`
3277
+ );
3278
+ }
3279
+ stats.maxDepth = Math.max(stats.maxDepth, depth);
3270
3280
  const includes = [];
3271
3281
  const entries = relationEntriesFromArgs(args, model);
3272
3282
  for (const [relName, relArgs] of entries) {
3273
3283
  if (relArgs === false) continue;
3284
+ stats.totalIncludes++;
3285
+ if (stats.totalIncludes > MAX_TOTAL_INCLUDES) {
3286
+ throw new Error(
3287
+ `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.`
3288
+ );
3289
+ }
3290
+ stats.totalSubqueries++;
3291
+ if (stats.totalSubqueries > MAX_TOTAL_SUBQUERIES) {
3292
+ throw new Error(
3293
+ `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.`
3294
+ );
3295
+ }
3274
3296
  const resolved = resolveRelationOrThrow(model, schemas, relName);
3275
- const include = buildSingleInclude(
3276
- relName,
3277
- relArgs,
3278
- resolved.field,
3279
- resolved.relModel,
3280
- {
3297
+ const relationPath = `${model.name}.${relName}`;
3298
+ const currentPath = [...visitPath, relationPath];
3299
+ if (visitPath.includes(relationPath)) {
3300
+ throw new Error(
3301
+ `Circular include detected: ${currentPath.join(" -> ")}. Relation '${relationPath}' creates an infinite loop.`
3302
+ );
3303
+ }
3304
+ const modelOccurrences = currentPath.filter(
3305
+ (p) => p.startsWith(`${resolved.relModel.name}.`)
3306
+ ).length;
3307
+ if (modelOccurrences > 2) {
3308
+ throw new Error(
3309
+ `Include too deeply nested: model '${resolved.relModel.name}' appears ${modelOccurrences} times in path: ${currentPath.join(" -> ")}`
3310
+ );
3311
+ }
3312
+ includes.push(
3313
+ buildSingleInclude(relName, relArgs, resolved.field, resolved.relModel, {
3281
3314
  model,
3282
3315
  schemas,
3283
3316
  parentAlias,
3284
3317
  aliasGen,
3285
3318
  dialect,
3286
- params
3287
- }
3319
+ params,
3320
+ visitPath: currentPath,
3321
+ depth: depth + 1,
3322
+ stats
3323
+ })
3288
3324
  );
3289
- includes.push(include);
3290
3325
  }
3291
3326
  return includes;
3292
3327
  }
3293
3328
  function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
3294
3329
  const aliasGen = createAliasGenerator();
3330
+ const stats = {
3331
+ totalIncludes: 0,
3332
+ totalSubqueries: 0,
3333
+ maxDepth: 0
3334
+ };
3295
3335
  return buildIncludeSqlInternal(
3296
3336
  args,
3297
3337
  model,
@@ -3299,7 +3339,10 @@ function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
3299
3339
  parentAlias,
3300
3340
  aliasGen,
3301
3341
  params,
3302
- dialect
3342
+ dialect,
3343
+ [],
3344
+ 0,
3345
+ stats
3303
3346
  );
3304
3347
  }
3305
3348
  function resolveCountRelationOrThrow(relName, model, schemas) {
@@ -3310,10 +3353,14 @@ function resolveCountRelationOrThrow(relName, model, schemas) {
3310
3353
  );
3311
3354
  }
3312
3355
  const field = model.fields.find((f) => f.name === relName);
3313
- if (!field) {
3356
+ if (!field)
3314
3357
  throw new Error(
3315
3358
  `_count.${relName} references unknown relation on model ${model.name}`
3316
3359
  );
3360
+ if (!isValidRelationField(field)) {
3361
+ throw new Error(
3362
+ `_count.${relName} has invalid relation metadata on model ${model.name}`
3363
+ );
3317
3364
  }
3318
3365
  const relModel = schemas.find((m) => m.name === field.relatedModel);
3319
3366
  if (!relModel) {
@@ -3323,31 +3370,78 @@ function resolveCountRelationOrThrow(relName, model, schemas) {
3323
3370
  }
3324
3371
  return { field, relModel };
3325
3372
  }
3326
- function groupByColForCount(field, countAlias) {
3327
- const fkFields = normalizeKeyList(field.foreignKey);
3328
- const refFields = normalizeKeyList(field.references);
3329
- return field.isForeignKeyLocal ? `${countAlias}.${quote(refFields[0] || "id")}` : `${countAlias}.${quote(fkFields[0])}`;
3373
+ function defaultReferencesForCount(fkCount) {
3374
+ if (fkCount === 1) return ["id"];
3375
+ throw new Error(
3376
+ "Relation count for composite keys requires explicit references matching foreignKey length"
3377
+ );
3330
3378
  }
3331
- function leftJoinOnForCount(field, parentAlias, joinAlias) {
3379
+ function resolveCountKeyPairs(field) {
3332
3380
  const fkFields = normalizeKeyList(field.foreignKey);
3333
- const refFields = normalizeKeyList(field.references);
3334
- return field.isForeignKeyLocal ? `${joinAlias}.__fk = ${parentAlias}.${quote(fkFields[0])}` : `${joinAlias}.__fk = ${parentAlias}.${quote(refFields[0] || "id")}`;
3381
+ if (fkFields.length === 0)
3382
+ throw new Error("Relation count requires foreignKey");
3383
+ const refsRaw = field.references;
3384
+ const refs = normalizeKeyList(refsRaw);
3385
+ const refFields = refs.length > 0 ? refs : defaultReferencesForCount(fkFields.length);
3386
+ if (refFields.length !== fkFields.length) {
3387
+ throw new Error(
3388
+ "Relation count requires references count to match foreignKey count"
3389
+ );
3390
+ }
3391
+ const relKeyFields = field.isForeignKeyLocal ? refFields : fkFields;
3392
+ const parentKeyFields = field.isForeignKeyLocal ? fkFields : refFields;
3393
+ return { relKeyFields, parentKeyFields };
3394
+ }
3395
+ function aliasQualifiedColumn(alias, model, field) {
3396
+ return `${alias}.${quoteColumn(model, field)}`;
3397
+ }
3398
+ function subqueryForCount(args) {
3399
+ const selectKeys = args.relKeyFields.map(
3400
+ (f, i) => `${aliasQualifiedColumn(args.countAlias, args.relModel, f)} AS "__fk${i}"`
3401
+ ).join(SQL_SEPARATORS.FIELD_LIST);
3402
+ const groupByKeys = args.relKeyFields.map((f) => aliasQualifiedColumn(args.countAlias, args.relModel, f)).join(SQL_SEPARATORS.FIELD_LIST);
3403
+ const cntExpr = args.dialect === "postgres" ? "COUNT(*)::int AS __cnt" : "COUNT(*) AS __cnt";
3404
+ return `(SELECT ${selectKeys}${SQL_SEPARATORS.FIELD_LIST}${cntExpr} FROM ${args.relTable} ${args.countAlias} GROUP BY ${groupByKeys})`;
3405
+ }
3406
+ function leftJoinOnForCount(args) {
3407
+ const parts = args.parentKeyFields.map(
3408
+ (f, i) => `${args.joinAlias}."__fk${i}" = ${aliasQualifiedColumn(args.parentAlias, args.parentModel, f)}`
3409
+ );
3410
+ return parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`;
3335
3411
  }
3336
- function subqueryForCount(dialect, relTable, countAlias, groupByCol) {
3337
- 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})`;
3412
+ function nextAliasAvoiding(aliasGen, base, forbidden) {
3413
+ let a = aliasGen.next(base);
3414
+ while (forbidden.has(a)) a = aliasGen.next(base);
3415
+ return a;
3338
3416
  }
3339
3417
  function buildCountJoinAndPair(args) {
3340
3418
  const relTable = getRelationTableReference(args.relModel, args.dialect);
3341
- const countAlias = `__tp_cnt_${args.relName}`;
3342
- const groupByCol = groupByColForCount(args.field, countAlias);
3343
- const subquery = subqueryForCount(
3344
- args.dialect,
3419
+ const { relKeyFields, parentKeyFields } = resolveCountKeyPairs(args.field);
3420
+ const forbidden = /* @__PURE__ */ new Set([args.parentAlias]);
3421
+ const countAlias = nextAliasAvoiding(
3422
+ args.aliasGen,
3423
+ `__tp_cnt_${args.relName}`,
3424
+ forbidden
3425
+ );
3426
+ forbidden.add(countAlias);
3427
+ const subquery = subqueryForCount({
3428
+ dialect: args.dialect,
3345
3429
  relTable,
3346
3430
  countAlias,
3347
- groupByCol
3431
+ relModel: args.relModel,
3432
+ relKeyFields
3433
+ });
3434
+ const joinAlias = nextAliasAvoiding(
3435
+ args.aliasGen,
3436
+ `__tp_cnt_j_${args.relName}`,
3437
+ forbidden
3348
3438
  );
3349
- const joinAlias = `__tp_cnt_j_${args.relName}`;
3350
- const leftJoinOn = leftJoinOnForCount(args.field, args.parentAlias, joinAlias);
3439
+ const leftJoinOn = leftJoinOnForCount({
3440
+ joinAlias,
3441
+ parentAlias: args.parentAlias,
3442
+ parentModel: args.parentModel,
3443
+ parentKeyFields
3444
+ });
3351
3445
  return {
3352
3446
  joinSql: `LEFT JOIN ${subquery} ${joinAlias} ON ${leftJoinOn}`,
3353
3447
  pairSql: `${sqlStringLiteral(args.relName)}, COALESCE(${joinAlias}.__cnt, 0)`
@@ -3356,6 +3450,7 @@ function buildCountJoinAndPair(args) {
3356
3450
  function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params, dialect) {
3357
3451
  const joins = [];
3358
3452
  const pairs = [];
3453
+ const aliasGen = createAliasGenerator();
3359
3454
  for (const [relName, shouldCount] of Object.entries(countSelect)) {
3360
3455
  if (!shouldCount) continue;
3361
3456
  const resolved = resolveCountRelationOrThrow(relName, model, schemas);
@@ -3363,29 +3458,33 @@ function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params
3363
3458
  relName,
3364
3459
  field: resolved.field,
3365
3460
  relModel: resolved.relModel,
3461
+ parentModel: model,
3366
3462
  parentAlias,
3367
- dialect
3463
+ dialect,
3464
+ aliasGen
3368
3465
  });
3369
3466
  joins.push(built.joinSql);
3370
3467
  pairs.push(built.pairSql);
3371
3468
  }
3372
- return {
3373
- joins,
3374
- jsonPairs: pairs.join(SQL_SEPARATORS.FIELD_LIST)
3375
- };
3469
+ return { joins, jsonPairs: pairs.join(SQL_SEPARATORS.FIELD_LIST) };
3376
3470
  }
3377
3471
 
3378
3472
  // src/builder/select/assembly.ts
3379
- var SIMPLE_SELECT_RE_CACHE = /* @__PURE__ */ new Map();
3380
- function normalizeFinalParams(params) {
3381
- return params.map(normalizeValue);
3382
- }
3473
+ var ALIAS_CAPTURE = "([A-Za-z_][A-Za-z0-9_]*)";
3474
+ var COLUMN_PART = '(?:"([^"]+)"|([a-z_][a-z0-9_]*))';
3475
+ var AS_PART = `(?:\\s+AS\\s+${COLUMN_PART})?`;
3476
+ var SIMPLE_COLUMN_PATTERN = `^${ALIAS_CAPTURE}\\.${COLUMN_PART}${AS_PART}$`;
3477
+ var SIMPLE_COLUMN_RE = new RegExp(SIMPLE_COLUMN_PATTERN, "i");
3383
3478
  function joinNonEmpty(parts, sep) {
3384
3479
  return parts.filter((s) => s.trim().length > 0).join(sep);
3385
3480
  }
3386
3481
  function buildWhereSql(conditions) {
3387
3482
  if (!isNonEmptyArray(conditions)) return "";
3388
- return ` ${SQL_TEMPLATES.WHERE} ${conditions.join(SQL_SEPARATORS.CONDITION_AND)}`;
3483
+ const parts = [
3484
+ SQL_TEMPLATES.WHERE,
3485
+ conditions.join(SQL_SEPARATORS.CONDITION_AND)
3486
+ ];
3487
+ return ` ${parts.join(" ")}`;
3389
3488
  }
3390
3489
  function buildJoinsSql(...joinGroups) {
3391
3490
  const all = [];
@@ -3400,37 +3499,43 @@ function buildSelectList(baseSelect, extraCols) {
3400
3499
  if (base && extra) return `${base}${SQL_SEPARATORS.FIELD_LIST}${extra}`;
3401
3500
  return base || extra;
3402
3501
  }
3403
- function finalizeSql(sql, params) {
3502
+ function finalizeSql(sql, params, dialect) {
3404
3503
  const snapshot = params.snapshot();
3405
3504
  validateSelectQuery(sql);
3406
- validateParamConsistency(sql, snapshot.params);
3505
+ validateParamConsistencyByDialect(
3506
+ sql,
3507
+ snapshot.params,
3508
+ dialect === "sqlite" ? "postgres" : dialect
3509
+ );
3407
3510
  return Object.freeze({
3408
3511
  sql,
3409
- params: normalizeFinalParams(snapshot.params),
3512
+ params: snapshot.params,
3410
3513
  paramMappings: Object.freeze(snapshot.mappings)
3411
3514
  });
3412
3515
  }
3413
- function parseSimpleScalarSelect(select, alias) {
3414
- var _a, _b;
3516
+ function parseSimpleScalarSelect(select, fromAlias) {
3517
+ var _a, _b, _c, _d;
3415
3518
  const raw = select.trim();
3416
3519
  if (raw.length === 0) return [];
3417
- let re = SIMPLE_SELECT_RE_CACHE.get(alias);
3418
- if (!re) {
3419
- const safeAlias2 = alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3420
- re = new RegExp(`^${safeAlias2}\\.(?:"([^"]+)"|([a-z_][a-z0-9_]*))$`, "i");
3421
- SIMPLE_SELECT_RE_CACHE.set(alias, re);
3422
- }
3423
3520
  const parts = raw.split(SQL_SEPARATORS.FIELD_LIST);
3424
3521
  const names = [];
3425
3522
  for (const part of parts) {
3426
3523
  const p = part.trim();
3427
- const m = p.match(re);
3524
+ const m = p.match(SIMPLE_COLUMN_RE);
3428
3525
  if (!m) {
3429
3526
  throw new Error(
3430
- `sqlite distinct emulation requires scalar select fields to be simple columns. Got: ${p}`
3527
+ `sqlite distinct emulation requires scalar select fields to be simple columns (optionally with AS). Got: ${p}`
3528
+ );
3529
+ }
3530
+ const actualAlias = m[1];
3531
+ if (actualAlias.toLowerCase() !== fromAlias.toLowerCase()) {
3532
+ throw new Error(
3533
+ `Expected alias '${fromAlias}', got '${actualAlias}' in: ${p}`
3431
3534
  );
3432
3535
  }
3433
- const name = ((_b = (_a = m[1]) != null ? _a : m[2]) != null ? _b : "").trim();
3536
+ const columnName = ((_b = (_a = m[2]) != null ? _a : m[3]) != null ? _b : "").trim();
3537
+ const outAlias = ((_d = (_c = m[4]) != null ? _c : m[5]) != null ? _d : "").trim();
3538
+ const name = outAlias.length > 0 ? outAlias : columnName;
3434
3539
  if (name.length === 0) {
3435
3540
  throw new Error(`Failed to parse selected column name from: ${p}`);
3436
3541
  }
@@ -3439,18 +3544,18 @@ function parseSimpleScalarSelect(select, alias) {
3439
3544
  return names;
3440
3545
  }
3441
3546
  function replaceOrderByAlias(orderBy, fromAlias, outerAlias) {
3442
- const needle = `${fromAlias}.`;
3443
- const replacement = `${outerAlias}.`;
3444
- return orderBy.split(needle).join(replacement);
3547
+ const src = String(fromAlias);
3548
+ if (src.length === 0) return orderBy;
3549
+ const escaped = src.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3550
+ const re = new RegExp(`\\b${escaped}\\.`, "gi");
3551
+ return orderBy.replace(re, `${outerAlias}.`);
3445
3552
  }
3446
3553
  function buildDistinctColumns(distinct, fromAlias, model) {
3447
3554
  return distinct.map((f) => col(fromAlias, f, model)).join(SQL_SEPARATORS.FIELD_LIST);
3448
3555
  }
3449
3556
  function buildOutputColumns(scalarNames, includeNames, hasCount) {
3450
3557
  const outputCols = [...scalarNames, ...includeNames];
3451
- if (hasCount) {
3452
- outputCols.push("_count");
3453
- }
3558
+ if (hasCount) outputCols.push("_count");
3454
3559
  const formatted = outputCols.map((n) => quote(n)).join(SQL_SEPARATORS.FIELD_LIST);
3455
3560
  if (!isNonEmptyString(formatted)) {
3456
3561
  throw new Error("distinct emulation requires at least one output column");
@@ -3459,9 +3564,10 @@ function buildOutputColumns(scalarNames, includeNames, hasCount) {
3459
3564
  }
3460
3565
  function buildWindowOrder(args) {
3461
3566
  const { baseOrder, idField, fromAlias, model } = args;
3567
+ const fromLower = String(fromAlias).toLowerCase();
3462
3568
  const orderFields = baseOrder.split(SQL_SEPARATORS.ORDER_BY).map((s) => s.trim().toLowerCase());
3463
3569
  const hasIdInOrder = orderFields.some(
3464
- (f) => f.startsWith(`${fromAlias}.id `) || f.startsWith(`${fromAlias}."id" `)
3570
+ (f) => f.startsWith(`${fromLower}.id `) || f.startsWith(`${fromLower}."id" `)
3465
3571
  );
3466
3572
  if (hasIdInOrder) return baseOrder;
3467
3573
  const idTiebreaker = idField ? `, ${col(fromAlias, "id", model)} ASC` : "";
@@ -3496,15 +3602,37 @@ function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
3496
3602
  const outerOrder = isNonEmptyString(orderBy) ? replaceOrderByAlias(orderBy, from.alias, `"__tp_distinct"`) : replaceOrderByAlias(fallbackOrder, from.alias, `"__tp_distinct"`);
3497
3603
  const joins = buildJoinsSql(whereJoins, countJoins);
3498
3604
  const conditions = [];
3499
- if (whereClause && whereClause !== "1=1") {
3500
- conditions.push(whereClause);
3501
- }
3605
+ if (whereClause && whereClause !== "1=1") conditions.push(whereClause);
3502
3606
  const whereSql = buildWhereSql(conditions);
3503
3607
  const innerSelectList = selectWithIncludes.trim();
3504
3608
  const innerComma = innerSelectList.length > 0 ? SQL_SEPARATORS.FIELD_LIST : "";
3505
- 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}`;
3506
- 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}` : "");
3507
- return outer;
3609
+ const innerParts = [
3610
+ SQL_TEMPLATES.SELECT,
3611
+ innerSelectList + innerComma,
3612
+ `ROW_NUMBER() OVER (PARTITION BY ${distinctCols} ORDER BY ${windowOrder})`,
3613
+ SQL_TEMPLATES.AS,
3614
+ '"__tp_rn"',
3615
+ SQL_TEMPLATES.FROM,
3616
+ from.table,
3617
+ from.alias
3618
+ ];
3619
+ if (joins) innerParts.push(joins);
3620
+ if (whereSql) innerParts.push(whereSql);
3621
+ const inner = innerParts.filter(Boolean).join(" ");
3622
+ const outerParts = [
3623
+ SQL_TEMPLATES.SELECT,
3624
+ outerSelectCols,
3625
+ SQL_TEMPLATES.FROM,
3626
+ `(${inner})`,
3627
+ SQL_TEMPLATES.AS,
3628
+ '"__tp_distinct"',
3629
+ SQL_TEMPLATES.WHERE,
3630
+ '"__tp_rn" = 1'
3631
+ ];
3632
+ if (isNonEmptyString(outerOrder)) {
3633
+ outerParts.push(SQL_TEMPLATES.ORDER_BY, outerOrder);
3634
+ }
3635
+ return outerParts.filter(Boolean).join(" ");
3508
3636
  }
3509
3637
  function buildIncludeColumns(spec) {
3510
3638
  var _a, _b;
@@ -3632,6 +3760,7 @@ function constructFinalSql(spec) {
3632
3760
  orderBy,
3633
3761
  distinct,
3634
3762
  method,
3763
+ cursorCte,
3635
3764
  cursorClause,
3636
3765
  params,
3637
3766
  dialect,
@@ -3646,9 +3775,13 @@ function constructFinalSql(spec) {
3646
3775
  const spec2 = withCountJoins(spec, countJoins, whereJoins);
3647
3776
  let sql2 = buildSqliteDistinctQuery(spec2, selectWithIncludes).trim();
3648
3777
  sql2 = appendPagination(sql2, spec);
3649
- return finalizeSql(sql2, params);
3778
+ return finalizeSql(sql2, params, dialect);
3779
+ }
3780
+ const parts = [];
3781
+ if (cursorCte) {
3782
+ parts.push(`WITH ${cursorCte}`);
3650
3783
  }
3651
- const parts = [SQL_TEMPLATES.SELECT];
3784
+ parts.push(SQL_TEMPLATES.SELECT);
3652
3785
  const distinctOn = dialect === "postgres" ? buildPostgresDistinctOnClause(from.alias, distinct, model) : null;
3653
3786
  if (distinctOn) parts.push(distinctOn);
3654
3787
  const baseSelect = (select != null ? select : "").trim();
@@ -3664,7 +3797,7 @@ function constructFinalSql(spec) {
3664
3797
  if (isNonEmptyString(orderBy)) parts.push(SQL_TEMPLATES.ORDER_BY, orderBy);
3665
3798
  let sql = parts.join(" ").trim();
3666
3799
  sql = appendPagination(sql, spec);
3667
- return finalizeSql(sql, params);
3800
+ return finalizeSql(sql, params, dialect);
3668
3801
  }
3669
3802
 
3670
3803
  // src/builder/select.ts
@@ -3697,7 +3830,7 @@ function buildPostgresDistinctOrderBy(distinctFields, existing) {
3697
3830
  }
3698
3831
  return next;
3699
3832
  }
3700
- function applyPostgresDistinctOrderBy(args, _model) {
3833
+ function applyPostgresDistinctOrderBy(args) {
3701
3834
  const distinctFields = normalizeDistinctFields(args.distinct);
3702
3835
  if (distinctFields.length === 0) return args;
3703
3836
  if (!isNotNullish(args.orderBy)) return args;
@@ -3707,19 +3840,6 @@ function applyPostgresDistinctOrderBy(args, _model) {
3707
3840
  orderBy: buildPostgresDistinctOrderBy(distinctFields, existing)
3708
3841
  });
3709
3842
  }
3710
- function assertScalarFieldOnModel(model, fieldName, ctx) {
3711
- const f = model.fields.find((x) => x.name === fieldName);
3712
- if (!f) {
3713
- throw new Error(
3714
- `${ctx} references unknown field '${fieldName}' on model ${model.name}`
3715
- );
3716
- }
3717
- if (f.isRelation) {
3718
- throw new Error(
3719
- `${ctx} does not support relation field '${fieldName}' on model ${model.name}`
3720
- );
3721
- }
3722
- }
3723
3843
  function validateDistinct(model, distinct) {
3724
3844
  if (!isNotNullish(distinct) || !isNonEmptyArray(distinct)) return;
3725
3845
  const seen = /* @__PURE__ */ new Set();
@@ -3730,24 +3850,24 @@ function validateDistinct(model, distinct) {
3730
3850
  throw new Error(`distinct must not contain duplicates (field: '${f}')`);
3731
3851
  }
3732
3852
  seen.add(f);
3733
- assertScalarFieldOnModel(model, f, "distinct");
3853
+ assertScalarField(model, f, "distinct");
3734
3854
  }
3735
3855
  }
3736
- function validateOrderByValue(fieldName, v) {
3737
- parseOrderByValue(v, fieldName);
3738
- }
3739
3856
  function validateOrderBy(model, orderBy) {
3740
3857
  if (!isNotNullish(orderBy)) return;
3741
3858
  const items = normalizeOrderByInput2(orderBy);
3742
3859
  if (items.length === 0) return;
3743
3860
  for (const it of items) {
3744
3861
  const entries = Object.entries(it);
3862
+ if (entries.length !== 1) {
3863
+ throw new Error("orderBy array entries must have exactly one field");
3864
+ }
3745
3865
  const fieldName = String(entries[0][0]).trim();
3746
3866
  if (fieldName.length === 0) {
3747
3867
  throw new Error("orderBy field name cannot be empty");
3748
3868
  }
3749
- assertScalarFieldOnModel(model, fieldName, "orderBy");
3750
- validateOrderByValue(fieldName, entries[0][1]);
3869
+ assertScalarField(model, fieldName, "orderBy");
3870
+ parseOrderByValue(entries[0][1], fieldName);
3751
3871
  }
3752
3872
  }
3753
3873
  function validateCursor(model, cursor) {
@@ -3764,7 +3884,7 @@ function validateCursor(model, cursor) {
3764
3884
  if (f.length === 0) {
3765
3885
  throw new Error("cursor field name cannot be empty");
3766
3886
  }
3767
- assertScalarFieldOnModel(model, f, "cursor");
3887
+ assertScalarField(model, f, "cursor");
3768
3888
  }
3769
3889
  }
3770
3890
  function resolveDialect(dialect) {
@@ -3783,20 +3903,21 @@ function normalizeArgsForNegativeTake(method, args) {
3783
3903
  orderBy: reverseOrderByInput(args.orderBy)
3784
3904
  });
3785
3905
  }
3786
- function normalizeArgsForDialect(dialect, args, model) {
3906
+ function normalizeArgsForDialect(dialect, args) {
3787
3907
  if (dialect !== "postgres") return args;
3788
3908
  return applyPostgresDistinctOrderBy(args);
3789
3909
  }
3790
3910
  function buildCursorClauseIfAny(input) {
3791
- const { cursor, orderBy, tableName, alias, params, dialect } = input;
3792
- if (!isNotNullish(cursor)) return void 0;
3911
+ const { cursor, orderBy, tableName, alias, params, dialect, model } = input;
3912
+ if (!isNotNullish(cursor)) return {};
3793
3913
  return buildCursorCondition(
3794
3914
  cursor,
3795
3915
  orderBy,
3796
3916
  tableName,
3797
3917
  alias,
3798
3918
  params,
3799
- dialect
3919
+ dialect,
3920
+ model
3800
3921
  );
3801
3922
  }
3802
3923
  function buildSelectSpec(input) {
@@ -3835,14 +3956,20 @@ function buildSelectSpec(input) {
3835
3956
  params,
3836
3957
  dialect
3837
3958
  );
3838
- const cursorClause = buildCursorClauseIfAny({
3959
+ const cursorResult = buildCursorClauseIfAny({
3839
3960
  cursor,
3840
3961
  orderBy: normalizedArgs.orderBy,
3841
3962
  tableName,
3842
3963
  alias,
3843
3964
  params,
3844
- dialect
3965
+ dialect,
3966
+ model
3845
3967
  });
3968
+ if (dialect === "sqlite" && isNonEmptyArray(normalizedArgs.distinct) && cursorResult.condition) {
3969
+ throw new Error(
3970
+ "Cursor pagination with distinct is not supported in SQLite due to window function limitations. Use findMany with skip/take instead, or remove distinct."
3971
+ );
3972
+ }
3846
3973
  return {
3847
3974
  select: selectFields,
3848
3975
  includes,
@@ -3853,7 +3980,8 @@ function buildSelectSpec(input) {
3853
3980
  pagination: { take, skip },
3854
3981
  distinct: normalizedArgs.distinct,
3855
3982
  method,
3856
- cursorClause,
3983
+ cursorCte: cursorResult.cte,
3984
+ cursorClause: cursorResult.condition,
3857
3985
  params,
3858
3986
  dialect,
3859
3987
  model,
@@ -3867,9 +3995,7 @@ function buildSelectSql(input) {
3867
3995
  assertSafeTableRef(from.tableName);
3868
3996
  const dialectToUse = resolveDialect(dialect);
3869
3997
  const argsForSql = normalizeArgsForNegativeTake(method, args);
3870
- const normalizedArgs = normalizeArgsForDialect(
3871
- dialectToUse,
3872
- argsForSql);
3998
+ const normalizedArgs = normalizeArgsForDialect(dialectToUse, argsForSql);
3873
3999
  validateDistinct(model, normalizedArgs.distinct);
3874
4000
  validateOrderBy(model, normalizedArgs.orderBy);
3875
4001
  validateCursor(model, normalizedArgs.cursor);
@@ -3885,8 +4011,21 @@ function buildSelectSql(input) {
3885
4011
  });
3886
4012
  return constructFinalSql(spec);
3887
4013
  }
3888
- var MODEL_FIELD_CACHE = /* @__PURE__ */ new WeakMap();
3889
- var NUMERIC_TYPES = /* @__PURE__ */ new Set(["Int", "Float", "Decimal", "BigInt"]);
4014
+
4015
+ // src/builder/shared/comparison-builder.ts
4016
+ function buildComparisons(expr, filter, params, dialect, builder, excludeKeys = /* @__PURE__ */ new Set(["mode"])) {
4017
+ const out = [];
4018
+ for (const [op, val] of Object.entries(filter)) {
4019
+ if (excludeKeys.has(op) || val === void 0) continue;
4020
+ const built = builder(expr, op, val, params, dialect);
4021
+ if (built && built.trim().length > 0) {
4022
+ out.push(built);
4023
+ }
4024
+ }
4025
+ return out;
4026
+ }
4027
+
4028
+ // src/builder/aggregates.ts
3890
4029
  var AGGREGATES = [
3891
4030
  ["_sum", "SUM"],
3892
4031
  ["_avg", "AVG"],
@@ -3901,22 +4040,32 @@ var COMPARISON_OPS = {
3901
4040
  [Ops.LT]: "<",
3902
4041
  [Ops.LTE]: "<="
3903
4042
  };
3904
- function normalizeFinalParams2(params) {
3905
- return params.map(normalizeValue);
3906
- }
3907
- function getModelFieldMap(model) {
3908
- const cached = MODEL_FIELD_CACHE.get(model);
3909
- if (cached) return cached;
3910
- const m = /* @__PURE__ */ new Map();
3911
- for (const f of model.fields) {
3912
- m.set(f.name, { name: f.name, type: f.type, isRelation: !!f.isRelation });
3913
- }
3914
- MODEL_FIELD_CACHE.set(model, m);
3915
- return m;
3916
- }
4043
+ var HAVING_ALLOWED_OPS = /* @__PURE__ */ new Set([
4044
+ Ops.EQUALS,
4045
+ Ops.NOT,
4046
+ Ops.GT,
4047
+ Ops.GTE,
4048
+ Ops.LT,
4049
+ Ops.LTE,
4050
+ Ops.IN,
4051
+ Ops.NOT_IN
4052
+ ]);
3917
4053
  function isTruthySelection(v) {
3918
4054
  return v === true;
3919
4055
  }
4056
+ function isLogicalKey(key) {
4057
+ return key === LogicalOps.AND || key === LogicalOps.OR || key === LogicalOps.NOT;
4058
+ }
4059
+ function isAggregateKey(key) {
4060
+ return key === "_count" || key === "_sum" || key === "_avg" || key === "_min" || key === "_max";
4061
+ }
4062
+ function assertHavingOp(op) {
4063
+ if (!HAVING_ALLOWED_OPS.has(op)) {
4064
+ throw new Error(
4065
+ `Unsupported HAVING operator '${op}'. Allowed: ${[...HAVING_ALLOWED_OPS].join(", ")}`
4066
+ );
4067
+ }
4068
+ }
3920
4069
  function aggExprForField(aggKey, field, alias, model) {
3921
4070
  if (aggKey === "_count") {
3922
4071
  return field === "_all" ? `COUNT(*)` : `COUNT(${col(alias, field, model)})`;
@@ -3950,32 +4099,9 @@ function normalizeLogicalValue2(operator, value) {
3950
4099
  }
3951
4100
  return out;
3952
4101
  }
3953
- if (isPlainObject(value)) {
3954
- return [value];
3955
- }
4102
+ if (isPlainObject(value)) return [value];
3956
4103
  throw new Error(`${operator} must be an object or array of objects in HAVING`);
3957
4104
  }
3958
- function assertScalarField2(model, fieldName, ctx) {
3959
- const m = getModelFieldMap(model);
3960
- const field = m.get(fieldName);
3961
- if (!field) {
3962
- throw new Error(
3963
- `${ctx} references unknown field '${fieldName}' on model ${model.name}. Available fields: ${model.fields.map((f) => f.name).join(", ")}`
3964
- );
3965
- }
3966
- if (field.isRelation) {
3967
- throw new Error(`${ctx} does not support relation field '${fieldName}'`);
3968
- }
3969
- return { name: field.name, type: field.type };
3970
- }
3971
- function assertAggregateFieldType(aggKey, fieldType, fieldName, modelName) {
3972
- const baseType = fieldType.replace(/\[\]|\?/g, "");
3973
- if ((aggKey === "_sum" || aggKey === "_avg") && !NUMERIC_TYPES.has(baseType)) {
3974
- throw new Error(
3975
- `Cannot use ${aggKey} on non-numeric field '${fieldName}' (type: ${fieldType}) on model ${modelName}`
3976
- );
3977
- }
3978
- }
3979
4105
  function buildNullComparison(expr, op) {
3980
4106
  if (op === Ops.EQUALS) return `${expr} ${SQL_TEMPLATES.IS_NULL}`;
3981
4107
  if (op === Ops.NOT) return `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
@@ -4002,6 +4128,7 @@ function buildBinaryComparison(expr, op, val, params) {
4002
4128
  return `${expr} ${sqlOp} ${placeholder}`;
4003
4129
  }
4004
4130
  function buildSimpleComparison(expr, op, val, params, dialect) {
4131
+ assertHavingOp(op);
4005
4132
  if (val === null) return buildNullComparison(expr, op);
4006
4133
  if (op === Ops.NOT && isPlainObject(val)) {
4007
4134
  return buildNotComposite(
@@ -4018,12 +4145,6 @@ function buildSimpleComparison(expr, op, val, params, dialect) {
4018
4145
  }
4019
4146
  return buildBinaryComparison(expr, op, val, params);
4020
4147
  }
4021
- function isLogicalKey(key) {
4022
- return key === LogicalOps.AND || key === LogicalOps.OR || key === LogicalOps.NOT;
4023
- }
4024
- function isAggregateKey(key) {
4025
- return key === "_count" || key === "_sum" || key === "_avg" || key === "_min" || key === "_max";
4026
- }
4027
4148
  function negateClauses(subClauses) {
4028
4149
  if (subClauses.length === 1) return `${SQL_TEMPLATES.NOT} ${subClauses[0]}`;
4029
4150
  return `${SQL_TEMPLATES.NOT} (${subClauses.join(SQL_SEPARATORS.CONDITION_AND)})`;
@@ -4032,16 +4153,75 @@ function combineLogical(key, subClauses) {
4032
4153
  if (key === LogicalOps.NOT) return negateClauses(subClauses);
4033
4154
  return subClauses.join(` ${key} `);
4034
4155
  }
4156
+ function buildHavingNode(node, alias, params, dialect, model) {
4157
+ const clauses = [];
4158
+ const entries = Object.entries(node);
4159
+ for (const [key, value] of entries) {
4160
+ const built = buildHavingEntry(key, value, alias, params, dialect, model);
4161
+ for (const c of built) {
4162
+ if (c && c.trim().length > 0) clauses.push(c);
4163
+ }
4164
+ }
4165
+ return clauses.join(SQL_SEPARATORS.CONDITION_AND);
4166
+ }
4035
4167
  function buildLogicalClause2(key, value, alias, params, dialect, model) {
4036
4168
  const items = normalizeLogicalValue2(key, value);
4037
4169
  const subClauses = [];
4038
4170
  for (const it of items) {
4039
4171
  const c = buildHavingNode(it, alias, params, dialect, model);
4040
- if (c && c !== "") subClauses.push(`(${c})`);
4172
+ if (c && c.trim().length > 0) subClauses.push(`(${c})`);
4041
4173
  }
4042
4174
  if (subClauses.length === 0) return "";
4043
4175
  return combineLogical(key, subClauses);
4044
4176
  }
4177
+ function assertHavingAggTarget(aggKey, field, model) {
4178
+ if (field === "_all") {
4179
+ if (aggKey !== "_count")
4180
+ throw new Error(`HAVING '${aggKey}' does not support '_all'`);
4181
+ return;
4182
+ }
4183
+ if (aggKey === "_sum" || aggKey === "_avg") {
4184
+ assertNumericField(model, field, "HAVING");
4185
+ } else {
4186
+ assertScalarField(model, field, "HAVING");
4187
+ }
4188
+ }
4189
+ function buildHavingOpsForExpr(expr, filter, params, dialect) {
4190
+ return buildComparisons(expr, filter, params, dialect, buildSimpleComparison);
4191
+ }
4192
+ function buildHavingForAggregateFirstShape(aggKey, target, alias, params, dialect, model) {
4193
+ if (!isPlainObject(target)) {
4194
+ throw new Error(`HAVING '${aggKey}' must be an object`);
4195
+ }
4196
+ const out = [];
4197
+ for (const [field, filter] of Object.entries(target)) {
4198
+ assertHavingAggTarget(aggKey, field, model);
4199
+ if (!isPlainObject(filter) || Object.keys(filter).length === 0) continue;
4200
+ const expr = aggExprForField(aggKey, field, alias, model);
4201
+ out.push(...buildHavingOpsForExpr(expr, filter, params, dialect));
4202
+ }
4203
+ return out;
4204
+ }
4205
+ function buildHavingForFieldFirstShape(fieldName, target, alias, params, dialect, model) {
4206
+ if (!isPlainObject(target)) {
4207
+ throw new Error(`HAVING '${fieldName}' must be an object`);
4208
+ }
4209
+ assertScalarField(model, fieldName, "HAVING");
4210
+ const out = [];
4211
+ const obj = target;
4212
+ const keys = ["_count", "_sum", "_avg", "_min", "_max"];
4213
+ for (const aggKey of keys) {
4214
+ const aggFilter = obj[aggKey];
4215
+ if (!isPlainObject(aggFilter)) continue;
4216
+ if (Object.keys(aggFilter).length === 0) continue;
4217
+ if (aggKey === "_sum" || aggKey === "_avg") {
4218
+ assertNumericField(model, fieldName, "HAVING");
4219
+ }
4220
+ const expr = aggExprForField(aggKey, fieldName, alias, model);
4221
+ out.push(...buildHavingOpsForExpr(expr, aggFilter, params, dialect));
4222
+ }
4223
+ return out;
4224
+ }
4045
4225
  function buildHavingEntry(key, value, alias, params, dialect, model) {
4046
4226
  if (isLogicalKey(key)) {
4047
4227
  const logical = buildLogicalClause2(
@@ -4073,71 +4253,10 @@ function buildHavingEntry(key, value, alias, params, dialect, model) {
4073
4253
  model
4074
4254
  );
4075
4255
  }
4076
- function buildHavingNode(node, alias, params, dialect, model) {
4077
- const clauses = [];
4078
- for (const [key, value] of Object.entries(node)) {
4079
- const built = buildHavingEntry(key, value, alias, params, dialect, model);
4080
- for (const c of built) {
4081
- if (c && c.trim().length > 0) clauses.push(c);
4082
- }
4083
- }
4084
- return clauses.join(SQL_SEPARATORS.CONDITION_AND);
4085
- }
4086
- function assertHavingAggTarget(aggKey, field, model) {
4087
- if (field === "_all") {
4088
- if (aggKey !== "_count") {
4089
- throw new Error(`HAVING '${aggKey}' does not support '_all'`);
4090
- }
4091
- return;
4092
- }
4093
- const f = assertScalarField2(model, field, "HAVING");
4094
- assertAggregateFieldType(aggKey, f.type, f.name, model.name);
4095
- }
4096
- function buildHavingOpsForExpr(expr, filter, params, dialect) {
4097
- const out = [];
4098
- for (const [op, val] of Object.entries(filter)) {
4099
- if (op === "mode") continue;
4100
- const built = buildSimpleComparison(expr, op, val, params, dialect);
4101
- if (built && built.trim().length > 0) out.push(built);
4102
- }
4103
- return out;
4104
- }
4105
- function buildHavingForAggregateFirstShape(aggKey, target, alias, params, dialect, model) {
4106
- if (!isPlainObject(target)) return [];
4107
- const out = [];
4108
- for (const [field, filter] of Object.entries(target)) {
4109
- assertHavingAggTarget(aggKey, field, model);
4110
- if (!isPlainObject(filter) || Object.keys(filter).length === 0) continue;
4111
- const expr = aggExprForField(aggKey, field, alias, model);
4112
- out.push(...buildHavingOpsForExpr(expr, filter, params, dialect));
4113
- }
4114
- return out;
4115
- }
4116
- function buildHavingForFieldFirstShape(fieldName, target, alias, params, dialect, model) {
4117
- if (!isPlainObject(target)) return [];
4118
- const field = assertScalarField2(model, fieldName, "HAVING");
4119
- const out = [];
4120
- const obj = target;
4121
- const keys = ["_count", "_sum", "_avg", "_min", "_max"];
4122
- for (const aggKey of keys) {
4123
- const aggFilter = obj[aggKey];
4124
- if (!isPlainObject(aggFilter)) continue;
4125
- assertAggregateFieldType(aggKey, field.type, field.name, model.name);
4126
- const entries = Object.entries(aggFilter);
4127
- if (entries.length === 0) continue;
4128
- const expr = aggExprForField(aggKey, fieldName, alias, model);
4129
- for (const [op, val] of entries) {
4130
- if (op === "mode") continue;
4131
- const built = buildSimpleComparison(expr, op, val, params, dialect);
4132
- if (built && built.trim().length > 0) out.push(built);
4133
- }
4134
- }
4135
- return out;
4136
- }
4137
4256
  function buildHavingClause(having, alias, params, model, dialect) {
4138
4257
  if (!isNotNullish(having)) return "";
4139
4258
  const d = dialect != null ? dialect : getGlobalDialect();
4140
- if (!isPlainObject(having)) return "";
4259
+ if (!isPlainObject(having)) throw new Error("having must be an object");
4141
4260
  return buildHavingNode(having, alias, params, d, model);
4142
4261
  }
4143
4262
  function normalizeCountArg(v) {
@@ -4151,26 +4270,13 @@ function pushCountAllField(fields) {
4151
4270
  `${SQL_TEMPLATES.COUNT_ALL} ${SQL_TEMPLATES.AS} ${quote("_count._all")}`
4152
4271
  );
4153
4272
  }
4154
- function assertCountableScalarField(fieldMap, model, fieldName) {
4155
- const field = fieldMap.get(fieldName);
4156
- if (!field) {
4157
- throw new Error(
4158
- `Field '${fieldName}' does not exist on model ${model.name}`
4159
- );
4160
- }
4161
- if (field.isRelation) {
4162
- throw new Error(
4163
- `Cannot use _count on relation field '${fieldName}' on model ${model.name}`
4164
- );
4165
- }
4166
- }
4167
4273
  function pushCountField(fields, alias, fieldName, model) {
4168
4274
  const outAlias = `_count.${fieldName}`;
4169
4275
  fields.push(
4170
4276
  `COUNT(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4171
4277
  );
4172
4278
  }
4173
- function addCountFields(fields, countArg, alias, model, fieldMap) {
4279
+ function addCountFields(fields, countArg, alias, model) {
4174
4280
  if (!isNotNullish(countArg)) return;
4175
4281
  if (countArg === true) {
4176
4282
  pushCountAllField(fields);
@@ -4184,7 +4290,7 @@ function addCountFields(fields, countArg, alias, model, fieldMap) {
4184
4290
  ([f, v]) => f !== "_all" && isTruthySelection(v)
4185
4291
  );
4186
4292
  for (const [f] of selected) {
4187
- assertCountableScalarField(fieldMap, model, f);
4293
+ assertScalarField(model, f, "_count");
4188
4294
  pushCountField(fields, alias, f, model);
4189
4295
  }
4190
4296
  }
@@ -4192,19 +4298,12 @@ function getAggregateSelectionObject(args, agg) {
4192
4298
  const obj = args[agg];
4193
4299
  return isPlainObject(obj) ? obj : void 0;
4194
4300
  }
4195
- function assertAggregatableScalarField(fieldMap, model, agg, fieldName) {
4196
- const field = fieldMap.get(fieldName);
4197
- if (!field) {
4198
- throw new Error(
4199
- `Field '${fieldName}' does not exist on model ${model.name}`
4200
- );
4201
- }
4202
- if (field.isRelation) {
4203
- throw new Error(
4204
- `Cannot use ${agg} on relation field '${fieldName}' on model ${model.name}`
4205
- );
4301
+ function assertAggregatableScalarField(model, agg, fieldName) {
4302
+ if (agg === "_sum" || agg === "_avg") {
4303
+ assertNumericField(model, fieldName, agg);
4304
+ } else {
4305
+ assertScalarField(model, fieldName, agg);
4206
4306
  }
4207
- return field;
4208
4307
  }
4209
4308
  function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
4210
4309
  const outAlias = `${agg}.${fieldName}`;
@@ -4212,7 +4311,7 @@ function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
4212
4311
  `${aggFn}(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4213
4312
  );
4214
4313
  }
4215
- function addAggregateFields(fields, args, alias, model, fieldMap) {
4314
+ function addAggregateFields(fields, args, alias, model) {
4216
4315
  for (const [agg, aggFn] of AGGREGATES) {
4217
4316
  const obj = getAggregateSelectionObject(args, agg);
4218
4317
  if (!obj) continue;
@@ -4220,23 +4319,16 @@ function addAggregateFields(fields, args, alias, model, fieldMap) {
4220
4319
  if (fieldName === "_all")
4221
4320
  throw new Error(`'${agg}' does not support '_all'`);
4222
4321
  if (!isTruthySelection(selection)) continue;
4223
- const field = assertAggregatableScalarField(
4224
- fieldMap,
4225
- model,
4226
- agg,
4227
- fieldName
4228
- );
4229
- assertAggregateFieldType(agg, field.type, fieldName, model.name);
4322
+ assertAggregatableScalarField(model, agg, fieldName);
4230
4323
  pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model);
4231
4324
  }
4232
4325
  }
4233
4326
  }
4234
4327
  function buildAggregateFields(args, alias, model) {
4235
4328
  const fields = [];
4236
- const fieldMap = getModelFieldMap(model);
4237
4329
  const countArg = normalizeCountArg(args._count);
4238
- addCountFields(fields, countArg, alias, model, fieldMap);
4239
- addAggregateFields(fields, args, alias, model, fieldMap);
4330
+ addCountFields(fields, countArg, alias, model);
4331
+ addAggregateFields(fields, args, alias, model);
4240
4332
  return fields;
4241
4333
  }
4242
4334
  function buildAggregateSql(args, whereResult, tableName, alias, model) {
@@ -4260,7 +4352,7 @@ function buildAggregateSql(args, whereResult, tableName, alias, model) {
4260
4352
  validateParamConsistency(sql, whereResult.params);
4261
4353
  return Object.freeze({
4262
4354
  sql,
4263
- params: Object.freeze(normalizeFinalParams2([...whereResult.params])),
4355
+ params: Object.freeze([...whereResult.params]),
4264
4356
  paramMappings: Object.freeze([...whereResult.paramMappings])
4265
4357
  });
4266
4358
  }
@@ -4273,32 +4365,22 @@ function assertGroupByBy(args, model) {
4273
4365
  if (bySet.size !== byFields.length) {
4274
4366
  throw new Error("buildGroupBySql: by must not contain duplicates");
4275
4367
  }
4276
- const modelFieldMap = getModelFieldMap(model);
4277
4368
  for (const f of byFields) {
4278
- const field = modelFieldMap.get(f);
4279
- if (!field) {
4280
- throw new Error(
4281
- `groupBy.by references unknown field '${f}' on model ${model.name}`
4282
- );
4283
- }
4284
- if (field.isRelation) {
4285
- throw new Error(
4286
- `groupBy.by does not support relation field '${f}' on model ${model.name}`
4287
- );
4288
- }
4369
+ assertScalarField(model, f, "groupBy.by");
4289
4370
  }
4290
4371
  return byFields;
4291
4372
  }
4292
4373
  function buildGroupBySelectParts(args, alias, model, byFields) {
4293
4374
  const groupCols = byFields.map((f) => col(alias, f, model));
4375
+ const selectCols = byFields.map((f) => colWithAlias(alias, f, model));
4294
4376
  const groupFields = groupCols.join(SQL_SEPARATORS.FIELD_LIST);
4295
4377
  const aggFields = buildAggregateFields(args, alias, model);
4296
- const selectFields = isNonEmptyArray(aggFields) ? groupCols.concat(aggFields).join(SQL_SEPARATORS.FIELD_LIST) : groupCols.join(SQL_SEPARATORS.FIELD_LIST);
4378
+ const selectFields = isNonEmptyArray(aggFields) ? selectCols.concat(aggFields).join(SQL_SEPARATORS.FIELD_LIST) : selectCols.join(SQL_SEPARATORS.FIELD_LIST);
4297
4379
  return { groupCols, groupFields, selectFields };
4298
4380
  }
4299
4381
  function buildGroupByHaving(args, alias, params, model, dialect) {
4300
4382
  if (!isNotNullish(args.having)) return "";
4301
- if (!isPlainObject(args.having)) return "";
4383
+ if (!isPlainObject(args.having)) throw new Error("having must be an object");
4302
4384
  const h = buildHavingClause(args.having, alias, params, model, dialect);
4303
4385
  if (!h || h.trim().length === 0) return "";
4304
4386
  return `${SQL_TEMPLATES.HAVING} ${h}`;
@@ -4331,64 +4413,60 @@ function buildGroupBySql(args, whereResult, tableName, alias, model, dialect) {
4331
4413
  const snapshot = params.snapshot();
4332
4414
  validateSelectQuery(sql);
4333
4415
  validateParamConsistency(sql, [...whereResult.params, ...snapshot.params]);
4334
- const mergedParams = [...whereResult.params, ...snapshot.params];
4335
4416
  return Object.freeze({
4336
4417
  sql,
4337
- params: Object.freeze(normalizeFinalParams2(mergedParams)),
4418
+ params: Object.freeze([...whereResult.params, ...snapshot.params]),
4338
4419
  paramMappings: Object.freeze([
4339
4420
  ...whereResult.paramMappings,
4340
4421
  ...snapshot.mappings
4341
4422
  ])
4342
4423
  });
4343
4424
  }
4344
- function buildCountSql(whereResult, tableName, alias, skip, dialect) {
4425
+ function buildCountSql(whereResult, tableName, alias, skip, _dialect) {
4345
4426
  assertSafeAlias(alias);
4346
4427
  assertSafeTableRef(tableName);
4347
- const d = getGlobalDialect();
4428
+ if (skip !== void 0 && skip !== null) {
4429
+ if (schemaParser.isDynamicParameter(skip)) {
4430
+ throw new Error(
4431
+ "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."
4432
+ );
4433
+ }
4434
+ if (typeof skip === "string") {
4435
+ const s = skip.trim();
4436
+ if (s.length > 0) {
4437
+ const n = Number(s);
4438
+ if (Number.isFinite(n) && Number.isInteger(n) && n > 0) {
4439
+ throw new Error(
4440
+ "count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
4441
+ );
4442
+ }
4443
+ }
4444
+ }
4445
+ if (typeof skip === "number" && Number.isFinite(skip) && Number.isInteger(skip) && skip > 0) {
4446
+ throw new Error(
4447
+ "count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
4448
+ );
4449
+ }
4450
+ }
4348
4451
  const whereClause = isValidWhereClause(whereResult.clause) ? `${SQL_TEMPLATES.WHERE} ${whereResult.clause}` : "";
4349
- const params = createParamStore(whereResult.nextParamIndex);
4350
- const baseSubSelect = [
4351
- SQL_TEMPLATES.SELECT,
4352
- "1",
4353
- SQL_TEMPLATES.FROM,
4354
- tableName,
4355
- alias,
4356
- whereClause
4357
- ].filter((x) => x && String(x).trim().length > 0).join(" ").trim();
4358
- const normalizedSkip = normalizeSkipLike(skip);
4359
- const subSelect = applyCountSkip(baseSubSelect, normalizedSkip, params, d);
4360
4452
  const sql = [
4361
4453
  SQL_TEMPLATES.SELECT,
4362
4454
  SQL_TEMPLATES.COUNT_ALL,
4363
4455
  SQL_TEMPLATES.AS,
4364
4456
  quote("_count._all"),
4365
4457
  SQL_TEMPLATES.FROM,
4366
- `(${subSelect})`,
4367
- SQL_TEMPLATES.AS,
4368
- `"sub"`
4458
+ tableName,
4459
+ alias,
4460
+ whereClause
4369
4461
  ].filter((x) => x && String(x).trim().length > 0).join(" ").trim();
4370
4462
  validateSelectQuery(sql);
4371
- const snapshot = params.snapshot();
4372
- const mergedParams = [...whereResult.params, ...snapshot.params];
4373
- validateParamConsistency(sql, mergedParams);
4463
+ validateParamConsistency(sql, whereResult.params);
4374
4464
  return Object.freeze({
4375
4465
  sql,
4376
- params: Object.freeze(normalizeFinalParams2(mergedParams)),
4377
- paramMappings: Object.freeze([
4378
- ...whereResult.paramMappings,
4379
- ...snapshot.mappings
4380
- ])
4466
+ params: Object.freeze([...whereResult.params]),
4467
+ paramMappings: Object.freeze([...whereResult.paramMappings])
4381
4468
  });
4382
4469
  }
4383
- function applyCountSkip(subSelect, normalizedSkip, params, dialect) {
4384
- const shouldApply = schemaParser.isDynamicParameter(normalizedSkip) || typeof normalizedSkip === "number" && normalizedSkip > 0;
4385
- if (!shouldApply) return subSelect;
4386
- const placeholder = addAutoScoped(params, normalizedSkip, "count.skip");
4387
- if (dialect === "sqlite") {
4388
- return `${subSelect} ${SQL_TEMPLATES.LIMIT} -1 ${SQL_TEMPLATES.OFFSET} ${placeholder}`;
4389
- }
4390
- return `${subSelect} ${SQL_TEMPLATES.OFFSET} ${placeholder}`;
4391
- }
4392
4470
  function safeAlias(input) {
4393
4471
  const raw = String(input).toLowerCase();
4394
4472
  const cleaned = raw.replace(/[^a-z0-9_]/g, "_");
@@ -4538,8 +4616,12 @@ function buildAndNormalizeSql(args) {
4538
4616
  );
4539
4617
  }
4540
4618
  function finalizeDirective(args) {
4541
- const { directive, normalizedSql, normalizedMappings } = args;
4542
- validateSqlPositions(normalizedSql, normalizedMappings, getGlobalDialect());
4619
+ const { directive, normalizedSql, normalizedMappings, dialect } = args;
4620
+ const params = normalizedMappings.map((m) => {
4621
+ var _a;
4622
+ return (_a = m.value) != null ? _a : void 0;
4623
+ });
4624
+ validateParamConsistencyByDialect(normalizedSql, params, dialect);
4543
4625
  const { staticParams, dynamicKeys } = buildParamsFromMappings(normalizedMappings);
4544
4626
  return {
4545
4627
  method: directive.method,
@@ -4576,7 +4658,8 @@ function generateSQL(directive) {
4576
4658
  return finalizeDirective({
4577
4659
  directive,
4578
4660
  normalizedSql: normalized.sql,
4579
- normalizedMappings: normalized.paramMappings
4661
+ normalizedMappings: normalized.paramMappings,
4662
+ dialect
4580
4663
  });
4581
4664
  }
4582
4665
  function generateSQL2(directive) {
@@ -4684,18 +4767,57 @@ function generateCode(models, queries, dialect, datamodel) {
4684
4767
  return `// Generated by @prisma-sql/generator - DO NOT EDIT
4685
4768
  import { buildSQL, transformQueryResults, type PrismaMethod, type Model } from 'prisma-sql'
4686
4769
 
4687
- function normalizeValue(value: unknown): unknown {
4770
+ /**
4771
+ * Normalize values for SQL params.
4772
+ * Synced from src/utils/normalize-value.ts
4773
+ */
4774
+ function normalizeValue(value: unknown, seen = new WeakSet<object>(), depth = 0): unknown {
4775
+ const MAX_DEPTH = 20
4776
+ if (depth > MAX_DEPTH) {
4777
+ throw new Error(\`Max normalization depth exceeded (\${MAX_DEPTH} levels)\`)
4778
+ }
4688
4779
  if (value instanceof Date) {
4780
+ const t = value.getTime()
4781
+ if (!Number.isFinite(t)) {
4782
+ throw new Error('Invalid Date value in SQL params')
4783
+ }
4689
4784
  return value.toISOString()
4690
4785
  }
4691
-
4786
+ if (typeof value === 'bigint') {
4787
+ return value.toString()
4788
+ }
4692
4789
  if (Array.isArray(value)) {
4693
- return value.map(normalizeValue)
4790
+ const arrRef = value as unknown as object
4791
+ if (seen.has(arrRef)) {
4792
+ throw new Error('Circular reference in SQL params')
4793
+ }
4794
+ seen.add(arrRef)
4795
+ const out = value.map((v) => normalizeValue(v, seen, depth + 1))
4796
+ seen.delete(arrRef)
4797
+ return out
4798
+ }
4799
+ if (value && typeof value === 'object') {
4800
+ if (value instanceof Uint8Array) return value
4801
+ if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) return value
4802
+ const proto = Object.getPrototypeOf(value)
4803
+ const isPlain = proto === Object.prototype || proto === null
4804
+ if (!isPlain) return value
4805
+ const obj = value as Record<string, unknown>
4806
+ if (seen.has(obj)) {
4807
+ throw new Error('Circular reference in SQL params')
4808
+ }
4809
+ seen.add(obj)
4810
+ const out: Record<string, unknown> = {}
4811
+ for (const [k, v] of Object.entries(obj)) {
4812
+ out[k] = normalizeValue(v, seen, depth + 1)
4813
+ }
4814
+ seen.delete(obj)
4815
+ return out
4694
4816
  }
4695
-
4696
4817
  return value
4697
4818
  }
4698
4819
 
4820
+
4699
4821
  export const MODELS: Model[] = ${JSON.stringify(cleanModels, null, 2)}
4700
4822
 
4701
4823
  const ENUM_MAPPINGS: Record<string, Record<string, string>> = ${JSON.stringify(mappings, null, 2)}
@@ -4835,34 +4957,39 @@ function normalizeQuery(args: any): string {
4835
4957
 
4836
4958
  function extractDynamicParams(args: any, dynamicKeys: string[]): unknown[] {
4837
4959
  const params: unknown[] = []
4838
-
4960
+
4839
4961
  for (const key of dynamicKeys) {
4840
4962
  const parts = key.split(':')
4841
4963
  const lookupKey = parts.length === 2 ? parts[1] : key
4842
- const value = args[lookupKey]
4843
-
4964
+
4965
+ const value =
4966
+ lookupKey.includes('.') ? getByPath(args, lookupKey) : args?.[lookupKey]
4967
+
4844
4968
  if (value === undefined) {
4845
4969
  throw new Error(\`Missing required parameter: \${key}\`)
4846
4970
  }
4847
-
4971
+
4848
4972
  params.push(normalizeValue(value))
4849
4973
  }
4850
-
4974
+
4851
4975
  return params
4852
4976
  }
4853
4977
 
4978
+
4854
4979
  async function executeQuery(client: any, sql: string, params: unknown[]): Promise<unknown[]> {
4980
+ const normalizedParams = normalizeParams(params)
4981
+
4855
4982
  if (DIALECT === 'postgres') {
4856
- return await client.unsafe(sql, params)
4983
+ return await client.unsafe(sql, normalizedParams)
4857
4984
  }
4858
-
4985
+
4859
4986
  const stmt = client.prepare(sql)
4860
-
4987
+
4861
4988
  if (sql.toUpperCase().includes('COUNT(*) AS')) {
4862
- return [stmt.get(...params)]
4989
+ return [stmt.get(...normalizedParams)]
4863
4990
  }
4864
-
4865
- return stmt.all(...params)
4991
+
4992
+ return stmt.all(...normalizedParams)
4866
4993
  }
4867
4994
 
4868
4995
  export function speedExtension(config: {
@@ -4907,18 +5034,21 @@ export function speedExtension(config: {
4907
5034
 
4908
5035
  if (prebakedQuery) {
4909
5036
  sql = prebakedQuery.sql
4910
- params = [...prebakedQuery.params, ...extractDynamicParams(transformedArgs, prebakedQuery.dynamicKeys)]
5037
+ params = normalizeParams([
5038
+ ...prebakedQuery.params,
5039
+ ...extractDynamicParams(transformedArgs, prebakedQuery.dynamicKeys),
5040
+ ])
4911
5041
  prebaked = true
4912
5042
  } else {
4913
5043
  const model = MODELS.find((m) => m.name === modelName)
4914
-
5044
+
4915
5045
  if (!model) {
4916
5046
  return this.$parent[modelName][method](args)
4917
5047
  }
4918
5048
 
4919
5049
  const result = buildSQL(model, MODELS, method, transformedArgs, DIALECT)
4920
5050
  sql = result.sql
4921
- params = result.params
5051
+ params = normalizeParams(result.params as unknown[])
4922
5052
  }
4923
5053
 
4924
5054
  if (debug) {