prisma-sql 1.44.0 → 1.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.45.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
@@ -642,7 +693,7 @@ function scanDollarPlaceholders(sql, markUpTo) {
642
693
  }
643
694
  return { count, min, max, seen };
644
695
  }
645
- function assertNoGaps(scan, rangeMin, rangeMax, sql) {
696
+ function assertNoGapsDollar(scan, rangeMin, rangeMax, sql) {
646
697
  for (let k = rangeMin; k <= rangeMax; k++) {
647
698
  if (scan.seen[k] !== 1) {
648
699
  throw new Error(
@@ -670,7 +721,7 @@ function validateParamConsistency(sql, params) {
670
721
  `CRITICAL: Parameter mismatch - SQL max placeholder is $${scan.max} but ${paramLen} params provided. This will cause SQL execution to fail. SQL: ${sqlPreview(sql)}`
671
722
  );
672
723
  }
673
- assertNoGaps(scan, 1, scan.max, sql);
724
+ assertNoGapsDollar(scan, 1, scan.max, sql);
674
725
  }
675
726
  function needsQuoting(id) {
676
727
  if (!isNonEmptyString(id)) return true;
@@ -688,14 +739,11 @@ function validateParamConsistencyFragment(sql, params) {
688
739
  `CRITICAL: Parameter mismatch - SQL references $${scan.max} but only ${paramLen} params provided. SQL: ${sqlPreview(sql)}`
689
740
  );
690
741
  }
691
- assertNoGaps(scan, scan.min, scan.max, sql);
742
+ assertNoGapsDollar(scan, scan.min, scan.max, sql);
692
743
  }
693
744
  function assertOrThrow(condition, message) {
694
745
  if (!condition) throw new Error(message);
695
746
  }
696
- function dialectPlaceholderPrefix(dialect) {
697
- return dialect === "sqlite" ? "?" : "$";
698
- }
699
747
  function parseSqlitePlaceholderIndices(sql) {
700
748
  const re = /\?(?:(\d+))?/g;
701
749
  const indices = [];
@@ -715,112 +763,70 @@ function parseSqlitePlaceholderIndices(sql) {
715
763
  }
716
764
  return { indices, sawNumbered, sawAnonymous };
717
765
  }
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
766
  function maxIndex(indices) {
733
767
  return indices.length > 0 ? Math.max(...indices) : 0;
734
768
  }
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);
769
+ function ensureSequentialIndices(seen, max, prefix) {
749
770
  for (let i = 1; i <= max; i++) {
750
771
  assertOrThrow(
751
- placeholders.has(i),
772
+ seen.has(i),
752
773
  `CRITICAL: Missing SQL placeholder ${prefix}${i} - placeholders must be sequential 1..${max}.`
753
774
  );
754
775
  }
755
776
  }
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) {
777
+ function validateSqlitePlaceholders(sql, params) {
778
+ const paramLen = params.length;
779
+ const { indices, sawNumbered, sawAnonymous } = parseSqlitePlaceholderIndices(sql);
780
+ if (indices.length === 0) {
781
+ if (paramLen !== 0) {
782
+ throw new Error(
783
+ `CRITICAL: Parameter mismatch - SQL has no sqlite placeholders but ${paramLen} params provided. SQL: ${sqlPreview(sql)}`
784
+ );
785
+ }
786
+ return;
787
+ }
763
788
  assertOrThrow(
764
- !mappingIndices.has(index),
765
- `CRITICAL: Duplicate ParamMapping index ${index} - each placeholder index must map to exactly one ParamMap.`
789
+ !(sawNumbered && sawAnonymous),
790
+ `CRITICAL: Mixed sqlite placeholders ('?' and '?NNN') are not supported.`
766
791
  );
767
- mappingIndices.add(index);
768
- }
769
- function ensureMappingIndexExistsInSql(placeholders, index) {
792
+ const max = maxIndex(indices);
770
793
  assertOrThrow(
771
- placeholders.has(index),
772
- `CRITICAL: ParamMapping index ${index} not found in SQL placeholders.`
794
+ max === paramLen,
795
+ `CRITICAL: SQL placeholder max mismatch - max is ?${max}, but params length is ${paramLen}. SQL: ${sqlPreview(sql)}`
773
796
  );
797
+ const set = new Set(indices);
798
+ ensureSequentialIndices(set, max, "?");
774
799
  }
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
- );
800
+ function validateDollarPlaceholders(sql, params) {
801
+ validateParamConsistency(sql, params);
784
802
  }
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.`
791
- );
792
- }
803
+ function detectPlaceholderStyle(sql) {
804
+ const hasDollar = /\$\d+/.test(sql);
805
+ const hasSqliteQ = /\?(?:\d+)?/.test(sql);
806
+ return { hasDollar, hasSqliteQ };
793
807
  }
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);
808
+ function validateParamConsistencyByDialect(sql, params, dialect) {
809
+ const { hasDollar, hasSqliteQ } = detectPlaceholderStyle(sql);
810
+ if (dialect !== "sqlite") {
811
+ if (hasSqliteQ && !hasDollar) {
812
+ throw new Error(
813
+ `CRITICAL: Non-sqlite dialect query contains sqlite '?' placeholders. SQL: ${sqlPreview(sql)}`
814
+ );
815
+ }
816
+ return validateDollarPlaceholders(sql, params);
801
817
  }
802
- ensureMappingsCoverAllIndices(mappingIndices, max, dialect);
803
- }
804
- function validateSqlPositions(sql, mappings, dialect) {
805
- const { indices, sawNumbered, sawAnonymous } = getPlaceholderIndices(
806
- sql,
807
- dialect
808
- );
809
- if (dialect === "sqlite") {
810
- ensureNoMixedSqlitePlaceholders(sawNumbered, sawAnonymous);
818
+ if (hasDollar && hasSqliteQ) {
819
+ throw new Error(
820
+ `CRITICAL: Mixed placeholder styles ($N and ?/ ?NNN) are not supported. SQL: ${sqlPreview(sql)}`
821
+ );
811
822
  }
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);
823
+ if (hasSqliteQ) return validateSqlitePlaceholders(sql, params);
824
+ return validateDollarPlaceholders(sql, params);
818
825
  }
819
826
 
820
827
  // src/builder/shared/sql-utils.ts
821
- var NUL = String.fromCharCode(0);
822
828
  function containsControlChars(s) {
823
- return s.includes(NUL) || s.includes("\n") || s.includes("\r");
829
+ return /[\u0000-\u001F\u007F]/.test(s);
824
830
  }
825
831
  function assertNoControlChars(label, s) {
826
832
  if (containsControlChars(s)) {
@@ -1039,16 +1045,46 @@ function buildTableReference(schemaName, tableName, dialect) {
1039
1045
  return `"${safeSchema}"."${safeTable}"`;
1040
1046
  }
1041
1047
  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");
1048
+ if (typeof alias !== "string") {
1049
+ throw new Error(`Invalid alias: expected string, got ${typeof alias}`);
1050
+ }
1051
+ const a = alias.trim();
1052
+ if (a.length === 0) {
1053
+ throw new Error("Invalid alias: required and cannot be empty");
1045
1054
  }
1046
- if (containsControlChars(a) || a.includes(";")) {
1047
- throw new Error(`alias contains unsafe characters: ${JSON.stringify(a)}`);
1055
+ if (a !== alias) {
1056
+ throw new Error("Invalid alias: leading/trailing whitespace");
1048
1057
  }
1049
- if (!/^[A-Za-z_]\w*$/.test(a)) {
1058
+ if (/[\u0000-\u001F\u007F]/.test(a)) {
1050
1059
  throw new Error(
1051
- `alias must be a simple identifier, got: ${JSON.stringify(a)}`
1060
+ "Invalid alias: contains unsafe characters (control characters)"
1061
+ );
1062
+ }
1063
+ if (a.includes('"') || a.includes("'") || a.includes("`")) {
1064
+ throw new Error("Invalid alias: contains unsafe characters (quotes)");
1065
+ }
1066
+ if (a.includes(";")) {
1067
+ throw new Error("Invalid alias: contains unsafe characters (semicolon)");
1068
+ }
1069
+ if (a.includes("--") || a.includes("/*") || a.includes("*/")) {
1070
+ throw new Error(
1071
+ "Invalid alias: contains unsafe characters (SQL comment tokens)"
1072
+ );
1073
+ }
1074
+ if (/\s/.test(a)) {
1075
+ throw new Error(
1076
+ "Invalid alias: must be a simple identifier without whitespace"
1077
+ );
1078
+ }
1079
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(a)) {
1080
+ throw new Error(
1081
+ `Invalid alias: must be a simple identifier (alphanumeric with underscores): "${alias}"`
1082
+ );
1083
+ }
1084
+ const lowered = a.toLowerCase();
1085
+ if (ALIAS_FORBIDDEN_KEYWORDS.has(lowered)) {
1086
+ throw new Error(
1087
+ `Invalid alias: '${alias}' is a SQL keyword that would break query parsing. Forbidden aliases: ${[...ALIAS_FORBIDDEN_KEYWORDS].join(", ")}`
1052
1088
  );
1053
1089
  }
1054
1090
  }
@@ -1090,7 +1126,9 @@ function isValidRelationField(field) {
1090
1126
  if (fk.length === 0) return false;
1091
1127
  const refsRaw = field.references;
1092
1128
  const refs = normalizeKeyList(refsRaw);
1093
- if (refs.length === 0) return false;
1129
+ if (refs.length === 0) {
1130
+ return fk.length === 1;
1131
+ }
1094
1132
  if (refs.length !== fk.length) return false;
1095
1133
  return true;
1096
1134
  }
@@ -1105,6 +1143,8 @@ function getReferenceFieldNames(field, foreignKeyCount) {
1105
1143
  return refs;
1106
1144
  }
1107
1145
  function joinCondition(field, parentModel, childModel, parentAlias, childAlias) {
1146
+ assertSafeAlias(parentAlias);
1147
+ assertSafeAlias(childAlias);
1108
1148
  const fkFields = normalizeKeyList(field.foreignKey);
1109
1149
  if (fkFields.length === 0) {
1110
1150
  throw createError(
@@ -1254,6 +1294,32 @@ function normalizeOrderByInput(orderBy, parseValue) {
1254
1294
  throw new Error("orderBy must be an object or array of objects");
1255
1295
  }
1256
1296
 
1297
+ // src/builder/shared/order-by-determinism.ts
1298
+ function modelHasScalarId(model) {
1299
+ if (!model) return false;
1300
+ return getScalarFieldSet(model).has("id");
1301
+ }
1302
+ function hasIdTiebreaker(orderBy, parse) {
1303
+ if (!isNotNullish(orderBy)) return false;
1304
+ const normalized = normalizeOrderByInput(orderBy, parse);
1305
+ return normalized.some(
1306
+ (obj) => Object.prototype.hasOwnProperty.call(obj, "id")
1307
+ );
1308
+ }
1309
+ function addIdTiebreaker(orderBy) {
1310
+ if (Array.isArray(orderBy)) return [...orderBy, { id: "asc" }];
1311
+ return [orderBy, { id: "asc" }];
1312
+ }
1313
+ function ensureDeterministicOrderByInput(args) {
1314
+ const { orderBy, model, parseValue } = args;
1315
+ if (!modelHasScalarId(model)) return orderBy;
1316
+ if (!isNotNullish(orderBy)) {
1317
+ return { id: "asc" };
1318
+ }
1319
+ if (hasIdTiebreaker(orderBy, parseValue)) return orderBy;
1320
+ return addIdTiebreaker(orderBy);
1321
+ }
1322
+
1257
1323
  // src/builder/pagination.ts
1258
1324
  var MAX_LIMIT_OFFSET = 2147483647;
1259
1325
  function parseDirectionRaw(raw, errorLabel) {
@@ -1316,7 +1382,15 @@ function hasNonNullishProp(v, key) {
1316
1382
  }
1317
1383
  function normalizeIntegerOrDynamic(name, v) {
1318
1384
  if (schemaParser.isDynamicParameter(v)) return v;
1319
- return normalizeFiniteInteger(name, v);
1385
+ const result = normalizeIntLike(name, v, {
1386
+ min: Number.MIN_SAFE_INTEGER,
1387
+ max: MAX_LIMIT_OFFSET,
1388
+ allowZero: true
1389
+ });
1390
+ if (result === void 0) {
1391
+ throw new Error(`${name} normalization returned undefined`);
1392
+ }
1393
+ return result;
1320
1394
  }
1321
1395
  function readSkipTake(relArgs) {
1322
1396
  const hasSkip = hasNonNullishProp(relArgs, "skip");
@@ -1382,7 +1456,7 @@ function buildCursorFilterParts(cursor, cursorAlias, params, model) {
1382
1456
  const placeholdersByField = /* @__PURE__ */ new Map();
1383
1457
  const parts = [];
1384
1458
  for (const [field, value] of entries) {
1385
- const c = `${cursorAlias}.${quote(field)}`;
1459
+ const c = `${cursorAlias}.${quoteColumn(model, field)}`;
1386
1460
  if (value === null) {
1387
1461
  parts.push(`${c} IS NULL`);
1388
1462
  continue;
@@ -1396,13 +1470,6 @@ function buildCursorFilterParts(cursor, cursorAlias, params, model) {
1396
1470
  placeholdersByField
1397
1471
  };
1398
1472
  }
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
1473
  function buildCursorEqualityExpr(columnExpr, valueExpr) {
1407
1474
  return `((${valueExpr} IS NULL AND ${columnExpr} IS NULL) OR (${valueExpr} IS NOT NULL AND ${columnExpr} = ${valueExpr}))`;
1408
1475
  }
@@ -1441,7 +1508,7 @@ function buildOrderEntries(orderBy) {
1441
1508
  } else {
1442
1509
  entries.push({
1443
1510
  field,
1444
- direction: value.sort,
1511
+ direction: value.direction,
1445
1512
  nulls: value.nulls
1446
1513
  });
1447
1514
  }
@@ -1449,16 +1516,53 @@ function buildOrderEntries(orderBy) {
1449
1516
  }
1450
1517
  return entries;
1451
1518
  }
1519
+ function buildCursorCteSelectList(cursorEntries, orderEntries, model) {
1520
+ const set = /* @__PURE__ */ new Set();
1521
+ for (const [f] of cursorEntries) set.add(f);
1522
+ for (const e of orderEntries) set.add(e.field);
1523
+ const cols = [...set].map((f) => quoteColumn(model, f));
1524
+ if (cols.length === 0) {
1525
+ throw new Error("cursor cte select list is empty");
1526
+ }
1527
+ return cols.join(SQL_SEPARATORS.FIELD_LIST);
1528
+ }
1529
+ function truncateIdent(name, maxLen) {
1530
+ const s = String(name);
1531
+ if (s.length <= maxLen) return s;
1532
+ return s.slice(0, maxLen);
1533
+ }
1534
+ function buildCursorNames(outerAlias) {
1535
+ const maxLen = 63;
1536
+ const base = outerAlias.toLowerCase();
1537
+ const cteName = truncateIdent(`__tp_cursor_${base}`, maxLen);
1538
+ const srcAlias = truncateIdent(`__tp_cursor_src_${base}`, maxLen);
1539
+ if (cteName === outerAlias || srcAlias === outerAlias) {
1540
+ return {
1541
+ cteName: truncateIdent(`__tp_cursor_${base}_x`, maxLen),
1542
+ srcAlias: truncateIdent(`__tp_cursor_src_${base}_x`, maxLen)
1543
+ };
1544
+ }
1545
+ return { cteName, srcAlias };
1546
+ }
1452
1547
  function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect, model) {
1453
1548
  var _a;
1549
+ assertSafeTableRef(tableName);
1550
+ assertSafeAlias(alias);
1454
1551
  const d = dialect != null ? dialect : getGlobalDialect();
1455
1552
  const cursorEntries = Object.entries(cursor);
1456
1553
  if (cursorEntries.length === 0) {
1457
1554
  throw new Error("cursor must have at least one field");
1458
1555
  }
1459
- const cursorAlias = "__tp_cursor_src";
1460
- const { whereSql: cursorWhereSql, placeholdersByField } = buildCursorFilterParts(cursor, cursorAlias, params);
1461
- let orderEntries = buildOrderEntries(orderBy);
1556
+ const { cteName, srcAlias } = buildCursorNames(alias);
1557
+ assertSafeAlias(cteName);
1558
+ assertSafeAlias(srcAlias);
1559
+ const { whereSql: cursorWhereSql, placeholdersByField } = buildCursorFilterParts(cursor, srcAlias, params, model);
1560
+ const deterministicOrderBy = ensureDeterministicOrderByInput({
1561
+ orderBy,
1562
+ model,
1563
+ parseValue: parseOrderByValue
1564
+ });
1565
+ let orderEntries = buildOrderEntries(deterministicOrderBy);
1462
1566
  if (orderEntries.length === 0) {
1463
1567
  orderEntries = cursorEntries.map(([field]) => ({
1464
1568
  field,
@@ -1467,11 +1571,21 @@ function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect
1467
1571
  } else {
1468
1572
  orderEntries = ensureCursorFieldsInOrder(orderEntries, cursorEntries);
1469
1573
  }
1470
- const existsExpr = buildCursorRowExistsExpr(
1471
- tableName,
1472
- cursorAlias,
1473
- cursorWhereSql
1574
+ const cursorOrderBy = orderEntries.map(
1575
+ (e) => `${srcAlias}.${quoteColumn(model, e.field)} ${e.direction.toUpperCase()}`
1576
+ ).join(", ");
1577
+ const selectList = buildCursorCteSelectList(
1578
+ cursorEntries,
1579
+ orderEntries,
1580
+ model
1474
1581
  );
1582
+ const cte = `${cteName} AS (
1583
+ SELECT ${selectList} FROM ${tableName} ${srcAlias}
1584
+ WHERE ${cursorWhereSql}
1585
+ ORDER BY ${cursorOrderBy}
1586
+ LIMIT 1
1587
+ )`;
1588
+ const existsExpr = `EXISTS (SELECT 1 FROM ${cteName})`;
1475
1589
  const outerCursorMatch = buildOuterCursorMatch(
1476
1590
  cursor,
1477
1591
  alias,
@@ -1479,34 +1593,31 @@ function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect
1479
1593
  params,
1480
1594
  model
1481
1595
  );
1596
+ const getValueExpr = (field) => {
1597
+ return `(SELECT ${quoteColumn(model, field)} FROM ${cteName})`;
1598
+ };
1482
1599
  const orClauses = [];
1483
1600
  for (let level = 0; level < orderEntries.length; level++) {
1484
1601
  const andParts = [];
1485
1602
  for (let i = 0; i < level; i++) {
1486
1603
  const e2 = orderEntries[i];
1487
1604
  const c2 = col(alias, e2.field, model);
1488
- const v2 = cursorValueExpr(
1489
- tableName,
1490
- cursorAlias,
1491
- cursorWhereSql,
1492
- e2.field);
1605
+ const v2 = getValueExpr(e2.field);
1493
1606
  andParts.push(buildCursorEqualityExpr(c2, v2));
1494
1607
  }
1495
1608
  const e = orderEntries[level];
1496
1609
  const c = col(alias, e.field, model);
1497
- const v = cursorValueExpr(
1498
- tableName,
1499
- cursorAlias,
1500
- cursorWhereSql,
1501
- e.field);
1610
+ const v = getValueExpr(e.field);
1502
1611
  const nulls = (_a = e.nulls) != null ? _a : defaultNullsFor(d, e.direction);
1503
1612
  andParts.push(buildCursorInequalityExpr(c, e.direction, nulls, v));
1504
1613
  orClauses.push(`(${andParts.join(SQL_SEPARATORS.CONDITION_AND)})`);
1505
1614
  }
1506
1615
  const exclusive = orClauses.join(SQL_SEPARATORS.CONDITION_OR);
1507
- return `(${existsExpr} ${SQL_SEPARATORS.CONDITION_AND} ((${exclusive})${SQL_SEPARATORS.CONDITION_OR}(${outerCursorMatch})))`;
1616
+ const condition = `(${existsExpr} ${SQL_SEPARATORS.CONDITION_AND} ((${exclusive})${SQL_SEPARATORS.CONDITION_OR}(${outerCursorMatch})))`;
1617
+ return { cte, condition };
1508
1618
  }
1509
1619
  function buildOrderBy(orderBy, alias, dialect, model) {
1620
+ assertSafeAlias(alias);
1510
1621
  const entries = buildOrderEntries(orderBy);
1511
1622
  if (entries.length === 0) return "";
1512
1623
  const d = dialect != null ? dialect : getGlobalDialect();
@@ -1606,6 +1717,11 @@ function buildScalarOperator(expr, op, val, params, mode, fieldType, dialect) {
1606
1717
  }
1607
1718
  return handleInOperator(expr, op, val, params, dialect);
1608
1719
  }
1720
+ if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && !isNotNullish(dialect)) {
1721
+ throw createError(`Insensitive equals requires a SQL dialect`, {
1722
+ operator: op
1723
+ });
1724
+ }
1609
1725
  return handleComparisonOperator(expr, op, val, params);
1610
1726
  }
1611
1727
  function handleNullValue(expr, op) {
@@ -1621,6 +1737,28 @@ function normalizeMode(v) {
1621
1737
  function handleNotOperator(expr, val, params, outerMode, fieldType, dialect) {
1622
1738
  const innerMode = normalizeMode(val.mode);
1623
1739
  const effectiveMode = innerMode != null ? innerMode : outerMode;
1740
+ const entries = Object.entries(val).filter(
1741
+ ([k, v]) => k !== "mode" && v !== void 0
1742
+ );
1743
+ if (entries.length === 0) return "";
1744
+ if (!isNotNullish(dialect)) {
1745
+ const clauses = [];
1746
+ for (const [subOp, subVal] of entries) {
1747
+ const sub = buildScalarOperator(
1748
+ expr,
1749
+ subOp,
1750
+ subVal,
1751
+ params,
1752
+ effectiveMode,
1753
+ fieldType,
1754
+ void 0
1755
+ );
1756
+ if (sub && sub.trim().length > 0) clauses.push(`(${sub})`);
1757
+ }
1758
+ if (clauses.length === 0) return "";
1759
+ if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
1760
+ return `${SQL_TEMPLATES.NOT} (${clauses.join(` ${SQL_TEMPLATES.AND} `)})`;
1761
+ }
1624
1762
  return buildNotComposite(
1625
1763
  expr,
1626
1764
  val,
@@ -1835,6 +1973,7 @@ function handleArrayIsEmpty(expr, val, dialect) {
1835
1973
 
1836
1974
  // src/builder/where/operators-json.ts
1837
1975
  var SAFE_JSON_PATH_SEGMENT = /^[a-zA-Z_]\w*$/;
1976
+ var MAX_PATH_SEGMENT_LENGTH = 255;
1838
1977
  function validateJsonPathSegments(segments) {
1839
1978
  for (const segment of segments) {
1840
1979
  if (typeof segment !== "string") {
@@ -1843,6 +1982,12 @@ function validateJsonPathSegments(segments) {
1843
1982
  value: segment
1844
1983
  });
1845
1984
  }
1985
+ if (segment.length > MAX_PATH_SEGMENT_LENGTH) {
1986
+ throw createError(
1987
+ `JSON path segment too long: max ${MAX_PATH_SEGMENT_LENGTH} characters`,
1988
+ { operator: Ops.PATH, value: `[${segment.length} chars]` }
1989
+ );
1990
+ }
1846
1991
  if (!SAFE_JSON_PATH_SEGMENT.test(segment)) {
1847
1992
  throw createError(
1848
1993
  `Invalid JSON path segment: '${segment}'. Must be alphanumeric with underscores, starting with letter or underscore.`,
@@ -1931,6 +2076,9 @@ function handleJsonWildcard(expr, op, val, params, wildcards, dialect) {
1931
2076
 
1932
2077
  // src/builder/where/relations.ts
1933
2078
  var NO_JOINS = Object.freeze([]);
2079
+ function freezeJoins(items) {
2080
+ return Object.freeze([...items]);
2081
+ }
1934
2082
  function isListRelation(fieldType) {
1935
2083
  return typeof fieldType === "string" && fieldType.endsWith("[]");
1936
2084
  }
@@ -1993,7 +2141,7 @@ function buildListRelationFilters(args) {
1993
2141
  const whereClause = `${relAlias}.${quote(checkField.name)} IS NULL`;
1994
2142
  return Object.freeze({
1995
2143
  clause: whereClause,
1996
- joins: [leftJoinSql]
2144
+ joins: freezeJoins([leftJoinSql])
1997
2145
  });
1998
2146
  }
1999
2147
  }
@@ -2198,7 +2346,7 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
2198
2346
  Ops.HAS_EVERY,
2199
2347
  Ops.IS_EMPTY
2200
2348
  ]);
2201
- const JSON_OPS = /* @__PURE__ */ new Set([
2349
+ const JSON_OPS2 = /* @__PURE__ */ new Set([
2202
2350
  Ops.PATH,
2203
2351
  Ops.STRING_CONTAINS,
2204
2352
  Ops.STRING_STARTS_WITH,
@@ -2215,7 +2363,7 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
2215
2363
  modelName
2216
2364
  });
2217
2365
  }
2218
- const isJsonOp = JSON_OPS.has(op);
2366
+ const isJsonOp = JSON_OPS2.has(op);
2219
2367
  const isFieldJson = isJsonType(fieldType);
2220
2368
  const jsonOpMismatch = isJsonOp && !isFieldJson;
2221
2369
  if (jsonOpMismatch) {
@@ -2229,6 +2377,14 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
2229
2377
  }
2230
2378
 
2231
2379
  // src/builder/where/builder.ts
2380
+ var MAX_QUERY_DEPTH = 50;
2381
+ var EMPTY_JOINS = Object.freeze([]);
2382
+ var JSON_OPS = /* @__PURE__ */ new Set([
2383
+ Ops.PATH,
2384
+ Ops.STRING_CONTAINS,
2385
+ Ops.STRING_STARTS_WITH,
2386
+ Ops.STRING_ENDS_WITH
2387
+ ]);
2232
2388
  var WhereBuilder = class {
2233
2389
  build(where, ctx) {
2234
2390
  if (!isPlainObject(where)) {
@@ -2240,8 +2396,6 @@ var WhereBuilder = class {
2240
2396
  return buildWhereInternal(where, ctx, this);
2241
2397
  }
2242
2398
  };
2243
- var MAX_QUERY_DEPTH = 50;
2244
- var EMPTY_JOINS = Object.freeze([]);
2245
2399
  var whereBuilderInstance = new WhereBuilder();
2246
2400
  function freezeResult(clause, joins = EMPTY_JOINS) {
2247
2401
  return Object.freeze({ clause, joins });
@@ -2418,16 +2572,8 @@ function buildOperator(expr, op, val, ctx, mode, fieldType) {
2418
2572
  if (fieldType && isArrayType(fieldType)) {
2419
2573
  return buildArrayOperator(expr, op, val, ctx.params, fieldType, ctx.dialect);
2420
2574
  }
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
- }
2575
+ if (fieldType && isJsonType(fieldType) && JSON_OPS.has(op)) {
2576
+ return buildJsonOperator(expr, op, val, ctx.params, ctx.dialect);
2431
2577
  }
2432
2578
  return buildScalarOperator(
2433
2579
  expr,
@@ -2448,7 +2594,7 @@ function toSafeSqlIdentifier(input) {
2448
2594
  const base = startsOk ? cleaned : `_${cleaned}`;
2449
2595
  const fallback = base.length > 0 ? base : "_t";
2450
2596
  const lowered = fallback.toLowerCase();
2451
- return SQL_KEYWORDS.has(lowered) ? `_${lowered}` : lowered;
2597
+ return ALIAS_FORBIDDEN_KEYWORDS.has(lowered) ? `_${lowered}` : lowered;
2452
2598
  }
2453
2599
  function createAliasGenerator(maxAliases = 1e4) {
2454
2600
  let counter = 0;
@@ -2658,6 +2804,7 @@ function toPublicResult(clause, joins, params) {
2658
2804
  // src/builder/where.ts
2659
2805
  function buildWhereClause(where, options) {
2660
2806
  var _a, _b, _c, _d, _e;
2807
+ assertSafeAlias(options.alias);
2661
2808
  const dialect = options.dialect || getGlobalDialect();
2662
2809
  const params = (_a = options.params) != null ? _a : createParamStore();
2663
2810
  const ctx = {
@@ -2826,6 +2973,9 @@ function buildRelationSelect(relArgs, relModel, relAlias) {
2826
2973
  }
2827
2974
 
2828
2975
  // src/builder/select/includes.ts
2976
+ var MAX_INCLUDE_DEPTH = 10;
2977
+ var MAX_TOTAL_SUBQUERIES = 100;
2978
+ var MAX_TOTAL_INCLUDES = 50;
2829
2979
  function getRelationTableReference(relModel, dialect) {
2830
2980
  return buildTableReference(
2831
2981
  SQL_TEMPLATES.PUBLIC_SCHEMA,
@@ -2871,107 +3021,24 @@ function relationEntriesFromArgs(args, model) {
2871
3021
  pushFrom(args.select);
2872
3022
  return out;
2873
3023
  }
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
3024
  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;
3025
+ if (!isNotNullish(orderBy)) return;
3026
+ const scalarSet = getScalarFieldSet(model);
3027
+ const normalized = normalizeOrderByInput(orderBy, parseOrderByValue);
3028
+ for (const item of normalized) {
3029
+ const entries = Object.entries(item);
3030
+ if (entries.length !== 1) {
3031
+ throw new Error("orderBy array entries must have exactly one field");
2967
3032
  }
2968
- if (isPlainObject(v)) {
2969
- validateOrderByObject(fieldName, v);
2970
- continue;
3033
+ const fieldName = String(entries[0][0]).trim();
3034
+ if (fieldName.length === 0) {
3035
+ throw new Error("orderBy field name cannot be empty");
3036
+ }
3037
+ if (!scalarSet.has(fieldName)) {
3038
+ throw new Error(
3039
+ `orderBy references unknown or non-scalar field '${fieldName}' on model ${model.name}`
3040
+ );
2971
3041
  }
2972
- throw new Error(
2973
- `orderBy for '${fieldName}' must be 'asc' | 'desc' or { sort, nulls? }`
2974
- );
2975
3042
  }
2976
3043
  }
2977
3044
  function appendLimitOffset(sql, dialect, params, takeVal, skipVal, scope) {
@@ -3000,7 +3067,10 @@ function readWhereInput(relArgs) {
3000
3067
  function readOrderByInput(relArgs) {
3001
3068
  if (!isPlainObject(relArgs)) return { hasOrderBy: false, orderBy: void 0 };
3002
3069
  if (!("orderBy" in relArgs)) return { hasOrderBy: false, orderBy: void 0 };
3003
- return { hasOrderBy: true, orderBy: relArgs.orderBy };
3070
+ return {
3071
+ hasOrderBy: true,
3072
+ orderBy: relArgs.orderBy
3073
+ };
3004
3074
  }
3005
3075
  function extractRelationPaginationConfig(relArgs) {
3006
3076
  const { hasOrderBy, orderBy: rawOrderByInput } = readOrderByInput(relArgs);
@@ -3030,36 +3100,28 @@ function maybeReverseNegativeTake(takeVal, hasOrderBy, orderByInput) {
3030
3100
  orderByInput: reverseOrderByInput(orderByInput)
3031
3101
  };
3032
3102
  }
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);
3103
+ function ensureDeterministicOrderByForInclude(args) {
3104
+ if (!args.hasPagination) {
3105
+ if (args.hasOrderBy && isNotNullish(args.orderByInput)) {
3106
+ validateOrderByForModel(args.relModel, args.orderByInput);
3051
3107
  }
3052
- return orderByInput;
3108
+ return args.orderByInput;
3053
3109
  }
3054
- if (!hasOrderBy) {
3055
- return modelHasScalarId(relModel) ? { id: "asc" } : orderByInput;
3110
+ if (!args.hasOrderBy) {
3111
+ return ensureDeterministicOrderByInput({
3112
+ orderBy: void 0,
3113
+ model: args.relModel,
3114
+ parseValue: parseOrderByValue
3115
+ });
3056
3116
  }
3057
- if (isNotNullish(orderByInput)) {
3058
- validateOrderByForModel(relModel, orderByInput);
3117
+ if (isNotNullish(args.orderByInput)) {
3118
+ validateOrderByForModel(args.relModel, args.orderByInput);
3059
3119
  }
3060
- if (!modelHasScalarId(relModel)) return orderByInput;
3061
- if (hasIdTiebreaker(orderByInput)) return orderByInput;
3062
- return addIdTiebreaker(orderByInput);
3120
+ return ensureDeterministicOrderByInput({
3121
+ orderBy: args.orderByInput,
3122
+ model: args.relModel,
3123
+ parseValue: parseOrderByValue
3124
+ });
3063
3125
  }
3064
3126
  function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
3065
3127
  let relSelect = buildRelationSelect(relArgs, relModel, relAlias);
@@ -3070,7 +3132,10 @@ function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
3070
3132
  relAlias,
3071
3133
  ctx.aliasGen,
3072
3134
  ctx.params,
3073
- ctx.dialect
3135
+ ctx.dialect,
3136
+ ctx.visitPath || [],
3137
+ (ctx.depth || 0) + 1,
3138
+ ctx.stats
3074
3139
  ) : [];
3075
3140
  if (isNonEmptyArray(nestedIncludes)) {
3076
3141
  const emptyJson = ctx.dialect === "postgres" ? `'[]'::json` : `json('[]')`;
@@ -3220,12 +3285,12 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3220
3285
  paginationConfig.orderBy
3221
3286
  );
3222
3287
  const hasPagination = paginationConfig.hasSkip || paginationConfig.hasTake;
3223
- const finalOrderByInput = ensureDeterministicOrderBy(
3288
+ const finalOrderByInput = ensureDeterministicOrderByForInclude({
3224
3289
  relModel,
3225
- paginationConfig.hasOrderBy,
3226
- adjusted.orderByInput,
3290
+ hasOrderBy: paginationConfig.hasOrderBy,
3291
+ orderByInput: adjusted.orderByInput,
3227
3292
  hasPagination
3228
- );
3293
+ });
3229
3294
  const orderBySql = buildOrderBySql(
3230
3295
  finalOrderByInput,
3231
3296
  relAlias,
@@ -3266,12 +3331,48 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3266
3331
  ctx
3267
3332
  });
3268
3333
  }
3269
- function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, params, dialect) {
3334
+ function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, params, dialect, visitPath = [], depth = 0, stats) {
3335
+ if (!stats) {
3336
+ stats = { totalIncludes: 0, totalSubqueries: 0, maxDepth: 0 };
3337
+ }
3338
+ if (depth > MAX_INCLUDE_DEPTH) {
3339
+ throw new Error(
3340
+ `Maximum include depth of ${MAX_INCLUDE_DEPTH} exceeded. Path: ${visitPath.join(" -> ")}. Deep includes cause exponential SQL complexity and performance issues.`
3341
+ );
3342
+ }
3343
+ stats.maxDepth = Math.max(stats.maxDepth, depth);
3270
3344
  const includes = [];
3271
3345
  const entries = relationEntriesFromArgs(args, model);
3272
3346
  for (const [relName, relArgs] of entries) {
3273
3347
  if (relArgs === false) continue;
3348
+ stats.totalIncludes++;
3349
+ if (stats.totalIncludes > MAX_TOTAL_INCLUDES) {
3350
+ throw new Error(
3351
+ `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.`
3352
+ );
3353
+ }
3354
+ stats.totalSubqueries++;
3355
+ if (stats.totalSubqueries > MAX_TOTAL_SUBQUERIES) {
3356
+ throw new Error(
3357
+ `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.`
3358
+ );
3359
+ }
3274
3360
  const resolved = resolveRelationOrThrow(model, schemas, relName);
3361
+ const relationPath = `${model.name}.${relName}`;
3362
+ const currentPath = [...visitPath, relationPath];
3363
+ if (visitPath.includes(relationPath)) {
3364
+ throw new Error(
3365
+ `Circular include detected: ${currentPath.join(" -> ")}. Relation '${relationPath}' creates an infinite loop.`
3366
+ );
3367
+ }
3368
+ const modelOccurrences = currentPath.filter(
3369
+ (p) => p.startsWith(`${resolved.relModel.name}.`)
3370
+ ).length;
3371
+ if (modelOccurrences > 2) {
3372
+ throw new Error(
3373
+ `Include too deeply nested: model '${resolved.relModel.name}' appears ${modelOccurrences} times in path: ${currentPath.join(" -> ")}`
3374
+ );
3375
+ }
3275
3376
  const include = buildSingleInclude(
3276
3377
  relName,
3277
3378
  relArgs,
@@ -3283,7 +3384,10 @@ function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, pa
3283
3384
  parentAlias,
3284
3385
  aliasGen,
3285
3386
  dialect,
3286
- params
3387
+ params,
3388
+ visitPath: currentPath,
3389
+ depth: depth + 1,
3390
+ stats
3287
3391
  }
3288
3392
  );
3289
3393
  includes.push(include);
@@ -3292,6 +3396,11 @@ function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, pa
3292
3396
  }
3293
3397
  function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
3294
3398
  const aliasGen = createAliasGenerator();
3399
+ const stats = {
3400
+ totalIncludes: 0,
3401
+ totalSubqueries: 0,
3402
+ maxDepth: 0
3403
+ };
3295
3404
  return buildIncludeSqlInternal(
3296
3405
  args,
3297
3406
  model,
@@ -3299,7 +3408,10 @@ function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
3299
3408
  parentAlias,
3300
3409
  aliasGen,
3301
3410
  params,
3302
- dialect
3411
+ dialect,
3412
+ [],
3413
+ 0,
3414
+ stats
3303
3415
  );
3304
3416
  }
3305
3417
  function resolveCountRelationOrThrow(relName, model, schemas) {
@@ -3315,6 +3427,11 @@ function resolveCountRelationOrThrow(relName, model, schemas) {
3315
3427
  `_count.${relName} references unknown relation on model ${model.name}`
3316
3428
  );
3317
3429
  }
3430
+ if (!isValidRelationField(field)) {
3431
+ throw new Error(
3432
+ `_count.${relName} has invalid relation metadata on model ${model.name}`
3433
+ );
3434
+ }
3318
3435
  const relModel = schemas.find((m) => m.name === field.relatedModel);
3319
3436
  if (!relModel) {
3320
3437
  throw new Error(
@@ -3323,31 +3440,81 @@ function resolveCountRelationOrThrow(relName, model, schemas) {
3323
3440
  }
3324
3441
  return { field, relModel };
3325
3442
  }
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])}`;
3443
+ function defaultReferencesForCount(fkCount) {
3444
+ if (fkCount === 1) return ["id"];
3445
+ throw new Error(
3446
+ "Relation count for composite keys requires explicit references matching foreignKey length"
3447
+ );
3330
3448
  }
3331
- function leftJoinOnForCount(field, parentAlias, joinAlias) {
3449
+ function resolveCountKeyPairs(field) {
3332
3450
  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")}`;
3451
+ if (fkFields.length === 0) {
3452
+ throw new Error("Relation count requires foreignKey");
3453
+ }
3454
+ const refsRaw = field.references;
3455
+ const refs = normalizeKeyList(refsRaw);
3456
+ const refFields = refs.length > 0 ? refs : defaultReferencesForCount(fkFields.length);
3457
+ if (refFields.length !== fkFields.length) {
3458
+ throw new Error(
3459
+ "Relation count requires references count to match foreignKey count"
3460
+ );
3461
+ }
3462
+ const relKeyFields = field.isForeignKeyLocal ? refFields : fkFields;
3463
+ const parentKeyFields = field.isForeignKeyLocal ? fkFields : refFields;
3464
+ return { relKeyFields, parentKeyFields };
3465
+ }
3466
+ function aliasQualifiedColumn(alias, model, field) {
3467
+ return `${alias}.${quoteColumn(model, field)}`;
3335
3468
  }
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})`;
3469
+ function subqueryForCount(args) {
3470
+ const selectKeys = args.relKeyFields.map(
3471
+ (f, i) => `${aliasQualifiedColumn(args.countAlias, args.relModel, f)} AS "__fk${i}"`
3472
+ ).join(SQL_SEPARATORS.FIELD_LIST);
3473
+ const groupByKeys = args.relKeyFields.map((f) => aliasQualifiedColumn(args.countAlias, args.relModel, f)).join(SQL_SEPARATORS.FIELD_LIST);
3474
+ const cntExpr = args.dialect === "postgres" ? "COUNT(*)::int AS __cnt" : "COUNT(*) AS __cnt";
3475
+ return `(SELECT ${selectKeys}${SQL_SEPARATORS.FIELD_LIST}${cntExpr} FROM ${args.relTable} ${args.countAlias} GROUP BY ${groupByKeys})`;
3476
+ }
3477
+ function leftJoinOnForCount(args) {
3478
+ const parts = args.parentKeyFields.map(
3479
+ (f, i) => `${args.joinAlias}."__fk${i}" = ${aliasQualifiedColumn(args.parentAlias, args.parentModel, f)}`
3480
+ );
3481
+ return parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`;
3482
+ }
3483
+ function nextAliasAvoiding(aliasGen, base, forbidden) {
3484
+ let a = aliasGen.next(base);
3485
+ while (forbidden.has(a)) {
3486
+ a = aliasGen.next(base);
3487
+ }
3488
+ return a;
3338
3489
  }
3339
3490
  function buildCountJoinAndPair(args) {
3340
3491
  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,
3492
+ const { relKeyFields, parentKeyFields } = resolveCountKeyPairs(args.field);
3493
+ const forbidden = /* @__PURE__ */ new Set([args.parentAlias]);
3494
+ const countAlias = nextAliasAvoiding(
3495
+ args.aliasGen,
3496
+ `__tp_cnt_${args.relName}`,
3497
+ forbidden
3498
+ );
3499
+ forbidden.add(countAlias);
3500
+ const subquery = subqueryForCount({
3501
+ dialect: args.dialect,
3345
3502
  relTable,
3346
3503
  countAlias,
3347
- groupByCol
3504
+ relModel: args.relModel,
3505
+ relKeyFields
3506
+ });
3507
+ const joinAlias = nextAliasAvoiding(
3508
+ args.aliasGen,
3509
+ `__tp_cnt_j_${args.relName}`,
3510
+ forbidden
3348
3511
  );
3349
- const joinAlias = `__tp_cnt_j_${args.relName}`;
3350
- const leftJoinOn = leftJoinOnForCount(args.field, args.parentAlias, joinAlias);
3512
+ const leftJoinOn = leftJoinOnForCount({
3513
+ joinAlias,
3514
+ parentAlias: args.parentAlias,
3515
+ parentModel: args.parentModel,
3516
+ parentKeyFields
3517
+ });
3351
3518
  return {
3352
3519
  joinSql: `LEFT JOIN ${subquery} ${joinAlias} ON ${leftJoinOn}`,
3353
3520
  pairSql: `${sqlStringLiteral(args.relName)}, COALESCE(${joinAlias}.__cnt, 0)`
@@ -3356,6 +3523,7 @@ function buildCountJoinAndPair(args) {
3356
3523
  function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params, dialect) {
3357
3524
  const joins = [];
3358
3525
  const pairs = [];
3526
+ const aliasGen = createAliasGenerator();
3359
3527
  for (const [relName, shouldCount] of Object.entries(countSelect)) {
3360
3528
  if (!shouldCount) continue;
3361
3529
  const resolved = resolveCountRelationOrThrow(relName, model, schemas);
@@ -3363,8 +3531,10 @@ function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params
3363
3531
  relName,
3364
3532
  field: resolved.field,
3365
3533
  relModel: resolved.relModel,
3534
+ parentModel: model,
3366
3535
  parentAlias,
3367
- dialect
3536
+ dialect,
3537
+ aliasGen
3368
3538
  });
3369
3539
  joins.push(built.joinSql);
3370
3540
  pairs.push(built.pairSql);
@@ -3376,16 +3546,21 @@ function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params
3376
3546
  }
3377
3547
 
3378
3548
  // src/builder/select/assembly.ts
3379
- var SIMPLE_SELECT_RE_CACHE = /* @__PURE__ */ new Map();
3380
- function normalizeFinalParams(params) {
3381
- return params.map(normalizeValue);
3382
- }
3549
+ var ALIAS_CAPTURE = "([A-Za-z_][A-Za-z0-9_]*)";
3550
+ var COLUMN_PART = '(?:"([^"]+)"|([a-z_][a-z0-9_]*))';
3551
+ var AS_PART = `(?:\\s+AS\\s+${COLUMN_PART})?`;
3552
+ var SIMPLE_COLUMN_PATTERN = `^${ALIAS_CAPTURE}\\.${COLUMN_PART}${AS_PART}$`;
3553
+ var SIMPLE_COLUMN_RE = new RegExp(SIMPLE_COLUMN_PATTERN, "i");
3383
3554
  function joinNonEmpty(parts, sep) {
3384
3555
  return parts.filter((s) => s.trim().length > 0).join(sep);
3385
3556
  }
3386
3557
  function buildWhereSql(conditions) {
3387
3558
  if (!isNonEmptyArray(conditions)) return "";
3388
- return ` ${SQL_TEMPLATES.WHERE} ${conditions.join(SQL_SEPARATORS.CONDITION_AND)}`;
3559
+ const parts = [
3560
+ SQL_TEMPLATES.WHERE,
3561
+ conditions.join(SQL_SEPARATORS.CONDITION_AND)
3562
+ ];
3563
+ return ` ${parts.join(" ")}`;
3389
3564
  }
3390
3565
  function buildJoinsSql(...joinGroups) {
3391
3566
  const all = [];
@@ -3400,37 +3575,39 @@ function buildSelectList(baseSelect, extraCols) {
3400
3575
  if (base && extra) return `${base}${SQL_SEPARATORS.FIELD_LIST}${extra}`;
3401
3576
  return base || extra;
3402
3577
  }
3403
- function finalizeSql(sql, params) {
3578
+ function finalizeSql(sql, params, dialect) {
3404
3579
  const snapshot = params.snapshot();
3405
3580
  validateSelectQuery(sql);
3406
- validateParamConsistency(sql, snapshot.params);
3581
+ validateParamConsistencyByDialect(sql, snapshot.params, dialect);
3407
3582
  return Object.freeze({
3408
3583
  sql,
3409
- params: normalizeFinalParams(snapshot.params),
3584
+ params: snapshot.params,
3410
3585
  paramMappings: Object.freeze(snapshot.mappings)
3411
3586
  });
3412
3587
  }
3413
- function parseSimpleScalarSelect(select, alias) {
3414
- var _a, _b;
3588
+ function parseSimpleScalarSelect(select, fromAlias) {
3589
+ var _a, _b, _c, _d;
3415
3590
  const raw = select.trim();
3416
3591
  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
3592
  const parts = raw.split(SQL_SEPARATORS.FIELD_LIST);
3424
3593
  const names = [];
3425
3594
  for (const part of parts) {
3426
3595
  const p = part.trim();
3427
- const m = p.match(re);
3596
+ const m = p.match(SIMPLE_COLUMN_RE);
3428
3597
  if (!m) {
3429
3598
  throw new Error(
3430
- `sqlite distinct emulation requires scalar select fields to be simple columns. Got: ${p}`
3599
+ `sqlite distinct emulation requires scalar select fields to be simple columns (optionally with AS). Got: ${p}`
3600
+ );
3601
+ }
3602
+ const actualAlias = m[1];
3603
+ if (actualAlias.toLowerCase() !== fromAlias.toLowerCase()) {
3604
+ throw new Error(
3605
+ `Expected alias '${fromAlias}', got '${actualAlias}' in: ${p}`
3431
3606
  );
3432
3607
  }
3433
- const name = ((_b = (_a = m[1]) != null ? _a : m[2]) != null ? _b : "").trim();
3608
+ const columnName = ((_b = (_a = m[2]) != null ? _a : m[3]) != null ? _b : "").trim();
3609
+ const outAlias = ((_d = (_c = m[4]) != null ? _c : m[5]) != null ? _d : "").trim();
3610
+ const name = outAlias.length > 0 ? outAlias : columnName;
3434
3611
  if (name.length === 0) {
3435
3612
  throw new Error(`Failed to parse selected column name from: ${p}`);
3436
3613
  }
@@ -3439,18 +3616,18 @@ function parseSimpleScalarSelect(select, alias) {
3439
3616
  return names;
3440
3617
  }
3441
3618
  function replaceOrderByAlias(orderBy, fromAlias, outerAlias) {
3442
- const needle = `${fromAlias}.`;
3443
- const replacement = `${outerAlias}.`;
3444
- return orderBy.split(needle).join(replacement);
3619
+ const src = String(fromAlias);
3620
+ if (src.length === 0) return orderBy;
3621
+ const escaped = src.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3622
+ const re = new RegExp(`\\b${escaped}\\.`, "gi");
3623
+ return orderBy.replace(re, `${outerAlias}.`);
3445
3624
  }
3446
3625
  function buildDistinctColumns(distinct, fromAlias, model) {
3447
3626
  return distinct.map((f) => col(fromAlias, f, model)).join(SQL_SEPARATORS.FIELD_LIST);
3448
3627
  }
3449
3628
  function buildOutputColumns(scalarNames, includeNames, hasCount) {
3450
3629
  const outputCols = [...scalarNames, ...includeNames];
3451
- if (hasCount) {
3452
- outputCols.push("_count");
3453
- }
3630
+ if (hasCount) outputCols.push("_count");
3454
3631
  const formatted = outputCols.map((n) => quote(n)).join(SQL_SEPARATORS.FIELD_LIST);
3455
3632
  if (!isNonEmptyString(formatted)) {
3456
3633
  throw new Error("distinct emulation requires at least one output column");
@@ -3459,9 +3636,10 @@ function buildOutputColumns(scalarNames, includeNames, hasCount) {
3459
3636
  }
3460
3637
  function buildWindowOrder(args) {
3461
3638
  const { baseOrder, idField, fromAlias, model } = args;
3639
+ const fromLower = String(fromAlias).toLowerCase();
3462
3640
  const orderFields = baseOrder.split(SQL_SEPARATORS.ORDER_BY).map((s) => s.trim().toLowerCase());
3463
3641
  const hasIdInOrder = orderFields.some(
3464
- (f) => f.startsWith(`${fromAlias}.id `) || f.startsWith(`${fromAlias}."id" `)
3642
+ (f) => f.startsWith(`${fromLower}.id `) || f.startsWith(`${fromLower}."id" `)
3465
3643
  );
3466
3644
  if (hasIdInOrder) return baseOrder;
3467
3645
  const idTiebreaker = idField ? `, ${col(fromAlias, "id", model)} ASC` : "";
@@ -3496,15 +3674,37 @@ function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
3496
3674
  const outerOrder = isNonEmptyString(orderBy) ? replaceOrderByAlias(orderBy, from.alias, `"__tp_distinct"`) : replaceOrderByAlias(fallbackOrder, from.alias, `"__tp_distinct"`);
3497
3675
  const joins = buildJoinsSql(whereJoins, countJoins);
3498
3676
  const conditions = [];
3499
- if (whereClause && whereClause !== "1=1") {
3500
- conditions.push(whereClause);
3501
- }
3677
+ if (whereClause && whereClause !== "1=1") conditions.push(whereClause);
3502
3678
  const whereSql = buildWhereSql(conditions);
3503
3679
  const innerSelectList = selectWithIncludes.trim();
3504
3680
  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;
3681
+ const innerParts = [
3682
+ SQL_TEMPLATES.SELECT,
3683
+ innerSelectList + innerComma,
3684
+ `ROW_NUMBER() OVER (PARTITION BY ${distinctCols} ORDER BY ${windowOrder})`,
3685
+ SQL_TEMPLATES.AS,
3686
+ '"__tp_rn"',
3687
+ SQL_TEMPLATES.FROM,
3688
+ from.table,
3689
+ from.alias
3690
+ ];
3691
+ if (joins) innerParts.push(joins);
3692
+ if (whereSql) innerParts.push(whereSql);
3693
+ const inner = innerParts.filter(Boolean).join(" ");
3694
+ const outerParts = [
3695
+ SQL_TEMPLATES.SELECT,
3696
+ outerSelectCols,
3697
+ SQL_TEMPLATES.FROM,
3698
+ `(${inner})`,
3699
+ SQL_TEMPLATES.AS,
3700
+ '"__tp_distinct"',
3701
+ SQL_TEMPLATES.WHERE,
3702
+ '"__tp_rn" = 1'
3703
+ ];
3704
+ if (isNonEmptyString(outerOrder)) {
3705
+ outerParts.push(SQL_TEMPLATES.ORDER_BY, outerOrder);
3706
+ }
3707
+ return outerParts.filter(Boolean).join(" ");
3508
3708
  }
3509
3709
  function buildIncludeColumns(spec) {
3510
3710
  var _a, _b;
@@ -3632,6 +3832,7 @@ function constructFinalSql(spec) {
3632
3832
  orderBy,
3633
3833
  distinct,
3634
3834
  method,
3835
+ cursorCte,
3635
3836
  cursorClause,
3636
3837
  params,
3637
3838
  dialect,
@@ -3646,9 +3847,13 @@ function constructFinalSql(spec) {
3646
3847
  const spec2 = withCountJoins(spec, countJoins, whereJoins);
3647
3848
  let sql2 = buildSqliteDistinctQuery(spec2, selectWithIncludes).trim();
3648
3849
  sql2 = appendPagination(sql2, spec);
3649
- return finalizeSql(sql2, params);
3850
+ return finalizeSql(sql2, params, dialect);
3650
3851
  }
3651
- const parts = [SQL_TEMPLATES.SELECT];
3852
+ const parts = [];
3853
+ if (cursorCte) {
3854
+ parts.push(`WITH ${cursorCte}`);
3855
+ }
3856
+ parts.push(SQL_TEMPLATES.SELECT);
3652
3857
  const distinctOn = dialect === "postgres" ? buildPostgresDistinctOnClause(from.alias, distinct, model) : null;
3653
3858
  if (distinctOn) parts.push(distinctOn);
3654
3859
  const baseSelect = (select != null ? select : "").trim();
@@ -3664,7 +3869,41 @@ function constructFinalSql(spec) {
3664
3869
  if (isNonEmptyString(orderBy)) parts.push(SQL_TEMPLATES.ORDER_BY, orderBy);
3665
3870
  let sql = parts.join(" ").trim();
3666
3871
  sql = appendPagination(sql, spec);
3667
- return finalizeSql(sql, params);
3872
+ return finalizeSql(sql, params, dialect);
3873
+ }
3874
+
3875
+ // src/builder/shared/validators/field-assertions.ts
3876
+ var NUMERIC_TYPES = /* @__PURE__ */ new Set(["Int", "Float", "Decimal", "BigInt"]);
3877
+ function assertScalarField(model, fieldName, context) {
3878
+ const field = getFieldInfo(model, fieldName);
3879
+ if (!field) {
3880
+ throw createError(
3881
+ `${context} references unknown field '${fieldName}' on model ${model.name}`,
3882
+ {
3883
+ field: fieldName,
3884
+ modelName: model.name,
3885
+ availableFields: model.fields.map((f) => f.name)
3886
+ }
3887
+ );
3888
+ }
3889
+ if (field.isRelation) {
3890
+ throw createError(
3891
+ `${context} does not support relation field '${fieldName}'`,
3892
+ { field: fieldName, modelName: model.name }
3893
+ );
3894
+ }
3895
+ return field;
3896
+ }
3897
+ function assertNumericField(model, fieldName, context) {
3898
+ const field = assertScalarField(model, fieldName, context);
3899
+ const baseType = field.type.replace(/\[\]|\?/g, "");
3900
+ if (!NUMERIC_TYPES.has(baseType)) {
3901
+ throw createError(
3902
+ `${context} requires numeric field, got '${field.type}'`,
3903
+ { field: fieldName, modelName: model.name }
3904
+ );
3905
+ }
3906
+ return field;
3668
3907
  }
3669
3908
 
3670
3909
  // src/builder/select.ts
@@ -3697,7 +3936,7 @@ function buildPostgresDistinctOrderBy(distinctFields, existing) {
3697
3936
  }
3698
3937
  return next;
3699
3938
  }
3700
- function applyPostgresDistinctOrderBy(args, _model) {
3939
+ function applyPostgresDistinctOrderBy(args) {
3701
3940
  const distinctFields = normalizeDistinctFields(args.distinct);
3702
3941
  if (distinctFields.length === 0) return args;
3703
3942
  if (!isNotNullish(args.orderBy)) return args;
@@ -3707,19 +3946,6 @@ function applyPostgresDistinctOrderBy(args, _model) {
3707
3946
  orderBy: buildPostgresDistinctOrderBy(distinctFields, existing)
3708
3947
  });
3709
3948
  }
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
3949
  function validateDistinct(model, distinct) {
3724
3950
  if (!isNotNullish(distinct) || !isNonEmptyArray(distinct)) return;
3725
3951
  const seen = /* @__PURE__ */ new Set();
@@ -3730,24 +3956,24 @@ function validateDistinct(model, distinct) {
3730
3956
  throw new Error(`distinct must not contain duplicates (field: '${f}')`);
3731
3957
  }
3732
3958
  seen.add(f);
3733
- assertScalarFieldOnModel(model, f, "distinct");
3959
+ assertScalarField(model, f, "distinct");
3734
3960
  }
3735
3961
  }
3736
- function validateOrderByValue(fieldName, v) {
3737
- parseOrderByValue(v, fieldName);
3738
- }
3739
3962
  function validateOrderBy(model, orderBy) {
3740
3963
  if (!isNotNullish(orderBy)) return;
3741
3964
  const items = normalizeOrderByInput2(orderBy);
3742
3965
  if (items.length === 0) return;
3743
3966
  for (const it of items) {
3744
3967
  const entries = Object.entries(it);
3968
+ if (entries.length !== 1) {
3969
+ throw new Error("orderBy array entries must have exactly one field");
3970
+ }
3745
3971
  const fieldName = String(entries[0][0]).trim();
3746
3972
  if (fieldName.length === 0) {
3747
3973
  throw new Error("orderBy field name cannot be empty");
3748
3974
  }
3749
- assertScalarFieldOnModel(model, fieldName, "orderBy");
3750
- validateOrderByValue(fieldName, entries[0][1]);
3975
+ assertScalarField(model, fieldName, "orderBy");
3976
+ parseOrderByValue(entries[0][1], fieldName);
3751
3977
  }
3752
3978
  }
3753
3979
  function validateCursor(model, cursor) {
@@ -3764,7 +3990,7 @@ function validateCursor(model, cursor) {
3764
3990
  if (f.length === 0) {
3765
3991
  throw new Error("cursor field name cannot be empty");
3766
3992
  }
3767
- assertScalarFieldOnModel(model, f, "cursor");
3993
+ assertScalarField(model, f, "cursor");
3768
3994
  }
3769
3995
  }
3770
3996
  function resolveDialect(dialect) {
@@ -3783,20 +4009,21 @@ function normalizeArgsForNegativeTake(method, args) {
3783
4009
  orderBy: reverseOrderByInput(args.orderBy)
3784
4010
  });
3785
4011
  }
3786
- function normalizeArgsForDialect(dialect, args, model) {
4012
+ function normalizeArgsForDialect(dialect, args) {
3787
4013
  if (dialect !== "postgres") return args;
3788
4014
  return applyPostgresDistinctOrderBy(args);
3789
4015
  }
3790
4016
  function buildCursorClauseIfAny(input) {
3791
- const { cursor, orderBy, tableName, alias, params, dialect } = input;
3792
- if (!isNotNullish(cursor)) return void 0;
4017
+ const { cursor, orderBy, tableName, alias, params, dialect, model } = input;
4018
+ if (!isNotNullish(cursor)) return {};
3793
4019
  return buildCursorCondition(
3794
4020
  cursor,
3795
4021
  orderBy,
3796
4022
  tableName,
3797
4023
  alias,
3798
4024
  params,
3799
- dialect
4025
+ dialect,
4026
+ model
3800
4027
  );
3801
4028
  }
3802
4029
  function buildSelectSpec(input) {
@@ -3835,14 +4062,20 @@ function buildSelectSpec(input) {
3835
4062
  params,
3836
4063
  dialect
3837
4064
  );
3838
- const cursorClause = buildCursorClauseIfAny({
4065
+ const cursorResult = buildCursorClauseIfAny({
3839
4066
  cursor,
3840
4067
  orderBy: normalizedArgs.orderBy,
3841
4068
  tableName,
3842
4069
  alias,
3843
4070
  params,
3844
- dialect
4071
+ dialect,
4072
+ model
3845
4073
  });
4074
+ if (dialect === "sqlite" && isNonEmptyArray(normalizedArgs.distinct) && cursorResult.condition) {
4075
+ throw new Error(
4076
+ "Cursor pagination with distinct is not supported in SQLite due to window function limitations. Use findMany with skip/take instead, or remove distinct."
4077
+ );
4078
+ }
3846
4079
  return {
3847
4080
  select: selectFields,
3848
4081
  includes,
@@ -3853,7 +4086,8 @@ function buildSelectSpec(input) {
3853
4086
  pagination: { take, skip },
3854
4087
  distinct: normalizedArgs.distinct,
3855
4088
  method,
3856
- cursorClause,
4089
+ cursorCte: cursorResult.cte,
4090
+ cursorClause: cursorResult.condition,
3857
4091
  params,
3858
4092
  dialect,
3859
4093
  model,
@@ -3867,9 +4101,7 @@ function buildSelectSql(input) {
3867
4101
  assertSafeTableRef(from.tableName);
3868
4102
  const dialectToUse = resolveDialect(dialect);
3869
4103
  const argsForSql = normalizeArgsForNegativeTake(method, args);
3870
- const normalizedArgs = normalizeArgsForDialect(
3871
- dialectToUse,
3872
- argsForSql);
4104
+ const normalizedArgs = normalizeArgsForDialect(dialectToUse, argsForSql);
3873
4105
  validateDistinct(model, normalizedArgs.distinct);
3874
4106
  validateOrderBy(model, normalizedArgs.orderBy);
3875
4107
  validateCursor(model, normalizedArgs.cursor);
@@ -3885,8 +4117,21 @@ function buildSelectSql(input) {
3885
4117
  });
3886
4118
  return constructFinalSql(spec);
3887
4119
  }
3888
- var MODEL_FIELD_CACHE = /* @__PURE__ */ new WeakMap();
3889
- var NUMERIC_TYPES = /* @__PURE__ */ new Set(["Int", "Float", "Decimal", "BigInt"]);
4120
+
4121
+ // src/builder/shared/comparison-builder.ts
4122
+ function buildComparisons(expr, filter, params, dialect, builder, excludeKeys = /* @__PURE__ */ new Set(["mode"])) {
4123
+ const out = [];
4124
+ for (const [op, val] of Object.entries(filter)) {
4125
+ if (excludeKeys.has(op) || val === void 0) continue;
4126
+ const built = builder(expr, op, val, params, dialect);
4127
+ if (built && built.trim().length > 0) {
4128
+ out.push(built);
4129
+ }
4130
+ }
4131
+ return out;
4132
+ }
4133
+
4134
+ // src/builder/aggregates.ts
3890
4135
  var AGGREGATES = [
3891
4136
  ["_sum", "SUM"],
3892
4137
  ["_avg", "AVG"],
@@ -3901,19 +4146,16 @@ var COMPARISON_OPS = {
3901
4146
  [Ops.LT]: "<",
3902
4147
  [Ops.LTE]: "<="
3903
4148
  };
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
- }
4149
+ var HAVING_ALLOWED_OPS = /* @__PURE__ */ new Set([
4150
+ Ops.EQUALS,
4151
+ Ops.NOT,
4152
+ Ops.GT,
4153
+ Ops.GTE,
4154
+ Ops.LT,
4155
+ Ops.LTE,
4156
+ Ops.IN,
4157
+ Ops.NOT_IN
4158
+ ]);
3917
4159
  function isTruthySelection(v) {
3918
4160
  return v === true;
3919
4161
  }
@@ -3955,24 +4197,10 @@ function normalizeLogicalValue2(operator, value) {
3955
4197
  }
3956
4198
  throw new Error(`${operator} must be an object or array of objects in HAVING`);
3957
4199
  }
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)) {
4200
+ function assertHavingOp(op) {
4201
+ if (!HAVING_ALLOWED_OPS.has(op)) {
3974
4202
  throw new Error(
3975
- `Cannot use ${aggKey} on non-numeric field '${fieldName}' (type: ${fieldType}) on model ${modelName}`
4203
+ `Unsupported HAVING operator '${op}'. Allowed: ${[...HAVING_ALLOWED_OPS].join(", ")}`
3976
4204
  );
3977
4205
  }
3978
4206
  }
@@ -4002,6 +4230,7 @@ function buildBinaryComparison(expr, op, val, params) {
4002
4230
  return `${expr} ${sqlOp} ${placeholder}`;
4003
4231
  }
4004
4232
  function buildSimpleComparison(expr, op, val, params, dialect) {
4233
+ assertHavingOp(op);
4005
4234
  if (val === null) return buildNullComparison(expr, op);
4006
4235
  if (op === Ops.NOT && isPlainObject(val)) {
4007
4236
  return buildNotComposite(
@@ -4090,20 +4319,19 @@ function assertHavingAggTarget(aggKey, field, model) {
4090
4319
  }
4091
4320
  return;
4092
4321
  }
4093
- const f = assertScalarField2(model, field, "HAVING");
4094
- assertAggregateFieldType(aggKey, f.type, f.name, model.name);
4322
+ if (aggKey === "_sum" || aggKey === "_avg") {
4323
+ assertNumericField(model, field, "HAVING");
4324
+ } else {
4325
+ assertScalarField(model, field, "HAVING");
4326
+ }
4095
4327
  }
4096
4328
  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;
4329
+ return buildComparisons(expr, filter, params, dialect, buildSimpleComparison);
4104
4330
  }
4105
4331
  function buildHavingForAggregateFirstShape(aggKey, target, alias, params, dialect, model) {
4106
- if (!isPlainObject(target)) return [];
4332
+ if (!isPlainObject(target)) {
4333
+ throw new Error(`HAVING '${aggKey}' must be an object`);
4334
+ }
4107
4335
  const out = [];
4108
4336
  for (const [field, filter] of Object.entries(target)) {
4109
4337
  assertHavingAggTarget(aggKey, field, model);
@@ -4114,30 +4342,39 @@ function buildHavingForAggregateFirstShape(aggKey, target, alias, params, dialec
4114
4342
  return out;
4115
4343
  }
4116
4344
  function buildHavingForFieldFirstShape(fieldName, target, alias, params, dialect, model) {
4117
- if (!isPlainObject(target)) return [];
4118
- const field = assertScalarField2(model, fieldName, "HAVING");
4345
+ if (!isPlainObject(target)) {
4346
+ throw new Error(`HAVING '${fieldName}' must be an object`);
4347
+ }
4348
+ assertScalarField(model, fieldName, "HAVING");
4119
4349
  const out = [];
4120
4350
  const obj = target;
4121
4351
  const keys = ["_count", "_sum", "_avg", "_min", "_max"];
4122
4352
  for (const aggKey of keys) {
4123
4353
  const aggFilter = obj[aggKey];
4124
4354
  if (!isPlainObject(aggFilter)) continue;
4125
- assertAggregateFieldType(aggKey, field.type, field.name, model.name);
4355
+ if (aggKey === "_sum" || aggKey === "_avg") {
4356
+ assertNumericField(model, fieldName, "HAVING");
4357
+ }
4126
4358
  const entries = Object.entries(aggFilter);
4127
4359
  if (entries.length === 0) continue;
4128
4360
  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
- }
4361
+ const clauses = buildComparisons(
4362
+ expr,
4363
+ aggFilter,
4364
+ params,
4365
+ dialect,
4366
+ buildSimpleComparison
4367
+ );
4368
+ out.push(...clauses);
4134
4369
  }
4135
4370
  return out;
4136
4371
  }
4137
4372
  function buildHavingClause(having, alias, params, model, dialect) {
4138
4373
  if (!isNotNullish(having)) return "";
4139
4374
  const d = dialect != null ? dialect : getGlobalDialect();
4140
- if (!isPlainObject(having)) return "";
4375
+ if (!isPlainObject(having)) {
4376
+ throw new Error("having must be an object");
4377
+ }
4141
4378
  return buildHavingNode(having, alias, params, d, model);
4142
4379
  }
4143
4380
  function normalizeCountArg(v) {
@@ -4151,18 +4388,8 @@ function pushCountAllField(fields) {
4151
4388
  `${SQL_TEMPLATES.COUNT_ALL} ${SQL_TEMPLATES.AS} ${quote("_count._all")}`
4152
4389
  );
4153
4390
  }
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
- }
4391
+ function assertCountableScalarField(model, fieldName) {
4392
+ assertScalarField(model, fieldName, "_count");
4166
4393
  }
4167
4394
  function pushCountField(fields, alias, fieldName, model) {
4168
4395
  const outAlias = `_count.${fieldName}`;
@@ -4170,7 +4397,7 @@ function pushCountField(fields, alias, fieldName, model) {
4170
4397
  `COUNT(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4171
4398
  );
4172
4399
  }
4173
- function addCountFields(fields, countArg, alias, model, fieldMap) {
4400
+ function addCountFields(fields, countArg, alias, model) {
4174
4401
  if (!isNotNullish(countArg)) return;
4175
4402
  if (countArg === true) {
4176
4403
  pushCountAllField(fields);
@@ -4184,7 +4411,7 @@ function addCountFields(fields, countArg, alias, model, fieldMap) {
4184
4411
  ([f, v]) => f !== "_all" && isTruthySelection(v)
4185
4412
  );
4186
4413
  for (const [f] of selected) {
4187
- assertCountableScalarField(fieldMap, model, f);
4414
+ assertCountableScalarField(model, f);
4188
4415
  pushCountField(fields, alias, f, model);
4189
4416
  }
4190
4417
  }
@@ -4192,19 +4419,12 @@ function getAggregateSelectionObject(args, agg) {
4192
4419
  const obj = args[agg];
4193
4420
  return isPlainObject(obj) ? obj : void 0;
4194
4421
  }
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
- );
4422
+ function assertAggregatableScalarField(model, agg, fieldName) {
4423
+ if (agg === "_sum" || agg === "_avg") {
4424
+ assertNumericField(model, fieldName, agg);
4425
+ } else {
4426
+ assertScalarField(model, fieldName, agg);
4206
4427
  }
4207
- return field;
4208
4428
  }
4209
4429
  function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
4210
4430
  const outAlias = `${agg}.${fieldName}`;
@@ -4212,7 +4432,7 @@ function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
4212
4432
  `${aggFn}(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4213
4433
  );
4214
4434
  }
4215
- function addAggregateFields(fields, args, alias, model, fieldMap) {
4435
+ function addAggregateFields(fields, args, alias, model) {
4216
4436
  for (const [agg, aggFn] of AGGREGATES) {
4217
4437
  const obj = getAggregateSelectionObject(args, agg);
4218
4438
  if (!obj) continue;
@@ -4220,23 +4440,16 @@ function addAggregateFields(fields, args, alias, model, fieldMap) {
4220
4440
  if (fieldName === "_all")
4221
4441
  throw new Error(`'${agg}' does not support '_all'`);
4222
4442
  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);
4443
+ assertAggregatableScalarField(model, agg, fieldName);
4230
4444
  pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model);
4231
4445
  }
4232
4446
  }
4233
4447
  }
4234
4448
  function buildAggregateFields(args, alias, model) {
4235
4449
  const fields = [];
4236
- const fieldMap = getModelFieldMap(model);
4237
4450
  const countArg = normalizeCountArg(args._count);
4238
- addCountFields(fields, countArg, alias, model, fieldMap);
4239
- addAggregateFields(fields, args, alias, model, fieldMap);
4451
+ addCountFields(fields, countArg, alias, model);
4452
+ addAggregateFields(fields, args, alias, model);
4240
4453
  return fields;
4241
4454
  }
4242
4455
  function buildAggregateSql(args, whereResult, tableName, alias, model) {
@@ -4260,7 +4473,7 @@ function buildAggregateSql(args, whereResult, tableName, alias, model) {
4260
4473
  validateParamConsistency(sql, whereResult.params);
4261
4474
  return Object.freeze({
4262
4475
  sql,
4263
- params: Object.freeze(normalizeFinalParams2([...whereResult.params])),
4476
+ params: Object.freeze([...whereResult.params]),
4264
4477
  paramMappings: Object.freeze([...whereResult.paramMappings])
4265
4478
  });
4266
4479
  }
@@ -4273,32 +4486,24 @@ function assertGroupByBy(args, model) {
4273
4486
  if (bySet.size !== byFields.length) {
4274
4487
  throw new Error("buildGroupBySql: by must not contain duplicates");
4275
4488
  }
4276
- const modelFieldMap = getModelFieldMap(model);
4277
4489
  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
- }
4490
+ assertScalarField(model, f, "groupBy.by");
4289
4491
  }
4290
4492
  return byFields;
4291
4493
  }
4292
4494
  function buildGroupBySelectParts(args, alias, model, byFields) {
4293
4495
  const groupCols = byFields.map((f) => col(alias, f, model));
4496
+ const selectCols = byFields.map((f) => colWithAlias(alias, f, model));
4294
4497
  const groupFields = groupCols.join(SQL_SEPARATORS.FIELD_LIST);
4295
4498
  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);
4499
+ const selectFields = isNonEmptyArray(aggFields) ? selectCols.concat(aggFields).join(SQL_SEPARATORS.FIELD_LIST) : selectCols.join(SQL_SEPARATORS.FIELD_LIST);
4297
4500
  return { groupCols, groupFields, selectFields };
4298
4501
  }
4299
4502
  function buildGroupByHaving(args, alias, params, model, dialect) {
4300
4503
  if (!isNotNullish(args.having)) return "";
4301
- if (!isPlainObject(args.having)) return "";
4504
+ if (!isPlainObject(args.having)) {
4505
+ throw new Error("having must be an object");
4506
+ }
4302
4507
  const h = buildHavingClause(args.having, alias, params, model, dialect);
4303
4508
  if (!h || h.trim().length === 0) return "";
4304
4509
  return `${SQL_TEMPLATES.HAVING} ${h}`;
@@ -4334,61 +4539,58 @@ function buildGroupBySql(args, whereResult, tableName, alias, model, dialect) {
4334
4539
  const mergedParams = [...whereResult.params, ...snapshot.params];
4335
4540
  return Object.freeze({
4336
4541
  sql,
4337
- params: Object.freeze(normalizeFinalParams2(mergedParams)),
4542
+ params: Object.freeze(mergedParams),
4338
4543
  paramMappings: Object.freeze([
4339
4544
  ...whereResult.paramMappings,
4340
4545
  ...snapshot.mappings
4341
4546
  ])
4342
4547
  });
4343
4548
  }
4344
- function buildCountSql(whereResult, tableName, alias, skip, dialect) {
4549
+ function buildCountSql(whereResult, tableName, alias, skip, _dialect) {
4345
4550
  assertSafeAlias(alias);
4346
4551
  assertSafeTableRef(tableName);
4347
- const d = getGlobalDialect();
4552
+ if (skip !== void 0 && skip !== null) {
4553
+ if (schemaParser.isDynamicParameter(skip)) {
4554
+ throw new Error(
4555
+ "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."
4556
+ );
4557
+ }
4558
+ if (typeof skip === "string") {
4559
+ const s = skip.trim();
4560
+ if (s.length > 0) {
4561
+ const n = Number(s);
4562
+ if (Number.isFinite(n) && Number.isInteger(n) && n > 0) {
4563
+ throw new Error(
4564
+ "count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
4565
+ );
4566
+ }
4567
+ }
4568
+ }
4569
+ if (typeof skip === "number" && Number.isFinite(skip) && Number.isInteger(skip) && skip > 0) {
4570
+ throw new Error(
4571
+ "count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
4572
+ );
4573
+ }
4574
+ }
4348
4575
  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
4576
  const sql = [
4361
4577
  SQL_TEMPLATES.SELECT,
4362
4578
  SQL_TEMPLATES.COUNT_ALL,
4363
4579
  SQL_TEMPLATES.AS,
4364
4580
  quote("_count._all"),
4365
4581
  SQL_TEMPLATES.FROM,
4366
- `(${subSelect})`,
4367
- SQL_TEMPLATES.AS,
4368
- `"sub"`
4582
+ tableName,
4583
+ alias,
4584
+ whereClause
4369
4585
  ].filter((x) => x && String(x).trim().length > 0).join(" ").trim();
4370
4586
  validateSelectQuery(sql);
4371
- const snapshot = params.snapshot();
4372
- const mergedParams = [...whereResult.params, ...snapshot.params];
4373
- validateParamConsistency(sql, mergedParams);
4587
+ validateParamConsistency(sql, whereResult.params);
4374
4588
  return Object.freeze({
4375
4589
  sql,
4376
- params: Object.freeze(normalizeFinalParams2(mergedParams)),
4377
- paramMappings: Object.freeze([
4378
- ...whereResult.paramMappings,
4379
- ...snapshot.mappings
4380
- ])
4590
+ params: Object.freeze([...whereResult.params]),
4591
+ paramMappings: Object.freeze([...whereResult.paramMappings])
4381
4592
  });
4382
4593
  }
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
4594
  function safeAlias(input) {
4393
4595
  const raw = String(input).toLowerCase();
4394
4596
  const cleaned = raw.replace(/[^a-z0-9_]/g, "_");
@@ -4538,8 +4740,12 @@ function buildAndNormalizeSql(args) {
4538
4740
  );
4539
4741
  }
4540
4742
  function finalizeDirective(args) {
4541
- const { directive, normalizedSql, normalizedMappings } = args;
4542
- validateSqlPositions(normalizedSql, normalizedMappings, getGlobalDialect());
4743
+ const { directive, normalizedSql, normalizedMappings, dialect } = args;
4744
+ const params = normalizedMappings.map((m) => {
4745
+ var _a;
4746
+ return (_a = m.value) != null ? _a : void 0;
4747
+ });
4748
+ validateParamConsistencyByDialect(normalizedSql, params, dialect);
4543
4749
  const { staticParams, dynamicKeys } = buildParamsFromMappings(normalizedMappings);
4544
4750
  return {
4545
4751
  method: directive.method,
@@ -4576,7 +4782,8 @@ function generateSQL(directive) {
4576
4782
  return finalizeDirective({
4577
4783
  directive,
4578
4784
  normalizedSql: normalized.sql,
4579
- normalizedMappings: normalized.paramMappings
4785
+ normalizedMappings: normalized.paramMappings,
4786
+ dialect
4580
4787
  });
4581
4788
  }
4582
4789
  function generateSQL2(directive) {
@@ -4684,18 +4891,57 @@ function generateCode(models, queries, dialect, datamodel) {
4684
4891
  return `// Generated by @prisma-sql/generator - DO NOT EDIT
4685
4892
  import { buildSQL, transformQueryResults, type PrismaMethod, type Model } from 'prisma-sql'
4686
4893
 
4687
- function normalizeValue(value: unknown): unknown {
4894
+ /**
4895
+ * Normalize values for SQL params.
4896
+ * Synced from src/utils/normalize-value.ts
4897
+ */
4898
+ function normalizeValue(value: unknown, seen = new WeakSet<object>(), depth = 0): unknown {
4899
+ const MAX_DEPTH = 20
4900
+ if (depth > MAX_DEPTH) {
4901
+ throw new Error(\`Max normalization depth exceeded (\${MAX_DEPTH} levels)\`)
4902
+ }
4688
4903
  if (value instanceof Date) {
4904
+ const t = value.getTime()
4905
+ if (!Number.isFinite(t)) {
4906
+ throw new Error('Invalid Date value in SQL params')
4907
+ }
4689
4908
  return value.toISOString()
4690
4909
  }
4691
-
4910
+ if (typeof value === 'bigint') {
4911
+ return value.toString()
4912
+ }
4692
4913
  if (Array.isArray(value)) {
4693
- return value.map(normalizeValue)
4914
+ const arrRef = value as unknown as object
4915
+ if (seen.has(arrRef)) {
4916
+ throw new Error('Circular reference in SQL params')
4917
+ }
4918
+ seen.add(arrRef)
4919
+ const out = value.map((v) => normalizeValue(v, seen, depth + 1))
4920
+ seen.delete(arrRef)
4921
+ return out
4922
+ }
4923
+ if (value && typeof value === 'object') {
4924
+ if (value instanceof Uint8Array) return value
4925
+ if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) return value
4926
+ const proto = Object.getPrototypeOf(value)
4927
+ const isPlain = proto === Object.prototype || proto === null
4928
+ if (!isPlain) return value
4929
+ const obj = value as Record<string, unknown>
4930
+ if (seen.has(obj)) {
4931
+ throw new Error('Circular reference in SQL params')
4932
+ }
4933
+ seen.add(obj)
4934
+ const out: Record<string, unknown> = {}
4935
+ for (const [k, v] of Object.entries(obj)) {
4936
+ out[k] = normalizeValue(v, seen, depth + 1)
4937
+ }
4938
+ seen.delete(obj)
4939
+ return out
4694
4940
  }
4695
-
4696
4941
  return value
4697
4942
  }
4698
4943
 
4944
+
4699
4945
  export const MODELS: Model[] = ${JSON.stringify(cleanModels, null, 2)}
4700
4946
 
4701
4947
  const ENUM_MAPPINGS: Record<string, Record<string, string>> = ${JSON.stringify(mappings, null, 2)}