prisma-sql 1.38.0 → 1.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -42,6 +42,159 @@ var __async = (__this, __arguments, generator) => {
42
42
  });
43
43
  };
44
44
 
45
+ // src/builder/shared/constants.ts
46
+ var SQL_SEPARATORS = Object.freeze({
47
+ FIELD_LIST: ", ",
48
+ CONDITION_AND: " AND ",
49
+ CONDITION_OR: " OR ",
50
+ ORDER_BY: ", "
51
+ });
52
+ var SQL_RESERVED_WORDS = /* @__PURE__ */ new Set([
53
+ "select",
54
+ "from",
55
+ "where",
56
+ "and",
57
+ "or",
58
+ "not",
59
+ "in",
60
+ "like",
61
+ "between",
62
+ "order",
63
+ "by",
64
+ "group",
65
+ "having",
66
+ "limit",
67
+ "offset",
68
+ "join",
69
+ "inner",
70
+ "left",
71
+ "right",
72
+ "outer",
73
+ "on",
74
+ "as",
75
+ "table",
76
+ "column",
77
+ "index",
78
+ "user",
79
+ "users",
80
+ "values",
81
+ "update",
82
+ "insert",
83
+ "delete",
84
+ "create",
85
+ "drop",
86
+ "alter",
87
+ "truncate",
88
+ "grant",
89
+ "revoke",
90
+ "exec",
91
+ "execute",
92
+ "union",
93
+ "intersect",
94
+ "except",
95
+ "case",
96
+ "when",
97
+ "then",
98
+ "else",
99
+ "end",
100
+ "null",
101
+ "true",
102
+ "false",
103
+ "is",
104
+ "exists",
105
+ "all",
106
+ "any",
107
+ "some"
108
+ ]);
109
+ var SQL_KEYWORDS = SQL_RESERVED_WORDS;
110
+ var DEFAULT_WHERE_CLAUSE = "1=1";
111
+ var SPECIAL_FIELDS = Object.freeze({
112
+ ID: "id"
113
+ });
114
+ var SQL_TEMPLATES = Object.freeze({
115
+ PUBLIC_SCHEMA: "public",
116
+ WHERE: "WHERE",
117
+ SELECT: "SELECT",
118
+ FROM: "FROM",
119
+ ORDER_BY: "ORDER BY",
120
+ GROUP_BY: "GROUP BY",
121
+ HAVING: "HAVING",
122
+ LIMIT: "LIMIT",
123
+ OFFSET: "OFFSET",
124
+ COUNT_ALL: "COUNT(*)",
125
+ AS: "AS",
126
+ DISTINCT_ON: "DISTINCT ON",
127
+ IS_NULL: "IS NULL",
128
+ IS_NOT_NULL: "IS NOT NULL",
129
+ LIKE: "LIKE",
130
+ AND: "AND",
131
+ OR: "OR",
132
+ NOT: "NOT"
133
+ });
134
+ var SCHEMA_PREFIXES = Object.freeze({
135
+ INTERNAL: "@",
136
+ COMMENT: "//"
137
+ });
138
+ var Ops = Object.freeze({
139
+ EQUALS: "equals",
140
+ NOT: "not",
141
+ GT: "gt",
142
+ GTE: "gte",
143
+ LT: "lt",
144
+ LTE: "lte",
145
+ IN: "in",
146
+ NOT_IN: "notIn",
147
+ CONTAINS: "contains",
148
+ STARTS_WITH: "startsWith",
149
+ ENDS_WITH: "endsWith",
150
+ HAS: "has",
151
+ HAS_SOME: "hasSome",
152
+ HAS_EVERY: "hasEvery",
153
+ IS_EMPTY: "isEmpty",
154
+ PATH: "path",
155
+ STRING_CONTAINS: "string_contains",
156
+ STRING_STARTS_WITH: "string_starts_with",
157
+ STRING_ENDS_WITH: "string_ends_with"
158
+ });
159
+ var LogicalOps = Object.freeze({
160
+ AND: "AND",
161
+ OR: "OR",
162
+ NOT: "NOT"
163
+ });
164
+ var RelationFilters = Object.freeze({
165
+ SOME: "some",
166
+ EVERY: "every",
167
+ NONE: "none"
168
+ });
169
+ var Modes = Object.freeze({
170
+ INSENSITIVE: "insensitive",
171
+ DEFAULT: "default"
172
+ });
173
+ var Wildcards = Object.freeze({
174
+ [Ops.CONTAINS]: (v) => `%${v}%`,
175
+ [Ops.STARTS_WITH]: (v) => `${v}%`,
176
+ [Ops.ENDS_WITH]: (v) => `%${v}`
177
+ });
178
+ var REGEX_CACHE = {
179
+ VALID_IDENTIFIER: /^[a-z_][a-z0-9_]*$/
180
+ };
181
+ var LIMITS = Object.freeze({
182
+ MAX_QUERY_DEPTH: 50,
183
+ MAX_ARRAY_SIZE: 1e4,
184
+ MAX_STRING_LENGTH: 1e4
185
+ });
186
+
187
+ // src/utils/normalize-value.ts
188
+ function normalizeValue(value) {
189
+ if (value instanceof Date) {
190
+ return value.toISOString();
191
+ }
192
+ if (Array.isArray(value)) {
193
+ return value.map(normalizeValue);
194
+ }
195
+ return value;
196
+ }
197
+
45
198
  // src/sql-builder-dialect.ts
46
199
  var globalDialect = "postgres";
47
200
  function setGlobalDialect(dialect) {
@@ -218,153 +371,11 @@ function prepareArrayParam(value, dialect) {
218
371
  throw new Error("prepareArrayParam requires array value");
219
372
  }
220
373
  if (dialect === "postgres") {
221
- return value;
374
+ return value.map(normalizeValue);
222
375
  }
223
376
  return JSON.stringify(value);
224
377
  }
225
378
 
226
- // src/builder/shared/constants.ts
227
- var SQL_SEPARATORS = Object.freeze({
228
- FIELD_LIST: ", ",
229
- CONDITION_AND: " AND ",
230
- CONDITION_OR: " OR ",
231
- ORDER_BY: ", "
232
- });
233
- var SQL_RESERVED_WORDS = /* @__PURE__ */ new Set([
234
- "select",
235
- "from",
236
- "where",
237
- "and",
238
- "or",
239
- "not",
240
- "in",
241
- "like",
242
- "between",
243
- "order",
244
- "by",
245
- "group",
246
- "having",
247
- "limit",
248
- "offset",
249
- "join",
250
- "inner",
251
- "left",
252
- "right",
253
- "outer",
254
- "on",
255
- "as",
256
- "table",
257
- "column",
258
- "index",
259
- "user",
260
- "users",
261
- "values",
262
- "update",
263
- "insert",
264
- "delete",
265
- "create",
266
- "drop",
267
- "alter",
268
- "truncate",
269
- "grant",
270
- "revoke",
271
- "exec",
272
- "execute",
273
- "union",
274
- "intersect",
275
- "except",
276
- "case",
277
- "when",
278
- "then",
279
- "else",
280
- "end",
281
- "null",
282
- "true",
283
- "false",
284
- "is",
285
- "exists",
286
- "all",
287
- "any",
288
- "some"
289
- ]);
290
- var SQL_KEYWORDS = SQL_RESERVED_WORDS;
291
- var DEFAULT_WHERE_CLAUSE = "1=1";
292
- var SPECIAL_FIELDS = Object.freeze({
293
- ID: "id"
294
- });
295
- var SQL_TEMPLATES = Object.freeze({
296
- PUBLIC_SCHEMA: "public",
297
- WHERE: "WHERE",
298
- SELECT: "SELECT",
299
- FROM: "FROM",
300
- ORDER_BY: "ORDER BY",
301
- GROUP_BY: "GROUP BY",
302
- HAVING: "HAVING",
303
- LIMIT: "LIMIT",
304
- OFFSET: "OFFSET",
305
- COUNT_ALL: "COUNT(*)",
306
- AS: "AS",
307
- DISTINCT_ON: "DISTINCT ON",
308
- IS_NULL: "IS NULL",
309
- IS_NOT_NULL: "IS NOT NULL",
310
- LIKE: "LIKE",
311
- AND: "AND",
312
- OR: "OR",
313
- NOT: "NOT"
314
- });
315
- var SCHEMA_PREFIXES = Object.freeze({
316
- INTERNAL: "@",
317
- COMMENT: "//"
318
- });
319
- var Ops = Object.freeze({
320
- EQUALS: "equals",
321
- NOT: "not",
322
- GT: "gt",
323
- GTE: "gte",
324
- LT: "lt",
325
- LTE: "lte",
326
- IN: "in",
327
- NOT_IN: "notIn",
328
- CONTAINS: "contains",
329
- STARTS_WITH: "startsWith",
330
- ENDS_WITH: "endsWith",
331
- HAS: "has",
332
- HAS_SOME: "hasSome",
333
- HAS_EVERY: "hasEvery",
334
- IS_EMPTY: "isEmpty",
335
- PATH: "path",
336
- STRING_CONTAINS: "string_contains",
337
- STRING_STARTS_WITH: "string_starts_with",
338
- STRING_ENDS_WITH: "string_ends_with"
339
- });
340
- var LogicalOps = Object.freeze({
341
- AND: "AND",
342
- OR: "OR",
343
- NOT: "NOT"
344
- });
345
- var RelationFilters = Object.freeze({
346
- SOME: "some",
347
- EVERY: "every",
348
- NONE: "none"
349
- });
350
- var Modes = Object.freeze({
351
- INSENSITIVE: "insensitive",
352
- DEFAULT: "default"
353
- });
354
- var Wildcards = Object.freeze({
355
- [Ops.CONTAINS]: (v) => `%${v}%`,
356
- [Ops.STARTS_WITH]: (v) => `${v}%`,
357
- [Ops.ENDS_WITH]: (v) => `%${v}`
358
- });
359
- var REGEX_CACHE = {
360
- VALID_IDENTIFIER: /^[a-z_][a-z0-9_]*$/
361
- };
362
- var LIMITS = Object.freeze({
363
- MAX_QUERY_DEPTH: 50,
364
- MAX_ARRAY_SIZE: 1e4,
365
- MAX_STRING_LENGTH: 1e4
366
- });
367
-
368
379
  // src/builder/shared/validators/type-guards.ts
369
380
  function isNotNullish(value) {
370
381
  return value !== null && value !== void 0;
@@ -450,6 +461,19 @@ function getRelationFieldSet(model) {
450
461
  RELATION_SET_CACHE.set(model, s);
451
462
  return s;
452
463
  }
464
+ var COLUMN_MAP_CACHE = /* @__PURE__ */ new WeakMap();
465
+ function getColumnMap(model) {
466
+ const cached = COLUMN_MAP_CACHE.get(model);
467
+ if (cached) return cached;
468
+ const map = /* @__PURE__ */ new Map();
469
+ for (const f of model.fields) {
470
+ if (!f.isRelation) {
471
+ map.set(f.name, f.dbName || f.name);
472
+ }
473
+ }
474
+ COLUMN_MAP_CACHE.set(model, map);
475
+ return map;
476
+ }
453
477
 
454
478
  // src/builder/shared/validators/sql-validators.ts
455
479
  function isValidWhereClause(clause) {
@@ -837,27 +861,10 @@ function quote(id) {
837
861
  }
838
862
  return id;
839
863
  }
840
- function pickDbFieldName(field) {
841
- const candidates = [
842
- field == null ? void 0 : field.dbName,
843
- field == null ? void 0 : field.columnName,
844
- field == null ? void 0 : field.databaseName,
845
- field == null ? void 0 : field.mappedName
846
- ];
847
- for (const c of candidates) {
848
- if (typeof c === "string") {
849
- const s = c.trim();
850
- if (s.length > 0) return s;
851
- }
852
- }
853
- return void 0;
854
- }
855
864
  function resolveColumnName(model, fieldName) {
856
- var _a;
857
865
  if (!model) return fieldName;
858
- const f = model.fields.find((x) => (x == null ? void 0 : x.name) === fieldName);
859
- if (!f) return fieldName;
860
- return (_a = pickDbFieldName(f)) != null ? _a : fieldName;
866
+ const columnMap = getColumnMap(model);
867
+ return columnMap.get(fieldName) || fieldName;
861
868
  }
862
869
  function quoteColumn(model, fieldName) {
863
870
  return quote(resolveColumnName(model, fieldName));
@@ -1015,1570 +1022,1570 @@ function joinCondition(field, parentModel, childModel, parentAlias, childAlias)
1015
1022
  function getModelByName(schemas, name) {
1016
1023
  return schemas.find((m) => m.name === name);
1017
1024
  }
1018
- function buildNotComposite(expr, val, params, dialect, buildOp, separator) {
1019
- const entries = Object.entries(val).filter(
1020
- ([k, v]) => k !== "mode" && v !== void 0
1021
- );
1022
- if (entries.length === 0) return "";
1023
- const clauses = [];
1024
- for (const [subOp, subVal] of entries) {
1025
- const sub = buildOp(expr, subOp, subVal, params, dialect);
1026
- if (sub && sub.trim().length > 0) clauses.push(`(${sub})`);
1027
- }
1028
- if (clauses.length === 0) return "";
1029
- if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
1030
- return `${SQL_TEMPLATES.NOT} (${clauses.join(separator)})`;
1031
- }
1032
- function buildScalarOperator(expr, op, val, params, mode, fieldType, dialect) {
1033
- if (val === void 0) return "";
1034
- if (val === null) {
1035
- return handleNullValue(expr, op);
1036
- }
1037
- if (op === Ops.NOT && isPlainObject(val)) {
1038
- return handleNotOperator(expr, val, params, mode, fieldType, dialect);
1039
- }
1040
- if (op === Ops.NOT) {
1041
- const placeholder = params.addAuto(val);
1042
- return `${expr} <> ${placeholder}`;
1025
+ function normalizeIntLike(name, v, opts = {}) {
1026
+ var _a, _b;
1027
+ if (!isNotNullish(v)) return void 0;
1028
+ if (schemaParser.isDynamicParameter(v)) return v;
1029
+ if (typeof v !== "number" || !Number.isFinite(v) || !Number.isInteger(v)) {
1030
+ throw new Error(`${name} must be an integer`);
1043
1031
  }
1044
- if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && isNotNullish(dialect)) {
1045
- const placeholder = params.addAuto(val);
1046
- return caseInsensitiveEquals(expr, placeholder);
1032
+ const min = (_a = opts.min) != null ? _a : 0;
1033
+ const allowZero = (_b = opts.allowZero) != null ? _b : true;
1034
+ if (!allowZero && v === 0) {
1035
+ throw new Error(`${name} must be > 0`);
1047
1036
  }
1048
- const STRING_LIKE_OPS = /* @__PURE__ */ new Set([
1049
- Ops.CONTAINS,
1050
- Ops.STARTS_WITH,
1051
- Ops.ENDS_WITH
1052
- ]);
1053
- if (STRING_LIKE_OPS.has(op)) {
1054
- if (!isNotNullish(dialect)) {
1055
- throw createError(`Like operators require a SQL dialect`, {
1056
- operator: op
1057
- });
1058
- }
1059
- return handleLikeOperator(expr, op, val, params, mode, dialect);
1037
+ if (v < min) {
1038
+ throw new Error(`${name} must be >= ${min}`);
1060
1039
  }
1061
- if (op === Ops.IN || op === Ops.NOT_IN) {
1062
- if (!isNotNullish(dialect)) {
1063
- throw createError(`IN operators require a SQL dialect`, { operator: op });
1064
- }
1065
- return handleInOperator(expr, op, val, params, dialect);
1040
+ if (typeof opts.max === "number" && v > opts.max) {
1041
+ throw new Error(`${name} must be <= ${opts.max}`);
1066
1042
  }
1067
- return handleComparisonOperator(expr, op, val, params);
1068
- }
1069
- function handleNullValue(expr, op) {
1070
- if (op === Ops.EQUALS) return `${expr} ${SQL_TEMPLATES.IS_NULL}`;
1071
- if (op === Ops.NOT) return `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
1072
- throw createError(`Operator '${op}' doesn't support null`, { operator: op });
1073
- }
1074
- function normalizeMode(v) {
1075
- if (v === Modes.INSENSITIVE) return Modes.INSENSITIVE;
1076
- if (v === Modes.DEFAULT) return Modes.DEFAULT;
1077
- return void 0;
1043
+ return v;
1078
1044
  }
1079
- function handleNotOperator(expr, val, params, outerMode, fieldType, dialect) {
1080
- const innerMode = normalizeMode(val.mode);
1081
- const effectiveMode = innerMode != null ? innerMode : outerMode;
1082
- return buildNotComposite(
1083
- expr,
1084
- val,
1085
- params,
1086
- dialect,
1087
- (e, subOp, subVal, p, d) => buildScalarOperator(e, subOp, subVal, p, effectiveMode, fieldType, d),
1088
- ` ${SQL_TEMPLATES.AND} `
1089
- );
1045
+ function scopeName(scope, dynamicName) {
1046
+ const s = String(scope).trim();
1047
+ const dn = String(dynamicName).trim();
1048
+ if (s.length === 0) return dn;
1049
+ return `${s}:${dn}`;
1090
1050
  }
1091
- function buildDynamicLikePattern(op, placeholder, dialect) {
1092
- if (dialect === "postgres") {
1093
- switch (op) {
1094
- case Ops.CONTAINS:
1095
- return `('%' || ${placeholder} || '%')`;
1096
- case Ops.STARTS_WITH:
1097
- return `(${placeholder} || '%')`;
1098
- case Ops.ENDS_WITH:
1099
- return `('%' || ${placeholder})`;
1100
- default:
1101
- return placeholder;
1102
- }
1103
- }
1104
- switch (op) {
1105
- case Ops.CONTAINS:
1106
- return `('%' || ${placeholder} || '%')`;
1107
- case Ops.STARTS_WITH:
1108
- return `(${placeholder} || '%')`;
1109
- case Ops.ENDS_WITH:
1110
- return `('%' || ${placeholder})`;
1111
- default:
1112
- return placeholder;
1051
+ function addAutoScoped(params, value, scope) {
1052
+ if (schemaParser.isDynamicParameter(value)) {
1053
+ const dn = schemaParser.extractDynamicName(value);
1054
+ return params.add(void 0, scopeName(scope, dn));
1113
1055
  }
1056
+ return params.add(value);
1114
1057
  }
1115
- function handleLikeOperator(expr, op, val, params, mode, dialect) {
1116
- if (val === void 0) return "";
1117
- if (schemaParser.isDynamicParameter(val)) {
1118
- const placeholder2 = params.addAuto(val);
1119
- const patternExpr = buildDynamicLikePattern(op, placeholder2, dialect);
1120
- if (mode === Modes.INSENSITIVE) {
1121
- return caseInsensitiveLike(expr, patternExpr, dialect);
1122
- }
1123
- return `${expr} ${SQL_TEMPLATES.LIKE} ${patternExpr}`;
1058
+
1059
+ // src/builder/shared/order-by-utils.ts
1060
+ var flipNulls = (v) => {
1061
+ const s = String(v).toLowerCase();
1062
+ if (s === "first") return "last";
1063
+ if (s === "last") return "first";
1064
+ return v;
1065
+ };
1066
+ var flipSortString = (v) => {
1067
+ if (typeof v !== "string") return v;
1068
+ const s = v.toLowerCase();
1069
+ if (s === "asc") return "desc";
1070
+ if (s === "desc") return "asc";
1071
+ return v;
1072
+ };
1073
+ var getNextSort = (sortRaw) => {
1074
+ if (typeof sortRaw !== "string") return sortRaw;
1075
+ const s = sortRaw.toLowerCase();
1076
+ if (s === "asc") return "desc";
1077
+ if (s === "desc") return "asc";
1078
+ return sortRaw;
1079
+ };
1080
+ var flipObjectSort = (obj) => {
1081
+ const sortRaw = obj.sort;
1082
+ const out = __spreadProps(__spreadValues({}, obj), { sort: getNextSort(sortRaw) });
1083
+ const nullsRaw = obj.nulls;
1084
+ if (typeof nullsRaw === "string") {
1085
+ out.nulls = flipNulls(nullsRaw);
1124
1086
  }
1125
- const placeholder = params.add(Wildcards[op](String(val)));
1126
- if (mode === Modes.INSENSITIVE) {
1127
- return caseInsensitiveLike(expr, placeholder, dialect);
1087
+ return out;
1088
+ };
1089
+ var flipValue = (v) => {
1090
+ if (typeof v === "string") return flipSortString(v);
1091
+ if (isPlainObject(v)) return flipObjectSort(v);
1092
+ return v;
1093
+ };
1094
+ var assertSingleFieldObject = (item) => {
1095
+ if (!isPlainObject(item)) {
1096
+ throw new Error("orderBy array entries must be objects");
1128
1097
  }
1129
- return `${expr} ${SQL_TEMPLATES.LIKE} ${placeholder}`;
1130
- }
1131
- function handleInOperator(expr, op, val, params, dialect) {
1132
- if (val === void 0) return "";
1133
- if (schemaParser.isDynamicParameter(val)) {
1134
- const placeholder2 = params.addAuto(val);
1135
- return op === Ops.IN ? inArray(expr, placeholder2, dialect) : notInArray(expr, placeholder2, dialect);
1098
+ const entries = Object.entries(item);
1099
+ if (entries.length !== 1) {
1100
+ throw new Error("orderBy array entries must have exactly one field");
1136
1101
  }
1137
- if (!Array.isArray(val)) {
1138
- throw createError(`IN operators require array value`, {
1139
- operator: op,
1140
- value: val
1141
- });
1102
+ return entries[0];
1103
+ };
1104
+ var flipOrderByArray = (orderBy) => {
1105
+ return orderBy.map((item) => {
1106
+ const [k, v] = assertSingleFieldObject(item);
1107
+ return { [k]: flipValue(v) };
1108
+ });
1109
+ };
1110
+ var flipOrderByObject = (orderBy) => {
1111
+ const out = {};
1112
+ for (const [k, v] of Object.entries(orderBy)) {
1113
+ out[k] = flipValue(v);
1142
1114
  }
1143
- if (val.length === 0) {
1144
- return op === Ops.IN ? "0=1" : "1=1";
1115
+ return out;
1116
+ };
1117
+ function reverseOrderByInput(orderBy) {
1118
+ if (!isNotNullish(orderBy)) return orderBy;
1119
+ if (Array.isArray(orderBy)) {
1120
+ return flipOrderByArray(orderBy);
1145
1121
  }
1146
- const paramValue = prepareArrayParam(val, dialect);
1147
- const placeholder = params.add(paramValue);
1148
- return op === Ops.IN ? inArray(expr, placeholder, dialect) : notInArray(expr, placeholder, dialect);
1122
+ if (isPlainObject(orderBy)) {
1123
+ return flipOrderByObject(orderBy);
1124
+ }
1125
+ throw new Error("orderBy must be an object or array of objects");
1149
1126
  }
1150
- function handleComparisonOperator(expr, op, val, params) {
1151
- if (val === void 0) return "";
1152
- const COMPARISON_OPS2 = {
1153
- [Ops.EQUALS]: "=",
1154
- [Ops.GT]: ">",
1155
- [Ops.GTE]: ">=",
1156
- [Ops.LT]: "<",
1157
- [Ops.LTE]: "<="
1158
- };
1159
- const sqlOp = COMPARISON_OPS2[op];
1160
- if (!sqlOp) {
1161
- throw createError(`Unsupported scalar operator: ${op}`, { operator: op });
1162
- }
1163
- const placeholder = params.addAuto(val);
1164
- return `${expr} ${sqlOp} ${placeholder}`;
1165
- }
1166
- function buildArrayParam(val, params, dialect) {
1167
- if (schemaParser.isDynamicParameter(val)) {
1168
- return params.addAuto(val);
1127
+ var normalizePairs = (pairs, parseValue) => {
1128
+ return pairs.map(([field, rawValue]) => {
1129
+ const parsed = parseValue(rawValue, field);
1130
+ return {
1131
+ [field]: parsed.nulls !== void 0 ? { sort: parsed.direction, nulls: parsed.nulls } : parsed.direction
1132
+ };
1133
+ });
1134
+ };
1135
+ function normalizeOrderByInput(orderBy, parseValue) {
1136
+ if (!isNotNullish(orderBy)) return [];
1137
+ if (Array.isArray(orderBy)) {
1138
+ const pairs = orderBy.map(assertSingleFieldObject);
1139
+ return normalizePairs(pairs, parseValue);
1169
1140
  }
1170
- if (!Array.isArray(val)) {
1171
- throw createError(`Array operation requires array value`, { value: val });
1141
+ if (isPlainObject(orderBy)) {
1142
+ return normalizePairs(Object.entries(orderBy), parseValue);
1172
1143
  }
1173
- const paramValue = prepareArrayParam(val, dialect);
1174
- return params.add(paramValue);
1144
+ throw new Error("orderBy must be an object or array of objects");
1175
1145
  }
1176
- function buildArrayOperator(expr, op, val, params, fieldType, dialect) {
1177
- if (val === void 0) return "";
1178
- if (val === null) {
1179
- if (op === Ops.EQUALS) return `${expr} ${SQL_TEMPLATES.IS_NULL}`;
1180
- if (op === Ops.NOT) return `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
1181
- }
1182
- const cast = getArrayType(fieldType, dialect);
1183
- if (op === Ops.EQUALS) {
1184
- return handleArrayEquals(expr, val, params, cast, dialect);
1185
- }
1186
- if (op === Ops.NOT) {
1187
- return handleArrayNot(expr, val, params, cast, dialect);
1188
- }
1189
- switch (op) {
1190
- case Ops.HAS:
1191
- return handleArrayHas(expr, val, params, cast, dialect);
1192
- case Ops.HAS_SOME:
1193
- return handleArrayHasSome(expr, val, params, cast, dialect);
1194
- case Ops.HAS_EVERY:
1195
- return handleArrayHasEvery(expr, val, params, cast, dialect);
1196
- case Ops.IS_EMPTY:
1197
- return handleArrayIsEmpty(expr, val, dialect);
1198
- default:
1199
- throw createError(`Unknown array operator: ${op}`, { operator: op });
1200
- }
1146
+
1147
+ // src/builder/pagination.ts
1148
+ var MAX_LIMIT_OFFSET = 2147483647;
1149
+ function parseDirectionRaw(raw, errorLabel) {
1150
+ const s = String(raw).toLowerCase();
1151
+ if (s === "asc" || s === "desc") return s;
1152
+ throw new Error(`Invalid ${errorLabel}: ${raw}`);
1201
1153
  }
1202
- function handleArrayEquals(expr, val, params, cast, dialect) {
1203
- if (val === void 0) return "";
1204
- if (isEmptyArray(val)) {
1205
- return arrayIsEmpty(expr, dialect);
1154
+ function parseNullsRaw(raw, errorLabel) {
1155
+ if (!isNotNullish(raw)) return void 0;
1156
+ const s = String(raw).toLowerCase();
1157
+ if (s === "first" || s === "last") return s;
1158
+ throw new Error(`Invalid ${errorLabel}: ${raw}`);
1159
+ }
1160
+ function requireOrderByObject(v, errorPrefix) {
1161
+ if (!isPlainObject(v) || !("sort" in v)) {
1162
+ throw new Error(`${errorPrefix} must be 'asc' | 'desc' or { sort, nulls? }`);
1206
1163
  }
1207
- const placeholder = buildArrayParam(val, params, dialect);
1208
- return arrayEquals(expr, placeholder, cast, dialect);
1164
+ return v;
1209
1165
  }
1210
- function handleArrayNot(expr, val, params, cast, dialect) {
1211
- if (val === void 0) return "";
1212
- let target = val;
1213
- if (isPlainObject(val)) {
1214
- const entries = Object.entries(val).filter(([, v]) => v !== void 0);
1215
- if (entries.length === 1 && entries[0][0] === Ops.EQUALS) {
1216
- target = entries[0][1];
1217
- } else {
1218
- throw createError(`Array NOT only supports { equals: ... } shape`, {
1219
- operator: Ops.NOT,
1220
- value: val
1221
- });
1166
+ function assertAllowedOrderByKeys(obj, fieldName) {
1167
+ const allowed = /* @__PURE__ */ new Set(["sort", "nulls"]);
1168
+ for (const k of Object.keys(obj)) {
1169
+ if (!allowed.has(k)) {
1170
+ throw new Error(
1171
+ fieldName ? `Unsupported orderBy key '${k}' for field '${fieldName}'` : `Unsupported orderBy key '${k}'`
1172
+ );
1222
1173
  }
1223
1174
  }
1224
- if (target === null) {
1225
- return `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
1226
- }
1227
- if (isEmptyArray(target)) {
1228
- return arrayIsNotEmpty(expr, dialect);
1229
- }
1230
- const placeholder = buildArrayParam(target, params, dialect);
1231
- return `${SQL_TEMPLATES.NOT} (${arrayEquals(expr, placeholder, cast, dialect)})`;
1232
1175
  }
1233
- function handleArrayHas(expr, val, params, cast, dialect) {
1234
- if (val === void 0) return "";
1235
- if (val === null) {
1236
- throw createError(`has requires scalar value`, {
1237
- operator: Ops.HAS,
1238
- value: val
1239
- });
1240
- }
1241
- if (!schemaParser.isDynamicParameter(val) && Array.isArray(val)) {
1242
- throw createError(`has requires scalar value (single element), not array`, {
1243
- operator: Ops.HAS,
1244
- value: val
1245
- });
1246
- }
1247
- if (isPlainObject(val)) {
1248
- throw createError(`has requires scalar value`, {
1249
- operator: Ops.HAS,
1250
- value: val
1251
- });
1176
+ function parseOrderByValue(v, fieldName) {
1177
+ const errorPrefix = fieldName ? `orderBy for '${fieldName}'` : "orderBy value";
1178
+ if (typeof v === "string") {
1179
+ return { direction: parseDirectionRaw(v, `${errorPrefix} direction`) };
1252
1180
  }
1253
- const placeholder = params.addAuto(val);
1254
- return arrayContains(expr, placeholder, cast, dialect);
1181
+ const obj = requireOrderByObject(v, errorPrefix);
1182
+ const direction = parseDirectionRaw(obj.sort, `${errorPrefix}.sort`);
1183
+ const nulls = parseNullsRaw(obj.nulls, `${errorPrefix}.nulls`);
1184
+ assertAllowedOrderByKeys(obj, fieldName);
1185
+ return { direction, nulls };
1255
1186
  }
1256
- function handleArrayHasSome(expr, val, params, cast, dialect) {
1257
- if (val === void 0) return "";
1258
- if (schemaParser.isDynamicParameter(val)) {
1259
- const placeholder2 = params.addAuto(val);
1260
- return arrayOverlaps(expr, placeholder2, cast, dialect);
1187
+ function normalizeFiniteInteger(name, v) {
1188
+ if (typeof v !== "number" || !Number.isFinite(v) || !Number.isInteger(v)) {
1189
+ throw new Error(`${name} must be an integer`);
1261
1190
  }
1262
- if (!Array.isArray(val)) {
1263
- throw createError(`hasSome requires array value`, {
1264
- operator: Ops.HAS_SOME,
1265
- value: val
1266
- });
1191
+ return v;
1192
+ }
1193
+ function normalizeNonNegativeInt(name, v) {
1194
+ if (schemaParser.isDynamicParameter(v)) return v;
1195
+ const n = normalizeFiniteInteger(name, v);
1196
+ if (n < 0) {
1197
+ throw new Error(`${name} must be >= 0`);
1267
1198
  }
1268
- if (val.length > LIMITS.MAX_ARRAY_SIZE) {
1269
- throw createError(
1270
- `Array too large (${val.length} elements, max ${LIMITS.MAX_ARRAY_SIZE})`,
1271
- { operator: Ops.HAS_SOME, value: `[${val.length} items]` }
1272
- );
1199
+ if (n > MAX_LIMIT_OFFSET) {
1200
+ throw new Error(`${name} must be <= ${MAX_LIMIT_OFFSET}`);
1273
1201
  }
1274
- if (val.length === 0) return "0=1";
1275
- const paramValue = prepareArrayParam(val, dialect);
1276
- const placeholder = params.add(paramValue);
1277
- return arrayOverlaps(expr, placeholder, cast, dialect);
1202
+ return n;
1278
1203
  }
1279
- function handleArrayHasEvery(expr, val, params, cast, dialect) {
1280
- if (val === void 0) return "";
1281
- const placeholder = buildArrayParam(val, params, dialect);
1282
- return arrayContainsAll(expr, placeholder, cast, dialect);
1204
+ function hasNonNullishProp(v, key) {
1205
+ return isPlainObject(v) && key in v && isNotNullish(v[key]);
1283
1206
  }
1284
- function handleArrayIsEmpty(expr, val, dialect) {
1285
- if (typeof val !== "boolean") {
1286
- throw createError(`isEmpty requires boolean value`, {
1287
- operator: Ops.IS_EMPTY,
1288
- value: val
1289
- });
1207
+ function normalizeIntegerOrDynamic(name, v) {
1208
+ if (schemaParser.isDynamicParameter(v)) return v;
1209
+ return normalizeFiniteInteger(name, v);
1210
+ }
1211
+ function readSkipTake(relArgs) {
1212
+ const hasSkip = hasNonNullishProp(relArgs, "skip");
1213
+ const hasTake = hasNonNullishProp(relArgs, "take");
1214
+ if (!hasSkip && !hasTake) {
1215
+ return {
1216
+ hasSkip: false,
1217
+ hasTake: false,
1218
+ skipVal: void 0,
1219
+ takeVal: void 0
1220
+ };
1290
1221
  }
1291
- return val === true ? arrayIsEmpty(expr, dialect) : arrayIsNotEmpty(expr, dialect);
1222
+ const obj = relArgs;
1223
+ const skipVal = hasSkip ? normalizeNonNegativeInt("skip", obj.skip) : void 0;
1224
+ const takeVal = hasTake ? normalizeIntegerOrDynamic("take", obj.take) : void 0;
1225
+ return { hasSkip, hasTake, skipVal, takeVal };
1292
1226
  }
1293
-
1294
- // src/builder/where/operators-json.ts
1295
- var SAFE_JSON_PATH_SEGMENT = /^[a-zA-Z_]\w*$/;
1296
- function validateJsonPathSegments(segments) {
1297
- for (const segment of segments) {
1298
- if (typeof segment !== "string") {
1299
- throw createError("JSON path segments must be strings", {
1300
- operator: Ops.PATH,
1301
- value: segment
1302
- });
1227
+ function buildOrderByFragment(entries, alias, dialect, model) {
1228
+ if (entries.length === 0) return "";
1229
+ const out = [];
1230
+ for (const e of entries) {
1231
+ const dir = e.direction.toUpperCase();
1232
+ const c = col(alias, e.field, model);
1233
+ if (dialect === "postgres") {
1234
+ const nulls = isNotNullish(e.nulls) ? ` NULLS ${e.nulls.toUpperCase()}` : "";
1235
+ out.push(`${c} ${dir}${nulls}`);
1236
+ continue;
1303
1237
  }
1304
- if (!SAFE_JSON_PATH_SEGMENT.test(segment)) {
1305
- throw createError(
1306
- `Invalid JSON path segment: '${segment}'. Must be alphanumeric with underscores, starting with letter or underscore.`,
1307
- { operator: Ops.PATH, value: segment }
1308
- );
1238
+ if (isNotNullish(e.nulls)) {
1239
+ const isNullExpr = `(${c} IS NULL)`;
1240
+ const nullRankDir = e.nulls === "first" ? "DESC" : "ASC";
1241
+ out.push(`${isNullExpr} ${nullRankDir}`);
1242
+ out.push(`${c} ${dir}`);
1243
+ continue;
1309
1244
  }
1245
+ out.push(`${c} ${dir}`);
1310
1246
  }
1247
+ return out.join(SQL_SEPARATORS.ORDER_BY);
1311
1248
  }
1312
- function buildJsonOperator(expr, op, val, params, dialect) {
1313
- if (val === void 0) return "";
1314
- if (op === Ops.PATH && isPlainObject(val) && "path" in val) {
1315
- return handleJsonPath(expr, val, params, dialect);
1316
- }
1317
- const jsonWildcards = {
1318
- [Ops.STRING_CONTAINS]: (v) => `%${v}%`,
1319
- [Ops.STRING_STARTS_WITH]: (v) => `${v}%`,
1320
- [Ops.STRING_ENDS_WITH]: (v) => `%${v}`
1321
- };
1322
- if (op in jsonWildcards) {
1323
- return handleJsonWildcard(expr, op, val, params, jsonWildcards, dialect);
1249
+ function defaultNullsFor(dialect, direction) {
1250
+ if (dialect === "postgres") {
1251
+ return direction === "asc" ? "last" : "first";
1324
1252
  }
1325
- throw createError(`Unsupported JSON operator: ${op}`, { operator: op });
1253
+ return direction === "asc" ? "first" : "last";
1326
1254
  }
1327
- function handleJsonPath(expr, val, params, dialect) {
1328
- const v = val;
1329
- if (!Array.isArray(v.path)) {
1330
- throw createError("JSON path must be an array", { operator: Ops.PATH });
1331
- }
1332
- if (v.path.length === 0) {
1333
- throw createError("JSON path cannot be empty", { operator: Ops.PATH });
1255
+ function ensureCursorFieldsInOrder(orderEntries, cursorEntries) {
1256
+ const existing = /* @__PURE__ */ new Map();
1257
+ for (const e of orderEntries) existing.set(e.field, e);
1258
+ const out = [...orderEntries];
1259
+ for (const [field] of cursorEntries) {
1260
+ if (!existing.has(field)) {
1261
+ out.push({ field, direction: "asc" });
1262
+ existing.set(field, out[out.length - 1]);
1263
+ }
1334
1264
  }
1335
- validateJsonPathSegments(v.path);
1336
- const pathExpr = dialect === "sqlite" ? params.add(`$.${v.path.join(".")}`) : params.add(v.path);
1337
- const rawOps = [
1338
- ["=", v.equals],
1339
- [">", v.gt],
1340
- [">=", v.gte],
1341
- ["<", v.lt],
1342
- ["<=", v.lte]
1343
- ];
1344
- const ops = rawOps.filter(
1345
- ([, value]) => value !== void 0
1346
- );
1347
- if (ops.length === 0) {
1348
- throw createError("JSON path query missing comparison operator", {
1349
- operator: Ops.PATH
1350
- });
1265
+ return out;
1266
+ }
1267
+ function buildCursorFilterParts(cursor, cursorAlias, params, model) {
1268
+ const entries = Object.entries(cursor);
1269
+ if (entries.length === 0) {
1270
+ throw new Error("cursor must have at least one field");
1351
1271
  }
1272
+ const placeholdersByField = /* @__PURE__ */ new Map();
1352
1273
  const parts = [];
1353
- for (const [sqlOp, value] of ops) {
1274
+ for (const [field, value] of entries) {
1275
+ const c = `${cursorAlias}.${quote(field)}`;
1354
1276
  if (value === null) {
1355
- const base2 = jsonExtractText(expr, pathExpr, dialect);
1356
- parts.push(`${base2} ${SQL_TEMPLATES.IS_NULL}`);
1277
+ parts.push(`${c} IS NULL`);
1357
1278
  continue;
1358
1279
  }
1359
- const valPh = params.add(value);
1360
- const base = typeof value === "number" ? jsonExtractNumeric(expr, pathExpr, dialect) : jsonExtractText(expr, pathExpr, dialect);
1361
- parts.push(`${base} ${sqlOp} ${valPh}`);
1362
- }
1363
- return parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`;
1364
- }
1365
- function handleJsonWildcard(expr, op, val, params, wildcards, dialect) {
1366
- if (!isNotNullish(val)) {
1367
- throw createError(`JSON string operator requires non-null value`, {
1368
- operator: op,
1369
- value: val
1370
- });
1371
- }
1372
- if (isPlainObject(val) || Array.isArray(val)) {
1373
- throw createError(`JSON string operator requires scalar value`, {
1374
- operator: op,
1375
- value: val
1376
- });
1377
- }
1378
- const strVal = String(val);
1379
- if (strVal.length > LIMITS.MAX_STRING_LENGTH) {
1380
- throw createError(
1381
- `String too long (${strVal.length} chars, max ${LIMITS.MAX_STRING_LENGTH})`,
1382
- { operator: op }
1383
- );
1280
+ const ph = addAutoScoped(params, value, `cursor.filter.${field}`);
1281
+ placeholdersByField.set(field, ph);
1282
+ parts.push(`${c} = ${ph}`);
1384
1283
  }
1385
- const placeholder = params.add(wildcards[op](strVal));
1386
- const jsonText = jsonToText(expr, dialect);
1387
- return caseInsensitiveLike(jsonText, placeholder, dialect);
1284
+ return {
1285
+ whereSql: parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`,
1286
+ placeholdersByField
1287
+ };
1388
1288
  }
1389
-
1390
- // src/builder/where/relations.ts
1391
- var NO_JOINS = Object.freeze([]);
1392
- function isListRelation(fieldType) {
1393
- return typeof fieldType === "string" && fieldType.endsWith("[]");
1289
+ function cursorValueExpr(tableName, cursorAlias, cursorWhereSql, field, model) {
1290
+ const colName = quote(field);
1291
+ return `(SELECT ${cursorAlias}.${colName} ${SQL_TEMPLATES.FROM} ${tableName} ${cursorAlias} ${SQL_TEMPLATES.WHERE} ${cursorWhereSql} ${SQL_TEMPLATES.LIMIT} 1)`;
1394
1292
  }
1395
- function buildToOneNullCheck(field, parentAlias, relTable, relAlias, join, wantNull) {
1396
- const isLocal = field.isForeignKeyLocal === true;
1397
- const fkFields = normalizeKeyList(field.foreignKey);
1398
- if (isLocal) {
1399
- if (fkFields.length === 0) {
1400
- throw createError(`Relation '${field.name}' is missing foreignKey`, {
1401
- field: field.name
1402
- });
1403
- }
1404
- const parts = fkFields.map((fk) => {
1405
- const safe = fk.replace(/"/g, '""');
1406
- const expr = `${parentAlias}."${safe}"`;
1407
- return wantNull ? `${expr} ${SQL_TEMPLATES.IS_NULL}` : `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
1408
- });
1409
- if (parts.length === 1) return parts[0];
1410
- return wantNull ? `(${parts.join(" OR ")})` : `(${parts.join(" AND ")})`;
1411
- }
1412
- const existsSql = `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias} ${SQL_TEMPLATES.WHERE} ${join})`;
1413
- return wantNull ? `${SQL_TEMPLATES.NOT} ${existsSql}` : existsSql;
1293
+ function buildCursorRowExistsExpr(tableName, cursorAlias, cursorWhereSql) {
1294
+ return `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${tableName} ${cursorAlias} ${SQL_TEMPLATES.WHERE} ${cursorWhereSql} ${SQL_TEMPLATES.LIMIT} 1)`;
1414
1295
  }
1415
- function buildToOneExistsMatch(relTable, relAlias, join, sub) {
1416
- const joins = sub.joins.length > 0 ? ` ${sub.joins.join(" ")}` : "";
1417
- return `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias}${joins} ${SQL_TEMPLATES.WHERE} ${join} ${SQL_TEMPLATES.AND} ${sub.clause})`;
1296
+ function buildCursorEqualityExpr(columnExpr, valueExpr) {
1297
+ return `((${valueExpr} IS NULL AND ${columnExpr} IS NULL) OR (${valueExpr} IS NOT NULL AND ${columnExpr} = ${valueExpr}))`;
1418
1298
  }
1419
- function buildToOneNotExistsMatch(relTable, relAlias, join, sub) {
1420
- const joins = sub.joins.length > 0 ? ` ${sub.joins.join(" ")}` : "";
1421
- return `${SQL_TEMPLATES.NOT} EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias}${joins} ${SQL_TEMPLATES.WHERE} ${join} ${SQL_TEMPLATES.AND} ${sub.clause})`;
1299
+ function buildCursorInequalityExpr(columnExpr, direction, nulls, valueExpr) {
1300
+ const op = direction === "asc" ? ">" : "<";
1301
+ if (nulls === "first") {
1302
+ return `(CASE WHEN ${valueExpr} IS NULL THEN (${columnExpr} IS NOT NULL) ELSE (${columnExpr} ${op} ${valueExpr}) END)`;
1303
+ }
1304
+ return `(CASE WHEN ${valueExpr} IS NULL THEN 0=1 ELSE ((${columnExpr} ${op} ${valueExpr}) OR (${columnExpr} IS NULL)) END)`;
1422
1305
  }
1423
- function buildListRelationFilters(args) {
1424
- const {
1425
- fieldName,
1426
- value,
1427
- ctx,
1428
- whereBuilder,
1429
- relModel,
1430
- relTable,
1431
- relAlias,
1432
- join
1433
- } = args;
1434
- const noneValue = value[RelationFilters.NONE];
1435
- if (noneValue !== void 0 && noneValue !== null) {
1436
- const sub = whereBuilder.build(noneValue, __spreadProps(__spreadValues({}, ctx), {
1437
- alias: relAlias,
1438
- model: relModel,
1439
- path: [...ctx.path, fieldName, RelationFilters.NONE],
1440
- isSubquery: true,
1441
- depth: ctx.depth + 1
1442
- }));
1443
- const isEmptyFilter = isPlainObject(noneValue) && Object.keys(noneValue).length === 0;
1444
- const canOptimize = !ctx.isSubquery && isEmptyFilter && sub.clause === DEFAULT_WHERE_CLAUSE && sub.joins.length === 0;
1445
- if (canOptimize) {
1446
- const checkField = relModel.fields.find(
1447
- (f) => !f.isRelation && f.isRequired && f.name !== "id"
1448
- ) || relModel.fields.find((f) => !f.isRelation && f.name === "id");
1449
- if (checkField) {
1450
- const leftJoinSql = `LEFT JOIN ${relTable} ${relAlias} ON ${join}`;
1451
- const whereClause = `${relAlias}.${quote(checkField.name)} IS NULL`;
1452
- return Object.freeze({
1453
- clause: whereClause,
1454
- joins: [leftJoinSql]
1455
- });
1456
- }
1306
+ function buildOuterCursorMatch(cursor, outerAlias, placeholdersByField, params, model) {
1307
+ const parts = [];
1308
+ for (const [field, value] of Object.entries(cursor)) {
1309
+ const c = col(outerAlias, field, model);
1310
+ if (value === null) {
1311
+ parts.push(`${c} IS NULL`);
1312
+ continue;
1313
+ }
1314
+ const existing = placeholdersByField.get(field);
1315
+ if (typeof existing === "string" && existing.length > 0) {
1316
+ parts.push(`${c} = ${existing}`);
1317
+ continue;
1457
1318
  }
1319
+ const ph = addAutoScoped(params, value, `cursor.outerMatch.${field}`);
1320
+ parts.push(`${c} = ${ph}`);
1458
1321
  }
1459
- const filters = [
1460
- {
1461
- key: RelationFilters.SOME,
1462
- wrap: (c, j) => `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias}${j} ${SQL_TEMPLATES.WHERE} ${join} ${SQL_TEMPLATES.AND} ${c})`
1463
- },
1464
- {
1465
- key: RelationFilters.EVERY,
1466
- wrap: (c, j) => `${SQL_TEMPLATES.NOT} EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias}${j} ${SQL_TEMPLATES.WHERE} ${join} ${SQL_TEMPLATES.AND} ${SQL_TEMPLATES.NOT} (${c}))`
1467
- },
1468
- {
1469
- key: RelationFilters.NONE,
1470
- wrap: (c, j) => {
1471
- const condition = c === DEFAULT_WHERE_CLAUSE ? "" : ` ${SQL_TEMPLATES.AND} ${c}`;
1472
- return `${SQL_TEMPLATES.NOT} EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias}${j} ${SQL_TEMPLATES.WHERE} ${join}${condition})`;
1322
+ return parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`;
1323
+ }
1324
+ function buildOrderEntries(orderBy) {
1325
+ const normalized = normalizeOrderByInput(orderBy, parseOrderByValue);
1326
+ const entries = [];
1327
+ for (const item of normalized) {
1328
+ for (const [field, value] of Object.entries(item)) {
1329
+ if (typeof value === "string") {
1330
+ entries.push({ field, direction: value });
1331
+ } else {
1332
+ entries.push({
1333
+ field,
1334
+ direction: value.sort,
1335
+ nulls: value.nulls
1336
+ });
1473
1337
  }
1474
1338
  }
1475
- ];
1476
- const clauses = [];
1477
- for (const { key, wrap } of filters) {
1478
- const raw = value[key];
1479
- if (raw === void 0 || raw === null) continue;
1480
- const sub = whereBuilder.build(raw, __spreadProps(__spreadValues({}, ctx), {
1481
- alias: relAlias,
1482
- model: relModel,
1483
- path: [...ctx.path, fieldName, key],
1484
- isSubquery: true,
1485
- depth: ctx.depth + 1
1486
- }));
1487
- const j = sub.joins.length > 0 ? ` ${sub.joins.join(" ")}` : "";
1488
- clauses.push(wrap(sub.clause, j));
1489
- }
1490
- if (clauses.length === 0) {
1491
- throw createError(
1492
- `List relation '${fieldName}' requires one of { some, every, none }`,
1493
- { field: fieldName, path: ctx.path, modelName: ctx.model.name }
1494
- );
1495
1339
  }
1496
- return Object.freeze({
1497
- clause: clauses.join(SQL_SEPARATORS.CONDITION_AND),
1498
- joins: NO_JOINS
1499
- });
1340
+ return entries;
1500
1341
  }
1501
- function buildToOneRelationFilters(args) {
1502
- const {
1503
- fieldName,
1504
- value,
1505
- ctx,
1506
- whereBuilder,
1507
- field,
1508
- relModel,
1509
- relTable,
1510
- relAlias,
1511
- join
1512
- } = args;
1513
- const hasSomeEveryNone = isNotNullish(value[RelationFilters.SOME]) || isNotNullish(value[RelationFilters.EVERY]) || isNotNullish(value[RelationFilters.NONE]);
1514
- if (hasSomeEveryNone) {
1515
- throw createError(
1516
- `To-one relation '${fieldName}' does not support { some, every, none }; use { is, isNot }`,
1517
- { field: fieldName, path: ctx.path, modelName: ctx.model.name }
1518
- );
1342
+ function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect, model) {
1343
+ var _a;
1344
+ const d = dialect != null ? dialect : getGlobalDialect();
1345
+ const cursorEntries = Object.entries(cursor);
1346
+ if (cursorEntries.length === 0) {
1347
+ throw new Error("cursor must have at least one field");
1519
1348
  }
1520
- const hasIs = Object.prototype.hasOwnProperty.call(value, "is");
1521
- const hasIsNot = Object.prototype.hasOwnProperty.call(value, "isNot");
1522
- let filterKey;
1523
- let filterVal;
1524
- if (hasIs) {
1525
- filterKey = "is";
1526
- filterVal = value.is;
1527
- } else if (hasIsNot) {
1528
- filterKey = "isNot";
1529
- filterVal = value.isNot;
1349
+ const cursorAlias = "__tp_cursor_src";
1350
+ const { whereSql: cursorWhereSql, placeholdersByField } = buildCursorFilterParts(cursor, cursorAlias, params);
1351
+ let orderEntries = buildOrderEntries(orderBy);
1352
+ if (orderEntries.length === 0) {
1353
+ orderEntries = cursorEntries.map(([field]) => ({
1354
+ field,
1355
+ direction: "asc"
1356
+ }));
1530
1357
  } else {
1531
- filterKey = "is";
1532
- filterVal = value;
1358
+ orderEntries = ensureCursorFieldsInOrder(orderEntries, cursorEntries);
1359
+ }
1360
+ const existsExpr = buildCursorRowExistsExpr(
1361
+ tableName,
1362
+ cursorAlias,
1363
+ cursorWhereSql
1364
+ );
1365
+ const outerCursorMatch = buildOuterCursorMatch(
1366
+ cursor,
1367
+ alias,
1368
+ placeholdersByField,
1369
+ params,
1370
+ model
1371
+ );
1372
+ const orClauses = [];
1373
+ for (let level = 0; level < orderEntries.length; level++) {
1374
+ const andParts = [];
1375
+ for (let i = 0; i < level; i++) {
1376
+ const e2 = orderEntries[i];
1377
+ const c2 = col(alias, e2.field, model);
1378
+ const v2 = cursorValueExpr(
1379
+ tableName,
1380
+ cursorAlias,
1381
+ cursorWhereSql,
1382
+ e2.field);
1383
+ andParts.push(buildCursorEqualityExpr(c2, v2));
1384
+ }
1385
+ const e = orderEntries[level];
1386
+ const c = col(alias, e.field, model);
1387
+ const v = cursorValueExpr(
1388
+ tableName,
1389
+ cursorAlias,
1390
+ cursorWhereSql,
1391
+ e.field);
1392
+ const nulls = (_a = e.nulls) != null ? _a : defaultNullsFor(d, e.direction);
1393
+ andParts.push(buildCursorInequalityExpr(c, e.direction, nulls, v));
1394
+ orClauses.push(`(${andParts.join(SQL_SEPARATORS.CONDITION_AND)})`);
1395
+ }
1396
+ const exclusive = orClauses.join(SQL_SEPARATORS.CONDITION_OR);
1397
+ return `(${existsExpr} ${SQL_SEPARATORS.CONDITION_AND} ((${exclusive})${SQL_SEPARATORS.CONDITION_OR}(${outerCursorMatch})))`;
1398
+ }
1399
+ function buildOrderBy(orderBy, alias, dialect, model) {
1400
+ const entries = buildOrderEntries(orderBy);
1401
+ if (entries.length === 0) return "";
1402
+ const d = dialect != null ? dialect : getGlobalDialect();
1403
+ return buildOrderByFragment(entries, alias, d, model);
1404
+ }
1405
+ function buildOrderByClause(args, alias, dialect, model) {
1406
+ if (!isNotNullish(args.orderBy)) return "";
1407
+ const result = buildOrderBy(args.orderBy, alias, dialect, model);
1408
+ if (!isNonEmptyString(result)) {
1409
+ throw new Error(
1410
+ "buildOrderByClause: orderBy specified but produced empty result"
1411
+ );
1412
+ }
1413
+ return result;
1414
+ }
1415
+ function normalizeTakeLike(v) {
1416
+ const n = normalizeIntLike("take", v, {
1417
+ min: Number.MIN_SAFE_INTEGER,
1418
+ max: MAX_LIMIT_OFFSET,
1419
+ allowZero: true
1420
+ });
1421
+ if (typeof n === "number") {
1422
+ if (n === 0) return 0;
1423
+ }
1424
+ return n;
1425
+ }
1426
+ function normalizeSkipLike(v) {
1427
+ return normalizeIntLike("skip", v, {
1428
+ min: 0,
1429
+ max: MAX_LIMIT_OFFSET,
1430
+ allowZero: true
1431
+ });
1432
+ }
1433
+ function getPaginationParams(method, args) {
1434
+ if (method === "findMany") {
1435
+ return {
1436
+ take: normalizeTakeLike(args.take),
1437
+ skip: normalizeSkipLike(args.skip),
1438
+ cursor: args.cursor
1439
+ };
1440
+ }
1441
+ if (method === "findFirst") {
1442
+ const skip = normalizeSkipLike(args.skip);
1443
+ return { take: 1, skip: skip != null ? skip : 0 };
1444
+ }
1445
+ if (method === "findUnique") {
1446
+ return { take: 1, skip: 0 };
1447
+ }
1448
+ return {};
1449
+ }
1450
+ function buildNotComposite(expr, val, params, dialect, buildOp, separator) {
1451
+ const entries = Object.entries(val).filter(
1452
+ ([k, v]) => k !== "mode" && v !== void 0
1453
+ );
1454
+ if (entries.length === 0) return "";
1455
+ const clauses = [];
1456
+ for (const [subOp, subVal] of entries) {
1457
+ const sub = buildOp(expr, subOp, subVal, params, dialect);
1458
+ if (sub && sub.trim().length > 0) clauses.push(`(${sub})`);
1459
+ }
1460
+ if (clauses.length === 0) return "";
1461
+ if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
1462
+ return `${SQL_TEMPLATES.NOT} (${clauses.join(separator)})`;
1463
+ }
1464
+ function buildScalarOperator(expr, op, val, params, mode, fieldType, dialect) {
1465
+ if (val === void 0) return "";
1466
+ if (val === null) {
1467
+ return handleNullValue(expr, op);
1468
+ }
1469
+ if (op === Ops.NOT && isPlainObject(val)) {
1470
+ return handleNotOperator(expr, val, params, mode, fieldType, dialect);
1471
+ }
1472
+ if (op === Ops.NOT) {
1473
+ const placeholder = params.addAuto(val);
1474
+ return `${expr} <> ${placeholder}`;
1475
+ }
1476
+ if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && isNotNullish(dialect)) {
1477
+ const placeholder = params.addAuto(val);
1478
+ return caseInsensitiveEquals(expr, placeholder);
1479
+ }
1480
+ const STRING_LIKE_OPS = /* @__PURE__ */ new Set([
1481
+ Ops.CONTAINS,
1482
+ Ops.STARTS_WITH,
1483
+ Ops.ENDS_WITH
1484
+ ]);
1485
+ if (STRING_LIKE_OPS.has(op)) {
1486
+ if (!isNotNullish(dialect)) {
1487
+ throw createError(`Like operators require a SQL dialect`, {
1488
+ operator: op
1489
+ });
1490
+ }
1491
+ return handleLikeOperator(expr, op, val, params, mode, dialect);
1492
+ }
1493
+ if (op === Ops.IN || op === Ops.NOT_IN) {
1494
+ if (!isNotNullish(dialect)) {
1495
+ throw createError(`IN operators require a SQL dialect`, { operator: op });
1496
+ }
1497
+ return handleInOperator(expr, op, val, params, dialect);
1498
+ }
1499
+ return handleComparisonOperator(expr, op, val, params);
1500
+ }
1501
+ function handleNullValue(expr, op) {
1502
+ if (op === Ops.EQUALS) return `${expr} ${SQL_TEMPLATES.IS_NULL}`;
1503
+ if (op === Ops.NOT) return `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
1504
+ throw createError(`Operator '${op}' doesn't support null`, { operator: op });
1505
+ }
1506
+ function normalizeMode(v) {
1507
+ if (v === Modes.INSENSITIVE) return Modes.INSENSITIVE;
1508
+ if (v === Modes.DEFAULT) return Modes.DEFAULT;
1509
+ return void 0;
1510
+ }
1511
+ function handleNotOperator(expr, val, params, outerMode, fieldType, dialect) {
1512
+ const innerMode = normalizeMode(val.mode);
1513
+ const effectiveMode = innerMode != null ? innerMode : outerMode;
1514
+ return buildNotComposite(
1515
+ expr,
1516
+ val,
1517
+ params,
1518
+ dialect,
1519
+ (e, subOp, subVal, p, d) => buildScalarOperator(e, subOp, subVal, p, effectiveMode, fieldType, d),
1520
+ ` ${SQL_TEMPLATES.AND} `
1521
+ );
1522
+ }
1523
+ function buildDynamicLikePattern(op, placeholder, dialect) {
1524
+ if (dialect === "postgres") {
1525
+ switch (op) {
1526
+ case Ops.CONTAINS:
1527
+ return `('%' || ${placeholder} || '%')`;
1528
+ case Ops.STARTS_WITH:
1529
+ return `(${placeholder} || '%')`;
1530
+ case Ops.ENDS_WITH:
1531
+ return `('%' || ${placeholder})`;
1532
+ default:
1533
+ return placeholder;
1534
+ }
1535
+ }
1536
+ switch (op) {
1537
+ case Ops.CONTAINS:
1538
+ return `('%' || ${placeholder} || '%')`;
1539
+ case Ops.STARTS_WITH:
1540
+ return `(${placeholder} || '%')`;
1541
+ case Ops.ENDS_WITH:
1542
+ return `('%' || ${placeholder})`;
1543
+ default:
1544
+ return placeholder;
1545
+ }
1546
+ }
1547
+ function handleLikeOperator(expr, op, val, params, mode, dialect) {
1548
+ if (val === void 0) return "";
1549
+ if (schemaParser.isDynamicParameter(val)) {
1550
+ const placeholder2 = params.addAuto(val);
1551
+ const patternExpr = buildDynamicLikePattern(op, placeholder2, dialect);
1552
+ if (mode === Modes.INSENSITIVE) {
1553
+ return caseInsensitiveLike(expr, patternExpr, dialect);
1554
+ }
1555
+ return `${expr} ${SQL_TEMPLATES.LIKE} ${patternExpr}`;
1556
+ }
1557
+ const placeholder = params.add(Wildcards[op](String(val)));
1558
+ if (mode === Modes.INSENSITIVE) {
1559
+ return caseInsensitiveLike(expr, placeholder, dialect);
1560
+ }
1561
+ return `${expr} ${SQL_TEMPLATES.LIKE} ${placeholder}`;
1562
+ }
1563
+ function handleInOperator(expr, op, val, params, dialect) {
1564
+ if (val === void 0) return "";
1565
+ if (schemaParser.isDynamicParameter(val)) {
1566
+ const placeholder2 = params.addAuto(val);
1567
+ return op === Ops.IN ? inArray(expr, placeholder2, dialect) : notInArray(expr, placeholder2, dialect);
1568
+ }
1569
+ if (!Array.isArray(val)) {
1570
+ throw createError(`IN operators require array value`, {
1571
+ operator: op,
1572
+ value: val
1573
+ });
1574
+ }
1575
+ if (val.length === 0) {
1576
+ return op === Ops.IN ? "0=1" : "1=1";
1577
+ }
1578
+ const paramValue = prepareArrayParam(val, dialect);
1579
+ const placeholder = params.add(paramValue);
1580
+ return op === Ops.IN ? inArray(expr, placeholder, dialect) : notInArray(expr, placeholder, dialect);
1581
+ }
1582
+ function handleComparisonOperator(expr, op, val, params) {
1583
+ if (val === void 0) return "";
1584
+ const COMPARISON_OPS2 = {
1585
+ [Ops.EQUALS]: "=",
1586
+ [Ops.GT]: ">",
1587
+ [Ops.GTE]: ">=",
1588
+ [Ops.LT]: "<",
1589
+ [Ops.LTE]: "<="
1590
+ };
1591
+ const sqlOp = COMPARISON_OPS2[op];
1592
+ if (!sqlOp) {
1593
+ throw createError(`Unsupported scalar operator: ${op}`, { operator: op });
1594
+ }
1595
+ const placeholder = params.addAuto(val);
1596
+ return `${expr} ${sqlOp} ${placeholder}`;
1597
+ }
1598
+ function buildArrayParam(val, params, dialect) {
1599
+ if (schemaParser.isDynamicParameter(val)) {
1600
+ return params.addAuto(val);
1601
+ }
1602
+ if (!Array.isArray(val)) {
1603
+ throw createError(`Array operation requires array value`, { value: val });
1604
+ }
1605
+ const paramValue = prepareArrayParam(val, dialect);
1606
+ return params.add(paramValue);
1607
+ }
1608
+ function buildArrayOperator(expr, op, val, params, fieldType, dialect) {
1609
+ if (val === void 0) return "";
1610
+ if (val === null) {
1611
+ if (op === Ops.EQUALS) return `${expr} ${SQL_TEMPLATES.IS_NULL}`;
1612
+ if (op === Ops.NOT) return `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
1613
+ }
1614
+ const cast = getArrayType(fieldType, dialect);
1615
+ if (op === Ops.EQUALS) {
1616
+ return handleArrayEquals(expr, val, params, cast, dialect);
1617
+ }
1618
+ if (op === Ops.NOT) {
1619
+ return handleArrayNot(expr, val, params, cast, dialect);
1620
+ }
1621
+ switch (op) {
1622
+ case Ops.HAS:
1623
+ return handleArrayHas(expr, val, params, cast, dialect);
1624
+ case Ops.HAS_SOME:
1625
+ return handleArrayHasSome(expr, val, params, cast, dialect);
1626
+ case Ops.HAS_EVERY:
1627
+ return handleArrayHasEvery(expr, val, params, cast, dialect);
1628
+ case Ops.IS_EMPTY:
1629
+ return handleArrayIsEmpty(expr, val, dialect);
1630
+ default:
1631
+ throw createError(`Unknown array operator: ${op}`, { operator: op });
1632
+ }
1633
+ }
1634
+ function handleArrayEquals(expr, val, params, cast, dialect) {
1635
+ if (val === void 0) return "";
1636
+ if (isEmptyArray(val)) {
1637
+ return arrayIsEmpty(expr, dialect);
1638
+ }
1639
+ const placeholder = buildArrayParam(val, params, dialect);
1640
+ return arrayEquals(expr, placeholder, cast, dialect);
1641
+ }
1642
+ function handleArrayNot(expr, val, params, cast, dialect) {
1643
+ if (val === void 0) return "";
1644
+ let target = val;
1645
+ if (isPlainObject(val)) {
1646
+ const entries = Object.entries(val).filter(([, v]) => v !== void 0);
1647
+ if (entries.length === 1 && entries[0][0] === Ops.EQUALS) {
1648
+ target = entries[0][1];
1649
+ } else {
1650
+ throw createError(`Array NOT only supports { equals: ... } shape`, {
1651
+ operator: Ops.NOT,
1652
+ value: val
1653
+ });
1654
+ }
1655
+ }
1656
+ if (target === null) {
1657
+ return `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
1658
+ }
1659
+ if (isEmptyArray(target)) {
1660
+ return arrayIsNotEmpty(expr, dialect);
1533
1661
  }
1534
- if (filterVal === void 0) {
1535
- return Object.freeze({
1536
- clause: DEFAULT_WHERE_CLAUSE,
1537
- joins: NO_JOINS
1662
+ const placeholder = buildArrayParam(target, params, dialect);
1663
+ return `${SQL_TEMPLATES.NOT} (${arrayEquals(expr, placeholder, cast, dialect)})`;
1664
+ }
1665
+ function handleArrayHas(expr, val, params, cast, dialect) {
1666
+ if (val === void 0) return "";
1667
+ if (val === null) {
1668
+ throw createError(`has requires scalar value`, {
1669
+ operator: Ops.HAS,
1670
+ value: val
1538
1671
  });
1539
1672
  }
1540
- if (filterVal === null) {
1541
- const wantNull = filterKey === "is";
1542
- const clause2 = buildToOneNullCheck(
1543
- field,
1544
- ctx.alias,
1545
- relTable,
1546
- relAlias,
1547
- join,
1548
- wantNull
1549
- );
1550
- return Object.freeze({
1551
- clause: clause2,
1552
- joins: NO_JOINS
1673
+ if (!schemaParser.isDynamicParameter(val) && Array.isArray(val)) {
1674
+ throw createError(`has requires scalar value (single element), not array`, {
1675
+ operator: Ops.HAS,
1676
+ value: val
1553
1677
  });
1554
1678
  }
1555
- if (!isPlainObject(filterVal)) {
1556
- throw createError(
1557
- `Relation '${fieldName}' filter must be an object or null`,
1558
- {
1559
- field: fieldName,
1560
- path: ctx.path,
1561
- modelName: ctx.model.name,
1562
- value: filterVal
1563
- }
1564
- );
1565
- }
1566
- const sub = whereBuilder.build(filterVal, __spreadProps(__spreadValues({}, ctx), {
1567
- alias: relAlias,
1568
- model: relModel,
1569
- path: [...ctx.path, fieldName, filterKey],
1570
- isSubquery: true,
1571
- depth: ctx.depth + 1
1572
- }));
1573
- const clause = filterKey === "is" ? buildToOneExistsMatch(relTable, relAlias, join, sub) : buildToOneNotExistsMatch(relTable, relAlias, join, sub);
1574
- return Object.freeze({
1575
- clause,
1576
- joins: NO_JOINS
1577
- });
1578
- }
1579
- function ensureRelationFilterObject(fieldName, value, ctx) {
1580
- if (!isPlainObject(value)) {
1581
- throw createError(`Relation filter '${fieldName}' must be an object`, {
1582
- path: [...ctx.path, fieldName],
1583
- field: fieldName,
1584
- modelName: ctx.model.name,
1585
- value
1679
+ if (isPlainObject(val)) {
1680
+ throw createError(`has requires scalar value`, {
1681
+ operator: Ops.HAS,
1682
+ value: val
1586
1683
  });
1587
1684
  }
1685
+ const placeholder = params.addAuto(val);
1686
+ return arrayContains(expr, placeholder, cast, dialect);
1588
1687
  }
1589
- function buildRelation(fieldName, value, ctx, whereBuilder) {
1590
- const field = ctx.model.fields.find((f) => f.name === fieldName);
1591
- if (!isValidRelationField(field)) {
1592
- throw createError(`Invalid relation '${fieldName}'`, {
1593
- field: fieldName,
1594
- path: ctx.path,
1595
- modelName: ctx.model.name
1688
+ function handleArrayHasSome(expr, val, params, cast, dialect) {
1689
+ if (val === void 0) return "";
1690
+ if (schemaParser.isDynamicParameter(val)) {
1691
+ const placeholder2 = params.addAuto(val);
1692
+ return arrayOverlaps(expr, placeholder2, cast, dialect);
1693
+ }
1694
+ if (!Array.isArray(val)) {
1695
+ throw createError(`hasSome requires array value`, {
1696
+ operator: Ops.HAS_SOME,
1697
+ value: val
1596
1698
  });
1597
1699
  }
1598
- const relModel = ctx.schemaModels.find((m) => m.name === field.relatedModel);
1599
- if (!isNotNullish(relModel)) {
1700
+ if (val.length > LIMITS.MAX_ARRAY_SIZE) {
1600
1701
  throw createError(
1601
- `Related model '${field.relatedModel}' not found in schema. Available models: ${ctx.schemaModels.map((m) => m.name).join(", ")}`,
1602
- {
1603
- field: fieldName,
1604
- path: ctx.path,
1605
- modelName: ctx.model.name
1606
- }
1702
+ `Array too large (${val.length} elements, max ${LIMITS.MAX_ARRAY_SIZE})`,
1703
+ { operator: Ops.HAS_SOME, value: `[${val.length} items]` }
1607
1704
  );
1608
1705
  }
1609
- const relTable = buildTableReference(
1610
- SQL_TEMPLATES.PUBLIC_SCHEMA,
1611
- relModel.tableName,
1612
- ctx.dialect
1613
- );
1614
- const relAlias = ctx.aliasGen.next(fieldName);
1615
- const join = joinCondition(field, ctx.model, relModel, ctx.alias, relAlias);
1616
- const args = {
1617
- fieldName,
1618
- value,
1619
- ctx,
1620
- whereBuilder,
1621
- field,
1622
- relModel,
1623
- relTable,
1624
- relAlias,
1625
- join
1626
- };
1627
- if (isListRelation(field.type)) return buildListRelationFilters(args);
1628
- return buildToOneRelationFilters(args);
1629
- }
1630
- function buildTopLevelRelation(fieldName, value, ctx, whereBuilder) {
1631
- ensureRelationFilterObject(fieldName, value, ctx);
1632
- return buildRelation(fieldName, value, ctx, whereBuilder);
1633
- }
1634
- function buildNestedRelation(fieldName, value, ctx, whereBuilder) {
1635
- return buildTopLevelRelation(fieldName, value, ctx, whereBuilder);
1706
+ if (val.length === 0) return "0=1";
1707
+ const paramValue = prepareArrayParam(val, dialect);
1708
+ const placeholder = params.add(paramValue);
1709
+ return arrayOverlaps(expr, placeholder, cast, dialect);
1636
1710
  }
1637
-
1638
- // src/builder/shared/validators/field-validators.ts
1639
- function assertFieldExists(name, model, path) {
1640
- const field = model.fields.find((f) => f.name === name);
1641
- if (!isNotNullish(field)) {
1642
- throw createError(`Field '${name}' does not exist on '${model.name}'`, {
1643
- field: name,
1644
- path,
1645
- modelName: model.name,
1646
- availableFields: model.fields.map((f) => f.name)
1647
- });
1648
- }
1649
- return field;
1711
+ function handleArrayHasEvery(expr, val, params, cast, dialect) {
1712
+ if (val === void 0) return "";
1713
+ const placeholder = buildArrayParam(val, params, dialect);
1714
+ return arrayContainsAll(expr, placeholder, cast, dialect);
1650
1715
  }
1651
- function assertValidOperator(fieldName, op, fieldType, path, modelName) {
1652
- if (!isNotNullish(fieldType)) return;
1653
- const ARRAY_OPS = /* @__PURE__ */ new Set([
1654
- Ops.HAS,
1655
- Ops.HAS_SOME,
1656
- Ops.HAS_EVERY,
1657
- Ops.IS_EMPTY
1658
- ]);
1659
- const JSON_OPS = /* @__PURE__ */ new Set([
1660
- Ops.PATH,
1661
- Ops.STRING_CONTAINS,
1662
- Ops.STRING_STARTS_WITH,
1663
- Ops.STRING_ENDS_WITH
1664
- ]);
1665
- const isArrayOp = ARRAY_OPS.has(op);
1666
- const isFieldArray = isArrayType(fieldType);
1667
- const arrayOpMismatch = isArrayOp && !isFieldArray;
1668
- if (arrayOpMismatch) {
1669
- throw createError(`'${op}' requires array field, got '${fieldType}'`, {
1670
- operator: op,
1671
- field: fieldName,
1672
- path,
1673
- modelName
1674
- });
1675
- }
1676
- const isJsonOp = JSON_OPS.has(op);
1677
- const isFieldJson = isJsonType(fieldType);
1678
- const jsonOpMismatch = isJsonOp && !isFieldJson;
1679
- if (jsonOpMismatch) {
1680
- throw createError(`'${op}' requires JSON field, got '${fieldType}'`, {
1681
- operator: op,
1682
- field: fieldName,
1683
- path,
1684
- modelName
1716
+ function handleArrayIsEmpty(expr, val, dialect) {
1717
+ if (typeof val !== "boolean") {
1718
+ throw createError(`isEmpty requires boolean value`, {
1719
+ operator: Ops.IS_EMPTY,
1720
+ value: val
1685
1721
  });
1686
1722
  }
1723
+ return val === true ? arrayIsEmpty(expr, dialect) : arrayIsNotEmpty(expr, dialect);
1687
1724
  }
1688
1725
 
1689
- // src/builder/where/builder.ts
1690
- var WhereBuilder = class {
1691
- build(where, ctx) {
1692
- if (!isPlainObject(where)) {
1693
- throw createError("where must be an object", {
1694
- path: ctx.path,
1695
- modelName: ctx.model.name
1726
+ // src/builder/where/operators-json.ts
1727
+ var SAFE_JSON_PATH_SEGMENT = /^[a-zA-Z_]\w*$/;
1728
+ function validateJsonPathSegments(segments) {
1729
+ for (const segment of segments) {
1730
+ if (typeof segment !== "string") {
1731
+ throw createError("JSON path segments must be strings", {
1732
+ operator: Ops.PATH,
1733
+ value: segment
1696
1734
  });
1697
1735
  }
1698
- return buildWhereInternal(where, ctx, this);
1736
+ if (!SAFE_JSON_PATH_SEGMENT.test(segment)) {
1737
+ throw createError(
1738
+ `Invalid JSON path segment: '${segment}'. Must be alphanumeric with underscores, starting with letter or underscore.`,
1739
+ { operator: Ops.PATH, value: segment }
1740
+ );
1741
+ }
1699
1742
  }
1700
- };
1701
- var MAX_QUERY_DEPTH = 50;
1702
- var EMPTY_JOINS = Object.freeze([]);
1703
- var whereBuilderInstance = new WhereBuilder();
1704
- function freezeResult(clause, joins = EMPTY_JOINS) {
1705
- return Object.freeze({ clause, joins });
1706
1743
  }
1707
- function dedupePreserveOrder(items) {
1708
- if (items.length <= 1) return Object.freeze([...items]);
1709
- const seen = /* @__PURE__ */ new Set();
1710
- const out = [];
1711
- for (const s of items) {
1712
- if (!seen.has(s)) {
1713
- seen.add(s);
1714
- out.push(s);
1744
+ function buildJsonOperator(expr, op, val, params, dialect) {
1745
+ if (val === void 0) return "";
1746
+ if (op === Ops.PATH && isPlainObject(val) && "path" in val) {
1747
+ return handleJsonPath(expr, val, params, dialect);
1748
+ }
1749
+ const jsonWildcards = {
1750
+ [Ops.STRING_CONTAINS]: (v) => `%${v}%`,
1751
+ [Ops.STRING_STARTS_WITH]: (v) => `${v}%`,
1752
+ [Ops.STRING_ENDS_WITH]: (v) => `%${v}`
1753
+ };
1754
+ if (op in jsonWildcards) {
1755
+ return handleJsonWildcard(expr, op, val, params, jsonWildcards, dialect);
1756
+ }
1757
+ throw createError(`Unsupported JSON operator: ${op}`, { operator: op });
1758
+ }
1759
+ function handleJsonPath(expr, val, params, dialect) {
1760
+ const v = val;
1761
+ if (!Array.isArray(v.path)) {
1762
+ throw createError("JSON path must be an array", { operator: Ops.PATH });
1763
+ }
1764
+ if (v.path.length === 0) {
1765
+ throw createError("JSON path cannot be empty", { operator: Ops.PATH });
1766
+ }
1767
+ validateJsonPathSegments(v.path);
1768
+ const pathExpr = dialect === "sqlite" ? params.add(`$.${v.path.join(".")}`) : params.add(v.path);
1769
+ const rawOps = [
1770
+ ["=", v.equals],
1771
+ [">", v.gt],
1772
+ [">=", v.gte],
1773
+ ["<", v.lt],
1774
+ ["<=", v.lte]
1775
+ ];
1776
+ const ops = rawOps.filter(
1777
+ ([, value]) => value !== void 0
1778
+ );
1779
+ if (ops.length === 0) {
1780
+ throw createError("JSON path query missing comparison operator", {
1781
+ operator: Ops.PATH
1782
+ });
1783
+ }
1784
+ const parts = [];
1785
+ for (const [sqlOp, value] of ops) {
1786
+ if (value === null) {
1787
+ const base2 = jsonExtractText(expr, pathExpr, dialect);
1788
+ parts.push(`${base2} ${SQL_TEMPLATES.IS_NULL}`);
1789
+ continue;
1715
1790
  }
1791
+ const valPh = params.add(value);
1792
+ const base = typeof value === "number" ? jsonExtractNumeric(expr, pathExpr, dialect) : jsonExtractText(expr, pathExpr, dialect);
1793
+ parts.push(`${base} ${sqlOp} ${valPh}`);
1716
1794
  }
1717
- return Object.freeze(out);
1718
- }
1719
- function appendResult(result, clauses, allJoins) {
1720
- if (isValidWhereClause(result.clause)) clauses.push(result.clause);
1721
- if (isNonEmptyArray(result.joins)) allJoins.push(...result.joins);
1722
- }
1723
- function asLogicalOperator(key) {
1724
- if (key === LogicalOps.AND) return "AND";
1725
- if (key === LogicalOps.OR) return "OR";
1726
- if (key === LogicalOps.NOT) return "NOT";
1727
- return null;
1728
- }
1729
- function nextContext(ctx) {
1730
- return __spreadProps(__spreadValues({}, ctx), { depth: ctx.depth + 1 });
1795
+ return parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`;
1731
1796
  }
1732
- function buildRelationFilter(fieldName, value, ctx, builder) {
1733
- const ctx2 = nextContext(ctx);
1734
- if (ctx.isSubquery) {
1735
- return buildNestedRelation(fieldName, value, ctx2, builder);
1797
+ function handleJsonWildcard(expr, op, val, params, wildcards, dialect) {
1798
+ if (!isNotNullish(val)) {
1799
+ throw createError(`JSON string operator requires non-null value`, {
1800
+ operator: op,
1801
+ value: val
1802
+ });
1736
1803
  }
1737
- return buildTopLevelRelation(fieldName, value, ctx2, builder);
1738
- }
1739
- function buildWhereEntry(key, value, ctx, builder) {
1740
- const op = asLogicalOperator(key);
1741
- if (op) return buildLogical(op, value, ctx, builder);
1742
- if (isRelationField(key, ctx.model)) {
1743
- if (!isPlainObject(value)) {
1744
- throw createError(`Relation filter '${key}' must be an object`, {
1745
- path: [...ctx.path, key],
1746
- field: key,
1747
- modelName: ctx.model.name,
1748
- value
1749
- });
1750
- }
1751
- return buildRelationFilter(key, value, ctx, builder);
1804
+ if (isPlainObject(val) || Array.isArray(val)) {
1805
+ throw createError(`JSON string operator requires scalar value`, {
1806
+ operator: op,
1807
+ value: val
1808
+ });
1752
1809
  }
1753
- return buildScalarField(key, value, ctx);
1754
- }
1755
- function buildWhereInternal(where, ctx, builder) {
1756
- if (ctx.depth > MAX_QUERY_DEPTH) {
1810
+ const strVal = String(val);
1811
+ if (strVal.length > LIMITS.MAX_STRING_LENGTH) {
1757
1812
  throw createError(
1758
- `Query nesting too deep (max ${MAX_QUERY_DEPTH} levels). This usually indicates a circular reference.`,
1759
- { path: ctx.path, modelName: ctx.model.name }
1813
+ `String too long (${strVal.length} chars, max ${LIMITS.MAX_STRING_LENGTH})`,
1814
+ { operator: op }
1760
1815
  );
1761
1816
  }
1762
- if (isEmptyWhere(where)) {
1763
- return freezeResult(DEFAULT_WHERE_CLAUSE, EMPTY_JOINS);
1764
- }
1765
- const allJoins = [];
1766
- const clauses = [];
1767
- for (const [key, value] of Object.entries(where)) {
1768
- if (value === void 0) continue;
1769
- const result = buildWhereEntry(key, value, ctx, builder);
1770
- appendResult(result, clauses, allJoins);
1817
+ const placeholder = params.add(wildcards[op](strVal));
1818
+ const jsonText = jsonToText(expr, dialect);
1819
+ return caseInsensitiveLike(jsonText, placeholder, dialect);
1820
+ }
1821
+
1822
+ // src/builder/where/relations.ts
1823
+ var NO_JOINS = Object.freeze([]);
1824
+ function isListRelation(fieldType) {
1825
+ return typeof fieldType === "string" && fieldType.endsWith("[]");
1826
+ }
1827
+ function buildToOneNullCheck(field, parentAlias, relTable, relAlias, join, wantNull) {
1828
+ const isLocal = field.isForeignKeyLocal === true;
1829
+ const fkFields = normalizeKeyList(field.foreignKey);
1830
+ if (isLocal) {
1831
+ if (fkFields.length === 0) {
1832
+ throw createError(`Relation '${field.name}' is missing foreignKey`, {
1833
+ field: field.name
1834
+ });
1835
+ }
1836
+ const parts = fkFields.map((fk) => {
1837
+ const safe = fk.replace(/"/g, '""');
1838
+ const expr = `${parentAlias}."${safe}"`;
1839
+ return wantNull ? `${expr} ${SQL_TEMPLATES.IS_NULL}` : `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
1840
+ });
1841
+ if (parts.length === 1) return parts[0];
1842
+ return wantNull ? `(${parts.join(" OR ")})` : `(${parts.join(" AND ")})`;
1771
1843
  }
1772
- const finalClause = clauses.length > 0 ? clauses.join(SQL_SEPARATORS.CONDITION_AND) : DEFAULT_WHERE_CLAUSE;
1773
- return freezeResult(finalClause, dedupePreserveOrder(allJoins));
1844
+ const existsSql = `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias} ${SQL_TEMPLATES.WHERE} ${join})`;
1845
+ return wantNull ? `${SQL_TEMPLATES.NOT} ${existsSql}` : existsSql;
1774
1846
  }
1775
- function normalizeLogicalValue(operator, value, ctx) {
1776
- if (Array.isArray(value)) {
1777
- const out = [];
1778
- for (let i = 0; i < value.length; i++) {
1779
- const v = value[i];
1780
- if (v === void 0) continue;
1781
- if (!isPlainObject(v)) {
1782
- throw createError(`${operator} entries must be objects`, {
1783
- path: [...ctx.path, operator, String(i)],
1784
- modelName: ctx.model.name,
1785
- value: v
1847
+ function buildToOneExistsMatch(relTable, relAlias, join, sub) {
1848
+ const joins = sub.joins.length > 0 ? ` ${sub.joins.join(" ")}` : "";
1849
+ return `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias}${joins} ${SQL_TEMPLATES.WHERE} ${join} ${SQL_TEMPLATES.AND} ${sub.clause})`;
1850
+ }
1851
+ function buildToOneNotExistsMatch(relTable, relAlias, join, sub) {
1852
+ const joins = sub.joins.length > 0 ? ` ${sub.joins.join(" ")}` : "";
1853
+ return `${SQL_TEMPLATES.NOT} EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias}${joins} ${SQL_TEMPLATES.WHERE} ${join} ${SQL_TEMPLATES.AND} ${sub.clause})`;
1854
+ }
1855
+ function buildListRelationFilters(args) {
1856
+ const {
1857
+ fieldName,
1858
+ value,
1859
+ ctx,
1860
+ whereBuilder,
1861
+ relModel,
1862
+ relTable,
1863
+ relAlias,
1864
+ join
1865
+ } = args;
1866
+ const noneValue = value[RelationFilters.NONE];
1867
+ if (noneValue !== void 0 && noneValue !== null) {
1868
+ const sub = whereBuilder.build(noneValue, __spreadProps(__spreadValues({}, ctx), {
1869
+ alias: relAlias,
1870
+ model: relModel,
1871
+ path: [...ctx.path, fieldName, RelationFilters.NONE],
1872
+ isSubquery: true,
1873
+ depth: ctx.depth + 1
1874
+ }));
1875
+ const isEmptyFilter = isPlainObject(noneValue) && Object.keys(noneValue).length === 0;
1876
+ const canOptimize = !ctx.isSubquery && isEmptyFilter && sub.clause === DEFAULT_WHERE_CLAUSE && sub.joins.length === 0;
1877
+ if (canOptimize) {
1878
+ const checkField = relModel.fields.find(
1879
+ (f) => !f.isRelation && f.isRequired && f.name !== "id"
1880
+ ) || relModel.fields.find((f) => !f.isRelation && f.name === "id");
1881
+ if (checkField) {
1882
+ const leftJoinSql = `LEFT JOIN ${relTable} ${relAlias} ON ${join}`;
1883
+ const whereClause = `${relAlias}.${quote(checkField.name)} IS NULL`;
1884
+ return Object.freeze({
1885
+ clause: whereClause,
1886
+ joins: [leftJoinSql]
1786
1887
  });
1787
1888
  }
1788
- out.push(v);
1789
1889
  }
1790
- return out;
1791
- }
1792
- if (isPlainObject(value)) {
1793
- return [value];
1794
1890
  }
1795
- throw createError(`${operator} must be an object or array of objects`, {
1796
- path: [...ctx.path, operator],
1797
- modelName: ctx.model.name,
1798
- value
1799
- });
1800
- }
1801
- function collectLogicalParts(operator, conditions, ctx, builder) {
1802
- const allJoins = [];
1891
+ const filters = [
1892
+ {
1893
+ key: RelationFilters.SOME,
1894
+ wrap: (c, j) => `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias}${j} ${SQL_TEMPLATES.WHERE} ${join} ${SQL_TEMPLATES.AND} ${c})`
1895
+ },
1896
+ {
1897
+ key: RelationFilters.EVERY,
1898
+ wrap: (c, j) => `${SQL_TEMPLATES.NOT} EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias}${j} ${SQL_TEMPLATES.WHERE} ${join} ${SQL_TEMPLATES.AND} ${SQL_TEMPLATES.NOT} (${c}))`
1899
+ },
1900
+ {
1901
+ key: RelationFilters.NONE,
1902
+ wrap: (c, j) => {
1903
+ const condition = c === DEFAULT_WHERE_CLAUSE ? "" : ` ${SQL_TEMPLATES.AND} ${c}`;
1904
+ return `${SQL_TEMPLATES.NOT} EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias}${j} ${SQL_TEMPLATES.WHERE} ${join}${condition})`;
1905
+ }
1906
+ }
1907
+ ];
1803
1908
  const clauses = [];
1804
- for (let i = 0; i < conditions.length; i++) {
1805
- const result = builder.build(conditions[i], __spreadProps(__spreadValues({}, ctx), {
1806
- path: [...ctx.path, operator, String(i)],
1909
+ for (const { key, wrap } of filters) {
1910
+ const raw = value[key];
1911
+ if (raw === void 0 || raw === null) continue;
1912
+ const sub = whereBuilder.build(raw, __spreadProps(__spreadValues({}, ctx), {
1913
+ alias: relAlias,
1914
+ model: relModel,
1915
+ path: [...ctx.path, fieldName, key],
1916
+ isSubquery: true,
1807
1917
  depth: ctx.depth + 1
1808
1918
  }));
1809
- if (isNonEmptyArray(result.joins)) allJoins.push(...result.joins);
1810
- if (result.clause && result.clause !== DEFAULT_WHERE_CLAUSE) {
1811
- clauses.push(`(${result.clause})`);
1812
- }
1813
- }
1814
- return {
1815
- joins: dedupePreserveOrder(allJoins),
1816
- clauses: Object.freeze(clauses)
1817
- };
1818
- }
1819
- function buildLogicalClause(operator, clauses) {
1820
- if (clauses.length === 0) return DEFAULT_WHERE_CLAUSE;
1821
- if (operator === "NOT") {
1822
- if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
1823
- return `${SQL_TEMPLATES.NOT} (${clauses.join(SQL_SEPARATORS.CONDITION_AND)})`;
1824
- }
1825
- return clauses.join(` ${operator} `);
1826
- }
1827
- function buildLogical(operator, value, ctx, builder) {
1828
- const conditions = normalizeLogicalValue(operator, value, ctx);
1829
- if (conditions.length === 0) {
1830
- const clause2 = operator === "OR" ? "0=1" : DEFAULT_WHERE_CLAUSE;
1831
- return freezeResult(clause2, EMPTY_JOINS);
1832
- }
1833
- const { joins, clauses } = collectLogicalParts(
1834
- operator,
1835
- conditions,
1836
- ctx,
1837
- builder
1838
- );
1839
- const clause = buildLogicalClause(operator, clauses);
1840
- return freezeResult(clause, joins);
1841
- }
1842
- function buildScalarField(fieldName, value, ctx) {
1843
- const field = assertFieldExists(fieldName, ctx.model, ctx.path);
1844
- const expr = col(ctx.alias, fieldName, ctx.model);
1845
- if (value === null) {
1846
- return freezeResult(`${expr} ${SQL_TEMPLATES.IS_NULL}`, EMPTY_JOINS);
1919
+ const j = sub.joins.length > 0 ? ` ${sub.joins.join(" ")}` : "";
1920
+ clauses.push(wrap(sub.clause, j));
1847
1921
  }
1848
- if (isPlainObject(value)) {
1849
- const mode = value.mode;
1850
- const ops = Object.entries(value).filter(
1851
- ([k, v]) => k !== "mode" && v !== void 0
1852
- );
1853
- if (ops.length === 0) {
1854
- return freezeResult(DEFAULT_WHERE_CLAUSE, EMPTY_JOINS);
1855
- }
1856
- const parts = [];
1857
- for (const [op, val] of ops) {
1858
- assertValidOperator(fieldName, op, field.type, ctx.path, ctx.model.name);
1859
- const clause3 = buildOperator(expr, op, val, ctx, mode, field.type);
1860
- if (isValidWhereClause(clause3)) parts.push(clause3);
1861
- }
1862
- const clause2 = parts.length > 0 ? parts.join(SQL_SEPARATORS.CONDITION_AND) : DEFAULT_WHERE_CLAUSE;
1863
- return freezeResult(clause2, EMPTY_JOINS);
1922
+ if (clauses.length === 0) {
1923
+ throw createError(
1924
+ `List relation '${fieldName}' requires one of { some, every, none }`,
1925
+ { field: fieldName, path: ctx.path, modelName: ctx.model.name }
1926
+ );
1864
1927
  }
1865
- const clause = buildOperator(
1866
- expr,
1867
- Ops.EQUALS,
1928
+ return Object.freeze({
1929
+ clause: clauses.join(SQL_SEPARATORS.CONDITION_AND),
1930
+ joins: NO_JOINS
1931
+ });
1932
+ }
1933
+ function buildToOneRelationFilters(args) {
1934
+ const {
1935
+ fieldName,
1868
1936
  value,
1869
1937
  ctx,
1870
- void 0,
1871
- field.type
1872
- );
1873
- return freezeResult(clause || DEFAULT_WHERE_CLAUSE, EMPTY_JOINS);
1874
- }
1875
- function buildOperator(expr, op, val, ctx, mode, fieldType) {
1876
- if (fieldType && isArrayType(fieldType)) {
1877
- return buildArrayOperator(expr, op, val, ctx.params, fieldType, ctx.dialect);
1938
+ whereBuilder,
1939
+ field,
1940
+ relModel,
1941
+ relTable,
1942
+ relAlias,
1943
+ join
1944
+ } = args;
1945
+ const hasSomeEveryNone = isNotNullish(value[RelationFilters.SOME]) || isNotNullish(value[RelationFilters.EVERY]) || isNotNullish(value[RelationFilters.NONE]);
1946
+ if (hasSomeEveryNone) {
1947
+ throw createError(
1948
+ `To-one relation '${fieldName}' does not support { some, every, none }; use { is, isNot }`,
1949
+ { field: fieldName, path: ctx.path, modelName: ctx.model.name }
1950
+ );
1878
1951
  }
1879
- if (fieldType && isJsonType(fieldType)) {
1880
- const JSON_OPS = /* @__PURE__ */ new Set([
1881
- Ops.PATH,
1882
- Ops.STRING_CONTAINS,
1883
- Ops.STRING_STARTS_WITH,
1884
- Ops.STRING_ENDS_WITH
1885
- ]);
1886
- if (JSON_OPS.has(op)) {
1887
- return buildJsonOperator(expr, op, val, ctx.params, ctx.dialect);
1888
- }
1952
+ const hasIs = Object.prototype.hasOwnProperty.call(value, "is");
1953
+ const hasIsNot = Object.prototype.hasOwnProperty.call(value, "isNot");
1954
+ let filterKey;
1955
+ let filterVal;
1956
+ if (hasIs) {
1957
+ filterKey = "is";
1958
+ filterVal = value.is;
1959
+ } else if (hasIsNot) {
1960
+ filterKey = "isNot";
1961
+ filterVal = value.isNot;
1962
+ } else {
1963
+ filterKey = "is";
1964
+ filterVal = value;
1889
1965
  }
1890
- return buildScalarOperator(
1891
- expr,
1892
- op,
1893
- val,
1894
- ctx.params,
1895
- mode,
1896
- fieldType,
1897
- ctx.dialect
1898
- );
1899
- }
1900
-
1901
- // src/builder/shared/alias-generator.ts
1902
- function toSafeSqlIdentifier(input) {
1903
- const raw = String(input);
1904
- const cleaned = raw.replace(/\W/g, "_");
1905
- const startsOk = /^[a-zA-Z_]/.test(cleaned);
1906
- const base = startsOk ? cleaned : `_${cleaned}`;
1907
- const fallback = base.length > 0 ? base : "_t";
1908
- const lowered = fallback.toLowerCase();
1909
- return SQL_KEYWORDS.has(lowered) ? `_${lowered}` : lowered;
1910
- }
1911
- function createAliasGenerator(maxAliases = 1e4) {
1912
- let counter = 0;
1913
- const usedAliases = /* @__PURE__ */ new Set();
1914
- return {
1915
- next(baseName) {
1916
- if (usedAliases.size >= maxAliases) {
1917
- throw new Error(
1918
- `Alias generator exceeded maximum of ${maxAliases} aliases. This indicates a query complexity issue or potential infinite loop.`
1919
- );
1920
- }
1921
- const base = toSafeSqlIdentifier(baseName);
1922
- const suffix = `_${counter}`;
1923
- const maxLen = 63;
1924
- const baseMax = Math.max(1, maxLen - suffix.length);
1925
- const trimmedBase = base.length > baseMax ? base.slice(0, baseMax) : base;
1926
- const alias = `${trimmedBase}${suffix}`;
1927
- counter += 1;
1928
- if (usedAliases.has(alias)) {
1929
- throw new Error(
1930
- `CRITICAL: Duplicate alias '${alias}' at counter=${counter}. This indicates a bug in alias generation logic.`
1931
- );
1966
+ if (filterVal === void 0) {
1967
+ return Object.freeze({
1968
+ clause: DEFAULT_WHERE_CLAUSE,
1969
+ joins: NO_JOINS
1970
+ });
1971
+ }
1972
+ if (filterVal === null) {
1973
+ const wantNull = filterKey === "is";
1974
+ const clause2 = buildToOneNullCheck(
1975
+ field,
1976
+ ctx.alias,
1977
+ relTable,
1978
+ relAlias,
1979
+ join,
1980
+ wantNull
1981
+ );
1982
+ return Object.freeze({
1983
+ clause: clause2,
1984
+ joins: NO_JOINS
1985
+ });
1986
+ }
1987
+ if (!isPlainObject(filterVal)) {
1988
+ throw createError(
1989
+ `Relation '${fieldName}' filter must be an object or null`,
1990
+ {
1991
+ field: fieldName,
1992
+ path: ctx.path,
1993
+ modelName: ctx.model.name,
1994
+ value: filterVal
1932
1995
  }
1933
- usedAliases.add(alias);
1934
- return alias;
1935
- }
1936
- };
1937
- }
1938
- var MAX_PARAM_INDEX = Number.MAX_SAFE_INTEGER - 1e3;
1939
- function assertSameLength(params, mappings) {
1940
- if (params.length !== mappings.length) {
1941
- throw new Error(
1942
- `CRITICAL: State corruption - params=${params.length}, mappings=${mappings.length}`
1943
1996
  );
1944
1997
  }
1998
+ const sub = whereBuilder.build(filterVal, __spreadProps(__spreadValues({}, ctx), {
1999
+ alias: relAlias,
2000
+ model: relModel,
2001
+ path: [...ctx.path, fieldName, filterKey],
2002
+ isSubquery: true,
2003
+ depth: ctx.depth + 1
2004
+ }));
2005
+ const clause = filterKey === "is" ? buildToOneExistsMatch(relTable, relAlias, join, sub) : buildToOneNotExistsMatch(relTable, relAlias, join, sub);
2006
+ return Object.freeze({
2007
+ clause,
2008
+ joins: NO_JOINS
2009
+ });
1945
2010
  }
1946
- function assertValidNextIndex(index) {
1947
- if (!Number.isInteger(index) || index < 1) {
1948
- throw new Error(`CRITICAL: Index must be integer >= 1, got ${index}`);
2011
+ function ensureRelationFilterObject(fieldName, value, ctx) {
2012
+ if (!isPlainObject(value)) {
2013
+ throw createError(`Relation filter '${fieldName}' must be an object`, {
2014
+ path: [...ctx.path, fieldName],
2015
+ field: fieldName,
2016
+ modelName: ctx.model.name,
2017
+ value
2018
+ });
1949
2019
  }
1950
2020
  }
1951
- function assertNextIndexMatches(mappingsLength, nextIndex) {
1952
- const expected = mappingsLength + 1;
1953
- if (nextIndex !== expected) {
1954
- throw new Error(
1955
- `CRITICAL: Next index mismatch - expected ${expected}, got ${nextIndex}`
2021
+ function buildRelation(fieldName, value, ctx, whereBuilder) {
2022
+ const field = ctx.model.fields.find((f) => f.name === fieldName);
2023
+ if (!isValidRelationField(field)) {
2024
+ throw createError(`Invalid relation '${fieldName}'`, {
2025
+ field: fieldName,
2026
+ path: ctx.path,
2027
+ modelName: ctx.model.name
2028
+ });
2029
+ }
2030
+ const relModel = ctx.schemaModels.find((m) => m.name === field.relatedModel);
2031
+ if (!isNotNullish(relModel)) {
2032
+ throw createError(
2033
+ `Related model '${field.relatedModel}' not found in schema. Available models: ${ctx.schemaModels.map((m) => m.name).join(", ")}`,
2034
+ {
2035
+ field: fieldName,
2036
+ path: ctx.path,
2037
+ modelName: ctx.model.name
2038
+ }
1956
2039
  );
1957
2040
  }
2041
+ const relTable = buildTableReference(
2042
+ SQL_TEMPLATES.PUBLIC_SCHEMA,
2043
+ relModel.tableName,
2044
+ ctx.dialect
2045
+ );
2046
+ const relAlias = ctx.aliasGen.next(fieldName);
2047
+ const join = joinCondition(field, ctx.model, relModel, ctx.alias, relAlias);
2048
+ const args = {
2049
+ fieldName,
2050
+ value,
2051
+ ctx,
2052
+ whereBuilder,
2053
+ field,
2054
+ relModel,
2055
+ relTable,
2056
+ relAlias,
2057
+ join
2058
+ };
2059
+ if (isListRelation(field.type)) return buildListRelationFilters(args);
2060
+ return buildToOneRelationFilters(args);
1958
2061
  }
1959
- function assertSequentialIndex(actual, expected) {
1960
- if (actual !== expected) {
1961
- throw new Error(
1962
- `CRITICAL: Indices must be sequential from 1..N. Expected ${expected}, got ${actual}`
1963
- );
2062
+ function buildTopLevelRelation(fieldName, value, ctx, whereBuilder) {
2063
+ ensureRelationFilterObject(fieldName, value, ctx);
2064
+ return buildRelation(fieldName, value, ctx, whereBuilder);
2065
+ }
2066
+ function buildNestedRelation(fieldName, value, ctx, whereBuilder) {
2067
+ return buildTopLevelRelation(fieldName, value, ctx, whereBuilder);
2068
+ }
2069
+
2070
+ // src/builder/shared/validators/field-validators.ts
2071
+ function assertFieldExists(name, model, path) {
2072
+ const field = model.fields.find((f) => f.name === name);
2073
+ if (!isNotNullish(field)) {
2074
+ throw createError(`Field '${name}' does not exist on '${model.name}'`, {
2075
+ field: name,
2076
+ path,
2077
+ modelName: model.name,
2078
+ availableFields: model.fields.map((f) => f.name)
2079
+ });
1964
2080
  }
2081
+ return field;
1965
2082
  }
1966
- function assertExactlyOneOfDynamicOrValue(m) {
1967
- const hasDynamic = typeof m.dynamicName === "string";
1968
- const hasStatic = m.value !== void 0;
1969
- if (hasDynamic === hasStatic) {
1970
- throw new Error(
1971
- `CRITICAL: ParamMap ${m.index} must have exactly one of dynamicName or value`
1972
- );
2083
+ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
2084
+ if (!isNotNullish(fieldType)) return;
2085
+ const ARRAY_OPS = /* @__PURE__ */ new Set([
2086
+ Ops.HAS,
2087
+ Ops.HAS_SOME,
2088
+ Ops.HAS_EVERY,
2089
+ Ops.IS_EMPTY
2090
+ ]);
2091
+ const JSON_OPS = /* @__PURE__ */ new Set([
2092
+ Ops.PATH,
2093
+ Ops.STRING_CONTAINS,
2094
+ Ops.STRING_STARTS_WITH,
2095
+ Ops.STRING_ENDS_WITH
2096
+ ]);
2097
+ const isArrayOp = ARRAY_OPS.has(op);
2098
+ const isFieldArray = isArrayType(fieldType);
2099
+ const arrayOpMismatch = isArrayOp && !isFieldArray;
2100
+ if (arrayOpMismatch) {
2101
+ throw createError(`'${op}' requires array field, got '${fieldType}'`, {
2102
+ operator: op,
2103
+ field: fieldName,
2104
+ path,
2105
+ modelName
2106
+ });
1973
2107
  }
1974
- }
1975
- function normalizeDynamicNameOrThrow(dynamicName, index) {
1976
- const dn = dynamicName.trim();
1977
- if (dn.length === 0) {
1978
- throw new Error(`CRITICAL: dynamicName cannot be empty (index=${index})`);
2108
+ const isJsonOp = JSON_OPS.has(op);
2109
+ const isFieldJson = isJsonType(fieldType);
2110
+ const jsonOpMismatch = isJsonOp && !isFieldJson;
2111
+ if (jsonOpMismatch) {
2112
+ throw createError(`'${op}' requires JSON field, got '${fieldType}'`, {
2113
+ operator: op,
2114
+ field: fieldName,
2115
+ path,
2116
+ modelName
2117
+ });
1979
2118
  }
1980
- return dn;
1981
2119
  }
1982
- function assertUniqueDynamicName(dn, seen) {
1983
- if (seen.has(dn)) {
1984
- throw new Error(`CRITICAL: Duplicate dynamic param name in mappings: ${dn}`);
2120
+
2121
+ // src/builder/where/builder.ts
2122
+ var WhereBuilder = class {
2123
+ build(where, ctx) {
2124
+ if (!isPlainObject(where)) {
2125
+ throw createError("where must be an object", {
2126
+ path: ctx.path,
2127
+ modelName: ctx.model.name
2128
+ });
2129
+ }
2130
+ return buildWhereInternal(where, ctx, this);
1985
2131
  }
1986
- seen.add(dn);
2132
+ };
2133
+ var MAX_QUERY_DEPTH = 50;
2134
+ var EMPTY_JOINS = Object.freeze([]);
2135
+ var whereBuilderInstance = new WhereBuilder();
2136
+ function freezeResult(clause, joins = EMPTY_JOINS) {
2137
+ return Object.freeze({ clause, joins });
1987
2138
  }
1988
- function validateMappingEntry(m, expectedIndex, seenDynamic) {
1989
- assertSequentialIndex(m.index, expectedIndex);
1990
- assertExactlyOneOfDynamicOrValue(m);
1991
- if (typeof m.dynamicName === "string") {
1992
- const dn = normalizeDynamicNameOrThrow(m.dynamicName, m.index);
1993
- assertUniqueDynamicName(dn, seenDynamic);
2139
+ function dedupePreserveOrder(items) {
2140
+ if (items.length <= 1) return Object.freeze([...items]);
2141
+ const seen = /* @__PURE__ */ new Set();
2142
+ const out = [];
2143
+ for (const s of items) {
2144
+ if (!seen.has(s)) {
2145
+ seen.add(s);
2146
+ out.push(s);
2147
+ }
1994
2148
  }
2149
+ return Object.freeze(out);
1995
2150
  }
1996
- function validateMappings(mappings) {
1997
- const seenDynamic = /* @__PURE__ */ new Set();
1998
- for (let i = 0; i < mappings.length; i++) {
1999
- validateMappingEntry(mappings[i], i + 1, seenDynamic);
2000
- }
2151
+ function appendResult(result, clauses, allJoins) {
2152
+ if (isValidWhereClause(result.clause)) clauses.push(result.clause);
2153
+ if (isNonEmptyArray(result.joins)) allJoins.push(...result.joins);
2001
2154
  }
2002
- function validateState(params, mappings, index) {
2003
- assertSameLength(params, mappings);
2004
- assertValidNextIndex(index);
2005
- if (mappings.length === 0) return;
2006
- validateMappings(mappings);
2007
- assertNextIndexMatches(mappings.length, index);
2155
+ function asLogicalOperator(key) {
2156
+ if (key === LogicalOps.AND) return "AND";
2157
+ if (key === LogicalOps.OR) return "OR";
2158
+ if (key === LogicalOps.NOT) return "NOT";
2159
+ return null;
2008
2160
  }
2009
- function normalizeValue(value) {
2010
- if (value instanceof Date) {
2011
- return value.toISOString();
2012
- }
2013
- return value;
2161
+ function nextContext(ctx) {
2162
+ return __spreadProps(__spreadValues({}, ctx), { depth: ctx.depth + 1 });
2014
2163
  }
2015
- function createStoreInternal(startIndex, initialParams = [], initialMappings = []) {
2016
- let index = startIndex;
2017
- const params = [...initialParams];
2018
- const mappings = [...initialMappings];
2019
- const dynamicNameToIndex = /* @__PURE__ */ new Map();
2020
- for (const m of initialMappings) {
2021
- if (typeof m.dynamicName === "string") {
2022
- dynamicNameToIndex.set(m.dynamicName.trim(), m.index);
2023
- }
2024
- }
2025
- function assertCanAdd() {
2026
- if (index > MAX_PARAM_INDEX) {
2027
- throw new Error(
2028
- `CRITICAL: Cannot add param - would overflow MAX_SAFE_INTEGER. Current index: ${index}`
2029
- );
2030
- }
2031
- }
2032
- function normalizeDynamicName(dynamicName) {
2033
- const dn = dynamicName.trim();
2034
- if (dn.length === 0) {
2035
- throw new Error("CRITICAL: dynamicName cannot be empty");
2036
- }
2037
- return dn;
2038
- }
2039
- function format(position) {
2040
- return `$${position}`;
2041
- }
2042
- function addDynamic(dynamicName) {
2043
- const dn = normalizeDynamicName(dynamicName);
2044
- const existing = dynamicNameToIndex.get(dn);
2045
- if (existing !== void 0) {
2046
- return format(existing);
2047
- }
2048
- const position = index;
2049
- dynamicNameToIndex.set(dn, position);
2050
- params.push(void 0);
2051
- mappings.push({ index: position, dynamicName: dn });
2052
- index++;
2053
- return format(position);
2054
- }
2055
- function addStatic(value) {
2056
- const position = index;
2057
- const normalizedValue = normalizeValue(value);
2058
- params.push(normalizedValue);
2059
- mappings.push({ index: position, value: normalizedValue });
2060
- index++;
2061
- return format(position);
2062
- }
2063
- function add(value, dynamicName) {
2064
- assertCanAdd();
2065
- return dynamicName === void 0 ? addStatic(value) : addDynamic(dynamicName);
2164
+ function buildRelationFilter(fieldName, value, ctx, builder) {
2165
+ const ctx2 = nextContext(ctx);
2166
+ if (ctx.isSubquery) {
2167
+ return buildNestedRelation(fieldName, value, ctx2, builder);
2066
2168
  }
2067
- function addAuto(value) {
2068
- if (schemaParser.isDynamicParameter(value)) {
2069
- const dynamicName = schemaParser.extractDynamicName(value);
2070
- return add(void 0, dynamicName);
2169
+ return buildTopLevelRelation(fieldName, value, ctx2, builder);
2170
+ }
2171
+ function buildWhereEntry(key, value, ctx, builder) {
2172
+ const op = asLogicalOperator(key);
2173
+ if (op) return buildLogical(op, value, ctx, builder);
2174
+ if (isRelationField(key, ctx.model)) {
2175
+ if (!isPlainObject(value)) {
2176
+ throw createError(`Relation filter '${key}' must be an object`, {
2177
+ path: [...ctx.path, key],
2178
+ field: key,
2179
+ modelName: ctx.model.name,
2180
+ value
2181
+ });
2071
2182
  }
2072
- return add(value);
2073
- }
2074
- function snapshot() {
2075
- return Object.freeze({
2076
- index,
2077
- params: Object.freeze([...params]),
2078
- mappings: Object.freeze([...mappings])
2079
- });
2183
+ return buildRelationFilter(key, value, ctx, builder);
2080
2184
  }
2081
- return {
2082
- add,
2083
- addAuto,
2084
- snapshot,
2085
- get index() {
2086
- return index;
2087
- }
2088
- };
2185
+ return buildScalarField(key, value, ctx);
2089
2186
  }
2090
- function createParamStore(startIndex = 1) {
2091
- if (!Number.isInteger(startIndex) || startIndex < 1) {
2092
- throw new Error(`Start index must be integer >= 1, got ${startIndex}`);
2093
- }
2094
- if (startIndex > MAX_PARAM_INDEX) {
2095
- throw new Error(
2096
- `Start index too high (${startIndex}), risk of overflow at MAX_SAFE_INTEGER`
2187
+ function buildWhereInternal(where, ctx, builder) {
2188
+ if (ctx.depth > MAX_QUERY_DEPTH) {
2189
+ throw createError(
2190
+ `Query nesting too deep (max ${MAX_QUERY_DEPTH} levels). This usually indicates a circular reference.`,
2191
+ { path: ctx.path, modelName: ctx.model.name }
2097
2192
  );
2098
2193
  }
2099
- return createStoreInternal(startIndex);
2100
- }
2101
- function createParamStoreFrom(existingParams, existingMappings, nextIndex) {
2102
- validateState([...existingParams], [...existingMappings], nextIndex);
2103
- return createStoreInternal(
2104
- nextIndex,
2105
- [...existingParams],
2106
- [...existingMappings]
2107
- );
2108
- }
2109
-
2110
- // src/builder/shared/state.ts
2111
- function toPublicResult(clause, joins, params) {
2112
- const snapshot = params.snapshot();
2113
- return Object.freeze({
2114
- clause: clause || DEFAULT_WHERE_CLAUSE,
2115
- joins: Object.freeze([...joins]),
2116
- params: snapshot.params,
2117
- paramMappings: snapshot.mappings,
2118
- nextParamIndex: snapshot.index
2119
- });
2194
+ if (isEmptyWhere(where)) {
2195
+ return freezeResult(DEFAULT_WHERE_CLAUSE, EMPTY_JOINS);
2196
+ }
2197
+ const allJoins = [];
2198
+ const clauses = [];
2199
+ for (const [key, value] of Object.entries(where)) {
2200
+ if (value === void 0) continue;
2201
+ const result = buildWhereEntry(key, value, ctx, builder);
2202
+ appendResult(result, clauses, allJoins);
2203
+ }
2204
+ const finalClause = clauses.length > 0 ? clauses.join(SQL_SEPARATORS.CONDITION_AND) : DEFAULT_WHERE_CLAUSE;
2205
+ return freezeResult(finalClause, dedupePreserveOrder(allJoins));
2120
2206
  }
2121
-
2122
- // src/builder/where.ts
2123
- function buildWhereClause(where, options) {
2124
- var _a, _b, _c, _d, _e;
2125
- const dialect = options.dialect || getGlobalDialect();
2126
- const params = (_a = options.params) != null ? _a : createParamStore();
2127
- const ctx = {
2128
- alias: options.alias,
2129
- model: options.model,
2130
- schemaModels: (_b = options.schemaModels) != null ? _b : [],
2131
- path: (_c = options.path) != null ? _c : [],
2132
- isSubquery: (_d = options.isSubquery) != null ? _d : false,
2133
- aliasGen: (_e = options.aliasGen) != null ? _e : createAliasGenerator(),
2134
- dialect,
2135
- params,
2136
- depth: 0
2137
- };
2138
- const result = whereBuilderInstance.build(where, ctx);
2139
- const publicResult = toPublicResult(result.clause, result.joins, params);
2140
- if (!options.isSubquery) {
2141
- const nums = [...publicResult.clause.matchAll(/\$(\d+)/g)].map(
2142
- (m) => parseInt(m[1], 10)
2143
- );
2144
- if (nums.length > 0) {
2145
- const min = Math.min(...nums);
2146
- if (min === 1) {
2147
- validateParamConsistency(publicResult.clause, publicResult.params);
2148
- } else {
2149
- validateParamConsistencyFragment(
2150
- publicResult.clause,
2151
- publicResult.params
2152
- );
2207
+ function normalizeLogicalValue(operator, value, ctx) {
2208
+ if (Array.isArray(value)) {
2209
+ const out = [];
2210
+ for (let i = 0; i < value.length; i++) {
2211
+ const v = value[i];
2212
+ if (v === void 0) continue;
2213
+ if (!isPlainObject(v)) {
2214
+ throw createError(`${operator} entries must be objects`, {
2215
+ path: [...ctx.path, operator, String(i)],
2216
+ modelName: ctx.model.name,
2217
+ value: v
2218
+ });
2153
2219
  }
2220
+ out.push(v);
2154
2221
  }
2222
+ return out;
2155
2223
  }
2156
- return publicResult;
2157
- }
2158
- function normalizeIntLike(name, v, opts = {}) {
2159
- var _a, _b;
2160
- if (!isNotNullish(v)) return void 0;
2161
- if (schemaParser.isDynamicParameter(v)) return v;
2162
- if (typeof v !== "number" || !Number.isFinite(v) || !Number.isInteger(v)) {
2163
- throw new Error(`${name} must be an integer`);
2164
- }
2165
- const min = (_a = opts.min) != null ? _a : 0;
2166
- const allowZero = (_b = opts.allowZero) != null ? _b : true;
2167
- if (!allowZero && v === 0) {
2168
- throw new Error(`${name} must be > 0`);
2169
- }
2170
- if (v < min) {
2171
- throw new Error(`${name} must be >= ${min}`);
2172
- }
2173
- if (typeof opts.max === "number" && v > opts.max) {
2174
- throw new Error(`${name} must be <= ${opts.max}`);
2224
+ if (isPlainObject(value)) {
2225
+ return [value];
2175
2226
  }
2176
- return v;
2177
- }
2178
- function scopeName(scope, dynamicName) {
2179
- const s = String(scope).trim();
2180
- const dn = String(dynamicName).trim();
2181
- if (s.length === 0) return dn;
2182
- return `${s}:${dn}`;
2227
+ throw createError(`${operator} must be an object or array of objects`, {
2228
+ path: [...ctx.path, operator],
2229
+ modelName: ctx.model.name,
2230
+ value
2231
+ });
2183
2232
  }
2184
- function addAutoScoped(params, value, scope) {
2185
- if (schemaParser.isDynamicParameter(value)) {
2186
- const dn = schemaParser.extractDynamicName(value);
2187
- return params.add(void 0, scopeName(scope, dn));
2233
+ function collectLogicalParts(operator, conditions, ctx, builder) {
2234
+ const allJoins = [];
2235
+ const clauses = [];
2236
+ for (let i = 0; i < conditions.length; i++) {
2237
+ const result = builder.build(conditions[i], __spreadProps(__spreadValues({}, ctx), {
2238
+ path: [...ctx.path, operator, String(i)],
2239
+ depth: ctx.depth + 1
2240
+ }));
2241
+ if (isNonEmptyArray(result.joins)) allJoins.push(...result.joins);
2242
+ if (result.clause && result.clause !== DEFAULT_WHERE_CLAUSE) {
2243
+ clauses.push(`(${result.clause})`);
2244
+ }
2188
2245
  }
2189
- return params.add(value);
2246
+ return {
2247
+ joins: dedupePreserveOrder(allJoins),
2248
+ clauses: Object.freeze(clauses)
2249
+ };
2190
2250
  }
2191
-
2192
- // src/builder/shared/order-by-utils.ts
2193
- var flipNulls = (v) => {
2194
- const s = String(v).toLowerCase();
2195
- if (s === "first") return "last";
2196
- if (s === "last") return "first";
2197
- return v;
2198
- };
2199
- var flipSortString = (v) => {
2200
- if (typeof v !== "string") return v;
2201
- const s = v.toLowerCase();
2202
- if (s === "asc") return "desc";
2203
- if (s === "desc") return "asc";
2204
- return v;
2205
- };
2206
- var getNextSort = (sortRaw) => {
2207
- if (typeof sortRaw !== "string") return sortRaw;
2208
- const s = sortRaw.toLowerCase();
2209
- if (s === "asc") return "desc";
2210
- if (s === "desc") return "asc";
2211
- return sortRaw;
2212
- };
2213
- var flipObjectSort = (obj) => {
2214
- const sortRaw = obj.sort;
2215
- const out = __spreadProps(__spreadValues({}, obj), { sort: getNextSort(sortRaw) });
2216
- const nullsRaw = obj.nulls;
2217
- if (typeof nullsRaw === "string") {
2218
- out.nulls = flipNulls(nullsRaw);
2219
- }
2220
- return out;
2221
- };
2222
- var flipValue = (v) => {
2223
- if (typeof v === "string") return flipSortString(v);
2224
- if (isPlainObject(v)) return flipObjectSort(v);
2225
- return v;
2226
- };
2227
- var assertSingleFieldObject = (item) => {
2228
- if (!isPlainObject(item)) {
2229
- throw new Error("orderBy array entries must be objects");
2230
- }
2231
- const entries = Object.entries(item);
2232
- if (entries.length !== 1) {
2233
- throw new Error("orderBy array entries must have exactly one field");
2251
+ function buildLogicalClause(operator, clauses) {
2252
+ if (clauses.length === 0) return DEFAULT_WHERE_CLAUSE;
2253
+ if (operator === "NOT") {
2254
+ if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
2255
+ return `${SQL_TEMPLATES.NOT} (${clauses.join(SQL_SEPARATORS.CONDITION_AND)})`;
2234
2256
  }
2235
- return entries[0];
2236
- };
2237
- var flipOrderByArray = (orderBy) => {
2238
- return orderBy.map((item) => {
2239
- const [k, v] = assertSingleFieldObject(item);
2240
- return { [k]: flipValue(v) };
2241
- });
2242
- };
2243
- var flipOrderByObject = (orderBy) => {
2244
- const out = {};
2245
- for (const [k, v] of Object.entries(orderBy)) {
2246
- out[k] = flipValue(v);
2257
+ return clauses.join(` ${operator} `);
2258
+ }
2259
+ function buildLogical(operator, value, ctx, builder) {
2260
+ const conditions = normalizeLogicalValue(operator, value, ctx);
2261
+ if (conditions.length === 0) {
2262
+ const clause2 = operator === "OR" ? "0=1" : DEFAULT_WHERE_CLAUSE;
2263
+ return freezeResult(clause2, EMPTY_JOINS);
2247
2264
  }
2248
- return out;
2249
- };
2250
- function reverseOrderByInput(orderBy) {
2251
- if (!isNotNullish(orderBy)) return orderBy;
2252
- if (Array.isArray(orderBy)) {
2253
- return flipOrderByArray(orderBy);
2265
+ const { joins, clauses } = collectLogicalParts(
2266
+ operator,
2267
+ conditions,
2268
+ ctx,
2269
+ builder
2270
+ );
2271
+ const clause = buildLogicalClause(operator, clauses);
2272
+ return freezeResult(clause, joins);
2273
+ }
2274
+ function buildScalarField(fieldName, value, ctx) {
2275
+ const field = assertFieldExists(fieldName, ctx.model, ctx.path);
2276
+ const expr = col(ctx.alias, fieldName, ctx.model);
2277
+ if (value === null) {
2278
+ return freezeResult(`${expr} ${SQL_TEMPLATES.IS_NULL}`, EMPTY_JOINS);
2254
2279
  }
2255
- if (isPlainObject(orderBy)) {
2256
- return flipOrderByObject(orderBy);
2280
+ if (isPlainObject(value)) {
2281
+ const mode = value.mode;
2282
+ const ops = Object.entries(value).filter(
2283
+ ([k, v]) => k !== "mode" && v !== void 0
2284
+ );
2285
+ if (ops.length === 0) {
2286
+ return freezeResult(DEFAULT_WHERE_CLAUSE, EMPTY_JOINS);
2287
+ }
2288
+ const parts = [];
2289
+ for (const [op, val] of ops) {
2290
+ assertValidOperator(fieldName, op, field.type, ctx.path, ctx.model.name);
2291
+ const clause3 = buildOperator(expr, op, val, ctx, mode, field.type);
2292
+ if (isValidWhereClause(clause3)) parts.push(clause3);
2293
+ }
2294
+ const clause2 = parts.length > 0 ? parts.join(SQL_SEPARATORS.CONDITION_AND) : DEFAULT_WHERE_CLAUSE;
2295
+ return freezeResult(clause2, EMPTY_JOINS);
2257
2296
  }
2258
- throw new Error("orderBy must be an object or array of objects");
2297
+ const clause = buildOperator(
2298
+ expr,
2299
+ Ops.EQUALS,
2300
+ value,
2301
+ ctx,
2302
+ void 0,
2303
+ field.type
2304
+ );
2305
+ return freezeResult(clause || DEFAULT_WHERE_CLAUSE, EMPTY_JOINS);
2259
2306
  }
2260
- var normalizePairs = (pairs, parseValue) => {
2261
- return pairs.map(([field, rawValue]) => {
2262
- const parsed = parseValue(rawValue, field);
2263
- return {
2264
- [field]: parsed.nulls !== void 0 ? { sort: parsed.direction, nulls: parsed.nulls } : parsed.direction
2265
- };
2266
- });
2267
- };
2268
- function normalizeOrderByInput(orderBy, parseValue) {
2269
- if (!isNotNullish(orderBy)) return [];
2270
- if (Array.isArray(orderBy)) {
2271
- const pairs = orderBy.map(assertSingleFieldObject);
2272
- return normalizePairs(pairs, parseValue);
2307
+ function buildOperator(expr, op, val, ctx, mode, fieldType) {
2308
+ if (fieldType && isArrayType(fieldType)) {
2309
+ return buildArrayOperator(expr, op, val, ctx.params, fieldType, ctx.dialect);
2273
2310
  }
2274
- if (isPlainObject(orderBy)) {
2275
- return normalizePairs(Object.entries(orderBy), parseValue);
2311
+ if (fieldType && isJsonType(fieldType)) {
2312
+ const JSON_OPS = /* @__PURE__ */ new Set([
2313
+ Ops.PATH,
2314
+ Ops.STRING_CONTAINS,
2315
+ Ops.STRING_STARTS_WITH,
2316
+ Ops.STRING_ENDS_WITH
2317
+ ]);
2318
+ if (JSON_OPS.has(op)) {
2319
+ return buildJsonOperator(expr, op, val, ctx.params, ctx.dialect);
2320
+ }
2276
2321
  }
2277
- throw new Error("orderBy must be an object or array of objects");
2322
+ return buildScalarOperator(
2323
+ expr,
2324
+ op,
2325
+ val,
2326
+ ctx.params,
2327
+ mode,
2328
+ fieldType,
2329
+ ctx.dialect
2330
+ );
2278
2331
  }
2279
2332
 
2280
- // src/builder/pagination.ts
2281
- var MAX_LIMIT_OFFSET = 2147483647;
2282
- function parseDirectionRaw(raw, errorLabel) {
2283
- const s = String(raw).toLowerCase();
2284
- if (s === "asc" || s === "desc") return s;
2285
- throw new Error(`Invalid ${errorLabel}: ${raw}`);
2286
- }
2287
- function parseNullsRaw(raw, errorLabel) {
2288
- if (!isNotNullish(raw)) return void 0;
2289
- const s = String(raw).toLowerCase();
2290
- if (s === "first" || s === "last") return s;
2291
- throw new Error(`Invalid ${errorLabel}: ${raw}`);
2292
- }
2293
- function requireOrderByObject(v, errorPrefix) {
2294
- if (!isPlainObject(v) || !("sort" in v)) {
2295
- throw new Error(`${errorPrefix} must be 'asc' | 'desc' or { sort, nulls? }`);
2296
- }
2297
- return v;
2333
+ // src/builder/shared/alias-generator.ts
2334
+ function toSafeSqlIdentifier(input) {
2335
+ const raw = String(input);
2336
+ const cleaned = raw.replace(/\W/g, "_");
2337
+ const startsOk = /^[a-zA-Z_]/.test(cleaned);
2338
+ const base = startsOk ? cleaned : `_${cleaned}`;
2339
+ const fallback = base.length > 0 ? base : "_t";
2340
+ const lowered = fallback.toLowerCase();
2341
+ return SQL_KEYWORDS.has(lowered) ? `_${lowered}` : lowered;
2298
2342
  }
2299
- function assertAllowedOrderByKeys(obj, fieldName) {
2300
- const allowed = /* @__PURE__ */ new Set(["sort", "nulls"]);
2301
- for (const k of Object.keys(obj)) {
2302
- if (!allowed.has(k)) {
2303
- throw new Error(
2304
- fieldName ? `Unsupported orderBy key '${k}' for field '${fieldName}'` : `Unsupported orderBy key '${k}'`
2305
- );
2343
+ function createAliasGenerator(maxAliases = 1e4) {
2344
+ let counter = 0;
2345
+ const usedAliases = /* @__PURE__ */ new Set();
2346
+ return {
2347
+ next(baseName) {
2348
+ if (usedAliases.size >= maxAliases) {
2349
+ throw new Error(
2350
+ `Alias generator exceeded maximum of ${maxAliases} aliases. This indicates a query complexity issue or potential infinite loop.`
2351
+ );
2352
+ }
2353
+ const base = toSafeSqlIdentifier(baseName);
2354
+ const suffix = `_${counter}`;
2355
+ const maxLen = 63;
2356
+ const baseMax = Math.max(1, maxLen - suffix.length);
2357
+ const trimmedBase = base.length > baseMax ? base.slice(0, baseMax) : base;
2358
+ const alias = `${trimmedBase}${suffix}`;
2359
+ counter += 1;
2360
+ if (usedAliases.has(alias)) {
2361
+ throw new Error(
2362
+ `CRITICAL: Duplicate alias '${alias}' at counter=${counter}. This indicates a bug in alias generation logic.`
2363
+ );
2364
+ }
2365
+ usedAliases.add(alias);
2366
+ return alias;
2306
2367
  }
2307
- }
2368
+ };
2308
2369
  }
2309
- function parseOrderByValue(v, fieldName) {
2310
- const errorPrefix = fieldName ? `orderBy for '${fieldName}'` : "orderBy value";
2311
- if (typeof v === "string") {
2312
- return { direction: parseDirectionRaw(v, `${errorPrefix} direction`) };
2370
+ var MAX_PARAM_INDEX = Number.MAX_SAFE_INTEGER - 1e3;
2371
+ function assertSameLength(params, mappings) {
2372
+ if (params.length !== mappings.length) {
2373
+ throw new Error(
2374
+ `CRITICAL: State corruption - params=${params.length}, mappings=${mappings.length}`
2375
+ );
2313
2376
  }
2314
- const obj = requireOrderByObject(v, errorPrefix);
2315
- const direction = parseDirectionRaw(obj.sort, `${errorPrefix}.sort`);
2316
- const nulls = parseNullsRaw(obj.nulls, `${errorPrefix}.nulls`);
2317
- assertAllowedOrderByKeys(obj, fieldName);
2318
- return { direction, nulls };
2319
2377
  }
2320
- function normalizeFiniteInteger(name, v) {
2321
- if (typeof v !== "number" || !Number.isFinite(v) || !Number.isInteger(v)) {
2322
- throw new Error(`${name} must be an integer`);
2378
+ function assertValidNextIndex(index) {
2379
+ if (!Number.isInteger(index) || index < 1) {
2380
+ throw new Error(`CRITICAL: Index must be integer >= 1, got ${index}`);
2323
2381
  }
2324
- return v;
2325
2382
  }
2326
- function normalizeNonNegativeInt(name, v) {
2327
- if (schemaParser.isDynamicParameter(v)) return v;
2328
- const n = normalizeFiniteInteger(name, v);
2329
- if (n < 0) {
2330
- throw new Error(`${name} must be >= 0`);
2331
- }
2332
- if (n > MAX_LIMIT_OFFSET) {
2333
- throw new Error(`${name} must be <= ${MAX_LIMIT_OFFSET}`);
2383
+ function assertNextIndexMatches(mappingsLength, nextIndex) {
2384
+ const expected = mappingsLength + 1;
2385
+ if (nextIndex !== expected) {
2386
+ throw new Error(
2387
+ `CRITICAL: Next index mismatch - expected ${expected}, got ${nextIndex}`
2388
+ );
2334
2389
  }
2335
- return n;
2336
- }
2337
- function hasNonNullishProp(v, key) {
2338
- return isPlainObject(v) && key in v && isNotNullish(v[key]);
2339
- }
2340
- function normalizeIntegerOrDynamic(name, v) {
2341
- if (schemaParser.isDynamicParameter(v)) return v;
2342
- return normalizeFiniteInteger(name, v);
2343
2390
  }
2344
- function readSkipTake(relArgs) {
2345
- const hasSkip = hasNonNullishProp(relArgs, "skip");
2346
- const hasTake = hasNonNullishProp(relArgs, "take");
2347
- if (!hasSkip && !hasTake) {
2348
- return {
2349
- hasSkip: false,
2350
- hasTake: false,
2351
- skipVal: void 0,
2352
- takeVal: void 0
2353
- };
2391
+ function assertSequentialIndex(actual, expected) {
2392
+ if (actual !== expected) {
2393
+ throw new Error(
2394
+ `CRITICAL: Indices must be sequential from 1..N. Expected ${expected}, got ${actual}`
2395
+ );
2354
2396
  }
2355
- const obj = relArgs;
2356
- const skipVal = hasSkip ? normalizeNonNegativeInt("skip", obj.skip) : void 0;
2357
- const takeVal = hasTake ? normalizeIntegerOrDynamic("take", obj.take) : void 0;
2358
- return { hasSkip, hasTake, skipVal, takeVal };
2359
2397
  }
2360
- function buildOrderByFragment(entries, alias, dialect, model) {
2361
- if (entries.length === 0) return "";
2362
- const out = [];
2363
- for (const e of entries) {
2364
- const dir = e.direction.toUpperCase();
2365
- const c = col(alias, e.field, model);
2366
- if (dialect === "postgres") {
2367
- const nulls = isNotNullish(e.nulls) ? ` NULLS ${e.nulls.toUpperCase()}` : "";
2368
- out.push(`${c} ${dir}${nulls}`);
2369
- continue;
2370
- }
2371
- if (isNotNullish(e.nulls)) {
2372
- const isNullExpr = `(${c} IS NULL)`;
2373
- const nullRankDir = e.nulls === "first" ? "DESC" : "ASC";
2374
- out.push(`${isNullExpr} ${nullRankDir}`);
2375
- out.push(`${c} ${dir}`);
2376
- continue;
2377
- }
2378
- out.push(`${c} ${dir}`);
2398
+ function assertExactlyOneOfDynamicOrValue(m) {
2399
+ const hasDynamic = typeof m.dynamicName === "string";
2400
+ const hasStatic = m.value !== void 0;
2401
+ if (hasDynamic === hasStatic) {
2402
+ throw new Error(
2403
+ `CRITICAL: ParamMap ${m.index} must have exactly one of dynamicName or value`
2404
+ );
2379
2405
  }
2380
- return out.join(SQL_SEPARATORS.ORDER_BY);
2381
2406
  }
2382
- function defaultNullsFor(dialect, direction) {
2383
- if (dialect === "postgres") {
2384
- return direction === "asc" ? "last" : "first";
2407
+ function normalizeDynamicNameOrThrow(dynamicName, index) {
2408
+ const dn = dynamicName.trim();
2409
+ if (dn.length === 0) {
2410
+ throw new Error(`CRITICAL: dynamicName cannot be empty (index=${index})`);
2385
2411
  }
2386
- return direction === "asc" ? "first" : "last";
2412
+ return dn;
2387
2413
  }
2388
- function ensureCursorFieldsInOrder(orderEntries, cursorEntries) {
2389
- const existing = /* @__PURE__ */ new Map();
2390
- for (const e of orderEntries) existing.set(e.field, e);
2391
- const out = [...orderEntries];
2392
- for (const [field] of cursorEntries) {
2393
- if (!existing.has(field)) {
2394
- out.push({ field, direction: "asc" });
2395
- existing.set(field, out[out.length - 1]);
2396
- }
2414
+ function assertUniqueDynamicName(dn, seen) {
2415
+ if (seen.has(dn)) {
2416
+ throw new Error(`CRITICAL: Duplicate dynamic param name in mappings: ${dn}`);
2397
2417
  }
2398
- return out;
2418
+ seen.add(dn);
2399
2419
  }
2400
- function buildCursorFilterParts(cursor, cursorAlias, params, model) {
2401
- const entries = Object.entries(cursor);
2402
- if (entries.length === 0) {
2403
- throw new Error("cursor must have at least one field");
2404
- }
2405
- const placeholdersByField = /* @__PURE__ */ new Map();
2406
- const parts = [];
2407
- for (const [field, value] of entries) {
2408
- const c = `${cursorAlias}.${quote(field)}`;
2409
- if (value === null) {
2410
- parts.push(`${c} IS NULL`);
2411
- continue;
2412
- }
2413
- const ph = addAutoScoped(params, value, `cursor.filter.${field}`);
2414
- placeholdersByField.set(field, ph);
2415
- parts.push(`${c} = ${ph}`);
2420
+ function validateMappingEntry(m, expectedIndex, seenDynamic) {
2421
+ assertSequentialIndex(m.index, expectedIndex);
2422
+ assertExactlyOneOfDynamicOrValue(m);
2423
+ if (typeof m.dynamicName === "string") {
2424
+ const dn = normalizeDynamicNameOrThrow(m.dynamicName, m.index);
2425
+ assertUniqueDynamicName(dn, seenDynamic);
2416
2426
  }
2417
- return {
2418
- whereSql: parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`,
2419
- placeholdersByField
2420
- };
2421
- }
2422
- function cursorValueExpr(tableName, cursorAlias, cursorWhereSql, field, model) {
2423
- const colName = quote(field);
2424
- return `(SELECT ${cursorAlias}.${colName} ${SQL_TEMPLATES.FROM} ${tableName} ${cursorAlias} ${SQL_TEMPLATES.WHERE} ${cursorWhereSql} ${SQL_TEMPLATES.LIMIT} 1)`;
2425
2427
  }
2426
- function buildCursorRowExistsExpr(tableName, cursorAlias, cursorWhereSql) {
2427
- return `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${tableName} ${cursorAlias} ${SQL_TEMPLATES.WHERE} ${cursorWhereSql} ${SQL_TEMPLATES.LIMIT} 1)`;
2428
+ function validateMappings(mappings) {
2429
+ const seenDynamic = /* @__PURE__ */ new Set();
2430
+ for (let i = 0; i < mappings.length; i++) {
2431
+ validateMappingEntry(mappings[i], i + 1, seenDynamic);
2432
+ }
2428
2433
  }
2429
- function buildCursorEqualityExpr(columnExpr, valueExpr) {
2430
- return `((${valueExpr} IS NULL AND ${columnExpr} IS NULL) OR (${valueExpr} IS NOT NULL AND ${columnExpr} = ${valueExpr}))`;
2434
+ function validateState(params, mappings, index) {
2435
+ assertSameLength(params, mappings);
2436
+ assertValidNextIndex(index);
2437
+ if (mappings.length === 0) return;
2438
+ validateMappings(mappings);
2439
+ assertNextIndexMatches(mappings.length, index);
2431
2440
  }
2432
- function buildCursorInequalityExpr(columnExpr, direction, nulls, valueExpr) {
2433
- const op = direction === "asc" ? ">" : "<";
2434
- if (nulls === "first") {
2435
- return `(CASE WHEN ${valueExpr} IS NULL THEN (${columnExpr} IS NOT NULL) ELSE (${columnExpr} ${op} ${valueExpr}) END)`;
2441
+ function normalizeValue2(value) {
2442
+ if (value instanceof Date) {
2443
+ return value.toISOString();
2436
2444
  }
2437
- return `(CASE WHEN ${valueExpr} IS NULL THEN 0=1 ELSE ((${columnExpr} ${op} ${valueExpr}) OR (${columnExpr} IS NULL)) END)`;
2445
+ return value;
2438
2446
  }
2439
- function buildOuterCursorMatch(cursor, outerAlias, placeholdersByField, params, model) {
2440
- const parts = [];
2441
- for (const [field, value] of Object.entries(cursor)) {
2442
- const c = col(outerAlias, field, model);
2443
- if (value === null) {
2444
- parts.push(`${c} IS NULL`);
2445
- continue;
2447
+ function createStoreInternal(startIndex, initialParams = [], initialMappings = []) {
2448
+ let index = startIndex;
2449
+ const params = [...initialParams];
2450
+ const mappings = [...initialMappings];
2451
+ const dynamicNameToIndex = /* @__PURE__ */ new Map();
2452
+ for (const m of initialMappings) {
2453
+ if (typeof m.dynamicName === "string") {
2454
+ dynamicNameToIndex.set(m.dynamicName.trim(), m.index);
2446
2455
  }
2447
- const existing = placeholdersByField.get(field);
2448
- if (typeof existing === "string" && existing.length > 0) {
2449
- parts.push(`${c} = ${existing}`);
2450
- continue;
2456
+ }
2457
+ function assertCanAdd() {
2458
+ if (index > MAX_PARAM_INDEX) {
2459
+ throw new Error(
2460
+ `CRITICAL: Cannot add param - would overflow MAX_SAFE_INTEGER. Current index: ${index}`
2461
+ );
2451
2462
  }
2452
- const ph = addAutoScoped(params, value, `cursor.outerMatch.${field}`);
2453
- parts.push(`${c} = ${ph}`);
2454
2463
  }
2455
- return parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`;
2456
- }
2457
- function buildOrderEntries(orderBy) {
2458
- const normalized = normalizeOrderByInput(orderBy, parseOrderByValue);
2459
- const entries = [];
2460
- for (const item of normalized) {
2461
- for (const [field, value] of Object.entries(item)) {
2462
- if (typeof value === "string") {
2463
- entries.push({ field, direction: value });
2464
- } else {
2465
- entries.push({
2466
- field,
2467
- direction: value.sort,
2468
- nulls: value.nulls
2469
- });
2470
- }
2464
+ function normalizeDynamicName(dynamicName) {
2465
+ const dn = dynamicName.trim();
2466
+ if (dn.length === 0) {
2467
+ throw new Error("CRITICAL: dynamicName cannot be empty");
2471
2468
  }
2469
+ return dn;
2472
2470
  }
2473
- return entries;
2474
- }
2475
- function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect, model) {
2476
- var _a;
2477
- const d = dialect != null ? dialect : getGlobalDialect();
2478
- const cursorEntries = Object.entries(cursor);
2479
- if (cursorEntries.length === 0) {
2480
- throw new Error("cursor must have at least one field");
2471
+ function format(position) {
2472
+ return `$${position}`;
2473
+ }
2474
+ function addDynamic(dynamicName) {
2475
+ const dn = normalizeDynamicName(dynamicName);
2476
+ const existing = dynamicNameToIndex.get(dn);
2477
+ if (existing !== void 0) {
2478
+ return format(existing);
2479
+ }
2480
+ const position = index;
2481
+ dynamicNameToIndex.set(dn, position);
2482
+ params.push(void 0);
2483
+ mappings.push({ index: position, dynamicName: dn });
2484
+ index++;
2485
+ return format(position);
2486
+ }
2487
+ function addStatic(value) {
2488
+ const position = index;
2489
+ const normalizedValue = normalizeValue2(value);
2490
+ params.push(normalizedValue);
2491
+ mappings.push({ index: position, value: normalizedValue });
2492
+ index++;
2493
+ return format(position);
2481
2494
  }
2482
- const cursorAlias = "__tp_cursor_src";
2483
- const { whereSql: cursorWhereSql, placeholdersByField } = buildCursorFilterParts(cursor, cursorAlias, params);
2484
- let orderEntries = buildOrderEntries(orderBy);
2485
- if (orderEntries.length === 0) {
2486
- orderEntries = cursorEntries.map(([field]) => ({
2487
- field,
2488
- direction: "asc"
2489
- }));
2490
- } else {
2491
- orderEntries = ensureCursorFieldsInOrder(orderEntries, cursorEntries);
2495
+ function add(value, dynamicName) {
2496
+ assertCanAdd();
2497
+ return dynamicName === void 0 ? addStatic(value) : addDynamic(dynamicName);
2492
2498
  }
2493
- const existsExpr = buildCursorRowExistsExpr(
2494
- tableName,
2495
- cursorAlias,
2496
- cursorWhereSql
2497
- );
2498
- const outerCursorMatch = buildOuterCursorMatch(
2499
- cursor,
2500
- alias,
2501
- placeholdersByField,
2502
- params,
2503
- model
2504
- );
2505
- const orClauses = [];
2506
- for (let level = 0; level < orderEntries.length; level++) {
2507
- const andParts = [];
2508
- for (let i = 0; i < level; i++) {
2509
- const e2 = orderEntries[i];
2510
- const c2 = col(alias, e2.field, model);
2511
- const v2 = cursorValueExpr(
2512
- tableName,
2513
- cursorAlias,
2514
- cursorWhereSql,
2515
- e2.field);
2516
- andParts.push(buildCursorEqualityExpr(c2, v2));
2499
+ function addAuto(value) {
2500
+ if (schemaParser.isDynamicParameter(value)) {
2501
+ const dynamicName = schemaParser.extractDynamicName(value);
2502
+ return add(void 0, dynamicName);
2517
2503
  }
2518
- const e = orderEntries[level];
2519
- const c = col(alias, e.field, model);
2520
- const v = cursorValueExpr(
2521
- tableName,
2522
- cursorAlias,
2523
- cursorWhereSql,
2524
- e.field);
2525
- const nulls = (_a = e.nulls) != null ? _a : defaultNullsFor(d, e.direction);
2526
- andParts.push(buildCursorInequalityExpr(c, e.direction, nulls, v));
2527
- orClauses.push(`(${andParts.join(SQL_SEPARATORS.CONDITION_AND)})`);
2504
+ return add(value);
2528
2505
  }
2529
- const exclusive = orClauses.join(SQL_SEPARATORS.CONDITION_OR);
2530
- return `(${existsExpr} ${SQL_SEPARATORS.CONDITION_AND} ((${exclusive})${SQL_SEPARATORS.CONDITION_OR}(${outerCursorMatch})))`;
2531
- }
2532
- function buildOrderBy(orderBy, alias, dialect, model) {
2533
- const entries = buildOrderEntries(orderBy);
2534
- if (entries.length === 0) return "";
2535
- const d = dialect != null ? dialect : getGlobalDialect();
2536
- return buildOrderByFragment(entries, alias, d, model);
2506
+ function snapshot() {
2507
+ return Object.freeze({
2508
+ index,
2509
+ params: Object.freeze([...params]),
2510
+ mappings: Object.freeze([...mappings])
2511
+ });
2512
+ }
2513
+ return {
2514
+ add,
2515
+ addAuto,
2516
+ snapshot,
2517
+ get index() {
2518
+ return index;
2519
+ }
2520
+ };
2537
2521
  }
2538
- function buildOrderByClause(args, alias, dialect, model) {
2539
- if (!isNotNullish(args.orderBy)) return "";
2540
- const result = buildOrderBy(args.orderBy, alias, dialect, model);
2541
- if (!isNonEmptyString(result)) {
2522
+ function createParamStore(startIndex = 1) {
2523
+ if (!Number.isInteger(startIndex) || startIndex < 1) {
2524
+ throw new Error(`Start index must be integer >= 1, got ${startIndex}`);
2525
+ }
2526
+ if (startIndex > MAX_PARAM_INDEX) {
2542
2527
  throw new Error(
2543
- "buildOrderByClause: orderBy specified but produced empty result"
2528
+ `Start index too high (${startIndex}), risk of overflow at MAX_SAFE_INTEGER`
2544
2529
  );
2545
2530
  }
2546
- return result;
2531
+ return createStoreInternal(startIndex);
2547
2532
  }
2548
- function normalizeTakeLike(v) {
2549
- const n = normalizeIntLike("take", v, {
2550
- min: Number.MIN_SAFE_INTEGER,
2551
- max: MAX_LIMIT_OFFSET,
2552
- allowZero: true
2553
- });
2554
- if (typeof n === "number") {
2555
- if (n === 0) return 0;
2556
- }
2557
- return n;
2533
+ function createParamStoreFrom(existingParams, existingMappings, nextIndex) {
2534
+ validateState([...existingParams], [...existingMappings], nextIndex);
2535
+ return createStoreInternal(
2536
+ nextIndex,
2537
+ [...existingParams],
2538
+ [...existingMappings]
2539
+ );
2558
2540
  }
2559
- function normalizeSkipLike(v) {
2560
- return normalizeIntLike("skip", v, {
2561
- min: 0,
2562
- max: MAX_LIMIT_OFFSET,
2563
- allowZero: true
2541
+
2542
+ // src/builder/shared/state.ts
2543
+ function toPublicResult(clause, joins, params) {
2544
+ const snapshot = params.snapshot();
2545
+ return Object.freeze({
2546
+ clause: clause || DEFAULT_WHERE_CLAUSE,
2547
+ joins: Object.freeze([...joins]),
2548
+ params: snapshot.params,
2549
+ paramMappings: snapshot.mappings,
2550
+ nextParamIndex: snapshot.index
2564
2551
  });
2565
2552
  }
2566
- function getPaginationParams(method, args) {
2567
- if (method === "findMany") {
2568
- return {
2569
- take: normalizeTakeLike(args.take),
2570
- skip: normalizeSkipLike(args.skip),
2571
- cursor: args.cursor
2572
- };
2573
- }
2574
- if (method === "findFirst") {
2575
- const skip = normalizeSkipLike(args.skip);
2576
- return { take: 1, skip: skip != null ? skip : 0 };
2577
- }
2578
- if (method === "findUnique") {
2579
- return { take: 1, skip: 0 };
2553
+
2554
+ // src/builder/where.ts
2555
+ function buildWhereClause(where, options) {
2556
+ var _a, _b, _c, _d, _e;
2557
+ const dialect = options.dialect || getGlobalDialect();
2558
+ const params = (_a = options.params) != null ? _a : createParamStore();
2559
+ const ctx = {
2560
+ alias: options.alias,
2561
+ model: options.model,
2562
+ schemaModels: (_b = options.schemaModels) != null ? _b : [],
2563
+ path: (_c = options.path) != null ? _c : [],
2564
+ isSubquery: (_d = options.isSubquery) != null ? _d : false,
2565
+ aliasGen: (_e = options.aliasGen) != null ? _e : createAliasGenerator(),
2566
+ dialect,
2567
+ params,
2568
+ depth: 0
2569
+ };
2570
+ const result = whereBuilderInstance.build(where, ctx);
2571
+ const publicResult = toPublicResult(result.clause, result.joins, params);
2572
+ if (!options.isSubquery) {
2573
+ const nums = [...publicResult.clause.matchAll(/\$(\d+)/g)].map(
2574
+ (m) => parseInt(m[1], 10)
2575
+ );
2576
+ if (nums.length > 0) {
2577
+ const min = Math.min(...nums);
2578
+ if (min === 1) {
2579
+ validateParamConsistency(publicResult.clause, publicResult.params);
2580
+ } else {
2581
+ validateParamConsistencyFragment(
2582
+ publicResult.clause,
2583
+ publicResult.params
2584
+ );
2585
+ }
2586
+ }
2580
2587
  }
2581
- return {};
2588
+ return publicResult;
2582
2589
  }
2583
2590
 
2584
2591
  // src/builder/select/fields.ts
@@ -4512,35 +4519,301 @@ function transformQueryResults(method, results) {
4512
4519
  const transformer = RESULT_TRANSFORMERS[method];
4513
4520
  return transformer ? transformer(results) : results;
4514
4521
  }
4515
- var ACCELERATED_METHODS = /* @__PURE__ */ new Set([
4516
- "findMany",
4517
- "findFirst",
4518
- "findUnique",
4519
- "count",
4520
- "aggregate",
4521
- "groupBy"
4522
- ]);
4522
+
4523
+ // src/utils/s3-fifo.ts
4524
+ function withDispose(it) {
4525
+ const anyIt = it;
4526
+ if (anyIt[Symbol.dispose] === void 0) {
4527
+ anyIt[Symbol.dispose] = () => {
4528
+ };
4529
+ }
4530
+ return it;
4531
+ }
4532
+ var BoundedCache = class {
4533
+ constructor(maxSize) {
4534
+ this.map = /* @__PURE__ */ new Map();
4535
+ this.ghost = /* @__PURE__ */ new Set();
4536
+ this.smallHead = null;
4537
+ this.smallTail = null;
4538
+ this.smallSize = 0;
4539
+ this.mainHead = null;
4540
+ this.mainTail = null;
4541
+ this.mainSize = 0;
4542
+ this.maxSize = maxSize;
4543
+ this.smallLimit = Math.max(1, Math.floor(maxSize * 0.1));
4544
+ this.mainLimit = maxSize - this.smallLimit;
4545
+ this.ghostLimit = this.mainLimit;
4546
+ }
4547
+ get size() {
4548
+ return this.map.size;
4549
+ }
4550
+ get(key) {
4551
+ const node = this.map.get(key);
4552
+ if (!node) return void 0;
4553
+ node.freq = Math.min(node.freq + 1, 3);
4554
+ return node.value;
4555
+ }
4556
+ set(key, value) {
4557
+ const existing = this.map.get(key);
4558
+ if (existing) {
4559
+ existing.value = value;
4560
+ return this;
4561
+ }
4562
+ if (this.ghost.has(key)) {
4563
+ this.ghost.delete(key);
4564
+ const node2 = this.createNode(key, value, "main");
4565
+ this.map.set(key, node2);
4566
+ this.pushMain(node2);
4567
+ if (this.mainSize > this.mainLimit) this.evictMain();
4568
+ return this;
4569
+ }
4570
+ const node = this.createNode(key, value, "small");
4571
+ this.map.set(key, node);
4572
+ this.pushSmall(node);
4573
+ if (this.size > this.maxSize) {
4574
+ if (this.smallSize > this.smallLimit) this.evictSmall();
4575
+ else this.evictMain();
4576
+ }
4577
+ return this;
4578
+ }
4579
+ has(key) {
4580
+ return this.map.has(key);
4581
+ }
4582
+ delete(key) {
4583
+ const node = this.map.get(key);
4584
+ if (!node) return false;
4585
+ this.map.delete(key);
4586
+ this.removeNode(node);
4587
+ return true;
4588
+ }
4589
+ clear() {
4590
+ this.map.clear();
4591
+ this.ghost.clear();
4592
+ this.smallHead = this.smallTail = null;
4593
+ this.mainHead = this.mainTail = null;
4594
+ this.smallSize = this.mainSize = 0;
4595
+ }
4596
+ keys() {
4597
+ return withDispose(
4598
+ (function* (self) {
4599
+ for (const key of self.map.keys()) yield key;
4600
+ })(this)
4601
+ );
4602
+ }
4603
+ values() {
4604
+ return withDispose(
4605
+ (function* (self) {
4606
+ for (const node of self.map.values()) yield node.value;
4607
+ })(this)
4608
+ );
4609
+ }
4610
+ entries() {
4611
+ return withDispose(
4612
+ (function* (self) {
4613
+ for (const [key, node] of self.map.entries())
4614
+ yield [key, node.value];
4615
+ })(this)
4616
+ );
4617
+ }
4618
+ forEach(callbackfn, thisArg) {
4619
+ for (const [key, node] of this.map.entries()) {
4620
+ callbackfn.call(thisArg, node.value, key, this);
4621
+ }
4622
+ }
4623
+ [Symbol.iterator]() {
4624
+ return this.entries();
4625
+ }
4626
+ get [Symbol.toStringTag]() {
4627
+ return "BoundedCache";
4628
+ }
4629
+ createNode(key, value, queue) {
4630
+ return { key, value, freq: 0, queue, prev: null, next: null };
4631
+ }
4632
+ pushSmall(node) {
4633
+ node.next = this.smallHead;
4634
+ node.prev = null;
4635
+ if (this.smallHead) this.smallHead.prev = node;
4636
+ else this.smallTail = node;
4637
+ this.smallHead = node;
4638
+ this.smallSize++;
4639
+ }
4640
+ pushMain(node) {
4641
+ node.next = this.mainHead;
4642
+ node.prev = null;
4643
+ if (this.mainHead) this.mainHead.prev = node;
4644
+ else this.mainTail = node;
4645
+ this.mainHead = node;
4646
+ this.mainSize++;
4647
+ }
4648
+ popSmall() {
4649
+ if (!this.smallTail) return null;
4650
+ const node = this.smallTail;
4651
+ this.smallTail = node.prev;
4652
+ if (this.smallTail) this.smallTail.next = null;
4653
+ else this.smallHead = null;
4654
+ node.prev = null;
4655
+ node.next = null;
4656
+ this.smallSize--;
4657
+ return node;
4658
+ }
4659
+ popMain() {
4660
+ if (!this.mainTail) return null;
4661
+ const node = this.mainTail;
4662
+ this.mainTail = node.prev;
4663
+ if (this.mainTail) this.mainTail.next = null;
4664
+ else this.mainHead = null;
4665
+ node.prev = null;
4666
+ node.next = null;
4667
+ this.mainSize--;
4668
+ return node;
4669
+ }
4670
+ removeNode(node) {
4671
+ this.unlinkNode(node);
4672
+ if (node.queue === "small") {
4673
+ if (node === this.smallHead) this.smallHead = node.next;
4674
+ if (node === this.smallTail) this.smallTail = node.prev;
4675
+ this.smallSize--;
4676
+ } else {
4677
+ if (node === this.mainHead) this.mainHead = node.next;
4678
+ if (node === this.mainTail) this.mainTail = node.prev;
4679
+ this.mainSize--;
4680
+ }
4681
+ node.prev = null;
4682
+ node.next = null;
4683
+ }
4684
+ unlinkNode(node) {
4685
+ if (node.prev) node.prev.next = node.next;
4686
+ if (node.next) node.next.prev = node.prev;
4687
+ }
4688
+ shouldPromoteFromSmall(node) {
4689
+ return node.freq > 1;
4690
+ }
4691
+ shouldRetryInMain(node) {
4692
+ return node.freq >= 1;
4693
+ }
4694
+ promoteToMain(node) {
4695
+ node.queue = "main";
4696
+ this.pushMain(node);
4697
+ }
4698
+ addToGhost(key) {
4699
+ this.ghost.add(key);
4700
+ if (this.ghost.size <= this.ghostLimit) return;
4701
+ const firstGhost = this.ghost.values().next().value;
4702
+ if (firstGhost !== void 0) this.ghost.delete(firstGhost);
4703
+ }
4704
+ evictFromCache(node) {
4705
+ this.map.delete(node.key);
4706
+ }
4707
+ evictSmall() {
4708
+ while (this.smallSize > 0) {
4709
+ const node = this.popSmall();
4710
+ if (!node) return;
4711
+ if (this.shouldPromoteFromSmall(node)) {
4712
+ this.promoteToMain(node);
4713
+ if (this.mainSize > this.mainLimit) {
4714
+ this.evictMain();
4715
+ return;
4716
+ }
4717
+ continue;
4718
+ }
4719
+ this.evictFromCache(node);
4720
+ this.addToGhost(node.key);
4721
+ return;
4722
+ }
4723
+ }
4724
+ evictMain() {
4725
+ while (this.mainSize > 0) {
4726
+ const node = this.popMain();
4727
+ if (!node) return;
4728
+ if (this.shouldRetryInMain(node)) {
4729
+ node.freq--;
4730
+ this.pushMain(node);
4731
+ continue;
4732
+ }
4733
+ this.evictFromCache(node);
4734
+ return;
4735
+ }
4736
+ }
4737
+ };
4738
+ function createBoundedCache(maxSize) {
4739
+ return new BoundedCache(maxSize);
4740
+ }
4741
+
4742
+ // src/query-cache.ts
4743
+ var queryCache = createBoundedCache(1e3);
4523
4744
  function makeAlias(name) {
4524
4745
  const base = name.toLowerCase().replace(/[^a-z0-9_]/g, "_").slice(0, 50);
4525
4746
  const safe = /^[a-z_]/.test(base) ? base : `_${base}`;
4526
4747
  return SQL_RESERVED_WORDS.has(safe) ? `${safe}_t` : safe;
4527
4748
  }
4528
4749
  function toSqliteParams(sql, params) {
4529
- const positions = [];
4530
- const converted = sql.replace(/\$(\d+)/g, (_, num) => {
4531
- positions.push(parseInt(num, 10));
4532
- return "?";
4533
- });
4534
- const reordered = positions.map((pos) => {
4535
- const idx = pos - 1;
4536
- if (idx < 0 || idx >= params.length) {
4537
- throw new Error(`Param $${pos} out of bounds (have ${params.length})`);
4750
+ const reorderedParams = [];
4751
+ let lastIndex = 0;
4752
+ const parts = [];
4753
+ for (let i = 0; i < sql.length; i++) {
4754
+ if (sql[i] === "$" && i + 1 < sql.length) {
4755
+ let num = 0;
4756
+ let j = i + 1;
4757
+ while (j < sql.length && sql[j] >= "0" && sql[j] <= "9") {
4758
+ num = num * 10 + (sql.charCodeAt(j) - 48);
4759
+ j++;
4760
+ }
4761
+ if (j > i + 1) {
4762
+ parts.push(sql.substring(lastIndex, i));
4763
+ parts.push("?");
4764
+ reorderedParams.push(params[num - 1]);
4765
+ i = j - 1;
4766
+ lastIndex = j;
4767
+ }
4538
4768
  }
4539
- return params[idx];
4540
- });
4541
- return { sql: converted, params: reordered };
4769
+ }
4770
+ parts.push(sql.substring(lastIndex));
4771
+ return { sql: parts.join(""), params: reorderedParams };
4772
+ }
4773
+ function canonicalizeQuery(modelName, method, args) {
4774
+ function normalize(obj, path = []) {
4775
+ if (obj === null || typeof obj !== "object") return obj;
4776
+ if (Array.isArray(obj)) return obj.map((v, i) => normalize(v, [...path, String(i)]));
4777
+ const sorted = {};
4778
+ const keys = Object.keys(obj).sort();
4779
+ for (const key of keys) {
4780
+ const fullPath = [...path, key].join(".");
4781
+ const value = obj[key];
4782
+ if (schemaParser.isDynamicParameter(value)) {
4783
+ sorted[key] = `__DYN:${fullPath}__`;
4784
+ } else {
4785
+ sorted[key] = normalize(value, [...path, key]);
4786
+ }
4787
+ }
4788
+ return sorted;
4789
+ }
4790
+ const canonical = normalize(args);
4791
+ return `${modelName}:${method}:${JSON.stringify(canonical)}`;
4542
4792
  }
4543
- function buildSQL(model, models, method, args, dialect) {
4793
+ function extractValueFromPath(obj, path) {
4794
+ const parts = path.split(".");
4795
+ let current = obj;
4796
+ for (const part of parts) {
4797
+ if (current === void 0 || current === null) return void 0;
4798
+ current = current[part];
4799
+ }
4800
+ return current;
4801
+ }
4802
+ function rebuildParams(mappings, args) {
4803
+ const params = new Array(mappings.length);
4804
+ for (let i = 0; i < mappings.length; i++) {
4805
+ const mapping = mappings[i];
4806
+ if (mapping.dynamicName !== void 0) {
4807
+ const parts = mapping.dynamicName.split(":");
4808
+ const path = parts.length === 2 ? parts[1] : mapping.dynamicName;
4809
+ params[i] = extractValueFromPath(args, path);
4810
+ } else {
4811
+ params[i] = mapping.value;
4812
+ }
4813
+ }
4814
+ return params;
4815
+ }
4816
+ function buildSQLFull(model, models, method, args, dialect) {
4544
4817
  const tableName = buildTableReference(
4545
4818
  SQL_TEMPLATES.PUBLIC_SCHEMA,
4546
4819
  model.tableName,
@@ -4562,32 +4835,13 @@ function buildSQL(model, models, method, args, dialect) {
4562
4835
  let result;
4563
4836
  switch (method) {
4564
4837
  case "aggregate":
4565
- result = buildAggregateSql(
4566
- withMethod,
4567
- whereResult,
4568
- tableName,
4569
- alias,
4570
- model
4571
- );
4838
+ result = buildAggregateSql(withMethod, whereResult, tableName, alias, model);
4572
4839
  break;
4573
4840
  case "groupBy":
4574
- result = buildGroupBySql(
4575
- withMethod,
4576
- whereResult,
4577
- tableName,
4578
- alias,
4579
- model,
4580
- dialect
4581
- );
4841
+ result = buildGroupBySql(withMethod, whereResult, tableName, alias, model, dialect);
4582
4842
  break;
4583
4843
  case "count":
4584
- result = buildCountSql(
4585
- whereResult,
4586
- tableName,
4587
- alias,
4588
- args.skip,
4589
- dialect
4590
- );
4844
+ result = buildCountSql(whereResult, tableName, alias, args.skip, dialect);
4591
4845
  break;
4592
4846
  default:
4593
4847
  result = buildSelectSql({
@@ -4600,7 +4854,35 @@ function buildSQL(model, models, method, args, dialect) {
4600
4854
  dialect
4601
4855
  });
4602
4856
  }
4603
- return dialect === "sqlite" ? toSqliteParams(result.sql, result.params) : { sql: result.sql, params: [...result.params] };
4857
+ const finalResult = dialect === "sqlite" ? toSqliteParams(result.sql, result.params) : { sql: result.sql, params: [...result.params] };
4858
+ return __spreadProps(__spreadValues({}, finalResult), {
4859
+ paramMappings: result.paramMappings
4860
+ });
4861
+ }
4862
+ function buildSQLWithCache(model, models, method, args, dialect) {
4863
+ const cacheKey = canonicalizeQuery(model.name, method, args);
4864
+ const cached = queryCache.get(cacheKey);
4865
+ if (cached) {
4866
+ const params = rebuildParams(cached.paramMappings, args);
4867
+ return { sql: cached.sql, params };
4868
+ }
4869
+ const result = buildSQLFull(model, models, method, args, dialect);
4870
+ queryCache.set(cacheKey, {
4871
+ sql: result.sql,
4872
+ paramMappings: result.paramMappings
4873
+ });
4874
+ return { sql: result.sql, params: result.params };
4875
+ }
4876
+ var ACCELERATED_METHODS = /* @__PURE__ */ new Set([
4877
+ "findMany",
4878
+ "findFirst",
4879
+ "findUnique",
4880
+ "count",
4881
+ "aggregate",
4882
+ "groupBy"
4883
+ ]);
4884
+ function buildSQL(model, models, method, args, dialect) {
4885
+ return buildSQLWithCache(model, models, method, args, dialect);
4604
4886
  }
4605
4887
  function executePostgres(client, sql, params) {
4606
4888
  return __async(this, null, function* () {