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