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.
package/dist/generator.js CHANGED
@@ -54,7 +54,7 @@ var require_package = __commonJS({
54
54
  "package.json"(exports$1, module) {
55
55
  module.exports = {
56
56
  name: "prisma-sql",
57
- version: "1.44.0",
57
+ version: "1.45.0",
58
58
  description: "Convert Prisma queries to optimized SQL with type safety. 2-7x faster than Prisma Client.",
59
59
  main: "dist/index.cjs",
60
60
  module: "dist/index.js",
@@ -157,20 +157,13 @@ var SQL_SEPARATORS = Object.freeze({
157
157
  CONDITION_OR: " OR ",
158
158
  ORDER_BY: ", "
159
159
  });
160
- var SQL_RESERVED_WORDS = /* @__PURE__ */ new Set([
160
+ var ALIAS_FORBIDDEN_KEYWORDS = /* @__PURE__ */ new Set([
161
161
  "select",
162
162
  "from",
163
163
  "where",
164
- "and",
165
- "or",
166
- "not",
167
- "in",
168
- "like",
169
- "between",
164
+ "having",
170
165
  "order",
171
- "by",
172
166
  "group",
173
- "having",
174
167
  "limit",
175
168
  "offset",
176
169
  "join",
@@ -178,14 +171,42 @@ var SQL_RESERVED_WORDS = /* @__PURE__ */ new Set([
178
171
  "left",
179
172
  "right",
180
173
  "outer",
181
- "on",
174
+ "cross",
175
+ "full",
176
+ "and",
177
+ "or",
178
+ "not",
179
+ "by",
182
180
  "as",
181
+ "on",
182
+ "union",
183
+ "intersect",
184
+ "except",
185
+ "case",
186
+ "when",
187
+ "then",
188
+ "else",
189
+ "end"
190
+ ]);
191
+ var SQL_KEYWORDS = /* @__PURE__ */ new Set([
192
+ ...ALIAS_FORBIDDEN_KEYWORDS,
193
+ "user",
194
+ "users",
183
195
  "table",
184
196
  "column",
185
197
  "index",
186
- "user",
187
- "users",
188
198
  "values",
199
+ "in",
200
+ "like",
201
+ "between",
202
+ "is",
203
+ "exists",
204
+ "null",
205
+ "true",
206
+ "false",
207
+ "all",
208
+ "any",
209
+ "some",
189
210
  "update",
190
211
  "insert",
191
212
  "delete",
@@ -196,25 +217,9 @@ var SQL_RESERVED_WORDS = /* @__PURE__ */ new Set([
196
217
  "grant",
197
218
  "revoke",
198
219
  "exec",
199
- "execute",
200
- "union",
201
- "intersect",
202
- "except",
203
- "case",
204
- "when",
205
- "then",
206
- "else",
207
- "end",
208
- "null",
209
- "true",
210
- "false",
211
- "is",
212
- "exists",
213
- "all",
214
- "any",
215
- "some"
220
+ "execute"
216
221
  ]);
217
- var SQL_KEYWORDS = SQL_RESERVED_WORDS;
222
+ var SQL_RESERVED_WORDS = SQL_KEYWORDS;
218
223
  var DEFAULT_WHERE_CLAUSE = "1=1";
219
224
  var SPECIAL_FIELDS = Object.freeze({
220
225
  ID: "id"
@@ -293,12 +298,48 @@ var LIMITS = Object.freeze({
293
298
  });
294
299
 
295
300
  // src/utils/normalize-value.ts
296
- function normalizeValue(value) {
301
+ var MAX_DEPTH = 20;
302
+ function normalizeValue(value, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
303
+ if (depth > MAX_DEPTH) {
304
+ throw new Error(`Max normalization depth exceeded (${MAX_DEPTH} levels)`);
305
+ }
297
306
  if (value instanceof Date) {
307
+ const t = value.getTime();
308
+ if (!Number.isFinite(t)) {
309
+ throw new Error("Invalid Date value in SQL params");
310
+ }
298
311
  return value.toISOString();
299
312
  }
313
+ if (typeof value === "bigint") {
314
+ return value.toString();
315
+ }
300
316
  if (Array.isArray(value)) {
301
- return value.map(normalizeValue);
317
+ const arrRef = value;
318
+ if (seen.has(arrRef)) {
319
+ throw new Error("Circular reference in SQL params");
320
+ }
321
+ seen.add(arrRef);
322
+ const out = value.map((v) => normalizeValue(v, seen, depth + 1));
323
+ seen.delete(arrRef);
324
+ return out;
325
+ }
326
+ if (value && typeof value === "object") {
327
+ if (value instanceof Uint8Array) return value;
328
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) return value;
329
+ const proto = Object.getPrototypeOf(value);
330
+ const isPlain = proto === Object.prototype || proto === null;
331
+ if (!isPlain) return value;
332
+ const obj = value;
333
+ if (seen.has(obj)) {
334
+ throw new Error("Circular reference in SQL params");
335
+ }
336
+ seen.add(obj);
337
+ const out = {};
338
+ for (const [k, v] of Object.entries(obj)) {
339
+ out[k] = normalizeValue(v, seen, depth + 1);
340
+ }
341
+ seen.delete(obj);
342
+ return out;
302
343
  }
303
344
  return value;
304
345
  }
@@ -479,7 +520,7 @@ function prepareArrayParam(value, dialect) {
479
520
  throw new Error("prepareArrayParam requires array value");
480
521
  }
481
522
  if (dialect === "postgres") {
482
- return value.map(normalizeValue);
523
+ return value.map((v) => normalizeValue(v));
483
524
  }
484
525
  return JSON.stringify(value);
485
526
  }
@@ -551,36 +592,46 @@ function createError(message, ctx, code = "VALIDATION_ERROR") {
551
592
  }
552
593
 
553
594
  // src/builder/shared/model-field-cache.ts
554
- var SCALAR_SET_CACHE = /* @__PURE__ */ new WeakMap();
555
- var RELATION_SET_CACHE = /* @__PURE__ */ new WeakMap();
595
+ var MODEL_CACHE = /* @__PURE__ */ new WeakMap();
596
+ function ensureFullCache(model) {
597
+ let cache = MODEL_CACHE.get(model);
598
+ if (!cache) {
599
+ const fieldInfo = /* @__PURE__ */ new Map();
600
+ const scalarFields = /* @__PURE__ */ new Set();
601
+ const relationFields = /* @__PURE__ */ new Set();
602
+ const columnMap = /* @__PURE__ */ new Map();
603
+ for (const f of model.fields) {
604
+ const info = {
605
+ name: f.name,
606
+ dbName: f.dbName || f.name,
607
+ type: f.type,
608
+ isRelation: !!f.isRelation,
609
+ isRequired: !!f.isRequired
610
+ };
611
+ fieldInfo.set(f.name, info);
612
+ if (info.isRelation) {
613
+ relationFields.add(f.name);
614
+ } else {
615
+ scalarFields.add(f.name);
616
+ columnMap.set(f.name, info.dbName);
617
+ }
618
+ }
619
+ cache = { fieldInfo, scalarFields, relationFields, columnMap };
620
+ MODEL_CACHE.set(model, cache);
621
+ }
622
+ return cache;
623
+ }
624
+ function getFieldInfo(model, fieldName) {
625
+ return ensureFullCache(model).fieldInfo.get(fieldName);
626
+ }
556
627
  function getScalarFieldSet(model) {
557
- const cached = SCALAR_SET_CACHE.get(model);
558
- if (cached) return cached;
559
- const s = /* @__PURE__ */ new Set();
560
- for (const f of model.fields) if (!f.isRelation) s.add(f.name);
561
- SCALAR_SET_CACHE.set(model, s);
562
- return s;
628
+ return ensureFullCache(model).scalarFields;
563
629
  }
564
630
  function getRelationFieldSet(model) {
565
- const cached = RELATION_SET_CACHE.get(model);
566
- if (cached) return cached;
567
- const s = /* @__PURE__ */ new Set();
568
- for (const f of model.fields) if (f.isRelation) s.add(f.name);
569
- RELATION_SET_CACHE.set(model, s);
570
- return s;
571
- }
572
- var COLUMN_MAP_CACHE = /* @__PURE__ */ new WeakMap();
631
+ return ensureFullCache(model).relationFields;
632
+ }
573
633
  function getColumnMap(model) {
574
- const cached = COLUMN_MAP_CACHE.get(model);
575
- if (cached) return cached;
576
- const map = /* @__PURE__ */ new Map();
577
- for (const f of model.fields) {
578
- if (!f.isRelation) {
579
- map.set(f.name, f.dbName || f.name);
580
- }
581
- }
582
- COLUMN_MAP_CACHE.set(model, map);
583
- return map;
634
+ return ensureFullCache(model).columnMap;
584
635
  }
585
636
 
586
637
  // src/builder/shared/validators/sql-validators.ts
@@ -640,7 +691,7 @@ function scanDollarPlaceholders(sql, markUpTo) {
640
691
  }
641
692
  return { count, min, max, seen };
642
693
  }
643
- function assertNoGaps(scan, rangeMin, rangeMax, sql) {
694
+ function assertNoGapsDollar(scan, rangeMin, rangeMax, sql) {
644
695
  for (let k = rangeMin; k <= rangeMax; k++) {
645
696
  if (scan.seen[k] !== 1) {
646
697
  throw new Error(
@@ -668,7 +719,7 @@ function validateParamConsistency(sql, params) {
668
719
  `CRITICAL: Parameter mismatch - SQL max placeholder is $${scan.max} but ${paramLen} params provided. This will cause SQL execution to fail. SQL: ${sqlPreview(sql)}`
669
720
  );
670
721
  }
671
- assertNoGaps(scan, 1, scan.max, sql);
722
+ assertNoGapsDollar(scan, 1, scan.max, sql);
672
723
  }
673
724
  function needsQuoting(id) {
674
725
  if (!isNonEmptyString(id)) return true;
@@ -686,14 +737,11 @@ function validateParamConsistencyFragment(sql, params) {
686
737
  `CRITICAL: Parameter mismatch - SQL references $${scan.max} but only ${paramLen} params provided. SQL: ${sqlPreview(sql)}`
687
738
  );
688
739
  }
689
- assertNoGaps(scan, scan.min, scan.max, sql);
740
+ assertNoGapsDollar(scan, scan.min, scan.max, sql);
690
741
  }
691
742
  function assertOrThrow(condition, message) {
692
743
  if (!condition) throw new Error(message);
693
744
  }
694
- function dialectPlaceholderPrefix(dialect) {
695
- return dialect === "sqlite" ? "?" : "$";
696
- }
697
745
  function parseSqlitePlaceholderIndices(sql) {
698
746
  const re = /\?(?:(\d+))?/g;
699
747
  const indices = [];
@@ -713,112 +761,70 @@ function parseSqlitePlaceholderIndices(sql) {
713
761
  }
714
762
  return { indices, sawNumbered, sawAnonymous };
715
763
  }
716
- function parseDollarPlaceholderIndices(sql) {
717
- const re = /\$(\d+)/g;
718
- const indices = [];
719
- for (const m of sql.matchAll(re)) indices.push(parseInt(m[1], 10));
720
- return indices;
721
- }
722
- function getPlaceholderIndices(sql, dialect) {
723
- if (dialect === "sqlite") return parseSqlitePlaceholderIndices(sql);
724
- return {
725
- indices: parseDollarPlaceholderIndices(sql),
726
- sawNumbered: false,
727
- sawAnonymous: false
728
- };
729
- }
730
764
  function maxIndex(indices) {
731
765
  return indices.length > 0 ? Math.max(...indices) : 0;
732
766
  }
733
- function ensureNoMixedSqlitePlaceholders(sawNumbered, sawAnonymous) {
734
- assertOrThrow(
735
- !(sawNumbered && sawAnonymous),
736
- `CRITICAL: Mixed sqlite placeholders ('?' and '?NNN') are not supported.`
737
- );
738
- }
739
- function ensurePlaceholderMaxMatchesMappingsLength(max, mappingsLength, dialect) {
740
- assertOrThrow(
741
- max === mappingsLength,
742
- `CRITICAL: SQL placeholder max mismatch - max is ${dialectPlaceholderPrefix(dialect)}${max}, but mappings length is ${mappingsLength}.`
743
- );
744
- }
745
- function ensureSequentialPlaceholders(placeholders, max, dialect) {
746
- const prefix = dialectPlaceholderPrefix(dialect);
767
+ function ensureSequentialIndices(seen, max, prefix) {
747
768
  for (let i = 1; i <= max; i++) {
748
769
  assertOrThrow(
749
- placeholders.has(i),
770
+ seen.has(i),
750
771
  `CRITICAL: Missing SQL placeholder ${prefix}${i} - placeholders must be sequential 1..${max}.`
751
772
  );
752
773
  }
753
774
  }
754
- function validateMappingIndex(mapping, max) {
755
- assertOrThrow(
756
- Number.isInteger(mapping.index) && mapping.index >= 1 && mapping.index <= max,
757
- `CRITICAL: ParamMapping index ${mapping.index} out of range 1..${max}.`
758
- );
759
- }
760
- function ensureUniqueMappingIndex(mappingIndices, index, dialect) {
775
+ function validateSqlitePlaceholders(sql, params) {
776
+ const paramLen = params.length;
777
+ const { indices, sawNumbered, sawAnonymous } = parseSqlitePlaceholderIndices(sql);
778
+ if (indices.length === 0) {
779
+ if (paramLen !== 0) {
780
+ throw new Error(
781
+ `CRITICAL: Parameter mismatch - SQL has no sqlite placeholders but ${paramLen} params provided. SQL: ${sqlPreview(sql)}`
782
+ );
783
+ }
784
+ return;
785
+ }
761
786
  assertOrThrow(
762
- !mappingIndices.has(index),
763
- `CRITICAL: Duplicate ParamMapping index ${index} - each placeholder index must map to exactly one ParamMap.`
787
+ !(sawNumbered && sawAnonymous),
788
+ `CRITICAL: Mixed sqlite placeholders ('?' and '?NNN') are not supported.`
764
789
  );
765
- mappingIndices.add(index);
766
- }
767
- function ensureMappingIndexExistsInSql(placeholders, index) {
790
+ const max = maxIndex(indices);
768
791
  assertOrThrow(
769
- placeholders.has(index),
770
- `CRITICAL: ParamMapping index ${index} not found in SQL placeholders.`
792
+ max === paramLen,
793
+ `CRITICAL: SQL placeholder max mismatch - max is ?${max}, but params length is ${paramLen}. SQL: ${sqlPreview(sql)}`
771
794
  );
795
+ const set = new Set(indices);
796
+ ensureSequentialIndices(set, max, "?");
772
797
  }
773
- function validateMappingValueShape(mapping) {
774
- assertOrThrow(
775
- !(mapping.dynamicName !== void 0 && mapping.value !== void 0),
776
- `CRITICAL: ParamMap ${mapping.index} has both dynamicName and value`
777
- );
778
- assertOrThrow(
779
- !(mapping.dynamicName === void 0 && mapping.value === void 0),
780
- `CRITICAL: ParamMap ${mapping.index} has neither dynamicName nor value`
781
- );
798
+ function validateDollarPlaceholders(sql, params) {
799
+ validateParamConsistency(sql, params);
782
800
  }
783
- function ensureMappingsCoverAllIndices(mappingIndices, max, dialect) {
784
- const prefix = dialectPlaceholderPrefix(dialect);
785
- for (let i = 1; i <= max; i++) {
786
- assertOrThrow(
787
- mappingIndices.has(i),
788
- `CRITICAL: Missing ParamMap for placeholder ${prefix}${i} - mappings must cover 1..${max} with no gaps.`
789
- );
790
- }
801
+ function detectPlaceholderStyle(sql) {
802
+ const hasDollar = /\$\d+/.test(sql);
803
+ const hasSqliteQ = /\?(?:\d+)?/.test(sql);
804
+ return { hasDollar, hasSqliteQ };
791
805
  }
792
- function validateMappingsAgainstPlaceholders(mappings, placeholders, max, dialect) {
793
- const mappingIndices = /* @__PURE__ */ new Set();
794
- for (const mapping of mappings) {
795
- validateMappingIndex(mapping, max);
796
- ensureUniqueMappingIndex(mappingIndices, mapping.index);
797
- ensureMappingIndexExistsInSql(placeholders, mapping.index);
798
- validateMappingValueShape(mapping);
806
+ function validateParamConsistencyByDialect(sql, params, dialect) {
807
+ const { hasDollar, hasSqliteQ } = detectPlaceholderStyle(sql);
808
+ if (dialect !== "sqlite") {
809
+ if (hasSqliteQ && !hasDollar) {
810
+ throw new Error(
811
+ `CRITICAL: Non-sqlite dialect query contains sqlite '?' placeholders. SQL: ${sqlPreview(sql)}`
812
+ );
813
+ }
814
+ return validateDollarPlaceholders(sql, params);
799
815
  }
800
- ensureMappingsCoverAllIndices(mappingIndices, max, dialect);
801
- }
802
- function validateSqlPositions(sql, mappings, dialect) {
803
- const { indices, sawNumbered, sawAnonymous } = getPlaceholderIndices(
804
- sql,
805
- dialect
806
- );
807
- if (dialect === "sqlite") {
808
- ensureNoMixedSqlitePlaceholders(sawNumbered, sawAnonymous);
816
+ if (hasDollar && hasSqliteQ) {
817
+ throw new Error(
818
+ `CRITICAL: Mixed placeholder styles ($N and ?/ ?NNN) are not supported. SQL: ${sqlPreview(sql)}`
819
+ );
809
820
  }
810
- const placeholders = new Set(indices);
811
- if (placeholders.size === 0 && mappings.length === 0) return;
812
- const max = maxIndex(indices);
813
- ensurePlaceholderMaxMatchesMappingsLength(max, mappings.length, dialect);
814
- ensureSequentialPlaceholders(placeholders, max, dialect);
815
- validateMappingsAgainstPlaceholders(mappings, placeholders, max, dialect);
821
+ if (hasSqliteQ) return validateSqlitePlaceholders(sql, params);
822
+ return validateDollarPlaceholders(sql, params);
816
823
  }
817
824
 
818
825
  // src/builder/shared/sql-utils.ts
819
- var NUL = String.fromCharCode(0);
820
826
  function containsControlChars(s) {
821
- return s.includes(NUL) || s.includes("\n") || s.includes("\r");
827
+ return /[\u0000-\u001F\u007F]/.test(s);
822
828
  }
823
829
  function assertNoControlChars(label, s) {
824
830
  if (containsControlChars(s)) {
@@ -1037,16 +1043,46 @@ function buildTableReference(schemaName, tableName, dialect) {
1037
1043
  return `"${safeSchema}"."${safeTable}"`;
1038
1044
  }
1039
1045
  function assertSafeAlias(alias) {
1040
- const a = String(alias);
1041
- if (a.trim().length === 0) {
1042
- throw new Error("alias is required and cannot be empty");
1046
+ if (typeof alias !== "string") {
1047
+ throw new Error(`Invalid alias: expected string, got ${typeof alias}`);
1048
+ }
1049
+ const a = alias.trim();
1050
+ if (a.length === 0) {
1051
+ throw new Error("Invalid alias: required and cannot be empty");
1043
1052
  }
1044
- if (containsControlChars(a) || a.includes(";")) {
1045
- throw new Error(`alias contains unsafe characters: ${JSON.stringify(a)}`);
1053
+ if (a !== alias) {
1054
+ throw new Error("Invalid alias: leading/trailing whitespace");
1046
1055
  }
1047
- if (!/^[A-Za-z_]\w*$/.test(a)) {
1056
+ if (/[\u0000-\u001F\u007F]/.test(a)) {
1048
1057
  throw new Error(
1049
- `alias must be a simple identifier, got: ${JSON.stringify(a)}`
1058
+ "Invalid alias: contains unsafe characters (control characters)"
1059
+ );
1060
+ }
1061
+ if (a.includes('"') || a.includes("'") || a.includes("`")) {
1062
+ throw new Error("Invalid alias: contains unsafe characters (quotes)");
1063
+ }
1064
+ if (a.includes(";")) {
1065
+ throw new Error("Invalid alias: contains unsafe characters (semicolon)");
1066
+ }
1067
+ if (a.includes("--") || a.includes("/*") || a.includes("*/")) {
1068
+ throw new Error(
1069
+ "Invalid alias: contains unsafe characters (SQL comment tokens)"
1070
+ );
1071
+ }
1072
+ if (/\s/.test(a)) {
1073
+ throw new Error(
1074
+ "Invalid alias: must be a simple identifier without whitespace"
1075
+ );
1076
+ }
1077
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(a)) {
1078
+ throw new Error(
1079
+ `Invalid alias: must be a simple identifier (alphanumeric with underscores): "${alias}"`
1080
+ );
1081
+ }
1082
+ const lowered = a.toLowerCase();
1083
+ if (ALIAS_FORBIDDEN_KEYWORDS.has(lowered)) {
1084
+ throw new Error(
1085
+ `Invalid alias: '${alias}' is a SQL keyword that would break query parsing. Forbidden aliases: ${[...ALIAS_FORBIDDEN_KEYWORDS].join(", ")}`
1050
1086
  );
1051
1087
  }
1052
1088
  }
@@ -1088,7 +1124,9 @@ function isValidRelationField(field) {
1088
1124
  if (fk.length === 0) return false;
1089
1125
  const refsRaw = field.references;
1090
1126
  const refs = normalizeKeyList(refsRaw);
1091
- if (refs.length === 0) return false;
1127
+ if (refs.length === 0) {
1128
+ return fk.length === 1;
1129
+ }
1092
1130
  if (refs.length !== fk.length) return false;
1093
1131
  return true;
1094
1132
  }
@@ -1103,6 +1141,8 @@ function getReferenceFieldNames(field, foreignKeyCount) {
1103
1141
  return refs;
1104
1142
  }
1105
1143
  function joinCondition(field, parentModel, childModel, parentAlias, childAlias) {
1144
+ assertSafeAlias(parentAlias);
1145
+ assertSafeAlias(childAlias);
1106
1146
  const fkFields = normalizeKeyList(field.foreignKey);
1107
1147
  if (fkFields.length === 0) {
1108
1148
  throw createError(
@@ -1252,6 +1292,32 @@ function normalizeOrderByInput(orderBy, parseValue) {
1252
1292
  throw new Error("orderBy must be an object or array of objects");
1253
1293
  }
1254
1294
 
1295
+ // src/builder/shared/order-by-determinism.ts
1296
+ function modelHasScalarId(model) {
1297
+ if (!model) return false;
1298
+ return getScalarFieldSet(model).has("id");
1299
+ }
1300
+ function hasIdTiebreaker(orderBy, parse) {
1301
+ if (!isNotNullish(orderBy)) return false;
1302
+ const normalized = normalizeOrderByInput(orderBy, parse);
1303
+ return normalized.some(
1304
+ (obj) => Object.prototype.hasOwnProperty.call(obj, "id")
1305
+ );
1306
+ }
1307
+ function addIdTiebreaker(orderBy) {
1308
+ if (Array.isArray(orderBy)) return [...orderBy, { id: "asc" }];
1309
+ return [orderBy, { id: "asc" }];
1310
+ }
1311
+ function ensureDeterministicOrderByInput(args) {
1312
+ const { orderBy, model, parseValue } = args;
1313
+ if (!modelHasScalarId(model)) return orderBy;
1314
+ if (!isNotNullish(orderBy)) {
1315
+ return { id: "asc" };
1316
+ }
1317
+ if (hasIdTiebreaker(orderBy, parseValue)) return orderBy;
1318
+ return addIdTiebreaker(orderBy);
1319
+ }
1320
+
1255
1321
  // src/builder/pagination.ts
1256
1322
  var MAX_LIMIT_OFFSET = 2147483647;
1257
1323
  function parseDirectionRaw(raw, errorLabel) {
@@ -1314,7 +1380,15 @@ function hasNonNullishProp(v, key) {
1314
1380
  }
1315
1381
  function normalizeIntegerOrDynamic(name, v) {
1316
1382
  if (isDynamicParameter(v)) return v;
1317
- return normalizeFiniteInteger(name, v);
1383
+ const result = normalizeIntLike(name, v, {
1384
+ min: Number.MIN_SAFE_INTEGER,
1385
+ max: MAX_LIMIT_OFFSET,
1386
+ allowZero: true
1387
+ });
1388
+ if (result === void 0) {
1389
+ throw new Error(`${name} normalization returned undefined`);
1390
+ }
1391
+ return result;
1318
1392
  }
1319
1393
  function readSkipTake(relArgs) {
1320
1394
  const hasSkip = hasNonNullishProp(relArgs, "skip");
@@ -1380,7 +1454,7 @@ function buildCursorFilterParts(cursor, cursorAlias, params, model) {
1380
1454
  const placeholdersByField = /* @__PURE__ */ new Map();
1381
1455
  const parts = [];
1382
1456
  for (const [field, value] of entries) {
1383
- const c = `${cursorAlias}.${quote(field)}`;
1457
+ const c = `${cursorAlias}.${quoteColumn(model, field)}`;
1384
1458
  if (value === null) {
1385
1459
  parts.push(`${c} IS NULL`);
1386
1460
  continue;
@@ -1394,13 +1468,6 @@ function buildCursorFilterParts(cursor, cursorAlias, params, model) {
1394
1468
  placeholdersByField
1395
1469
  };
1396
1470
  }
1397
- function cursorValueExpr(tableName, cursorAlias, cursorWhereSql, field, model) {
1398
- const colName = quote(field);
1399
- return `(SELECT ${cursorAlias}.${colName} ${SQL_TEMPLATES.FROM} ${tableName} ${cursorAlias} ${SQL_TEMPLATES.WHERE} ${cursorWhereSql} ${SQL_TEMPLATES.LIMIT} 1)`;
1400
- }
1401
- function buildCursorRowExistsExpr(tableName, cursorAlias, cursorWhereSql) {
1402
- return `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${tableName} ${cursorAlias} ${SQL_TEMPLATES.WHERE} ${cursorWhereSql} ${SQL_TEMPLATES.LIMIT} 1)`;
1403
- }
1404
1471
  function buildCursorEqualityExpr(columnExpr, valueExpr) {
1405
1472
  return `((${valueExpr} IS NULL AND ${columnExpr} IS NULL) OR (${valueExpr} IS NOT NULL AND ${columnExpr} = ${valueExpr}))`;
1406
1473
  }
@@ -1439,7 +1506,7 @@ function buildOrderEntries(orderBy) {
1439
1506
  } else {
1440
1507
  entries.push({
1441
1508
  field,
1442
- direction: value.sort,
1509
+ direction: value.direction,
1443
1510
  nulls: value.nulls
1444
1511
  });
1445
1512
  }
@@ -1447,16 +1514,53 @@ function buildOrderEntries(orderBy) {
1447
1514
  }
1448
1515
  return entries;
1449
1516
  }
1517
+ function buildCursorCteSelectList(cursorEntries, orderEntries, model) {
1518
+ const set = /* @__PURE__ */ new Set();
1519
+ for (const [f] of cursorEntries) set.add(f);
1520
+ for (const e of orderEntries) set.add(e.field);
1521
+ const cols = [...set].map((f) => quoteColumn(model, f));
1522
+ if (cols.length === 0) {
1523
+ throw new Error("cursor cte select list is empty");
1524
+ }
1525
+ return cols.join(SQL_SEPARATORS.FIELD_LIST);
1526
+ }
1527
+ function truncateIdent(name, maxLen) {
1528
+ const s = String(name);
1529
+ if (s.length <= maxLen) return s;
1530
+ return s.slice(0, maxLen);
1531
+ }
1532
+ function buildCursorNames(outerAlias) {
1533
+ const maxLen = 63;
1534
+ const base = outerAlias.toLowerCase();
1535
+ const cteName = truncateIdent(`__tp_cursor_${base}`, maxLen);
1536
+ const srcAlias = truncateIdent(`__tp_cursor_src_${base}`, maxLen);
1537
+ if (cteName === outerAlias || srcAlias === outerAlias) {
1538
+ return {
1539
+ cteName: truncateIdent(`__tp_cursor_${base}_x`, maxLen),
1540
+ srcAlias: truncateIdent(`__tp_cursor_src_${base}_x`, maxLen)
1541
+ };
1542
+ }
1543
+ return { cteName, srcAlias };
1544
+ }
1450
1545
  function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect, model) {
1451
1546
  var _a;
1547
+ assertSafeTableRef(tableName);
1548
+ assertSafeAlias(alias);
1452
1549
  const d = dialect != null ? dialect : getGlobalDialect();
1453
1550
  const cursorEntries = Object.entries(cursor);
1454
1551
  if (cursorEntries.length === 0) {
1455
1552
  throw new Error("cursor must have at least one field");
1456
1553
  }
1457
- const cursorAlias = "__tp_cursor_src";
1458
- const { whereSql: cursorWhereSql, placeholdersByField } = buildCursorFilterParts(cursor, cursorAlias, params);
1459
- let orderEntries = buildOrderEntries(orderBy);
1554
+ const { cteName, srcAlias } = buildCursorNames(alias);
1555
+ assertSafeAlias(cteName);
1556
+ assertSafeAlias(srcAlias);
1557
+ const { whereSql: cursorWhereSql, placeholdersByField } = buildCursorFilterParts(cursor, srcAlias, params, model);
1558
+ const deterministicOrderBy = ensureDeterministicOrderByInput({
1559
+ orderBy,
1560
+ model,
1561
+ parseValue: parseOrderByValue
1562
+ });
1563
+ let orderEntries = buildOrderEntries(deterministicOrderBy);
1460
1564
  if (orderEntries.length === 0) {
1461
1565
  orderEntries = cursorEntries.map(([field]) => ({
1462
1566
  field,
@@ -1465,11 +1569,21 @@ function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect
1465
1569
  } else {
1466
1570
  orderEntries = ensureCursorFieldsInOrder(orderEntries, cursorEntries);
1467
1571
  }
1468
- const existsExpr = buildCursorRowExistsExpr(
1469
- tableName,
1470
- cursorAlias,
1471
- cursorWhereSql
1572
+ const cursorOrderBy = orderEntries.map(
1573
+ (e) => `${srcAlias}.${quoteColumn(model, e.field)} ${e.direction.toUpperCase()}`
1574
+ ).join(", ");
1575
+ const selectList = buildCursorCteSelectList(
1576
+ cursorEntries,
1577
+ orderEntries,
1578
+ model
1472
1579
  );
1580
+ const cte = `${cteName} AS (
1581
+ SELECT ${selectList} FROM ${tableName} ${srcAlias}
1582
+ WHERE ${cursorWhereSql}
1583
+ ORDER BY ${cursorOrderBy}
1584
+ LIMIT 1
1585
+ )`;
1586
+ const existsExpr = `EXISTS (SELECT 1 FROM ${cteName})`;
1473
1587
  const outerCursorMatch = buildOuterCursorMatch(
1474
1588
  cursor,
1475
1589
  alias,
@@ -1477,34 +1591,31 @@ function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect
1477
1591
  params,
1478
1592
  model
1479
1593
  );
1594
+ const getValueExpr = (field) => {
1595
+ return `(SELECT ${quoteColumn(model, field)} FROM ${cteName})`;
1596
+ };
1480
1597
  const orClauses = [];
1481
1598
  for (let level = 0; level < orderEntries.length; level++) {
1482
1599
  const andParts = [];
1483
1600
  for (let i = 0; i < level; i++) {
1484
1601
  const e2 = orderEntries[i];
1485
1602
  const c2 = col(alias, e2.field, model);
1486
- const v2 = cursorValueExpr(
1487
- tableName,
1488
- cursorAlias,
1489
- cursorWhereSql,
1490
- e2.field);
1603
+ const v2 = getValueExpr(e2.field);
1491
1604
  andParts.push(buildCursorEqualityExpr(c2, v2));
1492
1605
  }
1493
1606
  const e = orderEntries[level];
1494
1607
  const c = col(alias, e.field, model);
1495
- const v = cursorValueExpr(
1496
- tableName,
1497
- cursorAlias,
1498
- cursorWhereSql,
1499
- e.field);
1608
+ const v = getValueExpr(e.field);
1500
1609
  const nulls = (_a = e.nulls) != null ? _a : defaultNullsFor(d, e.direction);
1501
1610
  andParts.push(buildCursorInequalityExpr(c, e.direction, nulls, v));
1502
1611
  orClauses.push(`(${andParts.join(SQL_SEPARATORS.CONDITION_AND)})`);
1503
1612
  }
1504
1613
  const exclusive = orClauses.join(SQL_SEPARATORS.CONDITION_OR);
1505
- return `(${existsExpr} ${SQL_SEPARATORS.CONDITION_AND} ((${exclusive})${SQL_SEPARATORS.CONDITION_OR}(${outerCursorMatch})))`;
1614
+ const condition = `(${existsExpr} ${SQL_SEPARATORS.CONDITION_AND} ((${exclusive})${SQL_SEPARATORS.CONDITION_OR}(${outerCursorMatch})))`;
1615
+ return { cte, condition };
1506
1616
  }
1507
1617
  function buildOrderBy(orderBy, alias, dialect, model) {
1618
+ assertSafeAlias(alias);
1508
1619
  const entries = buildOrderEntries(orderBy);
1509
1620
  if (entries.length === 0) return "";
1510
1621
  const d = dialect != null ? dialect : getGlobalDialect();
@@ -1604,6 +1715,11 @@ function buildScalarOperator(expr, op, val, params, mode, fieldType, dialect) {
1604
1715
  }
1605
1716
  return handleInOperator(expr, op, val, params, dialect);
1606
1717
  }
1718
+ if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && !isNotNullish(dialect)) {
1719
+ throw createError(`Insensitive equals requires a SQL dialect`, {
1720
+ operator: op
1721
+ });
1722
+ }
1607
1723
  return handleComparisonOperator(expr, op, val, params);
1608
1724
  }
1609
1725
  function handleNullValue(expr, op) {
@@ -1619,6 +1735,28 @@ function normalizeMode(v) {
1619
1735
  function handleNotOperator(expr, val, params, outerMode, fieldType, dialect) {
1620
1736
  const innerMode = normalizeMode(val.mode);
1621
1737
  const effectiveMode = innerMode != null ? innerMode : outerMode;
1738
+ const entries = Object.entries(val).filter(
1739
+ ([k, v]) => k !== "mode" && v !== void 0
1740
+ );
1741
+ if (entries.length === 0) return "";
1742
+ if (!isNotNullish(dialect)) {
1743
+ const clauses = [];
1744
+ for (const [subOp, subVal] of entries) {
1745
+ const sub = buildScalarOperator(
1746
+ expr,
1747
+ subOp,
1748
+ subVal,
1749
+ params,
1750
+ effectiveMode,
1751
+ fieldType,
1752
+ void 0
1753
+ );
1754
+ if (sub && sub.trim().length > 0) clauses.push(`(${sub})`);
1755
+ }
1756
+ if (clauses.length === 0) return "";
1757
+ if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
1758
+ return `${SQL_TEMPLATES.NOT} (${clauses.join(` ${SQL_TEMPLATES.AND} `)})`;
1759
+ }
1622
1760
  return buildNotComposite(
1623
1761
  expr,
1624
1762
  val,
@@ -1833,6 +1971,7 @@ function handleArrayIsEmpty(expr, val, dialect) {
1833
1971
 
1834
1972
  // src/builder/where/operators-json.ts
1835
1973
  var SAFE_JSON_PATH_SEGMENT = /^[a-zA-Z_]\w*$/;
1974
+ var MAX_PATH_SEGMENT_LENGTH = 255;
1836
1975
  function validateJsonPathSegments(segments) {
1837
1976
  for (const segment of segments) {
1838
1977
  if (typeof segment !== "string") {
@@ -1841,6 +1980,12 @@ function validateJsonPathSegments(segments) {
1841
1980
  value: segment
1842
1981
  });
1843
1982
  }
1983
+ if (segment.length > MAX_PATH_SEGMENT_LENGTH) {
1984
+ throw createError(
1985
+ `JSON path segment too long: max ${MAX_PATH_SEGMENT_LENGTH} characters`,
1986
+ { operator: Ops.PATH, value: `[${segment.length} chars]` }
1987
+ );
1988
+ }
1844
1989
  if (!SAFE_JSON_PATH_SEGMENT.test(segment)) {
1845
1990
  throw createError(
1846
1991
  `Invalid JSON path segment: '${segment}'. Must be alphanumeric with underscores, starting with letter or underscore.`,
@@ -1929,6 +2074,9 @@ function handleJsonWildcard(expr, op, val, params, wildcards, dialect) {
1929
2074
 
1930
2075
  // src/builder/where/relations.ts
1931
2076
  var NO_JOINS = Object.freeze([]);
2077
+ function freezeJoins(items) {
2078
+ return Object.freeze([...items]);
2079
+ }
1932
2080
  function isListRelation(fieldType) {
1933
2081
  return typeof fieldType === "string" && fieldType.endsWith("[]");
1934
2082
  }
@@ -1991,7 +2139,7 @@ function buildListRelationFilters(args) {
1991
2139
  const whereClause = `${relAlias}.${quote(checkField.name)} IS NULL`;
1992
2140
  return Object.freeze({
1993
2141
  clause: whereClause,
1994
- joins: [leftJoinSql]
2142
+ joins: freezeJoins([leftJoinSql])
1995
2143
  });
1996
2144
  }
1997
2145
  }
@@ -2196,7 +2344,7 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
2196
2344
  Ops.HAS_EVERY,
2197
2345
  Ops.IS_EMPTY
2198
2346
  ]);
2199
- const JSON_OPS = /* @__PURE__ */ new Set([
2347
+ const JSON_OPS2 = /* @__PURE__ */ new Set([
2200
2348
  Ops.PATH,
2201
2349
  Ops.STRING_CONTAINS,
2202
2350
  Ops.STRING_STARTS_WITH,
@@ -2213,7 +2361,7 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
2213
2361
  modelName
2214
2362
  });
2215
2363
  }
2216
- const isJsonOp = JSON_OPS.has(op);
2364
+ const isJsonOp = JSON_OPS2.has(op);
2217
2365
  const isFieldJson = isJsonType(fieldType);
2218
2366
  const jsonOpMismatch = isJsonOp && !isFieldJson;
2219
2367
  if (jsonOpMismatch) {
@@ -2227,6 +2375,14 @@ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
2227
2375
  }
2228
2376
 
2229
2377
  // src/builder/where/builder.ts
2378
+ var MAX_QUERY_DEPTH = 50;
2379
+ var EMPTY_JOINS = Object.freeze([]);
2380
+ var JSON_OPS = /* @__PURE__ */ new Set([
2381
+ Ops.PATH,
2382
+ Ops.STRING_CONTAINS,
2383
+ Ops.STRING_STARTS_WITH,
2384
+ Ops.STRING_ENDS_WITH
2385
+ ]);
2230
2386
  var WhereBuilder = class {
2231
2387
  build(where, ctx) {
2232
2388
  if (!isPlainObject(where)) {
@@ -2238,8 +2394,6 @@ var WhereBuilder = class {
2238
2394
  return buildWhereInternal(where, ctx, this);
2239
2395
  }
2240
2396
  };
2241
- var MAX_QUERY_DEPTH = 50;
2242
- var EMPTY_JOINS = Object.freeze([]);
2243
2397
  var whereBuilderInstance = new WhereBuilder();
2244
2398
  function freezeResult(clause, joins = EMPTY_JOINS) {
2245
2399
  return Object.freeze({ clause, joins });
@@ -2416,16 +2570,8 @@ function buildOperator(expr, op, val, ctx, mode, fieldType) {
2416
2570
  if (fieldType && isArrayType(fieldType)) {
2417
2571
  return buildArrayOperator(expr, op, val, ctx.params, fieldType, ctx.dialect);
2418
2572
  }
2419
- if (fieldType && isJsonType(fieldType)) {
2420
- const JSON_OPS = /* @__PURE__ */ new Set([
2421
- Ops.PATH,
2422
- Ops.STRING_CONTAINS,
2423
- Ops.STRING_STARTS_WITH,
2424
- Ops.STRING_ENDS_WITH
2425
- ]);
2426
- if (JSON_OPS.has(op)) {
2427
- return buildJsonOperator(expr, op, val, ctx.params, ctx.dialect);
2428
- }
2573
+ if (fieldType && isJsonType(fieldType) && JSON_OPS.has(op)) {
2574
+ return buildJsonOperator(expr, op, val, ctx.params, ctx.dialect);
2429
2575
  }
2430
2576
  return buildScalarOperator(
2431
2577
  expr,
@@ -2446,7 +2592,7 @@ function toSafeSqlIdentifier(input) {
2446
2592
  const base = startsOk ? cleaned : `_${cleaned}`;
2447
2593
  const fallback = base.length > 0 ? base : "_t";
2448
2594
  const lowered = fallback.toLowerCase();
2449
- return SQL_KEYWORDS.has(lowered) ? `_${lowered}` : lowered;
2595
+ return ALIAS_FORBIDDEN_KEYWORDS.has(lowered) ? `_${lowered}` : lowered;
2450
2596
  }
2451
2597
  function createAliasGenerator(maxAliases = 1e4) {
2452
2598
  let counter = 0;
@@ -2656,6 +2802,7 @@ function toPublicResult(clause, joins, params) {
2656
2802
  // src/builder/where.ts
2657
2803
  function buildWhereClause(where, options) {
2658
2804
  var _a, _b, _c, _d, _e;
2805
+ assertSafeAlias(options.alias);
2659
2806
  const dialect = options.dialect || getGlobalDialect();
2660
2807
  const params = (_a = options.params) != null ? _a : createParamStore();
2661
2808
  const ctx = {
@@ -2824,6 +2971,9 @@ function buildRelationSelect(relArgs, relModel, relAlias) {
2824
2971
  }
2825
2972
 
2826
2973
  // src/builder/select/includes.ts
2974
+ var MAX_INCLUDE_DEPTH = 10;
2975
+ var MAX_TOTAL_SUBQUERIES = 100;
2976
+ var MAX_TOTAL_INCLUDES = 50;
2827
2977
  function getRelationTableReference(relModel, dialect) {
2828
2978
  return buildTableReference(
2829
2979
  SQL_TEMPLATES.PUBLIC_SCHEMA,
@@ -2869,107 +3019,24 @@ function relationEntriesFromArgs(args, model) {
2869
3019
  pushFrom(args.select);
2870
3020
  return out;
2871
3021
  }
2872
- function assertScalarField(model, fieldName) {
2873
- const f = model.fields.find((x) => x.name === fieldName);
2874
- if (!f) {
2875
- throw new Error(
2876
- `orderBy references unknown field '${fieldName}' on model ${model.name}`
2877
- );
2878
- }
2879
- if (f.isRelation) {
2880
- throw new Error(
2881
- `orderBy does not support relation field '${fieldName}' on model ${model.name}`
2882
- );
2883
- }
2884
- }
2885
- function validateOrderByDirection(fieldName, v) {
2886
- const s = String(v).toLowerCase();
2887
- if (s !== "asc" && s !== "desc") {
2888
- throw new Error(
2889
- `Invalid orderBy direction for '${fieldName}': ${String(v)}`
2890
- );
2891
- }
2892
- }
2893
- function validateOrderByObject(fieldName, v) {
2894
- if (!("sort" in v)) {
2895
- throw new Error(
2896
- `orderBy for '${fieldName}' must be 'asc' | 'desc' or { sort, nulls? }`
2897
- );
2898
- }
2899
- validateOrderByDirection(fieldName, v.sort);
2900
- if ("nulls" in v && isNotNullish(v.nulls)) {
2901
- const n = String(v.nulls).toLowerCase();
2902
- if (n !== "first" && n !== "last") {
2903
- throw new Error(
2904
- `Invalid orderBy.nulls for '${fieldName}': ${String(v.nulls)}`
2905
- );
2906
- }
2907
- }
2908
- const allowed = /* @__PURE__ */ new Set(["sort", "nulls"]);
2909
- for (const k of Object.keys(v)) {
2910
- if (!allowed.has(k)) {
2911
- throw new Error(`Unsupported orderBy key '${k}' for field '${fieldName}'`);
2912
- }
2913
- }
2914
- }
2915
- function normalizeOrderByFieldName(name) {
2916
- const fieldName = String(name).trim();
2917
- if (fieldName.length === 0) {
2918
- throw new Error("orderBy field name cannot be empty");
2919
- }
2920
- return fieldName;
2921
- }
2922
- function requirePlainObjectForOrderByEntry(v) {
2923
- if (typeof v !== "object" || v === null || Array.isArray(v)) {
2924
- throw new Error("orderBy array entries must be objects");
2925
- }
2926
- return v;
2927
- }
2928
- function parseSingleFieldOrderByObject(obj) {
2929
- const entries = Object.entries(obj);
2930
- if (entries.length !== 1) {
2931
- throw new Error("orderBy array entries must have exactly one field");
2932
- }
2933
- const fieldName = normalizeOrderByFieldName(entries[0][0]);
2934
- return [fieldName, entries[0][1]];
2935
- }
2936
- function parseOrderByArray(orderBy) {
2937
- return orderBy.map(
2938
- (item) => parseSingleFieldOrderByObject(requirePlainObjectForOrderByEntry(item))
2939
- );
2940
- }
2941
- function parseOrderByObject(orderBy) {
2942
- const out = [];
2943
- for (const [k, v] of Object.entries(orderBy)) {
2944
- out.push([normalizeOrderByFieldName(k), v]);
2945
- }
2946
- return out;
2947
- }
2948
- function getOrderByEntries(orderBy) {
2949
- if (!isNotNullish(orderBy)) return [];
2950
- if (Array.isArray(orderBy)) {
2951
- return parseOrderByArray(orderBy);
2952
- }
2953
- if (typeof orderBy === "object" && orderBy !== null) {
2954
- return parseOrderByObject(orderBy);
2955
- }
2956
- throw new Error("orderBy must be an object or array of objects");
2957
- }
2958
3022
  function validateOrderByForModel(model, orderBy) {
2959
- const entries = getOrderByEntries(orderBy);
2960
- for (const [fieldName, v] of entries) {
2961
- assertScalarField(model, fieldName);
2962
- if (typeof v === "string") {
2963
- validateOrderByDirection(fieldName, v);
2964
- continue;
3023
+ if (!isNotNullish(orderBy)) return;
3024
+ const scalarSet = getScalarFieldSet(model);
3025
+ const normalized = normalizeOrderByInput(orderBy, parseOrderByValue);
3026
+ for (const item of normalized) {
3027
+ const entries = Object.entries(item);
3028
+ if (entries.length !== 1) {
3029
+ throw new Error("orderBy array entries must have exactly one field");
2965
3030
  }
2966
- if (isPlainObject(v)) {
2967
- validateOrderByObject(fieldName, v);
2968
- continue;
3031
+ const fieldName = String(entries[0][0]).trim();
3032
+ if (fieldName.length === 0) {
3033
+ throw new Error("orderBy field name cannot be empty");
3034
+ }
3035
+ if (!scalarSet.has(fieldName)) {
3036
+ throw new Error(
3037
+ `orderBy references unknown or non-scalar field '${fieldName}' on model ${model.name}`
3038
+ );
2969
3039
  }
2970
- throw new Error(
2971
- `orderBy for '${fieldName}' must be 'asc' | 'desc' or { sort, nulls? }`
2972
- );
2973
3040
  }
2974
3041
  }
2975
3042
  function appendLimitOffset(sql, dialect, params, takeVal, skipVal, scope) {
@@ -2998,7 +3065,10 @@ function readWhereInput(relArgs) {
2998
3065
  function readOrderByInput(relArgs) {
2999
3066
  if (!isPlainObject(relArgs)) return { hasOrderBy: false, orderBy: void 0 };
3000
3067
  if (!("orderBy" in relArgs)) return { hasOrderBy: false, orderBy: void 0 };
3001
- return { hasOrderBy: true, orderBy: relArgs.orderBy };
3068
+ return {
3069
+ hasOrderBy: true,
3070
+ orderBy: relArgs.orderBy
3071
+ };
3002
3072
  }
3003
3073
  function extractRelationPaginationConfig(relArgs) {
3004
3074
  const { hasOrderBy, orderBy: rawOrderByInput } = readOrderByInput(relArgs);
@@ -3028,36 +3098,28 @@ function maybeReverseNegativeTake(takeVal, hasOrderBy, orderByInput) {
3028
3098
  orderByInput: reverseOrderByInput(orderByInput)
3029
3099
  };
3030
3100
  }
3031
- function hasIdTiebreaker(orderByInput) {
3032
- const entries = Array.isArray(orderByInput) ? orderByInput : [orderByInput];
3033
- return entries.some(
3034
- (entry) => isPlainObject(entry) ? Object.prototype.hasOwnProperty.call(entry, "id") : false
3035
- );
3036
- }
3037
- function modelHasScalarId(relModel) {
3038
- const idField = relModel.fields.find((f) => f.name === "id");
3039
- return Boolean(idField && !idField.isRelation);
3040
- }
3041
- function addIdTiebreaker(orderByInput) {
3042
- if (Array.isArray(orderByInput)) return [...orderByInput, { id: "asc" }];
3043
- return [orderByInput, { id: "asc" }];
3044
- }
3045
- function ensureDeterministicOrderBy(relModel, hasOrderBy, orderByInput, hasPagination) {
3046
- if (!hasPagination) {
3047
- if (hasOrderBy && isNotNullish(orderByInput)) {
3048
- validateOrderByForModel(relModel, orderByInput);
3101
+ function ensureDeterministicOrderByForInclude(args) {
3102
+ if (!args.hasPagination) {
3103
+ if (args.hasOrderBy && isNotNullish(args.orderByInput)) {
3104
+ validateOrderByForModel(args.relModel, args.orderByInput);
3049
3105
  }
3050
- return orderByInput;
3106
+ return args.orderByInput;
3051
3107
  }
3052
- if (!hasOrderBy) {
3053
- return modelHasScalarId(relModel) ? { id: "asc" } : orderByInput;
3108
+ if (!args.hasOrderBy) {
3109
+ return ensureDeterministicOrderByInput({
3110
+ orderBy: void 0,
3111
+ model: args.relModel,
3112
+ parseValue: parseOrderByValue
3113
+ });
3054
3114
  }
3055
- if (isNotNullish(orderByInput)) {
3056
- validateOrderByForModel(relModel, orderByInput);
3115
+ if (isNotNullish(args.orderByInput)) {
3116
+ validateOrderByForModel(args.relModel, args.orderByInput);
3057
3117
  }
3058
- if (!modelHasScalarId(relModel)) return orderByInput;
3059
- if (hasIdTiebreaker(orderByInput)) return orderByInput;
3060
- return addIdTiebreaker(orderByInput);
3118
+ return ensureDeterministicOrderByInput({
3119
+ orderBy: args.orderByInput,
3120
+ model: args.relModel,
3121
+ parseValue: parseOrderByValue
3122
+ });
3061
3123
  }
3062
3124
  function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
3063
3125
  let relSelect = buildRelationSelect(relArgs, relModel, relAlias);
@@ -3068,7 +3130,10 @@ function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
3068
3130
  relAlias,
3069
3131
  ctx.aliasGen,
3070
3132
  ctx.params,
3071
- ctx.dialect
3133
+ ctx.dialect,
3134
+ ctx.visitPath || [],
3135
+ (ctx.depth || 0) + 1,
3136
+ ctx.stats
3072
3137
  ) : [];
3073
3138
  if (isNonEmptyArray(nestedIncludes)) {
3074
3139
  const emptyJson = ctx.dialect === "postgres" ? `'[]'::json` : `json('[]')`;
@@ -3218,12 +3283,12 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3218
3283
  paginationConfig.orderBy
3219
3284
  );
3220
3285
  const hasPagination = paginationConfig.hasSkip || paginationConfig.hasTake;
3221
- const finalOrderByInput = ensureDeterministicOrderBy(
3286
+ const finalOrderByInput = ensureDeterministicOrderByForInclude({
3222
3287
  relModel,
3223
- paginationConfig.hasOrderBy,
3224
- adjusted.orderByInput,
3288
+ hasOrderBy: paginationConfig.hasOrderBy,
3289
+ orderByInput: adjusted.orderByInput,
3225
3290
  hasPagination
3226
- );
3291
+ });
3227
3292
  const orderBySql = buildOrderBySql(
3228
3293
  finalOrderByInput,
3229
3294
  relAlias,
@@ -3264,12 +3329,48 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3264
3329
  ctx
3265
3330
  });
3266
3331
  }
3267
- function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, params, dialect) {
3332
+ function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, params, dialect, visitPath = [], depth = 0, stats) {
3333
+ if (!stats) {
3334
+ stats = { totalIncludes: 0, totalSubqueries: 0, maxDepth: 0 };
3335
+ }
3336
+ if (depth > MAX_INCLUDE_DEPTH) {
3337
+ throw new Error(
3338
+ `Maximum include depth of ${MAX_INCLUDE_DEPTH} exceeded. Path: ${visitPath.join(" -> ")}. Deep includes cause exponential SQL complexity and performance issues.`
3339
+ );
3340
+ }
3341
+ stats.maxDepth = Math.max(stats.maxDepth, depth);
3268
3342
  const includes = [];
3269
3343
  const entries = relationEntriesFromArgs(args, model);
3270
3344
  for (const [relName, relArgs] of entries) {
3271
3345
  if (relArgs === false) continue;
3346
+ stats.totalIncludes++;
3347
+ if (stats.totalIncludes > MAX_TOTAL_INCLUDES) {
3348
+ throw new Error(
3349
+ `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.`
3350
+ );
3351
+ }
3352
+ stats.totalSubqueries++;
3353
+ if (stats.totalSubqueries > MAX_TOTAL_SUBQUERIES) {
3354
+ throw new Error(
3355
+ `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.`
3356
+ );
3357
+ }
3272
3358
  const resolved = resolveRelationOrThrow(model, schemas, relName);
3359
+ const relationPath = `${model.name}.${relName}`;
3360
+ const currentPath = [...visitPath, relationPath];
3361
+ if (visitPath.includes(relationPath)) {
3362
+ throw new Error(
3363
+ `Circular include detected: ${currentPath.join(" -> ")}. Relation '${relationPath}' creates an infinite loop.`
3364
+ );
3365
+ }
3366
+ const modelOccurrences = currentPath.filter(
3367
+ (p) => p.startsWith(`${resolved.relModel.name}.`)
3368
+ ).length;
3369
+ if (modelOccurrences > 2) {
3370
+ throw new Error(
3371
+ `Include too deeply nested: model '${resolved.relModel.name}' appears ${modelOccurrences} times in path: ${currentPath.join(" -> ")}`
3372
+ );
3373
+ }
3273
3374
  const include = buildSingleInclude(
3274
3375
  relName,
3275
3376
  relArgs,
@@ -3281,7 +3382,10 @@ function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, pa
3281
3382
  parentAlias,
3282
3383
  aliasGen,
3283
3384
  dialect,
3284
- params
3385
+ params,
3386
+ visitPath: currentPath,
3387
+ depth: depth + 1,
3388
+ stats
3285
3389
  }
3286
3390
  );
3287
3391
  includes.push(include);
@@ -3290,6 +3394,11 @@ function buildIncludeSqlInternal(args, model, schemas, parentAlias, aliasGen, pa
3290
3394
  }
3291
3395
  function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
3292
3396
  const aliasGen = createAliasGenerator();
3397
+ const stats = {
3398
+ totalIncludes: 0,
3399
+ totalSubqueries: 0,
3400
+ maxDepth: 0
3401
+ };
3293
3402
  return buildIncludeSqlInternal(
3294
3403
  args,
3295
3404
  model,
@@ -3297,7 +3406,10 @@ function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
3297
3406
  parentAlias,
3298
3407
  aliasGen,
3299
3408
  params,
3300
- dialect
3409
+ dialect,
3410
+ [],
3411
+ 0,
3412
+ stats
3301
3413
  );
3302
3414
  }
3303
3415
  function resolveCountRelationOrThrow(relName, model, schemas) {
@@ -3313,6 +3425,11 @@ function resolveCountRelationOrThrow(relName, model, schemas) {
3313
3425
  `_count.${relName} references unknown relation on model ${model.name}`
3314
3426
  );
3315
3427
  }
3428
+ if (!isValidRelationField(field)) {
3429
+ throw new Error(
3430
+ `_count.${relName} has invalid relation metadata on model ${model.name}`
3431
+ );
3432
+ }
3316
3433
  const relModel = schemas.find((m) => m.name === field.relatedModel);
3317
3434
  if (!relModel) {
3318
3435
  throw new Error(
@@ -3321,31 +3438,81 @@ function resolveCountRelationOrThrow(relName, model, schemas) {
3321
3438
  }
3322
3439
  return { field, relModel };
3323
3440
  }
3324
- function groupByColForCount(field, countAlias) {
3325
- const fkFields = normalizeKeyList(field.foreignKey);
3326
- const refFields = normalizeKeyList(field.references);
3327
- return field.isForeignKeyLocal ? `${countAlias}.${quote(refFields[0] || "id")}` : `${countAlias}.${quote(fkFields[0])}`;
3441
+ function defaultReferencesForCount(fkCount) {
3442
+ if (fkCount === 1) return ["id"];
3443
+ throw new Error(
3444
+ "Relation count for composite keys requires explicit references matching foreignKey length"
3445
+ );
3328
3446
  }
3329
- function leftJoinOnForCount(field, parentAlias, joinAlias) {
3447
+ function resolveCountKeyPairs(field) {
3330
3448
  const fkFields = normalizeKeyList(field.foreignKey);
3331
- const refFields = normalizeKeyList(field.references);
3332
- return field.isForeignKeyLocal ? `${joinAlias}.__fk = ${parentAlias}.${quote(fkFields[0])}` : `${joinAlias}.__fk = ${parentAlias}.${quote(refFields[0] || "id")}`;
3449
+ if (fkFields.length === 0) {
3450
+ throw new Error("Relation count requires foreignKey");
3451
+ }
3452
+ const refsRaw = field.references;
3453
+ const refs = normalizeKeyList(refsRaw);
3454
+ const refFields = refs.length > 0 ? refs : defaultReferencesForCount(fkFields.length);
3455
+ if (refFields.length !== fkFields.length) {
3456
+ throw new Error(
3457
+ "Relation count requires references count to match foreignKey count"
3458
+ );
3459
+ }
3460
+ const relKeyFields = field.isForeignKeyLocal ? refFields : fkFields;
3461
+ const parentKeyFields = field.isForeignKeyLocal ? fkFields : refFields;
3462
+ return { relKeyFields, parentKeyFields };
3463
+ }
3464
+ function aliasQualifiedColumn(alias, model, field) {
3465
+ return `${alias}.${quoteColumn(model, field)}`;
3333
3466
  }
3334
- function subqueryForCount(dialect, relTable, countAlias, groupByCol) {
3335
- 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})`;
3467
+ function subqueryForCount(args) {
3468
+ const selectKeys = args.relKeyFields.map(
3469
+ (f, i) => `${aliasQualifiedColumn(args.countAlias, args.relModel, f)} AS "__fk${i}"`
3470
+ ).join(SQL_SEPARATORS.FIELD_LIST);
3471
+ const groupByKeys = args.relKeyFields.map((f) => aliasQualifiedColumn(args.countAlias, args.relModel, f)).join(SQL_SEPARATORS.FIELD_LIST);
3472
+ const cntExpr = args.dialect === "postgres" ? "COUNT(*)::int AS __cnt" : "COUNT(*) AS __cnt";
3473
+ return `(SELECT ${selectKeys}${SQL_SEPARATORS.FIELD_LIST}${cntExpr} FROM ${args.relTable} ${args.countAlias} GROUP BY ${groupByKeys})`;
3474
+ }
3475
+ function leftJoinOnForCount(args) {
3476
+ const parts = args.parentKeyFields.map(
3477
+ (f, i) => `${args.joinAlias}."__fk${i}" = ${aliasQualifiedColumn(args.parentAlias, args.parentModel, f)}`
3478
+ );
3479
+ return parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`;
3480
+ }
3481
+ function nextAliasAvoiding(aliasGen, base, forbidden) {
3482
+ let a = aliasGen.next(base);
3483
+ while (forbidden.has(a)) {
3484
+ a = aliasGen.next(base);
3485
+ }
3486
+ return a;
3336
3487
  }
3337
3488
  function buildCountJoinAndPair(args) {
3338
3489
  const relTable = getRelationTableReference(args.relModel, args.dialect);
3339
- const countAlias = `__tp_cnt_${args.relName}`;
3340
- const groupByCol = groupByColForCount(args.field, countAlias);
3341
- const subquery = subqueryForCount(
3342
- args.dialect,
3490
+ const { relKeyFields, parentKeyFields } = resolveCountKeyPairs(args.field);
3491
+ const forbidden = /* @__PURE__ */ new Set([args.parentAlias]);
3492
+ const countAlias = nextAliasAvoiding(
3493
+ args.aliasGen,
3494
+ `__tp_cnt_${args.relName}`,
3495
+ forbidden
3496
+ );
3497
+ forbidden.add(countAlias);
3498
+ const subquery = subqueryForCount({
3499
+ dialect: args.dialect,
3343
3500
  relTable,
3344
3501
  countAlias,
3345
- groupByCol
3502
+ relModel: args.relModel,
3503
+ relKeyFields
3504
+ });
3505
+ const joinAlias = nextAliasAvoiding(
3506
+ args.aliasGen,
3507
+ `__tp_cnt_j_${args.relName}`,
3508
+ forbidden
3346
3509
  );
3347
- const joinAlias = `__tp_cnt_j_${args.relName}`;
3348
- const leftJoinOn = leftJoinOnForCount(args.field, args.parentAlias, joinAlias);
3510
+ const leftJoinOn = leftJoinOnForCount({
3511
+ joinAlias,
3512
+ parentAlias: args.parentAlias,
3513
+ parentModel: args.parentModel,
3514
+ parentKeyFields
3515
+ });
3349
3516
  return {
3350
3517
  joinSql: `LEFT JOIN ${subquery} ${joinAlias} ON ${leftJoinOn}`,
3351
3518
  pairSql: `${sqlStringLiteral(args.relName)}, COALESCE(${joinAlias}.__cnt, 0)`
@@ -3354,6 +3521,7 @@ function buildCountJoinAndPair(args) {
3354
3521
  function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params, dialect) {
3355
3522
  const joins = [];
3356
3523
  const pairs = [];
3524
+ const aliasGen = createAliasGenerator();
3357
3525
  for (const [relName, shouldCount] of Object.entries(countSelect)) {
3358
3526
  if (!shouldCount) continue;
3359
3527
  const resolved = resolveCountRelationOrThrow(relName, model, schemas);
@@ -3361,8 +3529,10 @@ function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params
3361
3529
  relName,
3362
3530
  field: resolved.field,
3363
3531
  relModel: resolved.relModel,
3532
+ parentModel: model,
3364
3533
  parentAlias,
3365
- dialect
3534
+ dialect,
3535
+ aliasGen
3366
3536
  });
3367
3537
  joins.push(built.joinSql);
3368
3538
  pairs.push(built.pairSql);
@@ -3374,16 +3544,21 @@ function buildRelationCountSql(countSelect, model, schemas, parentAlias, _params
3374
3544
  }
3375
3545
 
3376
3546
  // src/builder/select/assembly.ts
3377
- var SIMPLE_SELECT_RE_CACHE = /* @__PURE__ */ new Map();
3378
- function normalizeFinalParams(params) {
3379
- return params.map(normalizeValue);
3380
- }
3547
+ var ALIAS_CAPTURE = "([A-Za-z_][A-Za-z0-9_]*)";
3548
+ var COLUMN_PART = '(?:"([^"]+)"|([a-z_][a-z0-9_]*))';
3549
+ var AS_PART = `(?:\\s+AS\\s+${COLUMN_PART})?`;
3550
+ var SIMPLE_COLUMN_PATTERN = `^${ALIAS_CAPTURE}\\.${COLUMN_PART}${AS_PART}$`;
3551
+ var SIMPLE_COLUMN_RE = new RegExp(SIMPLE_COLUMN_PATTERN, "i");
3381
3552
  function joinNonEmpty(parts, sep) {
3382
3553
  return parts.filter((s) => s.trim().length > 0).join(sep);
3383
3554
  }
3384
3555
  function buildWhereSql(conditions) {
3385
3556
  if (!isNonEmptyArray(conditions)) return "";
3386
- return ` ${SQL_TEMPLATES.WHERE} ${conditions.join(SQL_SEPARATORS.CONDITION_AND)}`;
3557
+ const parts = [
3558
+ SQL_TEMPLATES.WHERE,
3559
+ conditions.join(SQL_SEPARATORS.CONDITION_AND)
3560
+ ];
3561
+ return ` ${parts.join(" ")}`;
3387
3562
  }
3388
3563
  function buildJoinsSql(...joinGroups) {
3389
3564
  const all = [];
@@ -3398,37 +3573,39 @@ function buildSelectList(baseSelect, extraCols) {
3398
3573
  if (base && extra) return `${base}${SQL_SEPARATORS.FIELD_LIST}${extra}`;
3399
3574
  return base || extra;
3400
3575
  }
3401
- function finalizeSql(sql, params) {
3576
+ function finalizeSql(sql, params, dialect) {
3402
3577
  const snapshot = params.snapshot();
3403
3578
  validateSelectQuery(sql);
3404
- validateParamConsistency(sql, snapshot.params);
3579
+ validateParamConsistencyByDialect(sql, snapshot.params, dialect);
3405
3580
  return Object.freeze({
3406
3581
  sql,
3407
- params: normalizeFinalParams(snapshot.params),
3582
+ params: snapshot.params,
3408
3583
  paramMappings: Object.freeze(snapshot.mappings)
3409
3584
  });
3410
3585
  }
3411
- function parseSimpleScalarSelect(select, alias) {
3412
- var _a, _b;
3586
+ function parseSimpleScalarSelect(select, fromAlias) {
3587
+ var _a, _b, _c, _d;
3413
3588
  const raw = select.trim();
3414
3589
  if (raw.length === 0) return [];
3415
- let re = SIMPLE_SELECT_RE_CACHE.get(alias);
3416
- if (!re) {
3417
- const safeAlias2 = alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3418
- re = new RegExp(`^${safeAlias2}\\.(?:"([^"]+)"|([a-z_][a-z0-9_]*))$`, "i");
3419
- SIMPLE_SELECT_RE_CACHE.set(alias, re);
3420
- }
3421
3590
  const parts = raw.split(SQL_SEPARATORS.FIELD_LIST);
3422
3591
  const names = [];
3423
3592
  for (const part of parts) {
3424
3593
  const p = part.trim();
3425
- const m = p.match(re);
3594
+ const m = p.match(SIMPLE_COLUMN_RE);
3426
3595
  if (!m) {
3427
3596
  throw new Error(
3428
- `sqlite distinct emulation requires scalar select fields to be simple columns. Got: ${p}`
3597
+ `sqlite distinct emulation requires scalar select fields to be simple columns (optionally with AS). Got: ${p}`
3598
+ );
3599
+ }
3600
+ const actualAlias = m[1];
3601
+ if (actualAlias.toLowerCase() !== fromAlias.toLowerCase()) {
3602
+ throw new Error(
3603
+ `Expected alias '${fromAlias}', got '${actualAlias}' in: ${p}`
3429
3604
  );
3430
3605
  }
3431
- const name = ((_b = (_a = m[1]) != null ? _a : m[2]) != null ? _b : "").trim();
3606
+ const columnName = ((_b = (_a = m[2]) != null ? _a : m[3]) != null ? _b : "").trim();
3607
+ const outAlias = ((_d = (_c = m[4]) != null ? _c : m[5]) != null ? _d : "").trim();
3608
+ const name = outAlias.length > 0 ? outAlias : columnName;
3432
3609
  if (name.length === 0) {
3433
3610
  throw new Error(`Failed to parse selected column name from: ${p}`);
3434
3611
  }
@@ -3437,18 +3614,18 @@ function parseSimpleScalarSelect(select, alias) {
3437
3614
  return names;
3438
3615
  }
3439
3616
  function replaceOrderByAlias(orderBy, fromAlias, outerAlias) {
3440
- const needle = `${fromAlias}.`;
3441
- const replacement = `${outerAlias}.`;
3442
- return orderBy.split(needle).join(replacement);
3617
+ const src = String(fromAlias);
3618
+ if (src.length === 0) return orderBy;
3619
+ const escaped = src.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3620
+ const re = new RegExp(`\\b${escaped}\\.`, "gi");
3621
+ return orderBy.replace(re, `${outerAlias}.`);
3443
3622
  }
3444
3623
  function buildDistinctColumns(distinct, fromAlias, model) {
3445
3624
  return distinct.map((f) => col(fromAlias, f, model)).join(SQL_SEPARATORS.FIELD_LIST);
3446
3625
  }
3447
3626
  function buildOutputColumns(scalarNames, includeNames, hasCount) {
3448
3627
  const outputCols = [...scalarNames, ...includeNames];
3449
- if (hasCount) {
3450
- outputCols.push("_count");
3451
- }
3628
+ if (hasCount) outputCols.push("_count");
3452
3629
  const formatted = outputCols.map((n) => quote(n)).join(SQL_SEPARATORS.FIELD_LIST);
3453
3630
  if (!isNonEmptyString(formatted)) {
3454
3631
  throw new Error("distinct emulation requires at least one output column");
@@ -3457,9 +3634,10 @@ function buildOutputColumns(scalarNames, includeNames, hasCount) {
3457
3634
  }
3458
3635
  function buildWindowOrder(args) {
3459
3636
  const { baseOrder, idField, fromAlias, model } = args;
3637
+ const fromLower = String(fromAlias).toLowerCase();
3460
3638
  const orderFields = baseOrder.split(SQL_SEPARATORS.ORDER_BY).map((s) => s.trim().toLowerCase());
3461
3639
  const hasIdInOrder = orderFields.some(
3462
- (f) => f.startsWith(`${fromAlias}.id `) || f.startsWith(`${fromAlias}."id" `)
3640
+ (f) => f.startsWith(`${fromLower}.id `) || f.startsWith(`${fromLower}."id" `)
3463
3641
  );
3464
3642
  if (hasIdInOrder) return baseOrder;
3465
3643
  const idTiebreaker = idField ? `, ${col(fromAlias, "id", model)} ASC` : "";
@@ -3494,15 +3672,37 @@ function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
3494
3672
  const outerOrder = isNonEmptyString(orderBy) ? replaceOrderByAlias(orderBy, from.alias, `"__tp_distinct"`) : replaceOrderByAlias(fallbackOrder, from.alias, `"__tp_distinct"`);
3495
3673
  const joins = buildJoinsSql(whereJoins, countJoins);
3496
3674
  const conditions = [];
3497
- if (whereClause && whereClause !== "1=1") {
3498
- conditions.push(whereClause);
3499
- }
3675
+ if (whereClause && whereClause !== "1=1") conditions.push(whereClause);
3500
3676
  const whereSql = buildWhereSql(conditions);
3501
3677
  const innerSelectList = selectWithIncludes.trim();
3502
3678
  const innerComma = innerSelectList.length > 0 ? SQL_SEPARATORS.FIELD_LIST : "";
3503
- 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}`;
3504
- 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}` : "");
3505
- return outer;
3679
+ const innerParts = [
3680
+ SQL_TEMPLATES.SELECT,
3681
+ innerSelectList + innerComma,
3682
+ `ROW_NUMBER() OVER (PARTITION BY ${distinctCols} ORDER BY ${windowOrder})`,
3683
+ SQL_TEMPLATES.AS,
3684
+ '"__tp_rn"',
3685
+ SQL_TEMPLATES.FROM,
3686
+ from.table,
3687
+ from.alias
3688
+ ];
3689
+ if (joins) innerParts.push(joins);
3690
+ if (whereSql) innerParts.push(whereSql);
3691
+ const inner = innerParts.filter(Boolean).join(" ");
3692
+ const outerParts = [
3693
+ SQL_TEMPLATES.SELECT,
3694
+ outerSelectCols,
3695
+ SQL_TEMPLATES.FROM,
3696
+ `(${inner})`,
3697
+ SQL_TEMPLATES.AS,
3698
+ '"__tp_distinct"',
3699
+ SQL_TEMPLATES.WHERE,
3700
+ '"__tp_rn" = 1'
3701
+ ];
3702
+ if (isNonEmptyString(outerOrder)) {
3703
+ outerParts.push(SQL_TEMPLATES.ORDER_BY, outerOrder);
3704
+ }
3705
+ return outerParts.filter(Boolean).join(" ");
3506
3706
  }
3507
3707
  function buildIncludeColumns(spec) {
3508
3708
  var _a, _b;
@@ -3630,6 +3830,7 @@ function constructFinalSql(spec) {
3630
3830
  orderBy,
3631
3831
  distinct,
3632
3832
  method,
3833
+ cursorCte,
3633
3834
  cursorClause,
3634
3835
  params,
3635
3836
  dialect,
@@ -3644,9 +3845,13 @@ function constructFinalSql(spec) {
3644
3845
  const spec2 = withCountJoins(spec, countJoins, whereJoins);
3645
3846
  let sql2 = buildSqliteDistinctQuery(spec2, selectWithIncludes).trim();
3646
3847
  sql2 = appendPagination(sql2, spec);
3647
- return finalizeSql(sql2, params);
3848
+ return finalizeSql(sql2, params, dialect);
3648
3849
  }
3649
- const parts = [SQL_TEMPLATES.SELECT];
3850
+ const parts = [];
3851
+ if (cursorCte) {
3852
+ parts.push(`WITH ${cursorCte}`);
3853
+ }
3854
+ parts.push(SQL_TEMPLATES.SELECT);
3650
3855
  const distinctOn = dialect === "postgres" ? buildPostgresDistinctOnClause(from.alias, distinct, model) : null;
3651
3856
  if (distinctOn) parts.push(distinctOn);
3652
3857
  const baseSelect = (select != null ? select : "").trim();
@@ -3662,7 +3867,41 @@ function constructFinalSql(spec) {
3662
3867
  if (isNonEmptyString(orderBy)) parts.push(SQL_TEMPLATES.ORDER_BY, orderBy);
3663
3868
  let sql = parts.join(" ").trim();
3664
3869
  sql = appendPagination(sql, spec);
3665
- return finalizeSql(sql, params);
3870
+ return finalizeSql(sql, params, dialect);
3871
+ }
3872
+
3873
+ // src/builder/shared/validators/field-assertions.ts
3874
+ var NUMERIC_TYPES = /* @__PURE__ */ new Set(["Int", "Float", "Decimal", "BigInt"]);
3875
+ function assertScalarField(model, fieldName, context) {
3876
+ const field = getFieldInfo(model, fieldName);
3877
+ if (!field) {
3878
+ throw createError(
3879
+ `${context} references unknown field '${fieldName}' on model ${model.name}`,
3880
+ {
3881
+ field: fieldName,
3882
+ modelName: model.name,
3883
+ availableFields: model.fields.map((f) => f.name)
3884
+ }
3885
+ );
3886
+ }
3887
+ if (field.isRelation) {
3888
+ throw createError(
3889
+ `${context} does not support relation field '${fieldName}'`,
3890
+ { field: fieldName, modelName: model.name }
3891
+ );
3892
+ }
3893
+ return field;
3894
+ }
3895
+ function assertNumericField(model, fieldName, context) {
3896
+ const field = assertScalarField(model, fieldName, context);
3897
+ const baseType = field.type.replace(/\[\]|\?/g, "");
3898
+ if (!NUMERIC_TYPES.has(baseType)) {
3899
+ throw createError(
3900
+ `${context} requires numeric field, got '${field.type}'`,
3901
+ { field: fieldName, modelName: model.name }
3902
+ );
3903
+ }
3904
+ return field;
3666
3905
  }
3667
3906
 
3668
3907
  // src/builder/select.ts
@@ -3695,7 +3934,7 @@ function buildPostgresDistinctOrderBy(distinctFields, existing) {
3695
3934
  }
3696
3935
  return next;
3697
3936
  }
3698
- function applyPostgresDistinctOrderBy(args, _model) {
3937
+ function applyPostgresDistinctOrderBy(args) {
3699
3938
  const distinctFields = normalizeDistinctFields(args.distinct);
3700
3939
  if (distinctFields.length === 0) return args;
3701
3940
  if (!isNotNullish(args.orderBy)) return args;
@@ -3705,19 +3944,6 @@ function applyPostgresDistinctOrderBy(args, _model) {
3705
3944
  orderBy: buildPostgresDistinctOrderBy(distinctFields, existing)
3706
3945
  });
3707
3946
  }
3708
- function assertScalarFieldOnModel(model, fieldName, ctx) {
3709
- const f = model.fields.find((x) => x.name === fieldName);
3710
- if (!f) {
3711
- throw new Error(
3712
- `${ctx} references unknown field '${fieldName}' on model ${model.name}`
3713
- );
3714
- }
3715
- if (f.isRelation) {
3716
- throw new Error(
3717
- `${ctx} does not support relation field '${fieldName}' on model ${model.name}`
3718
- );
3719
- }
3720
- }
3721
3947
  function validateDistinct(model, distinct) {
3722
3948
  if (!isNotNullish(distinct) || !isNonEmptyArray(distinct)) return;
3723
3949
  const seen = /* @__PURE__ */ new Set();
@@ -3728,24 +3954,24 @@ function validateDistinct(model, distinct) {
3728
3954
  throw new Error(`distinct must not contain duplicates (field: '${f}')`);
3729
3955
  }
3730
3956
  seen.add(f);
3731
- assertScalarFieldOnModel(model, f, "distinct");
3957
+ assertScalarField(model, f, "distinct");
3732
3958
  }
3733
3959
  }
3734
- function validateOrderByValue(fieldName, v) {
3735
- parseOrderByValue(v, fieldName);
3736
- }
3737
3960
  function validateOrderBy(model, orderBy) {
3738
3961
  if (!isNotNullish(orderBy)) return;
3739
3962
  const items = normalizeOrderByInput2(orderBy);
3740
3963
  if (items.length === 0) return;
3741
3964
  for (const it of items) {
3742
3965
  const entries = Object.entries(it);
3966
+ if (entries.length !== 1) {
3967
+ throw new Error("orderBy array entries must have exactly one field");
3968
+ }
3743
3969
  const fieldName = String(entries[0][0]).trim();
3744
3970
  if (fieldName.length === 0) {
3745
3971
  throw new Error("orderBy field name cannot be empty");
3746
3972
  }
3747
- assertScalarFieldOnModel(model, fieldName, "orderBy");
3748
- validateOrderByValue(fieldName, entries[0][1]);
3973
+ assertScalarField(model, fieldName, "orderBy");
3974
+ parseOrderByValue(entries[0][1], fieldName);
3749
3975
  }
3750
3976
  }
3751
3977
  function validateCursor(model, cursor) {
@@ -3762,7 +3988,7 @@ function validateCursor(model, cursor) {
3762
3988
  if (f.length === 0) {
3763
3989
  throw new Error("cursor field name cannot be empty");
3764
3990
  }
3765
- assertScalarFieldOnModel(model, f, "cursor");
3991
+ assertScalarField(model, f, "cursor");
3766
3992
  }
3767
3993
  }
3768
3994
  function resolveDialect(dialect) {
@@ -3781,20 +4007,21 @@ function normalizeArgsForNegativeTake(method, args) {
3781
4007
  orderBy: reverseOrderByInput(args.orderBy)
3782
4008
  });
3783
4009
  }
3784
- function normalizeArgsForDialect(dialect, args, model) {
4010
+ function normalizeArgsForDialect(dialect, args) {
3785
4011
  if (dialect !== "postgres") return args;
3786
4012
  return applyPostgresDistinctOrderBy(args);
3787
4013
  }
3788
4014
  function buildCursorClauseIfAny(input) {
3789
- const { cursor, orderBy, tableName, alias, params, dialect } = input;
3790
- if (!isNotNullish(cursor)) return void 0;
4015
+ const { cursor, orderBy, tableName, alias, params, dialect, model } = input;
4016
+ if (!isNotNullish(cursor)) return {};
3791
4017
  return buildCursorCondition(
3792
4018
  cursor,
3793
4019
  orderBy,
3794
4020
  tableName,
3795
4021
  alias,
3796
4022
  params,
3797
- dialect
4023
+ dialect,
4024
+ model
3798
4025
  );
3799
4026
  }
3800
4027
  function buildSelectSpec(input) {
@@ -3833,14 +4060,20 @@ function buildSelectSpec(input) {
3833
4060
  params,
3834
4061
  dialect
3835
4062
  );
3836
- const cursorClause = buildCursorClauseIfAny({
4063
+ const cursorResult = buildCursorClauseIfAny({
3837
4064
  cursor,
3838
4065
  orderBy: normalizedArgs.orderBy,
3839
4066
  tableName,
3840
4067
  alias,
3841
4068
  params,
3842
- dialect
4069
+ dialect,
4070
+ model
3843
4071
  });
4072
+ if (dialect === "sqlite" && isNonEmptyArray(normalizedArgs.distinct) && cursorResult.condition) {
4073
+ throw new Error(
4074
+ "Cursor pagination with distinct is not supported in SQLite due to window function limitations. Use findMany with skip/take instead, or remove distinct."
4075
+ );
4076
+ }
3844
4077
  return {
3845
4078
  select: selectFields,
3846
4079
  includes,
@@ -3851,7 +4084,8 @@ function buildSelectSpec(input) {
3851
4084
  pagination: { take, skip },
3852
4085
  distinct: normalizedArgs.distinct,
3853
4086
  method,
3854
- cursorClause,
4087
+ cursorCte: cursorResult.cte,
4088
+ cursorClause: cursorResult.condition,
3855
4089
  params,
3856
4090
  dialect,
3857
4091
  model,
@@ -3865,9 +4099,7 @@ function buildSelectSql(input) {
3865
4099
  assertSafeTableRef(from.tableName);
3866
4100
  const dialectToUse = resolveDialect(dialect);
3867
4101
  const argsForSql = normalizeArgsForNegativeTake(method, args);
3868
- const normalizedArgs = normalizeArgsForDialect(
3869
- dialectToUse,
3870
- argsForSql);
4102
+ const normalizedArgs = normalizeArgsForDialect(dialectToUse, argsForSql);
3871
4103
  validateDistinct(model, normalizedArgs.distinct);
3872
4104
  validateOrderBy(model, normalizedArgs.orderBy);
3873
4105
  validateCursor(model, normalizedArgs.cursor);
@@ -3883,8 +4115,21 @@ function buildSelectSql(input) {
3883
4115
  });
3884
4116
  return constructFinalSql(spec);
3885
4117
  }
3886
- var MODEL_FIELD_CACHE = /* @__PURE__ */ new WeakMap();
3887
- var NUMERIC_TYPES = /* @__PURE__ */ new Set(["Int", "Float", "Decimal", "BigInt"]);
4118
+
4119
+ // src/builder/shared/comparison-builder.ts
4120
+ function buildComparisons(expr, filter, params, dialect, builder, excludeKeys = /* @__PURE__ */ new Set(["mode"])) {
4121
+ const out = [];
4122
+ for (const [op, val] of Object.entries(filter)) {
4123
+ if (excludeKeys.has(op) || val === void 0) continue;
4124
+ const built = builder(expr, op, val, params, dialect);
4125
+ if (built && built.trim().length > 0) {
4126
+ out.push(built);
4127
+ }
4128
+ }
4129
+ return out;
4130
+ }
4131
+
4132
+ // src/builder/aggregates.ts
3888
4133
  var AGGREGATES = [
3889
4134
  ["_sum", "SUM"],
3890
4135
  ["_avg", "AVG"],
@@ -3899,19 +4144,16 @@ var COMPARISON_OPS = {
3899
4144
  [Ops.LT]: "<",
3900
4145
  [Ops.LTE]: "<="
3901
4146
  };
3902
- function normalizeFinalParams2(params) {
3903
- return params.map(normalizeValue);
3904
- }
3905
- function getModelFieldMap(model) {
3906
- const cached = MODEL_FIELD_CACHE.get(model);
3907
- if (cached) return cached;
3908
- const m = /* @__PURE__ */ new Map();
3909
- for (const f of model.fields) {
3910
- m.set(f.name, { name: f.name, type: f.type, isRelation: !!f.isRelation });
3911
- }
3912
- MODEL_FIELD_CACHE.set(model, m);
3913
- return m;
3914
- }
4147
+ var HAVING_ALLOWED_OPS = /* @__PURE__ */ new Set([
4148
+ Ops.EQUALS,
4149
+ Ops.NOT,
4150
+ Ops.GT,
4151
+ Ops.GTE,
4152
+ Ops.LT,
4153
+ Ops.LTE,
4154
+ Ops.IN,
4155
+ Ops.NOT_IN
4156
+ ]);
3915
4157
  function isTruthySelection(v) {
3916
4158
  return v === true;
3917
4159
  }
@@ -3953,24 +4195,10 @@ function normalizeLogicalValue2(operator, value) {
3953
4195
  }
3954
4196
  throw new Error(`${operator} must be an object or array of objects in HAVING`);
3955
4197
  }
3956
- function assertScalarField2(model, fieldName, ctx) {
3957
- const m = getModelFieldMap(model);
3958
- const field = m.get(fieldName);
3959
- if (!field) {
3960
- throw new Error(
3961
- `${ctx} references unknown field '${fieldName}' on model ${model.name}. Available fields: ${model.fields.map((f) => f.name).join(", ")}`
3962
- );
3963
- }
3964
- if (field.isRelation) {
3965
- throw new Error(`${ctx} does not support relation field '${fieldName}'`);
3966
- }
3967
- return { name: field.name, type: field.type };
3968
- }
3969
- function assertAggregateFieldType(aggKey, fieldType, fieldName, modelName) {
3970
- const baseType = fieldType.replace(/\[\]|\?/g, "");
3971
- if ((aggKey === "_sum" || aggKey === "_avg") && !NUMERIC_TYPES.has(baseType)) {
4198
+ function assertHavingOp(op) {
4199
+ if (!HAVING_ALLOWED_OPS.has(op)) {
3972
4200
  throw new Error(
3973
- `Cannot use ${aggKey} on non-numeric field '${fieldName}' (type: ${fieldType}) on model ${modelName}`
4201
+ `Unsupported HAVING operator '${op}'. Allowed: ${[...HAVING_ALLOWED_OPS].join(", ")}`
3974
4202
  );
3975
4203
  }
3976
4204
  }
@@ -4000,6 +4228,7 @@ function buildBinaryComparison(expr, op, val, params) {
4000
4228
  return `${expr} ${sqlOp} ${placeholder}`;
4001
4229
  }
4002
4230
  function buildSimpleComparison(expr, op, val, params, dialect) {
4231
+ assertHavingOp(op);
4003
4232
  if (val === null) return buildNullComparison(expr, op);
4004
4233
  if (op === Ops.NOT && isPlainObject(val)) {
4005
4234
  return buildNotComposite(
@@ -4088,20 +4317,19 @@ function assertHavingAggTarget(aggKey, field, model) {
4088
4317
  }
4089
4318
  return;
4090
4319
  }
4091
- const f = assertScalarField2(model, field, "HAVING");
4092
- assertAggregateFieldType(aggKey, f.type, f.name, model.name);
4320
+ if (aggKey === "_sum" || aggKey === "_avg") {
4321
+ assertNumericField(model, field, "HAVING");
4322
+ } else {
4323
+ assertScalarField(model, field, "HAVING");
4324
+ }
4093
4325
  }
4094
4326
  function buildHavingOpsForExpr(expr, filter, params, dialect) {
4095
- const out = [];
4096
- for (const [op, val] of Object.entries(filter)) {
4097
- if (op === "mode") continue;
4098
- const built = buildSimpleComparison(expr, op, val, params, dialect);
4099
- if (built && built.trim().length > 0) out.push(built);
4100
- }
4101
- return out;
4327
+ return buildComparisons(expr, filter, params, dialect, buildSimpleComparison);
4102
4328
  }
4103
4329
  function buildHavingForAggregateFirstShape(aggKey, target, alias, params, dialect, model) {
4104
- if (!isPlainObject(target)) return [];
4330
+ if (!isPlainObject(target)) {
4331
+ throw new Error(`HAVING '${aggKey}' must be an object`);
4332
+ }
4105
4333
  const out = [];
4106
4334
  for (const [field, filter] of Object.entries(target)) {
4107
4335
  assertHavingAggTarget(aggKey, field, model);
@@ -4112,30 +4340,39 @@ function buildHavingForAggregateFirstShape(aggKey, target, alias, params, dialec
4112
4340
  return out;
4113
4341
  }
4114
4342
  function buildHavingForFieldFirstShape(fieldName, target, alias, params, dialect, model) {
4115
- if (!isPlainObject(target)) return [];
4116
- const field = assertScalarField2(model, fieldName, "HAVING");
4343
+ if (!isPlainObject(target)) {
4344
+ throw new Error(`HAVING '${fieldName}' must be an object`);
4345
+ }
4346
+ assertScalarField(model, fieldName, "HAVING");
4117
4347
  const out = [];
4118
4348
  const obj = target;
4119
4349
  const keys = ["_count", "_sum", "_avg", "_min", "_max"];
4120
4350
  for (const aggKey of keys) {
4121
4351
  const aggFilter = obj[aggKey];
4122
4352
  if (!isPlainObject(aggFilter)) continue;
4123
- assertAggregateFieldType(aggKey, field.type, field.name, model.name);
4353
+ if (aggKey === "_sum" || aggKey === "_avg") {
4354
+ assertNumericField(model, fieldName, "HAVING");
4355
+ }
4124
4356
  const entries = Object.entries(aggFilter);
4125
4357
  if (entries.length === 0) continue;
4126
4358
  const expr = aggExprForField(aggKey, fieldName, alias, model);
4127
- for (const [op, val] of entries) {
4128
- if (op === "mode") continue;
4129
- const built = buildSimpleComparison(expr, op, val, params, dialect);
4130
- if (built && built.trim().length > 0) out.push(built);
4131
- }
4359
+ const clauses = buildComparisons(
4360
+ expr,
4361
+ aggFilter,
4362
+ params,
4363
+ dialect,
4364
+ buildSimpleComparison
4365
+ );
4366
+ out.push(...clauses);
4132
4367
  }
4133
4368
  return out;
4134
4369
  }
4135
4370
  function buildHavingClause(having, alias, params, model, dialect) {
4136
4371
  if (!isNotNullish(having)) return "";
4137
4372
  const d = dialect != null ? dialect : getGlobalDialect();
4138
- if (!isPlainObject(having)) return "";
4373
+ if (!isPlainObject(having)) {
4374
+ throw new Error("having must be an object");
4375
+ }
4139
4376
  return buildHavingNode(having, alias, params, d, model);
4140
4377
  }
4141
4378
  function normalizeCountArg(v) {
@@ -4149,18 +4386,8 @@ function pushCountAllField(fields) {
4149
4386
  `${SQL_TEMPLATES.COUNT_ALL} ${SQL_TEMPLATES.AS} ${quote("_count._all")}`
4150
4387
  );
4151
4388
  }
4152
- function assertCountableScalarField(fieldMap, model, fieldName) {
4153
- const field = fieldMap.get(fieldName);
4154
- if (!field) {
4155
- throw new Error(
4156
- `Field '${fieldName}' does not exist on model ${model.name}`
4157
- );
4158
- }
4159
- if (field.isRelation) {
4160
- throw new Error(
4161
- `Cannot use _count on relation field '${fieldName}' on model ${model.name}`
4162
- );
4163
- }
4389
+ function assertCountableScalarField(model, fieldName) {
4390
+ assertScalarField(model, fieldName, "_count");
4164
4391
  }
4165
4392
  function pushCountField(fields, alias, fieldName, model) {
4166
4393
  const outAlias = `_count.${fieldName}`;
@@ -4168,7 +4395,7 @@ function pushCountField(fields, alias, fieldName, model) {
4168
4395
  `COUNT(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4169
4396
  );
4170
4397
  }
4171
- function addCountFields(fields, countArg, alias, model, fieldMap) {
4398
+ function addCountFields(fields, countArg, alias, model) {
4172
4399
  if (!isNotNullish(countArg)) return;
4173
4400
  if (countArg === true) {
4174
4401
  pushCountAllField(fields);
@@ -4182,7 +4409,7 @@ function addCountFields(fields, countArg, alias, model, fieldMap) {
4182
4409
  ([f, v]) => f !== "_all" && isTruthySelection(v)
4183
4410
  );
4184
4411
  for (const [f] of selected) {
4185
- assertCountableScalarField(fieldMap, model, f);
4412
+ assertCountableScalarField(model, f);
4186
4413
  pushCountField(fields, alias, f, model);
4187
4414
  }
4188
4415
  }
@@ -4190,19 +4417,12 @@ function getAggregateSelectionObject(args, agg) {
4190
4417
  const obj = args[agg];
4191
4418
  return isPlainObject(obj) ? obj : void 0;
4192
4419
  }
4193
- function assertAggregatableScalarField(fieldMap, model, agg, fieldName) {
4194
- const field = fieldMap.get(fieldName);
4195
- if (!field) {
4196
- throw new Error(
4197
- `Field '${fieldName}' does not exist on model ${model.name}`
4198
- );
4199
- }
4200
- if (field.isRelation) {
4201
- throw new Error(
4202
- `Cannot use ${agg} on relation field '${fieldName}' on model ${model.name}`
4203
- );
4420
+ function assertAggregatableScalarField(model, agg, fieldName) {
4421
+ if (agg === "_sum" || agg === "_avg") {
4422
+ assertNumericField(model, fieldName, agg);
4423
+ } else {
4424
+ assertScalarField(model, fieldName, agg);
4204
4425
  }
4205
- return field;
4206
4426
  }
4207
4427
  function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
4208
4428
  const outAlias = `${agg}.${fieldName}`;
@@ -4210,7 +4430,7 @@ function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
4210
4430
  `${aggFn}(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4211
4431
  );
4212
4432
  }
4213
- function addAggregateFields(fields, args, alias, model, fieldMap) {
4433
+ function addAggregateFields(fields, args, alias, model) {
4214
4434
  for (const [agg, aggFn] of AGGREGATES) {
4215
4435
  const obj = getAggregateSelectionObject(args, agg);
4216
4436
  if (!obj) continue;
@@ -4218,23 +4438,16 @@ function addAggregateFields(fields, args, alias, model, fieldMap) {
4218
4438
  if (fieldName === "_all")
4219
4439
  throw new Error(`'${agg}' does not support '_all'`);
4220
4440
  if (!isTruthySelection(selection)) continue;
4221
- const field = assertAggregatableScalarField(
4222
- fieldMap,
4223
- model,
4224
- agg,
4225
- fieldName
4226
- );
4227
- assertAggregateFieldType(agg, field.type, fieldName, model.name);
4441
+ assertAggregatableScalarField(model, agg, fieldName);
4228
4442
  pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model);
4229
4443
  }
4230
4444
  }
4231
4445
  }
4232
4446
  function buildAggregateFields(args, alias, model) {
4233
4447
  const fields = [];
4234
- const fieldMap = getModelFieldMap(model);
4235
4448
  const countArg = normalizeCountArg(args._count);
4236
- addCountFields(fields, countArg, alias, model, fieldMap);
4237
- addAggregateFields(fields, args, alias, model, fieldMap);
4449
+ addCountFields(fields, countArg, alias, model);
4450
+ addAggregateFields(fields, args, alias, model);
4238
4451
  return fields;
4239
4452
  }
4240
4453
  function buildAggregateSql(args, whereResult, tableName, alias, model) {
@@ -4258,7 +4471,7 @@ function buildAggregateSql(args, whereResult, tableName, alias, model) {
4258
4471
  validateParamConsistency(sql, whereResult.params);
4259
4472
  return Object.freeze({
4260
4473
  sql,
4261
- params: Object.freeze(normalizeFinalParams2([...whereResult.params])),
4474
+ params: Object.freeze([...whereResult.params]),
4262
4475
  paramMappings: Object.freeze([...whereResult.paramMappings])
4263
4476
  });
4264
4477
  }
@@ -4271,32 +4484,24 @@ function assertGroupByBy(args, model) {
4271
4484
  if (bySet.size !== byFields.length) {
4272
4485
  throw new Error("buildGroupBySql: by must not contain duplicates");
4273
4486
  }
4274
- const modelFieldMap = getModelFieldMap(model);
4275
4487
  for (const f of byFields) {
4276
- const field = modelFieldMap.get(f);
4277
- if (!field) {
4278
- throw new Error(
4279
- `groupBy.by references unknown field '${f}' on model ${model.name}`
4280
- );
4281
- }
4282
- if (field.isRelation) {
4283
- throw new Error(
4284
- `groupBy.by does not support relation field '${f}' on model ${model.name}`
4285
- );
4286
- }
4488
+ assertScalarField(model, f, "groupBy.by");
4287
4489
  }
4288
4490
  return byFields;
4289
4491
  }
4290
4492
  function buildGroupBySelectParts(args, alias, model, byFields) {
4291
4493
  const groupCols = byFields.map((f) => col(alias, f, model));
4494
+ const selectCols = byFields.map((f) => colWithAlias(alias, f, model));
4292
4495
  const groupFields = groupCols.join(SQL_SEPARATORS.FIELD_LIST);
4293
4496
  const aggFields = buildAggregateFields(args, alias, model);
4294
- const selectFields = isNonEmptyArray(aggFields) ? groupCols.concat(aggFields).join(SQL_SEPARATORS.FIELD_LIST) : groupCols.join(SQL_SEPARATORS.FIELD_LIST);
4497
+ const selectFields = isNonEmptyArray(aggFields) ? selectCols.concat(aggFields).join(SQL_SEPARATORS.FIELD_LIST) : selectCols.join(SQL_SEPARATORS.FIELD_LIST);
4295
4498
  return { groupCols, groupFields, selectFields };
4296
4499
  }
4297
4500
  function buildGroupByHaving(args, alias, params, model, dialect) {
4298
4501
  if (!isNotNullish(args.having)) return "";
4299
- if (!isPlainObject(args.having)) return "";
4502
+ if (!isPlainObject(args.having)) {
4503
+ throw new Error("having must be an object");
4504
+ }
4300
4505
  const h = buildHavingClause(args.having, alias, params, model, dialect);
4301
4506
  if (!h || h.trim().length === 0) return "";
4302
4507
  return `${SQL_TEMPLATES.HAVING} ${h}`;
@@ -4332,61 +4537,58 @@ function buildGroupBySql(args, whereResult, tableName, alias, model, dialect) {
4332
4537
  const mergedParams = [...whereResult.params, ...snapshot.params];
4333
4538
  return Object.freeze({
4334
4539
  sql,
4335
- params: Object.freeze(normalizeFinalParams2(mergedParams)),
4540
+ params: Object.freeze(mergedParams),
4336
4541
  paramMappings: Object.freeze([
4337
4542
  ...whereResult.paramMappings,
4338
4543
  ...snapshot.mappings
4339
4544
  ])
4340
4545
  });
4341
4546
  }
4342
- function buildCountSql(whereResult, tableName, alias, skip, dialect) {
4547
+ function buildCountSql(whereResult, tableName, alias, skip, _dialect) {
4343
4548
  assertSafeAlias(alias);
4344
4549
  assertSafeTableRef(tableName);
4345
- const d = getGlobalDialect();
4550
+ if (skip !== void 0 && skip !== null) {
4551
+ if (isDynamicParameter(skip)) {
4552
+ throw new Error(
4553
+ "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."
4554
+ );
4555
+ }
4556
+ if (typeof skip === "string") {
4557
+ const s = skip.trim();
4558
+ if (s.length > 0) {
4559
+ const n = Number(s);
4560
+ if (Number.isFinite(n) && Number.isInteger(n) && n > 0) {
4561
+ throw new Error(
4562
+ "count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
4563
+ );
4564
+ }
4565
+ }
4566
+ }
4567
+ if (typeof skip === "number" && Number.isFinite(skip) && Number.isInteger(skip) && skip > 0) {
4568
+ throw new Error(
4569
+ "count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
4570
+ );
4571
+ }
4572
+ }
4346
4573
  const whereClause = isValidWhereClause(whereResult.clause) ? `${SQL_TEMPLATES.WHERE} ${whereResult.clause}` : "";
4347
- const params = createParamStore(whereResult.nextParamIndex);
4348
- const baseSubSelect = [
4349
- SQL_TEMPLATES.SELECT,
4350
- "1",
4351
- SQL_TEMPLATES.FROM,
4352
- tableName,
4353
- alias,
4354
- whereClause
4355
- ].filter((x) => x && String(x).trim().length > 0).join(" ").trim();
4356
- const normalizedSkip = normalizeSkipLike(skip);
4357
- const subSelect = applyCountSkip(baseSubSelect, normalizedSkip, params, d);
4358
4574
  const sql = [
4359
4575
  SQL_TEMPLATES.SELECT,
4360
4576
  SQL_TEMPLATES.COUNT_ALL,
4361
4577
  SQL_TEMPLATES.AS,
4362
4578
  quote("_count._all"),
4363
4579
  SQL_TEMPLATES.FROM,
4364
- `(${subSelect})`,
4365
- SQL_TEMPLATES.AS,
4366
- `"sub"`
4580
+ tableName,
4581
+ alias,
4582
+ whereClause
4367
4583
  ].filter((x) => x && String(x).trim().length > 0).join(" ").trim();
4368
4584
  validateSelectQuery(sql);
4369
- const snapshot = params.snapshot();
4370
- const mergedParams = [...whereResult.params, ...snapshot.params];
4371
- validateParamConsistency(sql, mergedParams);
4585
+ validateParamConsistency(sql, whereResult.params);
4372
4586
  return Object.freeze({
4373
4587
  sql,
4374
- params: Object.freeze(normalizeFinalParams2(mergedParams)),
4375
- paramMappings: Object.freeze([
4376
- ...whereResult.paramMappings,
4377
- ...snapshot.mappings
4378
- ])
4588
+ params: Object.freeze([...whereResult.params]),
4589
+ paramMappings: Object.freeze([...whereResult.paramMappings])
4379
4590
  });
4380
4591
  }
4381
- function applyCountSkip(subSelect, normalizedSkip, params, dialect) {
4382
- const shouldApply = isDynamicParameter(normalizedSkip) || typeof normalizedSkip === "number" && normalizedSkip > 0;
4383
- if (!shouldApply) return subSelect;
4384
- const placeholder = addAutoScoped(params, normalizedSkip, "count.skip");
4385
- if (dialect === "sqlite") {
4386
- return `${subSelect} ${SQL_TEMPLATES.LIMIT} -1 ${SQL_TEMPLATES.OFFSET} ${placeholder}`;
4387
- }
4388
- return `${subSelect} ${SQL_TEMPLATES.OFFSET} ${placeholder}`;
4389
- }
4390
4592
  function safeAlias(input) {
4391
4593
  const raw = String(input).toLowerCase();
4392
4594
  const cleaned = raw.replace(/[^a-z0-9_]/g, "_");
@@ -4536,8 +4738,12 @@ function buildAndNormalizeSql(args) {
4536
4738
  );
4537
4739
  }
4538
4740
  function finalizeDirective(args) {
4539
- const { directive, normalizedSql, normalizedMappings } = args;
4540
- validateSqlPositions(normalizedSql, normalizedMappings, getGlobalDialect());
4741
+ const { directive, normalizedSql, normalizedMappings, dialect } = args;
4742
+ const params = normalizedMappings.map((m) => {
4743
+ var _a;
4744
+ return (_a = m.value) != null ? _a : void 0;
4745
+ });
4746
+ validateParamConsistencyByDialect(normalizedSql, params, dialect);
4541
4747
  const { staticParams, dynamicKeys } = buildParamsFromMappings(normalizedMappings);
4542
4748
  return {
4543
4749
  method: directive.method,
@@ -4574,7 +4780,8 @@ function generateSQL(directive) {
4574
4780
  return finalizeDirective({
4575
4781
  directive,
4576
4782
  normalizedSql: normalized.sql,
4577
- normalizedMappings: normalized.paramMappings
4783
+ normalizedMappings: normalized.paramMappings,
4784
+ dialect
4578
4785
  });
4579
4786
  }
4580
4787
  function generateSQL2(directive) {
@@ -4682,18 +4889,57 @@ function generateCode(models, queries, dialect, datamodel) {
4682
4889
  return `// Generated by @prisma-sql/generator - DO NOT EDIT
4683
4890
  import { buildSQL, transformQueryResults, type PrismaMethod, type Model } from 'prisma-sql'
4684
4891
 
4685
- function normalizeValue(value: unknown): unknown {
4892
+ /**
4893
+ * Normalize values for SQL params.
4894
+ * Synced from src/utils/normalize-value.ts
4895
+ */
4896
+ function normalizeValue(value: unknown, seen = new WeakSet<object>(), depth = 0): unknown {
4897
+ const MAX_DEPTH = 20
4898
+ if (depth > MAX_DEPTH) {
4899
+ throw new Error(\`Max normalization depth exceeded (\${MAX_DEPTH} levels)\`)
4900
+ }
4686
4901
  if (value instanceof Date) {
4902
+ const t = value.getTime()
4903
+ if (!Number.isFinite(t)) {
4904
+ throw new Error('Invalid Date value in SQL params')
4905
+ }
4687
4906
  return value.toISOString()
4688
4907
  }
4689
-
4908
+ if (typeof value === 'bigint') {
4909
+ return value.toString()
4910
+ }
4690
4911
  if (Array.isArray(value)) {
4691
- return value.map(normalizeValue)
4912
+ const arrRef = value as unknown as object
4913
+ if (seen.has(arrRef)) {
4914
+ throw new Error('Circular reference in SQL params')
4915
+ }
4916
+ seen.add(arrRef)
4917
+ const out = value.map((v) => normalizeValue(v, seen, depth + 1))
4918
+ seen.delete(arrRef)
4919
+ return out
4920
+ }
4921
+ if (value && typeof value === 'object') {
4922
+ if (value instanceof Uint8Array) return value
4923
+ if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) return value
4924
+ const proto = Object.getPrototypeOf(value)
4925
+ const isPlain = proto === Object.prototype || proto === null
4926
+ if (!isPlain) return value
4927
+ const obj = value as Record<string, unknown>
4928
+ if (seen.has(obj)) {
4929
+ throw new Error('Circular reference in SQL params')
4930
+ }
4931
+ seen.add(obj)
4932
+ const out: Record<string, unknown> = {}
4933
+ for (const [k, v] of Object.entries(obj)) {
4934
+ out[k] = normalizeValue(v, seen, depth + 1)
4935
+ }
4936
+ seen.delete(obj)
4937
+ return out
4692
4938
  }
4693
-
4694
4939
  return value
4695
4940
  }
4696
4941
 
4942
+
4697
4943
  export const MODELS: Model[] = ${JSON.stringify(cleanModels, null, 2)}
4698
4944
 
4699
4945
  const ENUM_MAPPINGS: Record<string, Record<string, string>> = ${JSON.stringify(mappings, null, 2)}