prisma-sql 1.43.0 → 1.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.43.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)}`;
3468
+ }
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 ")})`;
3335
3482
  }
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})`;
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,13 +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();
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");
3380
3554
  function joinNonEmpty(parts, sep) {
3381
3555
  return parts.filter((s) => s.trim().length > 0).join(sep);
3382
3556
  }
3383
3557
  function buildWhereSql(conditions) {
3384
3558
  if (!isNonEmptyArray(conditions)) return "";
3385
- 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(" ")}`;
3386
3564
  }
3387
3565
  function buildJoinsSql(...joinGroups) {
3388
3566
  const all = [];
@@ -3397,37 +3575,39 @@ function buildSelectList(baseSelect, extraCols) {
3397
3575
  if (base && extra) return `${base}${SQL_SEPARATORS.FIELD_LIST}${extra}`;
3398
3576
  return base || extra;
3399
3577
  }
3400
- function finalizeSql(sql, params) {
3578
+ function finalizeSql(sql, params, dialect) {
3401
3579
  const snapshot = params.snapshot();
3402
3580
  validateSelectQuery(sql);
3403
- validateParamConsistency(sql, snapshot.params);
3581
+ validateParamConsistencyByDialect(sql, snapshot.params, dialect);
3404
3582
  return Object.freeze({
3405
3583
  sql,
3406
3584
  params: snapshot.params,
3407
- paramMappings: snapshot.mappings
3585
+ paramMappings: Object.freeze(snapshot.mappings)
3408
3586
  });
3409
3587
  }
3410
- function parseSimpleScalarSelect(select, alias) {
3411
- var _a, _b;
3588
+ function parseSimpleScalarSelect(select, fromAlias) {
3589
+ var _a, _b, _c, _d;
3412
3590
  const raw = select.trim();
3413
3591
  if (raw.length === 0) return [];
3414
- let re = SIMPLE_SELECT_RE_CACHE.get(alias);
3415
- if (!re) {
3416
- const safeAlias2 = alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3417
- re = new RegExp(`^${safeAlias2}\\.(?:"([^"]+)"|([a-z_][a-z0-9_]*))$`, "i");
3418
- SIMPLE_SELECT_RE_CACHE.set(alias, re);
3419
- }
3420
3592
  const parts = raw.split(SQL_SEPARATORS.FIELD_LIST);
3421
3593
  const names = [];
3422
3594
  for (const part of parts) {
3423
3595
  const p = part.trim();
3424
- const m = p.match(re);
3596
+ const m = p.match(SIMPLE_COLUMN_RE);
3425
3597
  if (!m) {
3426
3598
  throw new Error(
3427
- `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}`
3428
3606
  );
3429
3607
  }
3430
- 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;
3431
3611
  if (name.length === 0) {
3432
3612
  throw new Error(`Failed to parse selected column name from: ${p}`);
3433
3613
  }
@@ -3436,18 +3616,18 @@ function parseSimpleScalarSelect(select, alias) {
3436
3616
  return names;
3437
3617
  }
3438
3618
  function replaceOrderByAlias(orderBy, fromAlias, outerAlias) {
3439
- const needle = `${fromAlias}.`;
3440
- const replacement = `${outerAlias}.`;
3441
- 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}.`);
3442
3624
  }
3443
3625
  function buildDistinctColumns(distinct, fromAlias, model) {
3444
3626
  return distinct.map((f) => col(fromAlias, f, model)).join(SQL_SEPARATORS.FIELD_LIST);
3445
3627
  }
3446
3628
  function buildOutputColumns(scalarNames, includeNames, hasCount) {
3447
3629
  const outputCols = [...scalarNames, ...includeNames];
3448
- if (hasCount) {
3449
- outputCols.push("_count");
3450
- }
3630
+ if (hasCount) outputCols.push("_count");
3451
3631
  const formatted = outputCols.map((n) => quote(n)).join(SQL_SEPARATORS.FIELD_LIST);
3452
3632
  if (!isNonEmptyString(formatted)) {
3453
3633
  throw new Error("distinct emulation requires at least one output column");
@@ -3456,9 +3636,10 @@ function buildOutputColumns(scalarNames, includeNames, hasCount) {
3456
3636
  }
3457
3637
  function buildWindowOrder(args) {
3458
3638
  const { baseOrder, idField, fromAlias, model } = args;
3639
+ const fromLower = String(fromAlias).toLowerCase();
3459
3640
  const orderFields = baseOrder.split(SQL_SEPARATORS.ORDER_BY).map((s) => s.trim().toLowerCase());
3460
3641
  const hasIdInOrder = orderFields.some(
3461
- (f) => f.startsWith(`${fromAlias}.id `) || f.startsWith(`${fromAlias}."id" `)
3642
+ (f) => f.startsWith(`${fromLower}.id `) || f.startsWith(`${fromLower}."id" `)
3462
3643
  );
3463
3644
  if (hasIdInOrder) return baseOrder;
3464
3645
  const idTiebreaker = idField ? `, ${col(fromAlias, "id", model)} ASC` : "";
@@ -3493,15 +3674,37 @@ function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
3493
3674
  const outerOrder = isNonEmptyString(orderBy) ? replaceOrderByAlias(orderBy, from.alias, `"__tp_distinct"`) : replaceOrderByAlias(fallbackOrder, from.alias, `"__tp_distinct"`);
3494
3675
  const joins = buildJoinsSql(whereJoins, countJoins);
3495
3676
  const conditions = [];
3496
- if (whereClause && whereClause !== "1=1") {
3497
- conditions.push(whereClause);
3498
- }
3677
+ if (whereClause && whereClause !== "1=1") conditions.push(whereClause);
3499
3678
  const whereSql = buildWhereSql(conditions);
3500
3679
  const innerSelectList = selectWithIncludes.trim();
3501
3680
  const innerComma = innerSelectList.length > 0 ? SQL_SEPARATORS.FIELD_LIST : "";
3502
- 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}`;
3503
- 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}` : "");
3504
- 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(" ");
3505
3708
  }
3506
3709
  function buildIncludeColumns(spec) {
3507
3710
  var _a, _b;
@@ -3629,6 +3832,7 @@ function constructFinalSql(spec) {
3629
3832
  orderBy,
3630
3833
  distinct,
3631
3834
  method,
3835
+ cursorCte,
3632
3836
  cursorClause,
3633
3837
  params,
3634
3838
  dialect,
@@ -3643,9 +3847,13 @@ function constructFinalSql(spec) {
3643
3847
  const spec2 = withCountJoins(spec, countJoins, whereJoins);
3644
3848
  let sql2 = buildSqliteDistinctQuery(spec2, selectWithIncludes).trim();
3645
3849
  sql2 = appendPagination(sql2, spec);
3646
- return finalizeSql(sql2, params);
3850
+ return finalizeSql(sql2, params, dialect);
3647
3851
  }
3648
- const parts = [SQL_TEMPLATES.SELECT];
3852
+ const parts = [];
3853
+ if (cursorCte) {
3854
+ parts.push(`WITH ${cursorCte}`);
3855
+ }
3856
+ parts.push(SQL_TEMPLATES.SELECT);
3649
3857
  const distinctOn = dialect === "postgres" ? buildPostgresDistinctOnClause(from.alias, distinct, model) : null;
3650
3858
  if (distinctOn) parts.push(distinctOn);
3651
3859
  const baseSelect = (select != null ? select : "").trim();
@@ -3661,7 +3869,41 @@ function constructFinalSql(spec) {
3661
3869
  if (isNonEmptyString(orderBy)) parts.push(SQL_TEMPLATES.ORDER_BY, orderBy);
3662
3870
  let sql = parts.join(" ").trim();
3663
3871
  sql = appendPagination(sql, spec);
3664
- 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;
3665
3907
  }
3666
3908
 
3667
3909
  // src/builder/select.ts
@@ -3694,7 +3936,7 @@ function buildPostgresDistinctOrderBy(distinctFields, existing) {
3694
3936
  }
3695
3937
  return next;
3696
3938
  }
3697
- function applyPostgresDistinctOrderBy(args, _model) {
3939
+ function applyPostgresDistinctOrderBy(args) {
3698
3940
  const distinctFields = normalizeDistinctFields(args.distinct);
3699
3941
  if (distinctFields.length === 0) return args;
3700
3942
  if (!isNotNullish(args.orderBy)) return args;
@@ -3704,19 +3946,6 @@ function applyPostgresDistinctOrderBy(args, _model) {
3704
3946
  orderBy: buildPostgresDistinctOrderBy(distinctFields, existing)
3705
3947
  });
3706
3948
  }
3707
- function assertScalarFieldOnModel(model, fieldName, ctx) {
3708
- const f = model.fields.find((x) => x.name === fieldName);
3709
- if (!f) {
3710
- throw new Error(
3711
- `${ctx} references unknown field '${fieldName}' on model ${model.name}`
3712
- );
3713
- }
3714
- if (f.isRelation) {
3715
- throw new Error(
3716
- `${ctx} does not support relation field '${fieldName}' on model ${model.name}`
3717
- );
3718
- }
3719
- }
3720
3949
  function validateDistinct(model, distinct) {
3721
3950
  if (!isNotNullish(distinct) || !isNonEmptyArray(distinct)) return;
3722
3951
  const seen = /* @__PURE__ */ new Set();
@@ -3727,24 +3956,24 @@ function validateDistinct(model, distinct) {
3727
3956
  throw new Error(`distinct must not contain duplicates (field: '${f}')`);
3728
3957
  }
3729
3958
  seen.add(f);
3730
- assertScalarFieldOnModel(model, f, "distinct");
3959
+ assertScalarField(model, f, "distinct");
3731
3960
  }
3732
3961
  }
3733
- function validateOrderByValue(fieldName, v) {
3734
- parseOrderByValue(v, fieldName);
3735
- }
3736
3962
  function validateOrderBy(model, orderBy) {
3737
3963
  if (!isNotNullish(orderBy)) return;
3738
3964
  const items = normalizeOrderByInput2(orderBy);
3739
3965
  if (items.length === 0) return;
3740
3966
  for (const it of items) {
3741
3967
  const entries = Object.entries(it);
3968
+ if (entries.length !== 1) {
3969
+ throw new Error("orderBy array entries must have exactly one field");
3970
+ }
3742
3971
  const fieldName = String(entries[0][0]).trim();
3743
3972
  if (fieldName.length === 0) {
3744
3973
  throw new Error("orderBy field name cannot be empty");
3745
3974
  }
3746
- assertScalarFieldOnModel(model, fieldName, "orderBy");
3747
- validateOrderByValue(fieldName, entries[0][1]);
3975
+ assertScalarField(model, fieldName, "orderBy");
3976
+ parseOrderByValue(entries[0][1], fieldName);
3748
3977
  }
3749
3978
  }
3750
3979
  function validateCursor(model, cursor) {
@@ -3761,7 +3990,7 @@ function validateCursor(model, cursor) {
3761
3990
  if (f.length === 0) {
3762
3991
  throw new Error("cursor field name cannot be empty");
3763
3992
  }
3764
- assertScalarFieldOnModel(model, f, "cursor");
3993
+ assertScalarField(model, f, "cursor");
3765
3994
  }
3766
3995
  }
3767
3996
  function resolveDialect(dialect) {
@@ -3780,20 +4009,21 @@ function normalizeArgsForNegativeTake(method, args) {
3780
4009
  orderBy: reverseOrderByInput(args.orderBy)
3781
4010
  });
3782
4011
  }
3783
- function normalizeArgsForDialect(dialect, args, model) {
4012
+ function normalizeArgsForDialect(dialect, args) {
3784
4013
  if (dialect !== "postgres") return args;
3785
4014
  return applyPostgresDistinctOrderBy(args);
3786
4015
  }
3787
4016
  function buildCursorClauseIfAny(input) {
3788
- const { cursor, orderBy, tableName, alias, params, dialect } = input;
3789
- if (!isNotNullish(cursor)) return void 0;
4017
+ const { cursor, orderBy, tableName, alias, params, dialect, model } = input;
4018
+ if (!isNotNullish(cursor)) return {};
3790
4019
  return buildCursorCondition(
3791
4020
  cursor,
3792
4021
  orderBy,
3793
4022
  tableName,
3794
4023
  alias,
3795
4024
  params,
3796
- dialect
4025
+ dialect,
4026
+ model
3797
4027
  );
3798
4028
  }
3799
4029
  function buildSelectSpec(input) {
@@ -3832,14 +4062,20 @@ function buildSelectSpec(input) {
3832
4062
  params,
3833
4063
  dialect
3834
4064
  );
3835
- const cursorClause = buildCursorClauseIfAny({
4065
+ const cursorResult = buildCursorClauseIfAny({
3836
4066
  cursor,
3837
4067
  orderBy: normalizedArgs.orderBy,
3838
4068
  tableName,
3839
4069
  alias,
3840
4070
  params,
3841
- dialect
4071
+ dialect,
4072
+ model
3842
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
+ }
3843
4079
  return {
3844
4080
  select: selectFields,
3845
4081
  includes,
@@ -3850,7 +4086,8 @@ function buildSelectSpec(input) {
3850
4086
  pagination: { take, skip },
3851
4087
  distinct: normalizedArgs.distinct,
3852
4088
  method,
3853
- cursorClause,
4089
+ cursorCte: cursorResult.cte,
4090
+ cursorClause: cursorResult.condition,
3854
4091
  params,
3855
4092
  dialect,
3856
4093
  model,
@@ -3864,9 +4101,7 @@ function buildSelectSql(input) {
3864
4101
  assertSafeTableRef(from.tableName);
3865
4102
  const dialectToUse = resolveDialect(dialect);
3866
4103
  const argsForSql = normalizeArgsForNegativeTake(method, args);
3867
- const normalizedArgs = normalizeArgsForDialect(
3868
- dialectToUse,
3869
- argsForSql);
4104
+ const normalizedArgs = normalizeArgsForDialect(dialectToUse, argsForSql);
3870
4105
  validateDistinct(model, normalizedArgs.distinct);
3871
4106
  validateOrderBy(model, normalizedArgs.orderBy);
3872
4107
  validateCursor(model, normalizedArgs.cursor);
@@ -3882,8 +4117,21 @@ function buildSelectSql(input) {
3882
4117
  });
3883
4118
  return constructFinalSql(spec);
3884
4119
  }
3885
- var MODEL_FIELD_CACHE = /* @__PURE__ */ new WeakMap();
3886
- 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
3887
4135
  var AGGREGATES = [
3888
4136
  ["_sum", "SUM"],
3889
4137
  ["_avg", "AVG"],
@@ -3898,16 +4146,16 @@ var COMPARISON_OPS = {
3898
4146
  [Ops.LT]: "<",
3899
4147
  [Ops.LTE]: "<="
3900
4148
  };
3901
- function getModelFieldMap(model) {
3902
- const cached = MODEL_FIELD_CACHE.get(model);
3903
- if (cached) return cached;
3904
- const m = /* @__PURE__ */ new Map();
3905
- for (const f of model.fields) {
3906
- m.set(f.name, { name: f.name, type: f.type, isRelation: !!f.isRelation });
3907
- }
3908
- MODEL_FIELD_CACHE.set(model, m);
3909
- return m;
3910
- }
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
+ ]);
3911
4159
  function isTruthySelection(v) {
3912
4160
  return v === true;
3913
4161
  }
@@ -3949,24 +4197,10 @@ function normalizeLogicalValue2(operator, value) {
3949
4197
  }
3950
4198
  throw new Error(`${operator} must be an object or array of objects in HAVING`);
3951
4199
  }
3952
- function assertScalarField2(model, fieldName, ctx) {
3953
- const m = getModelFieldMap(model);
3954
- const field = m.get(fieldName);
3955
- if (!field) {
3956
- throw new Error(
3957
- `${ctx} references unknown field '${fieldName}' on model ${model.name}. Available fields: ${model.fields.map((f) => f.name).join(", ")}`
3958
- );
3959
- }
3960
- if (field.isRelation) {
3961
- throw new Error(`${ctx} does not support relation field '${fieldName}'`);
3962
- }
3963
- return { name: field.name, type: field.type };
3964
- }
3965
- function assertAggregateFieldType(aggKey, fieldType, fieldName, modelName) {
3966
- const baseType = fieldType.replace(/\[\]|\?/g, "");
3967
- if ((aggKey === "_sum" || aggKey === "_avg") && !NUMERIC_TYPES.has(baseType)) {
4200
+ function assertHavingOp(op) {
4201
+ if (!HAVING_ALLOWED_OPS.has(op)) {
3968
4202
  throw new Error(
3969
- `Cannot use ${aggKey} on non-numeric field '${fieldName}' (type: ${fieldType}) on model ${modelName}`
4203
+ `Unsupported HAVING operator '${op}'. Allowed: ${[...HAVING_ALLOWED_OPS].join(", ")}`
3970
4204
  );
3971
4205
  }
3972
4206
  }
@@ -3996,6 +4230,7 @@ function buildBinaryComparison(expr, op, val, params) {
3996
4230
  return `${expr} ${sqlOp} ${placeholder}`;
3997
4231
  }
3998
4232
  function buildSimpleComparison(expr, op, val, params, dialect) {
4233
+ assertHavingOp(op);
3999
4234
  if (val === null) return buildNullComparison(expr, op);
4000
4235
  if (op === Ops.NOT && isPlainObject(val)) {
4001
4236
  return buildNotComposite(
@@ -4084,20 +4319,19 @@ function assertHavingAggTarget(aggKey, field, model) {
4084
4319
  }
4085
4320
  return;
4086
4321
  }
4087
- const f = assertScalarField2(model, field, "HAVING");
4088
- 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
+ }
4089
4327
  }
4090
4328
  function buildHavingOpsForExpr(expr, filter, params, dialect) {
4091
- const out = [];
4092
- for (const [op, val] of Object.entries(filter)) {
4093
- if (op === "mode") continue;
4094
- const built = buildSimpleComparison(expr, op, val, params, dialect);
4095
- if (built && built.trim().length > 0) out.push(built);
4096
- }
4097
- return out;
4329
+ return buildComparisons(expr, filter, params, dialect, buildSimpleComparison);
4098
4330
  }
4099
4331
  function buildHavingForAggregateFirstShape(aggKey, target, alias, params, dialect, model) {
4100
- if (!isPlainObject(target)) return [];
4332
+ if (!isPlainObject(target)) {
4333
+ throw new Error(`HAVING '${aggKey}' must be an object`);
4334
+ }
4101
4335
  const out = [];
4102
4336
  for (const [field, filter] of Object.entries(target)) {
4103
4337
  assertHavingAggTarget(aggKey, field, model);
@@ -4108,30 +4342,39 @@ function buildHavingForAggregateFirstShape(aggKey, target, alias, params, dialec
4108
4342
  return out;
4109
4343
  }
4110
4344
  function buildHavingForFieldFirstShape(fieldName, target, alias, params, dialect, model) {
4111
- if (!isPlainObject(target)) return [];
4112
- 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");
4113
4349
  const out = [];
4114
4350
  const obj = target;
4115
4351
  const keys = ["_count", "_sum", "_avg", "_min", "_max"];
4116
4352
  for (const aggKey of keys) {
4117
4353
  const aggFilter = obj[aggKey];
4118
4354
  if (!isPlainObject(aggFilter)) continue;
4119
- assertAggregateFieldType(aggKey, field.type, field.name, model.name);
4355
+ if (aggKey === "_sum" || aggKey === "_avg") {
4356
+ assertNumericField(model, fieldName, "HAVING");
4357
+ }
4120
4358
  const entries = Object.entries(aggFilter);
4121
4359
  if (entries.length === 0) continue;
4122
4360
  const expr = aggExprForField(aggKey, fieldName, alias, model);
4123
- for (const [op, val] of entries) {
4124
- if (op === "mode") continue;
4125
- const built = buildSimpleComparison(expr, op, val, params, dialect);
4126
- if (built && built.trim().length > 0) out.push(built);
4127
- }
4361
+ const clauses = buildComparisons(
4362
+ expr,
4363
+ aggFilter,
4364
+ params,
4365
+ dialect,
4366
+ buildSimpleComparison
4367
+ );
4368
+ out.push(...clauses);
4128
4369
  }
4129
4370
  return out;
4130
4371
  }
4131
4372
  function buildHavingClause(having, alias, params, model, dialect) {
4132
4373
  if (!isNotNullish(having)) return "";
4133
4374
  const d = dialect != null ? dialect : getGlobalDialect();
4134
- if (!isPlainObject(having)) return "";
4375
+ if (!isPlainObject(having)) {
4376
+ throw new Error("having must be an object");
4377
+ }
4135
4378
  return buildHavingNode(having, alias, params, d, model);
4136
4379
  }
4137
4380
  function normalizeCountArg(v) {
@@ -4145,18 +4388,8 @@ function pushCountAllField(fields) {
4145
4388
  `${SQL_TEMPLATES.COUNT_ALL} ${SQL_TEMPLATES.AS} ${quote("_count._all")}`
4146
4389
  );
4147
4390
  }
4148
- function assertCountableScalarField(fieldMap, model, fieldName) {
4149
- const field = fieldMap.get(fieldName);
4150
- if (!field) {
4151
- throw new Error(
4152
- `Field '${fieldName}' does not exist on model ${model.name}`
4153
- );
4154
- }
4155
- if (field.isRelation) {
4156
- throw new Error(
4157
- `Cannot use _count on relation field '${fieldName}' on model ${model.name}`
4158
- );
4159
- }
4391
+ function assertCountableScalarField(model, fieldName) {
4392
+ assertScalarField(model, fieldName, "_count");
4160
4393
  }
4161
4394
  function pushCountField(fields, alias, fieldName, model) {
4162
4395
  const outAlias = `_count.${fieldName}`;
@@ -4164,7 +4397,7 @@ function pushCountField(fields, alias, fieldName, model) {
4164
4397
  `COUNT(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4165
4398
  );
4166
4399
  }
4167
- function addCountFields(fields, countArg, alias, model, fieldMap) {
4400
+ function addCountFields(fields, countArg, alias, model) {
4168
4401
  if (!isNotNullish(countArg)) return;
4169
4402
  if (countArg === true) {
4170
4403
  pushCountAllField(fields);
@@ -4178,7 +4411,7 @@ function addCountFields(fields, countArg, alias, model, fieldMap) {
4178
4411
  ([f, v]) => f !== "_all" && isTruthySelection(v)
4179
4412
  );
4180
4413
  for (const [f] of selected) {
4181
- assertCountableScalarField(fieldMap, model, f);
4414
+ assertCountableScalarField(model, f);
4182
4415
  pushCountField(fields, alias, f, model);
4183
4416
  }
4184
4417
  }
@@ -4186,19 +4419,12 @@ function getAggregateSelectionObject(args, agg) {
4186
4419
  const obj = args[agg];
4187
4420
  return isPlainObject(obj) ? obj : void 0;
4188
4421
  }
4189
- function assertAggregatableScalarField(fieldMap, model, agg, fieldName) {
4190
- const field = fieldMap.get(fieldName);
4191
- if (!field) {
4192
- throw new Error(
4193
- `Field '${fieldName}' does not exist on model ${model.name}`
4194
- );
4195
- }
4196
- if (field.isRelation) {
4197
- throw new Error(
4198
- `Cannot use ${agg} on relation field '${fieldName}' on model ${model.name}`
4199
- );
4422
+ function assertAggregatableScalarField(model, agg, fieldName) {
4423
+ if (agg === "_sum" || agg === "_avg") {
4424
+ assertNumericField(model, fieldName, agg);
4425
+ } else {
4426
+ assertScalarField(model, fieldName, agg);
4200
4427
  }
4201
- return field;
4202
4428
  }
4203
4429
  function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
4204
4430
  const outAlias = `${agg}.${fieldName}`;
@@ -4206,7 +4432,7 @@ function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
4206
4432
  `${aggFn}(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4207
4433
  );
4208
4434
  }
4209
- function addAggregateFields(fields, args, alias, model, fieldMap) {
4435
+ function addAggregateFields(fields, args, alias, model) {
4210
4436
  for (const [agg, aggFn] of AGGREGATES) {
4211
4437
  const obj = getAggregateSelectionObject(args, agg);
4212
4438
  if (!obj) continue;
@@ -4214,23 +4440,16 @@ function addAggregateFields(fields, args, alias, model, fieldMap) {
4214
4440
  if (fieldName === "_all")
4215
4441
  throw new Error(`'${agg}' does not support '_all'`);
4216
4442
  if (!isTruthySelection(selection)) continue;
4217
- const field = assertAggregatableScalarField(
4218
- fieldMap,
4219
- model,
4220
- agg,
4221
- fieldName
4222
- );
4223
- assertAggregateFieldType(agg, field.type, fieldName, model.name);
4443
+ assertAggregatableScalarField(model, agg, fieldName);
4224
4444
  pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model);
4225
4445
  }
4226
4446
  }
4227
4447
  }
4228
4448
  function buildAggregateFields(args, alias, model) {
4229
4449
  const fields = [];
4230
- const fieldMap = getModelFieldMap(model);
4231
4450
  const countArg = normalizeCountArg(args._count);
4232
- addCountFields(fields, countArg, alias, model, fieldMap);
4233
- addAggregateFields(fields, args, alias, model, fieldMap);
4451
+ addCountFields(fields, countArg, alias, model);
4452
+ addAggregateFields(fields, args, alias, model);
4234
4453
  return fields;
4235
4454
  }
4236
4455
  function buildAggregateSql(args, whereResult, tableName, alias, model) {
@@ -4267,32 +4486,24 @@ function assertGroupByBy(args, model) {
4267
4486
  if (bySet.size !== byFields.length) {
4268
4487
  throw new Error("buildGroupBySql: by must not contain duplicates");
4269
4488
  }
4270
- const modelFieldMap = getModelFieldMap(model);
4271
4489
  for (const f of byFields) {
4272
- const field = modelFieldMap.get(f);
4273
- if (!field) {
4274
- throw new Error(
4275
- `groupBy.by references unknown field '${f}' on model ${model.name}`
4276
- );
4277
- }
4278
- if (field.isRelation) {
4279
- throw new Error(
4280
- `groupBy.by does not support relation field '${f}' on model ${model.name}`
4281
- );
4282
- }
4490
+ assertScalarField(model, f, "groupBy.by");
4283
4491
  }
4284
4492
  return byFields;
4285
4493
  }
4286
4494
  function buildGroupBySelectParts(args, alias, model, byFields) {
4287
4495
  const groupCols = byFields.map((f) => col(alias, f, model));
4496
+ const selectCols = byFields.map((f) => colWithAlias(alias, f, model));
4288
4497
  const groupFields = groupCols.join(SQL_SEPARATORS.FIELD_LIST);
4289
4498
  const aggFields = buildAggregateFields(args, alias, model);
4290
- 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);
4291
4500
  return { groupCols, groupFields, selectFields };
4292
4501
  }
4293
4502
  function buildGroupByHaving(args, alias, params, model, dialect) {
4294
4503
  if (!isNotNullish(args.having)) return "";
4295
- if (!isPlainObject(args.having)) return "";
4504
+ if (!isPlainObject(args.having)) {
4505
+ throw new Error("having must be an object");
4506
+ }
4296
4507
  const h = buildHavingClause(args.having, alias, params, model, dialect);
4297
4508
  if (!h || h.trim().length === 0) return "";
4298
4509
  return `${SQL_TEMPLATES.HAVING} ${h}`;
@@ -4325,63 +4536,61 @@ function buildGroupBySql(args, whereResult, tableName, alias, model, dialect) {
4325
4536
  const snapshot = params.snapshot();
4326
4537
  validateSelectQuery(sql);
4327
4538
  validateParamConsistency(sql, [...whereResult.params, ...snapshot.params]);
4539
+ const mergedParams = [...whereResult.params, ...snapshot.params];
4328
4540
  return Object.freeze({
4329
4541
  sql,
4330
- params: Object.freeze([...whereResult.params, ...snapshot.params]),
4542
+ params: Object.freeze(mergedParams),
4331
4543
  paramMappings: Object.freeze([
4332
4544
  ...whereResult.paramMappings,
4333
4545
  ...snapshot.mappings
4334
4546
  ])
4335
4547
  });
4336
4548
  }
4337
- function buildCountSql(whereResult, tableName, alias, skip, dialect) {
4549
+ function buildCountSql(whereResult, tableName, alias, skip, _dialect) {
4338
4550
  assertSafeAlias(alias);
4339
4551
  assertSafeTableRef(tableName);
4340
- 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
+ }
4341
4575
  const whereClause = isValidWhereClause(whereResult.clause) ? `${SQL_TEMPLATES.WHERE} ${whereResult.clause}` : "";
4342
- const params = createParamStore(whereResult.nextParamIndex);
4343
- const baseSubSelect = [
4344
- SQL_TEMPLATES.SELECT,
4345
- "1",
4346
- SQL_TEMPLATES.FROM,
4347
- tableName,
4348
- alias,
4349
- whereClause
4350
- ].filter((x) => x && String(x).trim().length > 0).join(" ").trim();
4351
- const normalizedSkip = normalizeSkipLike(skip);
4352
- const subSelect = applyCountSkip(baseSubSelect, normalizedSkip, params, d);
4353
4576
  const sql = [
4354
4577
  SQL_TEMPLATES.SELECT,
4355
4578
  SQL_TEMPLATES.COUNT_ALL,
4356
4579
  SQL_TEMPLATES.AS,
4357
4580
  quote("_count._all"),
4358
4581
  SQL_TEMPLATES.FROM,
4359
- `(${subSelect})`,
4360
- SQL_TEMPLATES.AS,
4361
- `"sub"`
4582
+ tableName,
4583
+ alias,
4584
+ whereClause
4362
4585
  ].filter((x) => x && String(x).trim().length > 0).join(" ").trim();
4363
4586
  validateSelectQuery(sql);
4364
- const snapshot = params.snapshot();
4365
- const mergedParams = [...whereResult.params, ...snapshot.params];
4366
- validateParamConsistency(sql, mergedParams);
4587
+ validateParamConsistency(sql, whereResult.params);
4367
4588
  return Object.freeze({
4368
4589
  sql,
4369
- params: Object.freeze(mergedParams),
4370
- paramMappings: Object.freeze([
4371
- ...whereResult.paramMappings,
4372
- ...snapshot.mappings
4373
- ])
4590
+ params: Object.freeze([...whereResult.params]),
4591
+ paramMappings: Object.freeze([...whereResult.paramMappings])
4374
4592
  });
4375
4593
  }
4376
- function applyCountSkip(subSelect, normalizedSkip, params, dialect) {
4377
- const shouldApply = schemaParser.isDynamicParameter(normalizedSkip) || typeof normalizedSkip === "number" && normalizedSkip > 0;
4378
- if (!shouldApply) return subSelect;
4379
- const placeholder = addAutoScoped(params, normalizedSkip, "count.skip");
4380
- if (dialect === "sqlite") {
4381
- return `${subSelect} ${SQL_TEMPLATES.LIMIT} -1 ${SQL_TEMPLATES.OFFSET} ${placeholder}`;
4382
- }
4383
- return `${subSelect} ${SQL_TEMPLATES.OFFSET} ${placeholder}`;
4384
- }
4385
4594
  function safeAlias(input) {
4386
4595
  const raw = String(input).toLowerCase();
4387
4596
  const cleaned = raw.replace(/[^a-z0-9_]/g, "_");
@@ -4531,8 +4740,12 @@ function buildAndNormalizeSql(args) {
4531
4740
  );
4532
4741
  }
4533
4742
  function finalizeDirective(args) {
4534
- const { directive, normalizedSql, normalizedMappings } = args;
4535
- 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);
4536
4749
  const { staticParams, dynamicKeys } = buildParamsFromMappings(normalizedMappings);
4537
4750
  return {
4538
4751
  method: directive.method,
@@ -4569,7 +4782,8 @@ function generateSQL(directive) {
4569
4782
  return finalizeDirective({
4570
4783
  directive,
4571
4784
  normalizedSql: normalized.sql,
4572
- normalizedMappings: normalized.paramMappings
4785
+ normalizedMappings: normalized.paramMappings,
4786
+ dialect
4573
4787
  });
4574
4788
  }
4575
4789
  function generateSQL2(directive) {
@@ -4677,18 +4891,57 @@ function generateCode(models, queries, dialect, datamodel) {
4677
4891
  return `// Generated by @prisma-sql/generator - DO NOT EDIT
4678
4892
  import { buildSQL, transformQueryResults, type PrismaMethod, type Model } from 'prisma-sql'
4679
4893
 
4680
- 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
+ }
4681
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
+ }
4682
4908
  return value.toISOString()
4683
4909
  }
4684
-
4910
+ if (typeof value === 'bigint') {
4911
+ return value.toString()
4912
+ }
4685
4913
  if (Array.isArray(value)) {
4686
- 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
4687
4940
  }
4688
-
4689
4941
  return value
4690
4942
  }
4691
4943
 
4944
+
4692
4945
  export const MODELS: Model[] = ${JSON.stringify(cleanModels, null, 2)}
4693
4946
 
4694
4947
  const ENUM_MAPPINGS: Record<string, Record<string, string>> = ${JSON.stringify(mappings, null, 2)}