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.
@@ -56,7 +56,7 @@ var require_package = __commonJS({
56
56
  "package.json"(exports$1, module) {
57
57
  module.exports = {
58
58
  name: "prisma-sql",
59
- version: "1.37.0",
59
+ version: "1.39.0",
60
60
  description: "Convert Prisma queries to optimized SQL with type safety. 2-7x faster than Prisma Client.",
61
61
  main: "dist/index.cjs",
62
62
  module: "dist/index.js",
@@ -129,7 +129,6 @@ var require_package = __commonJS({
129
129
  "@faker-js/faker": "^10.2.0",
130
130
  "@prisma/adapter-better-sqlite3": "^7.2.0",
131
131
  "@prisma/adapter-pg": "^7.2.0",
132
- "@prisma/client": "7.2.0",
133
132
  "@types/better-sqlite3": "^7.6.13",
134
133
  "@types/node": "^25.0.10",
135
134
  "@vitest/coverage-v8": "4.0.18",
@@ -137,11 +136,12 @@ var require_package = __commonJS({
137
136
  "drizzle-kit": "^0.31.8",
138
137
  "drizzle-orm": "^0.45.1",
139
138
  postgres: "^3.4.8",
140
- prisma: "7.2.0",
141
139
  tsup: "^8.5.1",
142
140
  tsx: "^4.21.0",
143
141
  typescript: "^5.9.3",
144
- vitest: "^4.0.18"
142
+ vitest: "^4.0.18",
143
+ "@prisma/client": "7.2.0",
144
+ prisma: "7.2.0"
145
145
  },
146
146
  engines: {
147
147
  node: ">=16.0.0"
@@ -150,6 +150,148 @@ var require_package = __commonJS({
150
150
  }
151
151
  });
152
152
 
153
+ // src/builder/shared/constants.ts
154
+ var SQL_SEPARATORS = Object.freeze({
155
+ FIELD_LIST: ", ",
156
+ CONDITION_AND: " AND ",
157
+ CONDITION_OR: " OR ",
158
+ ORDER_BY: ", "
159
+ });
160
+ var SQL_RESERVED_WORDS = /* @__PURE__ */ new Set([
161
+ "select",
162
+ "from",
163
+ "where",
164
+ "and",
165
+ "or",
166
+ "not",
167
+ "in",
168
+ "like",
169
+ "between",
170
+ "order",
171
+ "by",
172
+ "group",
173
+ "having",
174
+ "limit",
175
+ "offset",
176
+ "join",
177
+ "inner",
178
+ "left",
179
+ "right",
180
+ "outer",
181
+ "on",
182
+ "as",
183
+ "table",
184
+ "column",
185
+ "index",
186
+ "user",
187
+ "users",
188
+ "values",
189
+ "update",
190
+ "insert",
191
+ "delete",
192
+ "create",
193
+ "drop",
194
+ "alter",
195
+ "truncate",
196
+ "grant",
197
+ "revoke",
198
+ "exec",
199
+ "execute",
200
+ "union",
201
+ "intersect",
202
+ "except",
203
+ "case",
204
+ "when",
205
+ "then",
206
+ "else",
207
+ "end",
208
+ "null",
209
+ "true",
210
+ "false",
211
+ "is",
212
+ "exists",
213
+ "all",
214
+ "any",
215
+ "some"
216
+ ]);
217
+ var SQL_KEYWORDS = SQL_RESERVED_WORDS;
218
+ var DEFAULT_WHERE_CLAUSE = "1=1";
219
+ var SPECIAL_FIELDS = Object.freeze({
220
+ ID: "id"
221
+ });
222
+ var SQL_TEMPLATES = Object.freeze({
223
+ PUBLIC_SCHEMA: "public",
224
+ WHERE: "WHERE",
225
+ SELECT: "SELECT",
226
+ FROM: "FROM",
227
+ ORDER_BY: "ORDER BY",
228
+ GROUP_BY: "GROUP BY",
229
+ HAVING: "HAVING",
230
+ LIMIT: "LIMIT",
231
+ OFFSET: "OFFSET",
232
+ COUNT_ALL: "COUNT(*)",
233
+ AS: "AS",
234
+ DISTINCT_ON: "DISTINCT ON",
235
+ IS_NULL: "IS NULL",
236
+ IS_NOT_NULL: "IS NOT NULL",
237
+ LIKE: "LIKE",
238
+ AND: "AND",
239
+ OR: "OR",
240
+ NOT: "NOT"
241
+ });
242
+ var SCHEMA_PREFIXES = Object.freeze({
243
+ INTERNAL: "@",
244
+ COMMENT: "//"
245
+ });
246
+ var Ops = Object.freeze({
247
+ EQUALS: "equals",
248
+ NOT: "not",
249
+ GT: "gt",
250
+ GTE: "gte",
251
+ LT: "lt",
252
+ LTE: "lte",
253
+ IN: "in",
254
+ NOT_IN: "notIn",
255
+ CONTAINS: "contains",
256
+ STARTS_WITH: "startsWith",
257
+ ENDS_WITH: "endsWith",
258
+ HAS: "has",
259
+ HAS_SOME: "hasSome",
260
+ HAS_EVERY: "hasEvery",
261
+ IS_EMPTY: "isEmpty",
262
+ PATH: "path",
263
+ STRING_CONTAINS: "string_contains",
264
+ STRING_STARTS_WITH: "string_starts_with",
265
+ STRING_ENDS_WITH: "string_ends_with"
266
+ });
267
+ var LogicalOps = Object.freeze({
268
+ AND: "AND",
269
+ OR: "OR",
270
+ NOT: "NOT"
271
+ });
272
+ var RelationFilters = Object.freeze({
273
+ SOME: "some",
274
+ EVERY: "every",
275
+ NONE: "none"
276
+ });
277
+ var Modes = Object.freeze({
278
+ INSENSITIVE: "insensitive",
279
+ DEFAULT: "default"
280
+ });
281
+ var Wildcards = Object.freeze({
282
+ [Ops.CONTAINS]: (v) => `%${v}%`,
283
+ [Ops.STARTS_WITH]: (v) => `${v}%`,
284
+ [Ops.ENDS_WITH]: (v) => `%${v}`
285
+ });
286
+ var REGEX_CACHE = {
287
+ VALID_IDENTIFIER: /^[a-z_][a-z0-9_]*$/
288
+ };
289
+ var LIMITS = Object.freeze({
290
+ MAX_QUERY_DEPTH: 50,
291
+ MAX_ARRAY_SIZE: 1e4,
292
+ MAX_STRING_LENGTH: 1e4
293
+ });
294
+
153
295
  // src/sql-builder-dialect.ts
154
296
  var globalDialect = "postgres";
155
297
  function setGlobalDialect(dialect) {
@@ -304,7 +446,7 @@ function getArrayType(prismaType, dialect) {
304
446
  case "DateTime":
305
447
  return "timestamptz[]";
306
448
  default:
307
- return "text[]";
449
+ return `"${baseType}"[]`;
308
450
  }
309
451
  }
310
452
  function jsonAgg(content, dialect) {
@@ -331,148 +473,6 @@ function prepareArrayParam(value, dialect) {
331
473
  return JSON.stringify(value);
332
474
  }
333
475
 
334
- // src/builder/shared/constants.ts
335
- var SQL_SEPARATORS = Object.freeze({
336
- FIELD_LIST: ", ",
337
- CONDITION_AND: " AND ",
338
- CONDITION_OR: " OR ",
339
- ORDER_BY: ", "
340
- });
341
- var SQL_RESERVED_WORDS = /* @__PURE__ */ new Set([
342
- "select",
343
- "from",
344
- "where",
345
- "and",
346
- "or",
347
- "not",
348
- "in",
349
- "like",
350
- "between",
351
- "order",
352
- "by",
353
- "group",
354
- "having",
355
- "limit",
356
- "offset",
357
- "join",
358
- "inner",
359
- "left",
360
- "right",
361
- "outer",
362
- "on",
363
- "as",
364
- "table",
365
- "column",
366
- "index",
367
- "user",
368
- "users",
369
- "values",
370
- "update",
371
- "insert",
372
- "delete",
373
- "create",
374
- "drop",
375
- "alter",
376
- "truncate",
377
- "grant",
378
- "revoke",
379
- "exec",
380
- "execute",
381
- "union",
382
- "intersect",
383
- "except",
384
- "case",
385
- "when",
386
- "then",
387
- "else",
388
- "end",
389
- "null",
390
- "true",
391
- "false",
392
- "is",
393
- "exists",
394
- "all",
395
- "any",
396
- "some"
397
- ]);
398
- var SQL_KEYWORDS = SQL_RESERVED_WORDS;
399
- var DEFAULT_WHERE_CLAUSE = "1=1";
400
- var SPECIAL_FIELDS = Object.freeze({
401
- ID: "id"
402
- });
403
- var SQL_TEMPLATES = Object.freeze({
404
- PUBLIC_SCHEMA: "public",
405
- WHERE: "WHERE",
406
- SELECT: "SELECT",
407
- FROM: "FROM",
408
- ORDER_BY: "ORDER BY",
409
- GROUP_BY: "GROUP BY",
410
- HAVING: "HAVING",
411
- LIMIT: "LIMIT",
412
- OFFSET: "OFFSET",
413
- COUNT_ALL: "COUNT(*)",
414
- AS: "AS",
415
- DISTINCT_ON: "DISTINCT ON",
416
- IS_NULL: "IS NULL",
417
- IS_NOT_NULL: "IS NOT NULL",
418
- LIKE: "LIKE",
419
- AND: "AND",
420
- OR: "OR",
421
- NOT: "NOT"
422
- });
423
- var SCHEMA_PREFIXES = Object.freeze({
424
- INTERNAL: "@",
425
- COMMENT: "//"
426
- });
427
- var Ops = Object.freeze({
428
- EQUALS: "equals",
429
- NOT: "not",
430
- GT: "gt",
431
- GTE: "gte",
432
- LT: "lt",
433
- LTE: "lte",
434
- IN: "in",
435
- NOT_IN: "notIn",
436
- CONTAINS: "contains",
437
- STARTS_WITH: "startsWith",
438
- ENDS_WITH: "endsWith",
439
- HAS: "has",
440
- HAS_SOME: "hasSome",
441
- HAS_EVERY: "hasEvery",
442
- IS_EMPTY: "isEmpty",
443
- PATH: "path",
444
- STRING_CONTAINS: "string_contains",
445
- STRING_STARTS_WITH: "string_starts_with",
446
- STRING_ENDS_WITH: "string_ends_with"
447
- });
448
- var LogicalOps = Object.freeze({
449
- AND: "AND",
450
- OR: "OR",
451
- NOT: "NOT"
452
- });
453
- var RelationFilters = Object.freeze({
454
- SOME: "some",
455
- EVERY: "every",
456
- NONE: "none"
457
- });
458
- var Modes = Object.freeze({
459
- INSENSITIVE: "insensitive",
460
- DEFAULT: "default"
461
- });
462
- var Wildcards = Object.freeze({
463
- [Ops.CONTAINS]: (v) => `%${v}%`,
464
- [Ops.STARTS_WITH]: (v) => `${v}%`,
465
- [Ops.ENDS_WITH]: (v) => `%${v}`
466
- });
467
- var REGEX_CACHE = {
468
- VALID_IDENTIFIER: /^[a-z_][a-z0-9_]*$/
469
- };
470
- var LIMITS = Object.freeze({
471
- MAX_QUERY_DEPTH: 50,
472
- MAX_ARRAY_SIZE: 1e4,
473
- MAX_STRING_LENGTH: 1e4
474
- });
475
-
476
476
  // src/builder/shared/validators/type-guards.ts
477
477
  function isNotNullish(value) {
478
478
  return value !== null && value !== void 0;
@@ -558,6 +558,19 @@ function getRelationFieldSet(model) {
558
558
  RELATION_SET_CACHE.set(model, s);
559
559
  return s;
560
560
  }
561
+ var COLUMN_MAP_CACHE = /* @__PURE__ */ new WeakMap();
562
+ function getColumnMap(model) {
563
+ const cached = COLUMN_MAP_CACHE.get(model);
564
+ if (cached) return cached;
565
+ const map = /* @__PURE__ */ new Map();
566
+ for (const f of model.fields) {
567
+ if (!f.isRelation) {
568
+ map.set(f.name, f.dbName || f.name);
569
+ }
570
+ }
571
+ COLUMN_MAP_CACHE.set(model, map);
572
+ return map;
573
+ }
561
574
 
562
575
  // src/builder/shared/validators/sql-validators.ts
563
576
  function isValidWhereClause(clause) {
@@ -945,27 +958,10 @@ function quote(id) {
945
958
  }
946
959
  return id;
947
960
  }
948
- function pickDbFieldName(field) {
949
- const candidates = [
950
- field == null ? void 0 : field.dbName,
951
- field == null ? void 0 : field.columnName,
952
- field == null ? void 0 : field.databaseName,
953
- field == null ? void 0 : field.mappedName
954
- ];
955
- for (const c of candidates) {
956
- if (typeof c === "string") {
957
- const s = c.trim();
958
- if (s.length > 0) return s;
959
- }
960
- }
961
- return void 0;
962
- }
963
961
  function resolveColumnName(model, fieldName) {
964
- var _a;
965
962
  if (!model) return fieldName;
966
- const f = model.fields.find((x) => (x == null ? void 0 : x.name) === fieldName);
967
- if (!f) return fieldName;
968
- return (_a = pickDbFieldName(f)) != null ? _a : fieldName;
963
+ const columnMap = getColumnMap(model);
964
+ return columnMap.get(fieldName) || fieldName;
969
965
  }
970
966
  function quoteColumn(model, fieldName) {
971
967
  return quote(resolveColumnName(model, fieldName));
@@ -1123,1570 +1119,1570 @@ function joinCondition(field, parentModel, childModel, parentAlias, childAlias)
1123
1119
  function getModelByName(schemas, name) {
1124
1120
  return schemas.find((m) => m.name === name);
1125
1121
  }
1126
- function buildNotComposite(expr, val, params, dialect, buildOp, separator) {
1127
- const entries = Object.entries(val).filter(
1128
- ([k, v]) => k !== "mode" && v !== void 0
1129
- );
1130
- if (entries.length === 0) return "";
1131
- const clauses = [];
1132
- for (const [subOp, subVal] of entries) {
1133
- const sub = buildOp(expr, subOp, subVal, params, dialect);
1134
- if (sub && sub.trim().length > 0) clauses.push(`(${sub})`);
1135
- }
1136
- if (clauses.length === 0) return "";
1137
- if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
1138
- return `${SQL_TEMPLATES.NOT} (${clauses.join(separator)})`;
1139
- }
1140
- function buildScalarOperator(expr, op, val, params, mode, fieldType, dialect) {
1141
- if (val === void 0) return "";
1142
- if (val === null) {
1143
- return handleNullValue(expr, op);
1144
- }
1145
- if (op === Ops.NOT && isPlainObject(val)) {
1146
- return handleNotOperator(expr, val, params, mode, fieldType, dialect);
1147
- }
1148
- if (op === Ops.NOT) {
1149
- const placeholder = params.addAuto(val);
1150
- return `${expr} <> ${placeholder}`;
1122
+ function normalizeIntLike(name, v, opts = {}) {
1123
+ var _a, _b;
1124
+ if (!isNotNullish(v)) return void 0;
1125
+ if (schemaParser.isDynamicParameter(v)) return v;
1126
+ if (typeof v !== "number" || !Number.isFinite(v) || !Number.isInteger(v)) {
1127
+ throw new Error(`${name} must be an integer`);
1151
1128
  }
1152
- if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && isNotNullish(dialect)) {
1153
- const placeholder = params.addAuto(val);
1154
- return caseInsensitiveEquals(expr, placeholder);
1129
+ const min = (_a = opts.min) != null ? _a : 0;
1130
+ const allowZero = (_b = opts.allowZero) != null ? _b : true;
1131
+ if (!allowZero && v === 0) {
1132
+ throw new Error(`${name} must be > 0`);
1155
1133
  }
1156
- const STRING_LIKE_OPS = /* @__PURE__ */ new Set([
1157
- Ops.CONTAINS,
1158
- Ops.STARTS_WITH,
1159
- Ops.ENDS_WITH
1160
- ]);
1161
- if (STRING_LIKE_OPS.has(op)) {
1162
- if (!isNotNullish(dialect)) {
1163
- throw createError(`Like operators require a SQL dialect`, {
1164
- operator: op
1165
- });
1166
- }
1167
- return handleLikeOperator(expr, op, val, params, mode, dialect);
1134
+ if (v < min) {
1135
+ throw new Error(`${name} must be >= ${min}`);
1168
1136
  }
1169
- if (op === Ops.IN || op === Ops.NOT_IN) {
1170
- if (!isNotNullish(dialect)) {
1171
- throw createError(`IN operators require a SQL dialect`, { operator: op });
1172
- }
1173
- return handleInOperator(expr, op, val, params, dialect);
1137
+ if (typeof opts.max === "number" && v > opts.max) {
1138
+ throw new Error(`${name} must be <= ${opts.max}`);
1174
1139
  }
1175
- return handleComparisonOperator(expr, op, val, params);
1176
- }
1177
- function handleNullValue(expr, op) {
1178
- if (op === Ops.EQUALS) return `${expr} ${SQL_TEMPLATES.IS_NULL}`;
1179
- if (op === Ops.NOT) return `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
1180
- throw createError(`Operator '${op}' doesn't support null`, { operator: op });
1181
- }
1182
- function normalizeMode(v) {
1183
- if (v === Modes.INSENSITIVE) return Modes.INSENSITIVE;
1184
- if (v === Modes.DEFAULT) return Modes.DEFAULT;
1185
- return void 0;
1140
+ return v;
1186
1141
  }
1187
- function handleNotOperator(expr, val, params, outerMode, fieldType, dialect) {
1188
- const innerMode = normalizeMode(val.mode);
1189
- const effectiveMode = innerMode != null ? innerMode : outerMode;
1190
- return buildNotComposite(
1191
- expr,
1192
- val,
1193
- params,
1194
- dialect,
1195
- (e, subOp, subVal, p, d) => buildScalarOperator(e, subOp, subVal, p, effectiveMode, fieldType, d),
1196
- ` ${SQL_TEMPLATES.AND} `
1197
- );
1142
+ function scopeName(scope, dynamicName) {
1143
+ const s = String(scope).trim();
1144
+ const dn = String(dynamicName).trim();
1145
+ if (s.length === 0) return dn;
1146
+ return `${s}:${dn}`;
1198
1147
  }
1199
- function buildDynamicLikePattern(op, placeholder, dialect) {
1200
- if (dialect === "postgres") {
1201
- switch (op) {
1202
- case Ops.CONTAINS:
1203
- return `('%' || ${placeholder} || '%')`;
1204
- case Ops.STARTS_WITH:
1205
- return `(${placeholder} || '%')`;
1206
- case Ops.ENDS_WITH:
1207
- return `('%' || ${placeholder})`;
1208
- default:
1209
- return placeholder;
1210
- }
1211
- }
1212
- switch (op) {
1213
- case Ops.CONTAINS:
1214
- return `('%' || ${placeholder} || '%')`;
1215
- case Ops.STARTS_WITH:
1216
- return `(${placeholder} || '%')`;
1217
- case Ops.ENDS_WITH:
1218
- return `('%' || ${placeholder})`;
1219
- default:
1220
- return placeholder;
1148
+ function addAutoScoped(params, value, scope) {
1149
+ if (schemaParser.isDynamicParameter(value)) {
1150
+ const dn = schemaParser.extractDynamicName(value);
1151
+ return params.add(void 0, scopeName(scope, dn));
1221
1152
  }
1153
+ return params.add(value);
1222
1154
  }
1223
- function handleLikeOperator(expr, op, val, params, mode, dialect) {
1224
- if (val === void 0) return "";
1225
- if (schemaParser.isDynamicParameter(val)) {
1226
- const placeholder2 = params.addAuto(val);
1227
- const patternExpr = buildDynamicLikePattern(op, placeholder2, dialect);
1228
- if (mode === Modes.INSENSITIVE) {
1229
- return caseInsensitiveLike(expr, patternExpr, dialect);
1230
- }
1231
- return `${expr} ${SQL_TEMPLATES.LIKE} ${patternExpr}`;
1155
+
1156
+ // src/builder/shared/order-by-utils.ts
1157
+ var flipNulls = (v) => {
1158
+ const s = String(v).toLowerCase();
1159
+ if (s === "first") return "last";
1160
+ if (s === "last") return "first";
1161
+ return v;
1162
+ };
1163
+ var flipSortString = (v) => {
1164
+ if (typeof v !== "string") return v;
1165
+ const s = v.toLowerCase();
1166
+ if (s === "asc") return "desc";
1167
+ if (s === "desc") return "asc";
1168
+ return v;
1169
+ };
1170
+ var getNextSort = (sortRaw) => {
1171
+ if (typeof sortRaw !== "string") return sortRaw;
1172
+ const s = sortRaw.toLowerCase();
1173
+ if (s === "asc") return "desc";
1174
+ if (s === "desc") return "asc";
1175
+ return sortRaw;
1176
+ };
1177
+ var flipObjectSort = (obj) => {
1178
+ const sortRaw = obj.sort;
1179
+ const out = __spreadProps(__spreadValues({}, obj), { sort: getNextSort(sortRaw) });
1180
+ const nullsRaw = obj.nulls;
1181
+ if (typeof nullsRaw === "string") {
1182
+ out.nulls = flipNulls(nullsRaw);
1232
1183
  }
1233
- const placeholder = params.add(Wildcards[op](String(val)));
1234
- if (mode === Modes.INSENSITIVE) {
1235
- return caseInsensitiveLike(expr, placeholder, dialect);
1184
+ return out;
1185
+ };
1186
+ var flipValue = (v) => {
1187
+ if (typeof v === "string") return flipSortString(v);
1188
+ if (isPlainObject(v)) return flipObjectSort(v);
1189
+ return v;
1190
+ };
1191
+ var assertSingleFieldObject = (item) => {
1192
+ if (!isPlainObject(item)) {
1193
+ throw new Error("orderBy array entries must be objects");
1236
1194
  }
1237
- return `${expr} ${SQL_TEMPLATES.LIKE} ${placeholder}`;
1238
- }
1239
- function handleInOperator(expr, op, val, params, dialect) {
1240
- if (val === void 0) return "";
1241
- if (schemaParser.isDynamicParameter(val)) {
1242
- const placeholder2 = params.addAuto(val);
1243
- return op === Ops.IN ? inArray(expr, placeholder2, dialect) : notInArray(expr, placeholder2, dialect);
1195
+ const entries = Object.entries(item);
1196
+ if (entries.length !== 1) {
1197
+ throw new Error("orderBy array entries must have exactly one field");
1244
1198
  }
1245
- if (!Array.isArray(val)) {
1246
- throw createError(`IN operators require array value`, {
1247
- operator: op,
1248
- value: val
1249
- });
1199
+ return entries[0];
1200
+ };
1201
+ var flipOrderByArray = (orderBy) => {
1202
+ return orderBy.map((item) => {
1203
+ const [k, v] = assertSingleFieldObject(item);
1204
+ return { [k]: flipValue(v) };
1205
+ });
1206
+ };
1207
+ var flipOrderByObject = (orderBy) => {
1208
+ const out = {};
1209
+ for (const [k, v] of Object.entries(orderBy)) {
1210
+ out[k] = flipValue(v);
1250
1211
  }
1251
- if (val.length === 0) {
1252
- return op === Ops.IN ? "0=1" : "1=1";
1212
+ return out;
1213
+ };
1214
+ function reverseOrderByInput(orderBy) {
1215
+ if (!isNotNullish(orderBy)) return orderBy;
1216
+ if (Array.isArray(orderBy)) {
1217
+ return flipOrderByArray(orderBy);
1253
1218
  }
1254
- const paramValue = prepareArrayParam(val, dialect);
1255
- const placeholder = params.add(paramValue);
1256
- return op === Ops.IN ? inArray(expr, placeholder, dialect) : notInArray(expr, placeholder, dialect);
1219
+ if (isPlainObject(orderBy)) {
1220
+ return flipOrderByObject(orderBy);
1221
+ }
1222
+ throw new Error("orderBy must be an object or array of objects");
1257
1223
  }
1258
- function handleComparisonOperator(expr, op, val, params) {
1259
- if (val === void 0) return "";
1260
- const COMPARISON_OPS2 = {
1261
- [Ops.EQUALS]: "=",
1262
- [Ops.GT]: ">",
1263
- [Ops.GTE]: ">=",
1264
- [Ops.LT]: "<",
1265
- [Ops.LTE]: "<="
1266
- };
1267
- const sqlOp = COMPARISON_OPS2[op];
1268
- if (!sqlOp) {
1269
- throw createError(`Unsupported scalar operator: ${op}`, { operator: op });
1270
- }
1271
- const placeholder = params.addAuto(val);
1272
- return `${expr} ${sqlOp} ${placeholder}`;
1273
- }
1274
- function buildArrayParam(val, params, dialect) {
1275
- if (schemaParser.isDynamicParameter(val)) {
1276
- return params.addAuto(val);
1224
+ var normalizePairs = (pairs, parseValue) => {
1225
+ return pairs.map(([field, rawValue]) => {
1226
+ const parsed = parseValue(rawValue, field);
1227
+ return {
1228
+ [field]: parsed.nulls !== void 0 ? { sort: parsed.direction, nulls: parsed.nulls } : parsed.direction
1229
+ };
1230
+ });
1231
+ };
1232
+ function normalizeOrderByInput(orderBy, parseValue) {
1233
+ if (!isNotNullish(orderBy)) return [];
1234
+ if (Array.isArray(orderBy)) {
1235
+ const pairs = orderBy.map(assertSingleFieldObject);
1236
+ return normalizePairs(pairs, parseValue);
1277
1237
  }
1278
- if (!Array.isArray(val)) {
1279
- throw createError(`Array operation requires array value`, { value: val });
1238
+ if (isPlainObject(orderBy)) {
1239
+ return normalizePairs(Object.entries(orderBy), parseValue);
1280
1240
  }
1281
- const paramValue = prepareArrayParam(val, dialect);
1282
- return params.add(paramValue);
1241
+ throw new Error("orderBy must be an object or array of objects");
1283
1242
  }
1284
- function buildArrayOperator(expr, op, val, params, fieldType, dialect) {
1285
- if (val === void 0) return "";
1286
- if (val === null) {
1287
- if (op === Ops.EQUALS) return `${expr} ${SQL_TEMPLATES.IS_NULL}`;
1288
- if (op === Ops.NOT) return `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
1289
- }
1290
- const cast = getArrayType(fieldType, dialect);
1291
- if (op === Ops.EQUALS) {
1292
- return handleArrayEquals(expr, val, params, cast, dialect);
1293
- }
1294
- if (op === Ops.NOT) {
1295
- return handleArrayNot(expr, val, params, cast, dialect);
1296
- }
1297
- switch (op) {
1298
- case Ops.HAS:
1299
- return handleArrayHas(expr, val, params, cast, dialect);
1300
- case Ops.HAS_SOME:
1301
- return handleArrayHasSome(expr, val, params, cast, dialect);
1302
- case Ops.HAS_EVERY:
1303
- return handleArrayHasEvery(expr, val, params, cast, dialect);
1304
- case Ops.IS_EMPTY:
1305
- return handleArrayIsEmpty(expr, val, dialect);
1306
- default:
1307
- throw createError(`Unknown array operator: ${op}`, { operator: op });
1308
- }
1243
+
1244
+ // src/builder/pagination.ts
1245
+ var MAX_LIMIT_OFFSET = 2147483647;
1246
+ function parseDirectionRaw(raw, errorLabel) {
1247
+ const s = String(raw).toLowerCase();
1248
+ if (s === "asc" || s === "desc") return s;
1249
+ throw new Error(`Invalid ${errorLabel}: ${raw}`);
1309
1250
  }
1310
- function handleArrayEquals(expr, val, params, cast, dialect) {
1311
- if (val === void 0) return "";
1312
- if (isEmptyArray(val)) {
1313
- return arrayIsEmpty(expr, dialect);
1251
+ function parseNullsRaw(raw, errorLabel) {
1252
+ if (!isNotNullish(raw)) return void 0;
1253
+ const s = String(raw).toLowerCase();
1254
+ if (s === "first" || s === "last") return s;
1255
+ throw new Error(`Invalid ${errorLabel}: ${raw}`);
1256
+ }
1257
+ function requireOrderByObject(v, errorPrefix) {
1258
+ if (!isPlainObject(v) || !("sort" in v)) {
1259
+ throw new Error(`${errorPrefix} must be 'asc' | 'desc' or { sort, nulls? }`);
1314
1260
  }
1315
- const placeholder = buildArrayParam(val, params, dialect);
1316
- return arrayEquals(expr, placeholder, cast, dialect);
1261
+ return v;
1317
1262
  }
1318
- function handleArrayNot(expr, val, params, cast, dialect) {
1319
- if (val === void 0) return "";
1320
- let target = val;
1321
- if (isPlainObject(val)) {
1322
- const entries = Object.entries(val).filter(([, v]) => v !== void 0);
1323
- if (entries.length === 1 && entries[0][0] === Ops.EQUALS) {
1324
- target = entries[0][1];
1325
- } else {
1326
- throw createError(`Array NOT only supports { equals: ... } shape`, {
1327
- operator: Ops.NOT,
1328
- value: val
1329
- });
1263
+ function assertAllowedOrderByKeys(obj, fieldName) {
1264
+ const allowed = /* @__PURE__ */ new Set(["sort", "nulls"]);
1265
+ for (const k of Object.keys(obj)) {
1266
+ if (!allowed.has(k)) {
1267
+ throw new Error(
1268
+ fieldName ? `Unsupported orderBy key '${k}' for field '${fieldName}'` : `Unsupported orderBy key '${k}'`
1269
+ );
1330
1270
  }
1331
1271
  }
1332
- if (target === null) {
1333
- return `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
1334
- }
1335
- if (isEmptyArray(target)) {
1336
- return arrayIsNotEmpty(expr, dialect);
1337
- }
1338
- const placeholder = buildArrayParam(target, params, dialect);
1339
- return `${SQL_TEMPLATES.NOT} (${arrayEquals(expr, placeholder, cast, dialect)})`;
1340
1272
  }
1341
- function handleArrayHas(expr, val, params, cast, dialect) {
1342
- if (val === void 0) return "";
1343
- if (val === null) {
1344
- throw createError(`has requires scalar value`, {
1345
- operator: Ops.HAS,
1346
- value: val
1347
- });
1348
- }
1349
- if (!schemaParser.isDynamicParameter(val) && Array.isArray(val)) {
1350
- throw createError(`has requires scalar value (single element), not array`, {
1351
- operator: Ops.HAS,
1352
- value: val
1353
- });
1354
- }
1355
- if (isPlainObject(val)) {
1356
- throw createError(`has requires scalar value`, {
1357
- operator: Ops.HAS,
1358
- value: val
1359
- });
1273
+ function parseOrderByValue(v, fieldName) {
1274
+ const errorPrefix = fieldName ? `orderBy for '${fieldName}'` : "orderBy value";
1275
+ if (typeof v === "string") {
1276
+ return { direction: parseDirectionRaw(v, `${errorPrefix} direction`) };
1360
1277
  }
1361
- const placeholder = params.addAuto(val);
1362
- return arrayContains(expr, placeholder, cast, dialect);
1278
+ const obj = requireOrderByObject(v, errorPrefix);
1279
+ const direction = parseDirectionRaw(obj.sort, `${errorPrefix}.sort`);
1280
+ const nulls = parseNullsRaw(obj.nulls, `${errorPrefix}.nulls`);
1281
+ assertAllowedOrderByKeys(obj, fieldName);
1282
+ return { direction, nulls };
1363
1283
  }
1364
- function handleArrayHasSome(expr, val, params, cast, dialect) {
1365
- if (val === void 0) return "";
1366
- if (schemaParser.isDynamicParameter(val)) {
1367
- const placeholder2 = params.addAuto(val);
1368
- return arrayOverlaps(expr, placeholder2, cast, dialect);
1284
+ function normalizeFiniteInteger(name, v) {
1285
+ if (typeof v !== "number" || !Number.isFinite(v) || !Number.isInteger(v)) {
1286
+ throw new Error(`${name} must be an integer`);
1369
1287
  }
1370
- if (!Array.isArray(val)) {
1371
- throw createError(`hasSome requires array value`, {
1372
- operator: Ops.HAS_SOME,
1373
- value: val
1374
- });
1288
+ return v;
1289
+ }
1290
+ function normalizeNonNegativeInt(name, v) {
1291
+ if (schemaParser.isDynamicParameter(v)) return v;
1292
+ const n = normalizeFiniteInteger(name, v);
1293
+ if (n < 0) {
1294
+ throw new Error(`${name} must be >= 0`);
1375
1295
  }
1376
- if (val.length > LIMITS.MAX_ARRAY_SIZE) {
1377
- throw createError(
1378
- `Array too large (${val.length} elements, max ${LIMITS.MAX_ARRAY_SIZE})`,
1379
- { operator: Ops.HAS_SOME, value: `[${val.length} items]` }
1380
- );
1296
+ if (n > MAX_LIMIT_OFFSET) {
1297
+ throw new Error(`${name} must be <= ${MAX_LIMIT_OFFSET}`);
1381
1298
  }
1382
- if (val.length === 0) return "0=1";
1383
- const paramValue = prepareArrayParam(val, dialect);
1384
- const placeholder = params.add(paramValue);
1385
- return arrayOverlaps(expr, placeholder, cast, dialect);
1299
+ return n;
1386
1300
  }
1387
- function handleArrayHasEvery(expr, val, params, cast, dialect) {
1388
- if (val === void 0) return "";
1389
- const placeholder = buildArrayParam(val, params, dialect);
1390
- return arrayContainsAll(expr, placeholder, cast, dialect);
1301
+ function hasNonNullishProp(v, key) {
1302
+ return isPlainObject(v) && key in v && isNotNullish(v[key]);
1391
1303
  }
1392
- function handleArrayIsEmpty(expr, val, dialect) {
1393
- if (typeof val !== "boolean") {
1394
- throw createError(`isEmpty requires boolean value`, {
1395
- operator: Ops.IS_EMPTY,
1396
- value: val
1397
- });
1304
+ function normalizeIntegerOrDynamic(name, v) {
1305
+ if (schemaParser.isDynamicParameter(v)) return v;
1306
+ return normalizeFiniteInteger(name, v);
1307
+ }
1308
+ function readSkipTake(relArgs) {
1309
+ const hasSkip = hasNonNullishProp(relArgs, "skip");
1310
+ const hasTake = hasNonNullishProp(relArgs, "take");
1311
+ if (!hasSkip && !hasTake) {
1312
+ return {
1313
+ hasSkip: false,
1314
+ hasTake: false,
1315
+ skipVal: void 0,
1316
+ takeVal: void 0
1317
+ };
1398
1318
  }
1399
- return val === true ? arrayIsEmpty(expr, dialect) : arrayIsNotEmpty(expr, dialect);
1319
+ const obj = relArgs;
1320
+ const skipVal = hasSkip ? normalizeNonNegativeInt("skip", obj.skip) : void 0;
1321
+ const takeVal = hasTake ? normalizeIntegerOrDynamic("take", obj.take) : void 0;
1322
+ return { hasSkip, hasTake, skipVal, takeVal };
1400
1323
  }
1401
-
1402
- // src/builder/where/operators-json.ts
1403
- var SAFE_JSON_PATH_SEGMENT = /^[a-zA-Z_]\w*$/;
1404
- function validateJsonPathSegments(segments) {
1405
- for (const segment of segments) {
1406
- if (typeof segment !== "string") {
1407
- throw createError("JSON path segments must be strings", {
1408
- operator: Ops.PATH,
1409
- value: segment
1410
- });
1324
+ function buildOrderByFragment(entries, alias, dialect, model) {
1325
+ if (entries.length === 0) return "";
1326
+ const out = [];
1327
+ for (const e of entries) {
1328
+ const dir = e.direction.toUpperCase();
1329
+ const c = col(alias, e.field, model);
1330
+ if (dialect === "postgres") {
1331
+ const nulls = isNotNullish(e.nulls) ? ` NULLS ${e.nulls.toUpperCase()}` : "";
1332
+ out.push(`${c} ${dir}${nulls}`);
1333
+ continue;
1411
1334
  }
1412
- if (!SAFE_JSON_PATH_SEGMENT.test(segment)) {
1413
- throw createError(
1414
- `Invalid JSON path segment: '${segment}'. Must be alphanumeric with underscores, starting with letter or underscore.`,
1415
- { operator: Ops.PATH, value: segment }
1416
- );
1335
+ if (isNotNullish(e.nulls)) {
1336
+ const isNullExpr = `(${c} IS NULL)`;
1337
+ const nullRankDir = e.nulls === "first" ? "DESC" : "ASC";
1338
+ out.push(`${isNullExpr} ${nullRankDir}`);
1339
+ out.push(`${c} ${dir}`);
1340
+ continue;
1417
1341
  }
1342
+ out.push(`${c} ${dir}`);
1418
1343
  }
1344
+ return out.join(SQL_SEPARATORS.ORDER_BY);
1419
1345
  }
1420
- function buildJsonOperator(expr, op, val, params, dialect) {
1421
- if (val === void 0) return "";
1422
- if (op === Ops.PATH && isPlainObject(val) && "path" in val) {
1423
- return handleJsonPath(expr, val, params, dialect);
1424
- }
1425
- const jsonWildcards = {
1426
- [Ops.STRING_CONTAINS]: (v) => `%${v}%`,
1427
- [Ops.STRING_STARTS_WITH]: (v) => `${v}%`,
1428
- [Ops.STRING_ENDS_WITH]: (v) => `%${v}`
1429
- };
1430
- if (op in jsonWildcards) {
1431
- return handleJsonWildcard(expr, op, val, params, jsonWildcards, dialect);
1346
+ function defaultNullsFor(dialect, direction) {
1347
+ if (dialect === "postgres") {
1348
+ return direction === "asc" ? "last" : "first";
1432
1349
  }
1433
- throw createError(`Unsupported JSON operator: ${op}`, { operator: op });
1350
+ return direction === "asc" ? "first" : "last";
1434
1351
  }
1435
- function handleJsonPath(expr, val, params, dialect) {
1436
- const v = val;
1437
- if (!Array.isArray(v.path)) {
1438
- throw createError("JSON path must be an array", { operator: Ops.PATH });
1439
- }
1440
- if (v.path.length === 0) {
1441
- throw createError("JSON path cannot be empty", { operator: Ops.PATH });
1352
+ function ensureCursorFieldsInOrder(orderEntries, cursorEntries) {
1353
+ const existing = /* @__PURE__ */ new Map();
1354
+ for (const e of orderEntries) existing.set(e.field, e);
1355
+ const out = [...orderEntries];
1356
+ for (const [field] of cursorEntries) {
1357
+ if (!existing.has(field)) {
1358
+ out.push({ field, direction: "asc" });
1359
+ existing.set(field, out[out.length - 1]);
1360
+ }
1442
1361
  }
1443
- validateJsonPathSegments(v.path);
1444
- const pathExpr = dialect === "sqlite" ? params.add(`$.${v.path.join(".")}`) : params.add(v.path);
1445
- const rawOps = [
1446
- ["=", v.equals],
1447
- [">", v.gt],
1448
- [">=", v.gte],
1449
- ["<", v.lt],
1450
- ["<=", v.lte]
1451
- ];
1452
- const ops = rawOps.filter(
1453
- ([, value]) => value !== void 0
1454
- );
1455
- if (ops.length === 0) {
1456
- throw createError("JSON path query missing comparison operator", {
1457
- operator: Ops.PATH
1458
- });
1362
+ return out;
1363
+ }
1364
+ function buildCursorFilterParts(cursor, cursorAlias, params, model) {
1365
+ const entries = Object.entries(cursor);
1366
+ if (entries.length === 0) {
1367
+ throw new Error("cursor must have at least one field");
1459
1368
  }
1369
+ const placeholdersByField = /* @__PURE__ */ new Map();
1460
1370
  const parts = [];
1461
- for (const [sqlOp, value] of ops) {
1371
+ for (const [field, value] of entries) {
1372
+ const c = `${cursorAlias}.${quote(field)}`;
1462
1373
  if (value === null) {
1463
- const base2 = jsonExtractText(expr, pathExpr, dialect);
1464
- parts.push(`${base2} ${SQL_TEMPLATES.IS_NULL}`);
1374
+ parts.push(`${c} IS NULL`);
1465
1375
  continue;
1466
1376
  }
1467
- const valPh = params.add(value);
1468
- const base = typeof value === "number" ? jsonExtractNumeric(expr, pathExpr, dialect) : jsonExtractText(expr, pathExpr, dialect);
1469
- parts.push(`${base} ${sqlOp} ${valPh}`);
1470
- }
1471
- return parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`;
1472
- }
1473
- function handleJsonWildcard(expr, op, val, params, wildcards, dialect) {
1474
- if (!isNotNullish(val)) {
1475
- throw createError(`JSON string operator requires non-null value`, {
1476
- operator: op,
1477
- value: val
1478
- });
1479
- }
1480
- if (isPlainObject(val) || Array.isArray(val)) {
1481
- throw createError(`JSON string operator requires scalar value`, {
1482
- operator: op,
1483
- value: val
1484
- });
1485
- }
1486
- const strVal = String(val);
1487
- if (strVal.length > LIMITS.MAX_STRING_LENGTH) {
1488
- throw createError(
1489
- `String too long (${strVal.length} chars, max ${LIMITS.MAX_STRING_LENGTH})`,
1490
- { operator: op }
1491
- );
1377
+ const ph = addAutoScoped(params, value, `cursor.filter.${field}`);
1378
+ placeholdersByField.set(field, ph);
1379
+ parts.push(`${c} = ${ph}`);
1492
1380
  }
1493
- const placeholder = params.add(wildcards[op](strVal));
1494
- const jsonText = jsonToText(expr, dialect);
1495
- return caseInsensitiveLike(jsonText, placeholder, dialect);
1381
+ return {
1382
+ whereSql: parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`,
1383
+ placeholdersByField
1384
+ };
1496
1385
  }
1497
-
1498
- // src/builder/where/relations.ts
1499
- var NO_JOINS = Object.freeze([]);
1500
- function isListRelation(fieldType) {
1501
- return typeof fieldType === "string" && fieldType.endsWith("[]");
1386
+ function cursorValueExpr(tableName, cursorAlias, cursorWhereSql, field, model) {
1387
+ const colName = quote(field);
1388
+ return `(SELECT ${cursorAlias}.${colName} ${SQL_TEMPLATES.FROM} ${tableName} ${cursorAlias} ${SQL_TEMPLATES.WHERE} ${cursorWhereSql} ${SQL_TEMPLATES.LIMIT} 1)`;
1502
1389
  }
1503
- function buildToOneNullCheck(field, parentAlias, relTable, relAlias, join3, wantNull) {
1504
- const isLocal = field.isForeignKeyLocal === true;
1505
- const fkFields = normalizeKeyList(field.foreignKey);
1506
- if (isLocal) {
1507
- if (fkFields.length === 0) {
1508
- throw createError(`Relation '${field.name}' is missing foreignKey`, {
1509
- field: field.name
1510
- });
1511
- }
1512
- const parts = fkFields.map((fk) => {
1513
- const safe = fk.replace(/"/g, '""');
1514
- const expr = `${parentAlias}."${safe}"`;
1515
- return wantNull ? `${expr} ${SQL_TEMPLATES.IS_NULL}` : `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
1516
- });
1517
- if (parts.length === 1) return parts[0];
1518
- return wantNull ? `(${parts.join(" OR ")})` : `(${parts.join(" AND ")})`;
1519
- }
1520
- const existsSql = `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias} ${SQL_TEMPLATES.WHERE} ${join3})`;
1521
- return wantNull ? `${SQL_TEMPLATES.NOT} ${existsSql}` : existsSql;
1390
+ function buildCursorRowExistsExpr(tableName, cursorAlias, cursorWhereSql) {
1391
+ return `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${tableName} ${cursorAlias} ${SQL_TEMPLATES.WHERE} ${cursorWhereSql} ${SQL_TEMPLATES.LIMIT} 1)`;
1522
1392
  }
1523
- function buildToOneExistsMatch(relTable, relAlias, join3, sub) {
1524
- const joins = sub.joins.length > 0 ? ` ${sub.joins.join(" ")}` : "";
1525
- return `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias}${joins} ${SQL_TEMPLATES.WHERE} ${join3} ${SQL_TEMPLATES.AND} ${sub.clause})`;
1393
+ function buildCursorEqualityExpr(columnExpr, valueExpr) {
1394
+ return `((${valueExpr} IS NULL AND ${columnExpr} IS NULL) OR (${valueExpr} IS NOT NULL AND ${columnExpr} = ${valueExpr}))`;
1526
1395
  }
1527
- function buildToOneNotExistsMatch(relTable, relAlias, join3, sub) {
1528
- const joins = sub.joins.length > 0 ? ` ${sub.joins.join(" ")}` : "";
1529
- return `${SQL_TEMPLATES.NOT} EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias}${joins} ${SQL_TEMPLATES.WHERE} ${join3} ${SQL_TEMPLATES.AND} ${sub.clause})`;
1396
+ function buildCursorInequalityExpr(columnExpr, direction, nulls, valueExpr) {
1397
+ const op = direction === "asc" ? ">" : "<";
1398
+ if (nulls === "first") {
1399
+ return `(CASE WHEN ${valueExpr} IS NULL THEN (${columnExpr} IS NOT NULL) ELSE (${columnExpr} ${op} ${valueExpr}) END)`;
1400
+ }
1401
+ return `(CASE WHEN ${valueExpr} IS NULL THEN 0=1 ELSE ((${columnExpr} ${op} ${valueExpr}) OR (${columnExpr} IS NULL)) END)`;
1530
1402
  }
1531
- function buildListRelationFilters(args) {
1532
- const {
1533
- fieldName,
1534
- value,
1535
- ctx,
1536
- whereBuilder,
1537
- relModel,
1538
- relTable,
1539
- relAlias,
1540
- join: join3
1541
- } = args;
1542
- const noneValue = value[RelationFilters.NONE];
1543
- if (noneValue !== void 0 && noneValue !== null) {
1544
- const sub = whereBuilder.build(noneValue, __spreadProps(__spreadValues({}, ctx), {
1545
- alias: relAlias,
1546
- model: relModel,
1547
- path: [...ctx.path, fieldName, RelationFilters.NONE],
1548
- isSubquery: true,
1549
- depth: ctx.depth + 1
1550
- }));
1551
- const isEmptyFilter = isPlainObject(noneValue) && Object.keys(noneValue).length === 0;
1552
- const canOptimize = !ctx.isSubquery && isEmptyFilter && sub.clause === DEFAULT_WHERE_CLAUSE && sub.joins.length === 0;
1553
- if (canOptimize) {
1554
- const checkField = relModel.fields.find(
1555
- (f) => !f.isRelation && f.isRequired && f.name !== "id"
1556
- ) || relModel.fields.find((f) => !f.isRelation && f.name === "id");
1557
- if (checkField) {
1558
- const leftJoinSql = `LEFT JOIN ${relTable} ${relAlias} ON ${join3}`;
1559
- const whereClause = `${relAlias}.${quote(checkField.name)} IS NULL`;
1560
- return Object.freeze({
1561
- clause: whereClause,
1562
- joins: [leftJoinSql]
1563
- });
1564
- }
1403
+ function buildOuterCursorMatch(cursor, outerAlias, placeholdersByField, params, model) {
1404
+ const parts = [];
1405
+ for (const [field, value] of Object.entries(cursor)) {
1406
+ const c = col(outerAlias, field, model);
1407
+ if (value === null) {
1408
+ parts.push(`${c} IS NULL`);
1409
+ continue;
1410
+ }
1411
+ const existing = placeholdersByField.get(field);
1412
+ if (typeof existing === "string" && existing.length > 0) {
1413
+ parts.push(`${c} = ${existing}`);
1414
+ continue;
1565
1415
  }
1416
+ const ph = addAutoScoped(params, value, `cursor.outerMatch.${field}`);
1417
+ parts.push(`${c} = ${ph}`);
1566
1418
  }
1567
- const filters = [
1568
- {
1569
- key: RelationFilters.SOME,
1570
- wrap: (c, j) => `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias}${j} ${SQL_TEMPLATES.WHERE} ${join3} ${SQL_TEMPLATES.AND} ${c})`
1571
- },
1572
- {
1573
- key: RelationFilters.EVERY,
1574
- wrap: (c, j) => `${SQL_TEMPLATES.NOT} EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias}${j} ${SQL_TEMPLATES.WHERE} ${join3} ${SQL_TEMPLATES.AND} ${SQL_TEMPLATES.NOT} (${c}))`
1575
- },
1576
- {
1577
- key: RelationFilters.NONE,
1578
- wrap: (c, j) => {
1579
- const condition = c === DEFAULT_WHERE_CLAUSE ? "" : ` ${SQL_TEMPLATES.AND} ${c}`;
1580
- return `${SQL_TEMPLATES.NOT} EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias}${j} ${SQL_TEMPLATES.WHERE} ${join3}${condition})`;
1419
+ return parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`;
1420
+ }
1421
+ function buildOrderEntries(orderBy) {
1422
+ const normalized = normalizeOrderByInput(orderBy, parseOrderByValue);
1423
+ const entries = [];
1424
+ for (const item of normalized) {
1425
+ for (const [field, value] of Object.entries(item)) {
1426
+ if (typeof value === "string") {
1427
+ entries.push({ field, direction: value });
1428
+ } else {
1429
+ entries.push({
1430
+ field,
1431
+ direction: value.sort,
1432
+ nulls: value.nulls
1433
+ });
1581
1434
  }
1582
1435
  }
1583
- ];
1584
- const clauses = [];
1585
- for (const { key, wrap } of filters) {
1586
- const raw = value[key];
1587
- if (raw === void 0 || raw === null) continue;
1588
- const sub = whereBuilder.build(raw, __spreadProps(__spreadValues({}, ctx), {
1589
- alias: relAlias,
1590
- model: relModel,
1591
- path: [...ctx.path, fieldName, key],
1592
- isSubquery: true,
1593
- depth: ctx.depth + 1
1594
- }));
1595
- const j = sub.joins.length > 0 ? ` ${sub.joins.join(" ")}` : "";
1596
- clauses.push(wrap(sub.clause, j));
1597
- }
1598
- if (clauses.length === 0) {
1599
- throw createError(
1600
- `List relation '${fieldName}' requires one of { some, every, none }`,
1601
- { field: fieldName, path: ctx.path, modelName: ctx.model.name }
1602
- );
1603
1436
  }
1604
- return Object.freeze({
1605
- clause: clauses.join(SQL_SEPARATORS.CONDITION_AND),
1606
- joins: NO_JOINS
1607
- });
1437
+ return entries;
1608
1438
  }
1609
- function buildToOneRelationFilters(args) {
1610
- const {
1611
- fieldName,
1612
- value,
1613
- ctx,
1614
- whereBuilder,
1615
- field,
1616
- relModel,
1617
- relTable,
1618
- relAlias,
1619
- join: join3
1620
- } = args;
1621
- const hasSomeEveryNone = isNotNullish(value[RelationFilters.SOME]) || isNotNullish(value[RelationFilters.EVERY]) || isNotNullish(value[RelationFilters.NONE]);
1622
- if (hasSomeEveryNone) {
1623
- throw createError(
1624
- `To-one relation '${fieldName}' does not support { some, every, none }; use { is, isNot }`,
1625
- { field: fieldName, path: ctx.path, modelName: ctx.model.name }
1626
- );
1627
- }
1628
- const hasIs = Object.prototype.hasOwnProperty.call(value, "is");
1629
- const hasIsNot = Object.prototype.hasOwnProperty.call(value, "isNot");
1630
- let filterKey;
1631
- let filterVal;
1632
- if (hasIs) {
1633
- filterKey = "is";
1634
- filterVal = value.is;
1635
- } else if (hasIsNot) {
1636
- filterKey = "isNot";
1637
- filterVal = value.isNot;
1638
- } else {
1639
- filterKey = "is";
1640
- filterVal = value;
1439
+ function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect, model) {
1440
+ var _a;
1441
+ const d = dialect != null ? dialect : getGlobalDialect();
1442
+ const cursorEntries = Object.entries(cursor);
1443
+ if (cursorEntries.length === 0) {
1444
+ throw new Error("cursor must have at least one field");
1641
1445
  }
1642
- if (filterVal === void 0) {
1643
- return Object.freeze({
1644
- clause: DEFAULT_WHERE_CLAUSE,
1645
- joins: NO_JOINS
1646
- });
1647
- }
1648
- if (filterVal === null) {
1649
- const wantNull = filterKey === "is";
1650
- const clause2 = buildToOneNullCheck(
1446
+ const cursorAlias = "__tp_cursor_src";
1447
+ const { whereSql: cursorWhereSql, placeholdersByField } = buildCursorFilterParts(cursor, cursorAlias, params);
1448
+ let orderEntries = buildOrderEntries(orderBy);
1449
+ if (orderEntries.length === 0) {
1450
+ orderEntries = cursorEntries.map(([field]) => ({
1651
1451
  field,
1652
- ctx.alias,
1653
- relTable,
1654
- relAlias,
1655
- join3,
1656
- wantNull
1657
- );
1658
- return Object.freeze({
1659
- clause: clause2,
1660
- joins: NO_JOINS
1661
- });
1452
+ direction: "asc"
1453
+ }));
1454
+ } else {
1455
+ orderEntries = ensureCursorFieldsInOrder(orderEntries, cursorEntries);
1662
1456
  }
1663
- if (!isPlainObject(filterVal)) {
1664
- throw createError(
1665
- `Relation '${fieldName}' filter must be an object or null`,
1666
- {
1667
- field: fieldName,
1668
- path: ctx.path,
1669
- modelName: ctx.model.name,
1670
- value: filterVal
1671
- }
1672
- );
1457
+ const existsExpr = buildCursorRowExistsExpr(
1458
+ tableName,
1459
+ cursorAlias,
1460
+ cursorWhereSql
1461
+ );
1462
+ const outerCursorMatch = buildOuterCursorMatch(
1463
+ cursor,
1464
+ alias,
1465
+ placeholdersByField,
1466
+ params,
1467
+ model
1468
+ );
1469
+ const orClauses = [];
1470
+ for (let level = 0; level < orderEntries.length; level++) {
1471
+ const andParts = [];
1472
+ for (let i = 0; i < level; i++) {
1473
+ const e2 = orderEntries[i];
1474
+ const c2 = col(alias, e2.field, model);
1475
+ const v2 = cursorValueExpr(
1476
+ tableName,
1477
+ cursorAlias,
1478
+ cursorWhereSql,
1479
+ e2.field);
1480
+ andParts.push(buildCursorEqualityExpr(c2, v2));
1481
+ }
1482
+ const e = orderEntries[level];
1483
+ const c = col(alias, e.field, model);
1484
+ const v = cursorValueExpr(
1485
+ tableName,
1486
+ cursorAlias,
1487
+ cursorWhereSql,
1488
+ e.field);
1489
+ const nulls = (_a = e.nulls) != null ? _a : defaultNullsFor(d, e.direction);
1490
+ andParts.push(buildCursorInequalityExpr(c, e.direction, nulls, v));
1491
+ orClauses.push(`(${andParts.join(SQL_SEPARATORS.CONDITION_AND)})`);
1673
1492
  }
1674
- const sub = whereBuilder.build(filterVal, __spreadProps(__spreadValues({}, ctx), {
1675
- alias: relAlias,
1676
- model: relModel,
1677
- path: [...ctx.path, fieldName, filterKey],
1678
- isSubquery: true,
1679
- depth: ctx.depth + 1
1680
- }));
1681
- const clause = filterKey === "is" ? buildToOneExistsMatch(relTable, relAlias, join3, sub) : buildToOneNotExistsMatch(relTable, relAlias, join3, sub);
1682
- return Object.freeze({
1683
- clause,
1684
- joins: NO_JOINS
1685
- });
1493
+ const exclusive = orClauses.join(SQL_SEPARATORS.CONDITION_OR);
1494
+ return `(${existsExpr} ${SQL_SEPARATORS.CONDITION_AND} ((${exclusive})${SQL_SEPARATORS.CONDITION_OR}(${outerCursorMatch})))`;
1686
1495
  }
1687
- function ensureRelationFilterObject(fieldName, value, ctx) {
1688
- if (!isPlainObject(value)) {
1689
- throw createError(`Relation filter '${fieldName}' must be an object`, {
1690
- path: [...ctx.path, fieldName],
1691
- field: fieldName,
1692
- modelName: ctx.model.name,
1693
- value
1694
- });
1695
- }
1496
+ function buildOrderBy(orderBy, alias, dialect, model) {
1497
+ const entries = buildOrderEntries(orderBy);
1498
+ if (entries.length === 0) return "";
1499
+ const d = dialect != null ? dialect : getGlobalDialect();
1500
+ return buildOrderByFragment(entries, alias, d, model);
1696
1501
  }
1697
- function buildRelation(fieldName, value, ctx, whereBuilder) {
1698
- const field = ctx.model.fields.find((f) => f.name === fieldName);
1699
- if (!isValidRelationField(field)) {
1700
- throw createError(`Invalid relation '${fieldName}'`, {
1701
- field: fieldName,
1702
- path: ctx.path,
1703
- modelName: ctx.model.name
1704
- });
1705
- }
1706
- const relModel = ctx.schemaModels.find((m) => m.name === field.relatedModel);
1707
- if (!isNotNullish(relModel)) {
1708
- throw createError(
1709
- `Related model '${field.relatedModel}' not found in schema. Available models: ${ctx.schemaModels.map((m) => m.name).join(", ")}`,
1710
- {
1711
- field: fieldName,
1712
- path: ctx.path,
1713
- modelName: ctx.model.name
1714
- }
1502
+ function buildOrderByClause(args, alias, dialect, model) {
1503
+ if (!isNotNullish(args.orderBy)) return "";
1504
+ const result = buildOrderBy(args.orderBy, alias, dialect, model);
1505
+ if (!isNonEmptyString(result)) {
1506
+ throw new Error(
1507
+ "buildOrderByClause: orderBy specified but produced empty result"
1715
1508
  );
1716
1509
  }
1717
- const relTable = buildTableReference(
1718
- SQL_TEMPLATES.PUBLIC_SCHEMA,
1719
- relModel.tableName,
1720
- ctx.dialect
1721
- );
1722
- const relAlias = ctx.aliasGen.next(fieldName);
1723
- const join3 = joinCondition(field, ctx.model, relModel, ctx.alias, relAlias);
1724
- const args = {
1725
- fieldName,
1726
- value,
1727
- ctx,
1728
- whereBuilder,
1729
- field,
1730
- relModel,
1731
- relTable,
1732
- relAlias,
1733
- join: join3
1734
- };
1735
- if (isListRelation(field.type)) return buildListRelationFilters(args);
1736
- return buildToOneRelationFilters(args);
1510
+ return result;
1737
1511
  }
1738
- function buildTopLevelRelation(fieldName, value, ctx, whereBuilder) {
1739
- ensureRelationFilterObject(fieldName, value, ctx);
1740
- return buildRelation(fieldName, value, ctx, whereBuilder);
1512
+ function normalizeTakeLike(v) {
1513
+ const n = normalizeIntLike("take", v, {
1514
+ min: Number.MIN_SAFE_INTEGER,
1515
+ max: MAX_LIMIT_OFFSET,
1516
+ allowZero: true
1517
+ });
1518
+ if (typeof n === "number") {
1519
+ if (n === 0) return 0;
1520
+ }
1521
+ return n;
1741
1522
  }
1742
- function buildNestedRelation(fieldName, value, ctx, whereBuilder) {
1743
- return buildTopLevelRelation(fieldName, value, ctx, whereBuilder);
1523
+ function normalizeSkipLike(v) {
1524
+ return normalizeIntLike("skip", v, {
1525
+ min: 0,
1526
+ max: MAX_LIMIT_OFFSET,
1527
+ allowZero: true
1528
+ });
1744
1529
  }
1745
-
1746
- // src/builder/shared/validators/field-validators.ts
1747
- function assertFieldExists(name, model, path) {
1748
- const field = model.fields.find((f) => f.name === name);
1749
- if (!isNotNullish(field)) {
1750
- throw createError(`Field '${name}' does not exist on '${model.name}'`, {
1751
- field: name,
1752
- path,
1753
- modelName: model.name,
1754
- availableFields: model.fields.map((f) => f.name)
1755
- });
1530
+ function getPaginationParams(method, args) {
1531
+ if (method === "findMany") {
1532
+ return {
1533
+ take: normalizeTakeLike(args.take),
1534
+ skip: normalizeSkipLike(args.skip),
1535
+ cursor: args.cursor
1536
+ };
1756
1537
  }
1757
- return field;
1758
- }
1759
- function assertValidOperator(fieldName, op, fieldType, path, modelName) {
1760
- if (!isNotNullish(fieldType)) return;
1761
- const ARRAY_OPS = /* @__PURE__ */ new Set([
1762
- Ops.HAS,
1763
- Ops.HAS_SOME,
1764
- Ops.HAS_EVERY,
1765
- Ops.IS_EMPTY
1766
- ]);
1767
- const JSON_OPS = /* @__PURE__ */ new Set([
1768
- Ops.PATH,
1769
- Ops.STRING_CONTAINS,
1770
- Ops.STRING_STARTS_WITH,
1771
- Ops.STRING_ENDS_WITH
1772
- ]);
1773
- const isArrayOp = ARRAY_OPS.has(op);
1774
- const isFieldArray = isArrayType(fieldType);
1775
- const arrayOpMismatch = isArrayOp && !isFieldArray;
1776
- if (arrayOpMismatch) {
1777
- throw createError(`'${op}' requires array field, got '${fieldType}'`, {
1778
- operator: op,
1779
- field: fieldName,
1780
- path,
1781
- modelName
1782
- });
1538
+ if (method === "findFirst") {
1539
+ const skip = normalizeSkipLike(args.skip);
1540
+ return { take: 1, skip: skip != null ? skip : 0 };
1783
1541
  }
1784
- const isJsonOp = JSON_OPS.has(op);
1785
- const isFieldJson = isJsonType(fieldType);
1786
- const jsonOpMismatch = isJsonOp && !isFieldJson;
1787
- if (jsonOpMismatch) {
1788
- throw createError(`'${op}' requires JSON field, got '${fieldType}'`, {
1789
- operator: op,
1790
- field: fieldName,
1791
- path,
1792
- modelName
1793
- });
1542
+ if (method === "findUnique") {
1543
+ return { take: 1, skip: 0 };
1794
1544
  }
1545
+ return {};
1795
1546
  }
1796
-
1797
- // src/builder/where/builder.ts
1798
- var WhereBuilder = class {
1799
- build(where, ctx) {
1800
- if (!isPlainObject(where)) {
1801
- throw createError("where must be an object", {
1802
- path: ctx.path,
1803
- modelName: ctx.model.name
1804
- });
1805
- }
1806
- return buildWhereInternal(where, ctx, this);
1547
+ function buildNotComposite(expr, val, params, dialect, buildOp, separator) {
1548
+ const entries = Object.entries(val).filter(
1549
+ ([k, v]) => k !== "mode" && v !== void 0
1550
+ );
1551
+ if (entries.length === 0) return "";
1552
+ const clauses = [];
1553
+ for (const [subOp, subVal] of entries) {
1554
+ const sub = buildOp(expr, subOp, subVal, params, dialect);
1555
+ if (sub && sub.trim().length > 0) clauses.push(`(${sub})`);
1807
1556
  }
1808
- };
1809
- var MAX_QUERY_DEPTH = 50;
1810
- var EMPTY_JOINS = Object.freeze([]);
1811
- var whereBuilderInstance = new WhereBuilder();
1812
- function freezeResult(clause, joins = EMPTY_JOINS) {
1813
- return Object.freeze({ clause, joins });
1557
+ if (clauses.length === 0) return "";
1558
+ if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
1559
+ return `${SQL_TEMPLATES.NOT} (${clauses.join(separator)})`;
1814
1560
  }
1815
- function dedupePreserveOrder(items) {
1816
- if (items.length <= 1) return Object.freeze([...items]);
1817
- const seen = /* @__PURE__ */ new Set();
1818
- const out = [];
1819
- for (const s of items) {
1820
- if (!seen.has(s)) {
1821
- seen.add(s);
1822
- out.push(s);
1561
+ function buildScalarOperator(expr, op, val, params, mode, fieldType, dialect) {
1562
+ if (val === void 0) return "";
1563
+ if (val === null) {
1564
+ return handleNullValue(expr, op);
1565
+ }
1566
+ if (op === Ops.NOT && isPlainObject(val)) {
1567
+ return handleNotOperator(expr, val, params, mode, fieldType, dialect);
1568
+ }
1569
+ if (op === Ops.NOT) {
1570
+ const placeholder = params.addAuto(val);
1571
+ return `${expr} <> ${placeholder}`;
1572
+ }
1573
+ if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && isNotNullish(dialect)) {
1574
+ const placeholder = params.addAuto(val);
1575
+ return caseInsensitiveEquals(expr, placeholder);
1576
+ }
1577
+ const STRING_LIKE_OPS = /* @__PURE__ */ new Set([
1578
+ Ops.CONTAINS,
1579
+ Ops.STARTS_WITH,
1580
+ Ops.ENDS_WITH
1581
+ ]);
1582
+ if (STRING_LIKE_OPS.has(op)) {
1583
+ if (!isNotNullish(dialect)) {
1584
+ throw createError(`Like operators require a SQL dialect`, {
1585
+ operator: op
1586
+ });
1823
1587
  }
1588
+ return handleLikeOperator(expr, op, val, params, mode, dialect);
1824
1589
  }
1825
- return Object.freeze(out);
1590
+ if (op === Ops.IN || op === Ops.NOT_IN) {
1591
+ if (!isNotNullish(dialect)) {
1592
+ throw createError(`IN operators require a SQL dialect`, { operator: op });
1593
+ }
1594
+ return handleInOperator(expr, op, val, params, dialect);
1595
+ }
1596
+ return handleComparisonOperator(expr, op, val, params);
1826
1597
  }
1827
- function appendResult(result, clauses, allJoins) {
1828
- if (isValidWhereClause(result.clause)) clauses.push(result.clause);
1829
- if (isNonEmptyArray(result.joins)) allJoins.push(...result.joins);
1598
+ function handleNullValue(expr, op) {
1599
+ if (op === Ops.EQUALS) return `${expr} ${SQL_TEMPLATES.IS_NULL}`;
1600
+ if (op === Ops.NOT) return `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
1601
+ throw createError(`Operator '${op}' doesn't support null`, { operator: op });
1830
1602
  }
1831
- function asLogicalOperator(key) {
1832
- if (key === LogicalOps.AND) return "AND";
1833
- if (key === LogicalOps.OR) return "OR";
1834
- if (key === LogicalOps.NOT) return "NOT";
1835
- return null;
1603
+ function normalizeMode(v) {
1604
+ if (v === Modes.INSENSITIVE) return Modes.INSENSITIVE;
1605
+ if (v === Modes.DEFAULT) return Modes.DEFAULT;
1606
+ return void 0;
1836
1607
  }
1837
- function nextContext(ctx) {
1838
- return __spreadProps(__spreadValues({}, ctx), { depth: ctx.depth + 1 });
1608
+ function handleNotOperator(expr, val, params, outerMode, fieldType, dialect) {
1609
+ const innerMode = normalizeMode(val.mode);
1610
+ const effectiveMode = innerMode != null ? innerMode : outerMode;
1611
+ return buildNotComposite(
1612
+ expr,
1613
+ val,
1614
+ params,
1615
+ dialect,
1616
+ (e, subOp, subVal, p, d) => buildScalarOperator(e, subOp, subVal, p, effectiveMode, fieldType, d),
1617
+ ` ${SQL_TEMPLATES.AND} `
1618
+ );
1839
1619
  }
1840
- function buildRelationFilter(fieldName, value, ctx, builder) {
1841
- const ctx2 = nextContext(ctx);
1842
- if (ctx.isSubquery) {
1843
- return buildNestedRelation(fieldName, value, ctx2, builder);
1620
+ function buildDynamicLikePattern(op, placeholder, dialect) {
1621
+ if (dialect === "postgres") {
1622
+ switch (op) {
1623
+ case Ops.CONTAINS:
1624
+ return `('%' || ${placeholder} || '%')`;
1625
+ case Ops.STARTS_WITH:
1626
+ return `(${placeholder} || '%')`;
1627
+ case Ops.ENDS_WITH:
1628
+ return `('%' || ${placeholder})`;
1629
+ default:
1630
+ return placeholder;
1631
+ }
1632
+ }
1633
+ switch (op) {
1634
+ case Ops.CONTAINS:
1635
+ return `('%' || ${placeholder} || '%')`;
1636
+ case Ops.STARTS_WITH:
1637
+ return `(${placeholder} || '%')`;
1638
+ case Ops.ENDS_WITH:
1639
+ return `('%' || ${placeholder})`;
1640
+ default:
1641
+ return placeholder;
1844
1642
  }
1845
- return buildTopLevelRelation(fieldName, value, ctx2, builder);
1846
1643
  }
1847
- function buildWhereEntry(key, value, ctx, builder) {
1848
- const op = asLogicalOperator(key);
1849
- if (op) return buildLogical(op, value, ctx, builder);
1850
- if (isRelationField(key, ctx.model)) {
1851
- if (!isPlainObject(value)) {
1852
- throw createError(`Relation filter '${key}' must be an object`, {
1853
- path: [...ctx.path, key],
1854
- field: key,
1855
- modelName: ctx.model.name,
1856
- value
1857
- });
1644
+ function handleLikeOperator(expr, op, val, params, mode, dialect) {
1645
+ if (val === void 0) return "";
1646
+ if (schemaParser.isDynamicParameter(val)) {
1647
+ const placeholder2 = params.addAuto(val);
1648
+ const patternExpr = buildDynamicLikePattern(op, placeholder2, dialect);
1649
+ if (mode === Modes.INSENSITIVE) {
1650
+ return caseInsensitiveLike(expr, patternExpr, dialect);
1858
1651
  }
1859
- return buildRelationFilter(key, value, ctx, builder);
1652
+ return `${expr} ${SQL_TEMPLATES.LIKE} ${patternExpr}`;
1860
1653
  }
1861
- return buildScalarField(key, value, ctx);
1654
+ const placeholder = params.add(Wildcards[op](String(val)));
1655
+ if (mode === Modes.INSENSITIVE) {
1656
+ return caseInsensitiveLike(expr, placeholder, dialect);
1657
+ }
1658
+ return `${expr} ${SQL_TEMPLATES.LIKE} ${placeholder}`;
1862
1659
  }
1863
- function buildWhereInternal(where, ctx, builder) {
1864
- if (ctx.depth > MAX_QUERY_DEPTH) {
1865
- throw createError(
1866
- `Query nesting too deep (max ${MAX_QUERY_DEPTH} levels). This usually indicates a circular reference.`,
1867
- { path: ctx.path, modelName: ctx.model.name }
1868
- );
1660
+ function handleInOperator(expr, op, val, params, dialect) {
1661
+ if (val === void 0) return "";
1662
+ if (schemaParser.isDynamicParameter(val)) {
1663
+ const placeholder2 = params.addAuto(val);
1664
+ return op === Ops.IN ? inArray(expr, placeholder2, dialect) : notInArray(expr, placeholder2, dialect);
1869
1665
  }
1870
- if (isEmptyWhere(where)) {
1871
- return freezeResult(DEFAULT_WHERE_CLAUSE, EMPTY_JOINS);
1666
+ if (!Array.isArray(val)) {
1667
+ throw createError(`IN operators require array value`, {
1668
+ operator: op,
1669
+ value: val
1670
+ });
1872
1671
  }
1873
- const allJoins = [];
1874
- const clauses = [];
1875
- for (const [key, value] of Object.entries(where)) {
1876
- if (value === void 0) continue;
1877
- const result = buildWhereEntry(key, value, ctx, builder);
1878
- appendResult(result, clauses, allJoins);
1672
+ if (val.length === 0) {
1673
+ return op === Ops.IN ? "0=1" : "1=1";
1879
1674
  }
1880
- const finalClause = clauses.length > 0 ? clauses.join(SQL_SEPARATORS.CONDITION_AND) : DEFAULT_WHERE_CLAUSE;
1881
- return freezeResult(finalClause, dedupePreserveOrder(allJoins));
1675
+ const paramValue = prepareArrayParam(val, dialect);
1676
+ const placeholder = params.add(paramValue);
1677
+ return op === Ops.IN ? inArray(expr, placeholder, dialect) : notInArray(expr, placeholder, dialect);
1882
1678
  }
1883
- function normalizeLogicalValue(operator, value, ctx) {
1884
- if (Array.isArray(value)) {
1885
- const out = [];
1886
- for (let i = 0; i < value.length; i++) {
1887
- const v = value[i];
1888
- if (v === void 0) continue;
1889
- if (!isPlainObject(v)) {
1890
- throw createError(`${operator} entries must be objects`, {
1891
- path: [...ctx.path, operator, String(i)],
1892
- modelName: ctx.model.name,
1893
- value: v
1894
- });
1895
- }
1896
- out.push(v);
1897
- }
1898
- return out;
1899
- }
1900
- if (isPlainObject(value)) {
1901
- return [value];
1679
+ function handleComparisonOperator(expr, op, val, params) {
1680
+ if (val === void 0) return "";
1681
+ const COMPARISON_OPS2 = {
1682
+ [Ops.EQUALS]: "=",
1683
+ [Ops.GT]: ">",
1684
+ [Ops.GTE]: ">=",
1685
+ [Ops.LT]: "<",
1686
+ [Ops.LTE]: "<="
1687
+ };
1688
+ const sqlOp = COMPARISON_OPS2[op];
1689
+ if (!sqlOp) {
1690
+ throw createError(`Unsupported scalar operator: ${op}`, { operator: op });
1902
1691
  }
1903
- throw createError(`${operator} must be an object or array of objects`, {
1904
- path: [...ctx.path, operator],
1905
- modelName: ctx.model.name,
1906
- value
1907
- });
1692
+ const placeholder = params.addAuto(val);
1693
+ return `${expr} ${sqlOp} ${placeholder}`;
1908
1694
  }
1909
- function collectLogicalParts(operator, conditions, ctx, builder) {
1910
- const allJoins = [];
1911
- const clauses = [];
1912
- for (let i = 0; i < conditions.length; i++) {
1913
- const result = builder.build(conditions[i], __spreadProps(__spreadValues({}, ctx), {
1914
- path: [...ctx.path, operator, String(i)],
1915
- depth: ctx.depth + 1
1916
- }));
1917
- if (isNonEmptyArray(result.joins)) allJoins.push(...result.joins);
1918
- if (result.clause && result.clause !== DEFAULT_WHERE_CLAUSE) {
1919
- clauses.push(`(${result.clause})`);
1920
- }
1695
+ function buildArrayParam(val, params, dialect) {
1696
+ if (schemaParser.isDynamicParameter(val)) {
1697
+ return params.addAuto(val);
1921
1698
  }
1922
- return {
1923
- joins: dedupePreserveOrder(allJoins),
1924
- clauses: Object.freeze(clauses)
1925
- };
1926
- }
1927
- function buildLogicalClause(operator, clauses) {
1928
- if (clauses.length === 0) return DEFAULT_WHERE_CLAUSE;
1929
- if (operator === "NOT") {
1930
- if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
1931
- return `${SQL_TEMPLATES.NOT} (${clauses.join(SQL_SEPARATORS.CONDITION_AND)})`;
1699
+ if (!Array.isArray(val)) {
1700
+ throw createError(`Array operation requires array value`, { value: val });
1932
1701
  }
1933
- return clauses.join(` ${operator} `);
1702
+ const paramValue = prepareArrayParam(val, dialect);
1703
+ return params.add(paramValue);
1934
1704
  }
1935
- function buildLogical(operator, value, ctx, builder) {
1936
- const conditions = normalizeLogicalValue(operator, value, ctx);
1937
- if (conditions.length === 0) {
1938
- const clause2 = operator === "OR" ? "0=1" : DEFAULT_WHERE_CLAUSE;
1939
- return freezeResult(clause2, EMPTY_JOINS);
1705
+ function buildArrayOperator(expr, op, val, params, fieldType, dialect) {
1706
+ if (val === void 0) return "";
1707
+ if (val === null) {
1708
+ if (op === Ops.EQUALS) return `${expr} ${SQL_TEMPLATES.IS_NULL}`;
1709
+ if (op === Ops.NOT) return `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
1940
1710
  }
1941
- const { joins, clauses } = collectLogicalParts(
1942
- operator,
1943
- conditions,
1944
- ctx,
1945
- builder
1946
- );
1947
- const clause = buildLogicalClause(operator, clauses);
1948
- return freezeResult(clause, joins);
1949
- }
1950
- function buildScalarField(fieldName, value, ctx) {
1951
- const field = assertFieldExists(fieldName, ctx.model, ctx.path);
1952
- const expr = col(ctx.alias, fieldName);
1953
- if (value === null) {
1954
- return freezeResult(`${expr} ${SQL_TEMPLATES.IS_NULL}`, EMPTY_JOINS);
1711
+ const cast = getArrayType(fieldType, dialect);
1712
+ if (op === Ops.EQUALS) {
1713
+ return handleArrayEquals(expr, val, params, cast, dialect);
1955
1714
  }
1956
- if (isPlainObject(value)) {
1957
- const mode = value.mode;
1958
- const ops = Object.entries(value).filter(
1959
- ([k, v]) => k !== "mode" && v !== void 0
1960
- );
1961
- if (ops.length === 0) {
1962
- return freezeResult(DEFAULT_WHERE_CLAUSE, EMPTY_JOINS);
1963
- }
1964
- const parts = [];
1965
- for (const [op, val] of ops) {
1966
- assertValidOperator(fieldName, op, field.type, ctx.path, ctx.model.name);
1967
- const clause3 = buildOperator(expr, op, val, ctx, mode, field.type);
1968
- if (isValidWhereClause(clause3)) parts.push(clause3);
1969
- }
1970
- const clause2 = parts.length > 0 ? parts.join(SQL_SEPARATORS.CONDITION_AND) : DEFAULT_WHERE_CLAUSE;
1971
- return freezeResult(clause2, EMPTY_JOINS);
1972
- }
1973
- const clause = buildOperator(
1974
- expr,
1975
- Ops.EQUALS,
1976
- value,
1977
- ctx,
1978
- void 0,
1979
- field.type
1980
- );
1981
- return freezeResult(clause || DEFAULT_WHERE_CLAUSE, EMPTY_JOINS);
1982
- }
1983
- function buildOperator(expr, op, val, ctx, mode, fieldType) {
1984
- if (fieldType && isArrayType(fieldType)) {
1985
- return buildArrayOperator(expr, op, val, ctx.params, fieldType, ctx.dialect);
1715
+ if (op === Ops.NOT) {
1716
+ return handleArrayNot(expr, val, params, cast, dialect);
1986
1717
  }
1987
- if (fieldType && isJsonType(fieldType)) {
1988
- const JSON_OPS = /* @__PURE__ */ new Set([
1989
- Ops.PATH,
1990
- Ops.STRING_CONTAINS,
1991
- Ops.STRING_STARTS_WITH,
1992
- Ops.STRING_ENDS_WITH
1993
- ]);
1994
- if (JSON_OPS.has(op)) {
1995
- return buildJsonOperator(expr, op, val, ctx.params, ctx.dialect);
1996
- }
1718
+ switch (op) {
1719
+ case Ops.HAS:
1720
+ return handleArrayHas(expr, val, params, cast, dialect);
1721
+ case Ops.HAS_SOME:
1722
+ return handleArrayHasSome(expr, val, params, cast, dialect);
1723
+ case Ops.HAS_EVERY:
1724
+ return handleArrayHasEvery(expr, val, params, cast, dialect);
1725
+ case Ops.IS_EMPTY:
1726
+ return handleArrayIsEmpty(expr, val, dialect);
1727
+ default:
1728
+ throw createError(`Unknown array operator: ${op}`, { operator: op });
1997
1729
  }
1998
- return buildScalarOperator(
1999
- expr,
2000
- op,
2001
- val,
2002
- ctx.params,
2003
- mode,
2004
- fieldType,
2005
- ctx.dialect
2006
- );
2007
1730
  }
2008
-
2009
- // src/builder/shared/alias-generator.ts
2010
- function toSafeSqlIdentifier(input) {
2011
- const raw = String(input);
2012
- const cleaned = raw.replace(/\W/g, "_");
2013
- const startsOk = /^[a-zA-Z_]/.test(cleaned);
2014
- const base = startsOk ? cleaned : `_${cleaned}`;
2015
- const fallback = base.length > 0 ? base : "_t";
2016
- const lowered = fallback.toLowerCase();
2017
- return SQL_KEYWORDS.has(lowered) ? `_${lowered}` : lowered;
1731
+ function handleArrayEquals(expr, val, params, cast, dialect) {
1732
+ if (val === void 0) return "";
1733
+ if (isEmptyArray(val)) {
1734
+ return arrayIsEmpty(expr, dialect);
1735
+ }
1736
+ const placeholder = buildArrayParam(val, params, dialect);
1737
+ return arrayEquals(expr, placeholder, cast, dialect);
2018
1738
  }
2019
- function createAliasGenerator(maxAliases = 1e4) {
2020
- let counter = 0;
2021
- const usedAliases = /* @__PURE__ */ new Set();
2022
- return {
2023
- next(baseName) {
2024
- if (usedAliases.size >= maxAliases) {
2025
- throw new Error(
2026
- `Alias generator exceeded maximum of ${maxAliases} aliases. This indicates a query complexity issue or potential infinite loop.`
2027
- );
2028
- }
2029
- const base = toSafeSqlIdentifier(baseName);
2030
- const suffix = `_${counter}`;
2031
- const maxLen = 63;
2032
- const baseMax = Math.max(1, maxLen - suffix.length);
2033
- const trimmedBase = base.length > baseMax ? base.slice(0, baseMax) : base;
2034
- const alias = `${trimmedBase}${suffix}`;
2035
- counter += 1;
2036
- if (usedAliases.has(alias)) {
2037
- throw new Error(
2038
- `CRITICAL: Duplicate alias '${alias}' at counter=${counter}. This indicates a bug in alias generation logic.`
2039
- );
2040
- }
2041
- usedAliases.add(alias);
2042
- return alias;
1739
+ function handleArrayNot(expr, val, params, cast, dialect) {
1740
+ if (val === void 0) return "";
1741
+ let target = val;
1742
+ if (isPlainObject(val)) {
1743
+ const entries = Object.entries(val).filter(([, v]) => v !== void 0);
1744
+ if (entries.length === 1 && entries[0][0] === Ops.EQUALS) {
1745
+ target = entries[0][1];
1746
+ } else {
1747
+ throw createError(`Array NOT only supports { equals: ... } shape`, {
1748
+ operator: Ops.NOT,
1749
+ value: val
1750
+ });
2043
1751
  }
2044
- };
2045
- }
2046
- var MAX_PARAM_INDEX = Number.MAX_SAFE_INTEGER - 1e3;
2047
- function assertSameLength(params, mappings) {
2048
- if (params.length !== mappings.length) {
2049
- throw new Error(
2050
- `CRITICAL: State corruption - params=${params.length}, mappings=${mappings.length}`
2051
- );
2052
1752
  }
2053
- }
2054
- function assertValidNextIndex(index) {
2055
- if (!Number.isInteger(index) || index < 1) {
2056
- throw new Error(`CRITICAL: Index must be integer >= 1, got ${index}`);
1753
+ if (target === null) {
1754
+ return `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
2057
1755
  }
2058
- }
2059
- function assertNextIndexMatches(mappingsLength, nextIndex) {
2060
- const expected = mappingsLength + 1;
2061
- if (nextIndex !== expected) {
2062
- throw new Error(
2063
- `CRITICAL: Next index mismatch - expected ${expected}, got ${nextIndex}`
2064
- );
1756
+ if (isEmptyArray(target)) {
1757
+ return arrayIsNotEmpty(expr, dialect);
2065
1758
  }
1759
+ const placeholder = buildArrayParam(target, params, dialect);
1760
+ return `${SQL_TEMPLATES.NOT} (${arrayEquals(expr, placeholder, cast, dialect)})`;
2066
1761
  }
2067
- function assertSequentialIndex(actual, expected) {
2068
- if (actual !== expected) {
2069
- throw new Error(
2070
- `CRITICAL: Indices must be sequential from 1..N. Expected ${expected}, got ${actual}`
2071
- );
1762
+ function handleArrayHas(expr, val, params, cast, dialect) {
1763
+ if (val === void 0) return "";
1764
+ if (val === null) {
1765
+ throw createError(`has requires scalar value`, {
1766
+ operator: Ops.HAS,
1767
+ value: val
1768
+ });
2072
1769
  }
2073
- }
2074
- function assertExactlyOneOfDynamicOrValue(m) {
2075
- const hasDynamic = typeof m.dynamicName === "string";
2076
- const hasStatic = m.value !== void 0;
2077
- if (hasDynamic === hasStatic) {
2078
- throw new Error(
2079
- `CRITICAL: ParamMap ${m.index} must have exactly one of dynamicName or value`
2080
- );
1770
+ if (!schemaParser.isDynamicParameter(val) && Array.isArray(val)) {
1771
+ throw createError(`has requires scalar value (single element), not array`, {
1772
+ operator: Ops.HAS,
1773
+ value: val
1774
+ });
2081
1775
  }
2082
- }
2083
- function normalizeDynamicNameOrThrow(dynamicName, index) {
2084
- const dn = dynamicName.trim();
2085
- if (dn.length === 0) {
2086
- throw new Error(`CRITICAL: dynamicName cannot be empty (index=${index})`);
1776
+ if (isPlainObject(val)) {
1777
+ throw createError(`has requires scalar value`, {
1778
+ operator: Ops.HAS,
1779
+ value: val
1780
+ });
2087
1781
  }
2088
- return dn;
1782
+ const placeholder = params.addAuto(val);
1783
+ return arrayContains(expr, placeholder, cast, dialect);
2089
1784
  }
2090
- function assertUniqueDynamicName(dn, seen) {
2091
- if (seen.has(dn)) {
2092
- throw new Error(`CRITICAL: Duplicate dynamic param name in mappings: ${dn}`);
1785
+ function handleArrayHasSome(expr, val, params, cast, dialect) {
1786
+ if (val === void 0) return "";
1787
+ if (schemaParser.isDynamicParameter(val)) {
1788
+ const placeholder2 = params.addAuto(val);
1789
+ return arrayOverlaps(expr, placeholder2, cast, dialect);
2093
1790
  }
2094
- seen.add(dn);
2095
- }
2096
- function validateMappingEntry(m, expectedIndex, seenDynamic) {
2097
- assertSequentialIndex(m.index, expectedIndex);
2098
- assertExactlyOneOfDynamicOrValue(m);
2099
- if (typeof m.dynamicName === "string") {
2100
- const dn = normalizeDynamicNameOrThrow(m.dynamicName, m.index);
2101
- assertUniqueDynamicName(dn, seenDynamic);
1791
+ if (!Array.isArray(val)) {
1792
+ throw createError(`hasSome requires array value`, {
1793
+ operator: Ops.HAS_SOME,
1794
+ value: val
1795
+ });
2102
1796
  }
2103
- }
2104
- function validateMappings(mappings) {
2105
- const seenDynamic = /* @__PURE__ */ new Set();
2106
- for (let i = 0; i < mappings.length; i++) {
2107
- validateMappingEntry(mappings[i], i + 1, seenDynamic);
1797
+ if (val.length > LIMITS.MAX_ARRAY_SIZE) {
1798
+ throw createError(
1799
+ `Array too large (${val.length} elements, max ${LIMITS.MAX_ARRAY_SIZE})`,
1800
+ { operator: Ops.HAS_SOME, value: `[${val.length} items]` }
1801
+ );
2108
1802
  }
1803
+ if (val.length === 0) return "0=1";
1804
+ const paramValue = prepareArrayParam(val, dialect);
1805
+ const placeholder = params.add(paramValue);
1806
+ return arrayOverlaps(expr, placeholder, cast, dialect);
2109
1807
  }
2110
- function validateState(params, mappings, index) {
2111
- assertSameLength(params, mappings);
2112
- assertValidNextIndex(index);
2113
- if (mappings.length === 0) return;
2114
- validateMappings(mappings);
2115
- assertNextIndexMatches(mappings.length, index);
1808
+ function handleArrayHasEvery(expr, val, params, cast, dialect) {
1809
+ if (val === void 0) return "";
1810
+ const placeholder = buildArrayParam(val, params, dialect);
1811
+ return arrayContainsAll(expr, placeholder, cast, dialect);
2116
1812
  }
2117
- function normalizeValue(value) {
2118
- if (value instanceof Date) {
2119
- return value.toISOString();
1813
+ function handleArrayIsEmpty(expr, val, dialect) {
1814
+ if (typeof val !== "boolean") {
1815
+ throw createError(`isEmpty requires boolean value`, {
1816
+ operator: Ops.IS_EMPTY,
1817
+ value: val
1818
+ });
2120
1819
  }
2121
- return value;
1820
+ return val === true ? arrayIsEmpty(expr, dialect) : arrayIsNotEmpty(expr, dialect);
2122
1821
  }
2123
- function createStoreInternal(startIndex, initialParams = [], initialMappings = []) {
2124
- let index = startIndex;
2125
- const params = [...initialParams];
2126
- const mappings = [...initialMappings];
2127
- const dynamicNameToIndex = /* @__PURE__ */ new Map();
2128
- for (const m of initialMappings) {
2129
- if (typeof m.dynamicName === "string") {
2130
- dynamicNameToIndex.set(m.dynamicName.trim(), m.index);
1822
+
1823
+ // src/builder/where/operators-json.ts
1824
+ var SAFE_JSON_PATH_SEGMENT = /^[a-zA-Z_]\w*$/;
1825
+ function validateJsonPathSegments(segments) {
1826
+ for (const segment of segments) {
1827
+ if (typeof segment !== "string") {
1828
+ throw createError("JSON path segments must be strings", {
1829
+ operator: Ops.PATH,
1830
+ value: segment
1831
+ });
2131
1832
  }
2132
- }
2133
- function assertCanAdd() {
2134
- if (index > MAX_PARAM_INDEX) {
2135
- throw new Error(
2136
- `CRITICAL: Cannot add param - would overflow MAX_SAFE_INTEGER. Current index: ${index}`
1833
+ if (!SAFE_JSON_PATH_SEGMENT.test(segment)) {
1834
+ throw createError(
1835
+ `Invalid JSON path segment: '${segment}'. Must be alphanumeric with underscores, starting with letter or underscore.`,
1836
+ { operator: Ops.PATH, value: segment }
2137
1837
  );
2138
1838
  }
2139
1839
  }
2140
- function normalizeDynamicName(dynamicName) {
2141
- const dn = dynamicName.trim();
2142
- if (dn.length === 0) {
2143
- throw new Error("CRITICAL: dynamicName cannot be empty");
2144
- }
2145
- return dn;
1840
+ }
1841
+ function buildJsonOperator(expr, op, val, params, dialect) {
1842
+ if (val === void 0) return "";
1843
+ if (op === Ops.PATH && isPlainObject(val) && "path" in val) {
1844
+ return handleJsonPath(expr, val, params, dialect);
2146
1845
  }
2147
- function format(position) {
2148
- return `$${position}`;
1846
+ const jsonWildcards = {
1847
+ [Ops.STRING_CONTAINS]: (v) => `%${v}%`,
1848
+ [Ops.STRING_STARTS_WITH]: (v) => `${v}%`,
1849
+ [Ops.STRING_ENDS_WITH]: (v) => `%${v}`
1850
+ };
1851
+ if (op in jsonWildcards) {
1852
+ return handleJsonWildcard(expr, op, val, params, jsonWildcards, dialect);
2149
1853
  }
2150
- function addDynamic(dynamicName) {
2151
- const dn = normalizeDynamicName(dynamicName);
2152
- const existing = dynamicNameToIndex.get(dn);
2153
- if (existing !== void 0) {
2154
- return format(existing);
2155
- }
2156
- const position = index;
2157
- dynamicNameToIndex.set(dn, position);
2158
- params.push(void 0);
2159
- mappings.push({ index: position, dynamicName: dn });
2160
- index++;
2161
- return format(position);
1854
+ throw createError(`Unsupported JSON operator: ${op}`, { operator: op });
1855
+ }
1856
+ function handleJsonPath(expr, val, params, dialect) {
1857
+ const v = val;
1858
+ if (!Array.isArray(v.path)) {
1859
+ throw createError("JSON path must be an array", { operator: Ops.PATH });
2162
1860
  }
2163
- function addStatic(value) {
2164
- const position = index;
2165
- const normalizedValue = normalizeValue(value);
2166
- params.push(normalizedValue);
2167
- mappings.push({ index: position, value: normalizedValue });
2168
- index++;
2169
- return format(position);
1861
+ if (v.path.length === 0) {
1862
+ throw createError("JSON path cannot be empty", { operator: Ops.PATH });
2170
1863
  }
2171
- function add(value, dynamicName) {
2172
- assertCanAdd();
2173
- return dynamicName === void 0 ? addStatic(value) : addDynamic(dynamicName);
1864
+ validateJsonPathSegments(v.path);
1865
+ const pathExpr = dialect === "sqlite" ? params.add(`$.${v.path.join(".")}`) : params.add(v.path);
1866
+ const rawOps = [
1867
+ ["=", v.equals],
1868
+ [">", v.gt],
1869
+ [">=", v.gte],
1870
+ ["<", v.lt],
1871
+ ["<=", v.lte]
1872
+ ];
1873
+ const ops = rawOps.filter(
1874
+ ([, value]) => value !== void 0
1875
+ );
1876
+ if (ops.length === 0) {
1877
+ throw createError("JSON path query missing comparison operator", {
1878
+ operator: Ops.PATH
1879
+ });
2174
1880
  }
2175
- function addAuto(value) {
2176
- if (schemaParser.isDynamicParameter(value)) {
2177
- const dynamicName = schemaParser.extractDynamicName(value);
2178
- return add(void 0, dynamicName);
1881
+ const parts = [];
1882
+ for (const [sqlOp, value] of ops) {
1883
+ if (value === null) {
1884
+ const base2 = jsonExtractText(expr, pathExpr, dialect);
1885
+ parts.push(`${base2} ${SQL_TEMPLATES.IS_NULL}`);
1886
+ continue;
2179
1887
  }
2180
- return add(value);
1888
+ const valPh = params.add(value);
1889
+ const base = typeof value === "number" ? jsonExtractNumeric(expr, pathExpr, dialect) : jsonExtractText(expr, pathExpr, dialect);
1890
+ parts.push(`${base} ${sqlOp} ${valPh}`);
2181
1891
  }
2182
- function snapshot() {
2183
- return Object.freeze({
2184
- index,
2185
- params: Object.freeze([...params]),
2186
- mappings: Object.freeze([...mappings])
1892
+ return parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`;
1893
+ }
1894
+ function handleJsonWildcard(expr, op, val, params, wildcards, dialect) {
1895
+ if (!isNotNullish(val)) {
1896
+ throw createError(`JSON string operator requires non-null value`, {
1897
+ operator: op,
1898
+ value: val
2187
1899
  });
2188
1900
  }
2189
- return {
2190
- add,
2191
- addAuto,
2192
- snapshot,
2193
- get index() {
2194
- return index;
2195
- }
2196
- };
2197
- }
2198
- function createParamStore(startIndex = 1) {
2199
- if (!Number.isInteger(startIndex) || startIndex < 1) {
2200
- throw new Error(`Start index must be integer >= 1, got ${startIndex}`);
1901
+ if (isPlainObject(val) || Array.isArray(val)) {
1902
+ throw createError(`JSON string operator requires scalar value`, {
1903
+ operator: op,
1904
+ value: val
1905
+ });
2201
1906
  }
2202
- if (startIndex > MAX_PARAM_INDEX) {
2203
- throw new Error(
2204
- `Start index too high (${startIndex}), risk of overflow at MAX_SAFE_INTEGER`
1907
+ const strVal = String(val);
1908
+ if (strVal.length > LIMITS.MAX_STRING_LENGTH) {
1909
+ throw createError(
1910
+ `String too long (${strVal.length} chars, max ${LIMITS.MAX_STRING_LENGTH})`,
1911
+ { operator: op }
2205
1912
  );
2206
1913
  }
2207
- return createStoreInternal(startIndex);
2208
- }
2209
- function createParamStoreFrom(existingParams, existingMappings, nextIndex) {
2210
- validateState([...existingParams], [...existingMappings], nextIndex);
2211
- return createStoreInternal(
2212
- nextIndex,
2213
- [...existingParams],
2214
- [...existingMappings]
2215
- );
1914
+ const placeholder = params.add(wildcards[op](strVal));
1915
+ const jsonText = jsonToText(expr, dialect);
1916
+ return caseInsensitiveLike(jsonText, placeholder, dialect);
2216
1917
  }
2217
1918
 
2218
- // src/builder/shared/state.ts
2219
- function toPublicResult(clause, joins, params) {
2220
- const snapshot = params.snapshot();
1919
+ // src/builder/where/relations.ts
1920
+ var NO_JOINS = Object.freeze([]);
1921
+ function isListRelation(fieldType) {
1922
+ return typeof fieldType === "string" && fieldType.endsWith("[]");
1923
+ }
1924
+ function buildToOneNullCheck(field, parentAlias, relTable, relAlias, join3, wantNull) {
1925
+ const isLocal = field.isForeignKeyLocal === true;
1926
+ const fkFields = normalizeKeyList(field.foreignKey);
1927
+ if (isLocal) {
1928
+ if (fkFields.length === 0) {
1929
+ throw createError(`Relation '${field.name}' is missing foreignKey`, {
1930
+ field: field.name
1931
+ });
1932
+ }
1933
+ const parts = fkFields.map((fk) => {
1934
+ const safe = fk.replace(/"/g, '""');
1935
+ const expr = `${parentAlias}."${safe}"`;
1936
+ return wantNull ? `${expr} ${SQL_TEMPLATES.IS_NULL}` : `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
1937
+ });
1938
+ if (parts.length === 1) return parts[0];
1939
+ return wantNull ? `(${parts.join(" OR ")})` : `(${parts.join(" AND ")})`;
1940
+ }
1941
+ const existsSql = `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias} ${SQL_TEMPLATES.WHERE} ${join3})`;
1942
+ return wantNull ? `${SQL_TEMPLATES.NOT} ${existsSql}` : existsSql;
1943
+ }
1944
+ function buildToOneExistsMatch(relTable, relAlias, join3, sub) {
1945
+ const joins = sub.joins.length > 0 ? ` ${sub.joins.join(" ")}` : "";
1946
+ return `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias}${joins} ${SQL_TEMPLATES.WHERE} ${join3} ${SQL_TEMPLATES.AND} ${sub.clause})`;
1947
+ }
1948
+ function buildToOneNotExistsMatch(relTable, relAlias, join3, sub) {
1949
+ const joins = sub.joins.length > 0 ? ` ${sub.joins.join(" ")}` : "";
1950
+ return `${SQL_TEMPLATES.NOT} EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias}${joins} ${SQL_TEMPLATES.WHERE} ${join3} ${SQL_TEMPLATES.AND} ${sub.clause})`;
1951
+ }
1952
+ function buildListRelationFilters(args) {
1953
+ const {
1954
+ fieldName,
1955
+ value,
1956
+ ctx,
1957
+ whereBuilder,
1958
+ relModel,
1959
+ relTable,
1960
+ relAlias,
1961
+ join: join3
1962
+ } = args;
1963
+ const noneValue = value[RelationFilters.NONE];
1964
+ if (noneValue !== void 0 && noneValue !== null) {
1965
+ const sub = whereBuilder.build(noneValue, __spreadProps(__spreadValues({}, ctx), {
1966
+ alias: relAlias,
1967
+ model: relModel,
1968
+ path: [...ctx.path, fieldName, RelationFilters.NONE],
1969
+ isSubquery: true,
1970
+ depth: ctx.depth + 1
1971
+ }));
1972
+ const isEmptyFilter = isPlainObject(noneValue) && Object.keys(noneValue).length === 0;
1973
+ const canOptimize = !ctx.isSubquery && isEmptyFilter && sub.clause === DEFAULT_WHERE_CLAUSE && sub.joins.length === 0;
1974
+ if (canOptimize) {
1975
+ const checkField = relModel.fields.find(
1976
+ (f) => !f.isRelation && f.isRequired && f.name !== "id"
1977
+ ) || relModel.fields.find((f) => !f.isRelation && f.name === "id");
1978
+ if (checkField) {
1979
+ const leftJoinSql = `LEFT JOIN ${relTable} ${relAlias} ON ${join3}`;
1980
+ const whereClause = `${relAlias}.${quote(checkField.name)} IS NULL`;
1981
+ return Object.freeze({
1982
+ clause: whereClause,
1983
+ joins: [leftJoinSql]
1984
+ });
1985
+ }
1986
+ }
1987
+ }
1988
+ const filters = [
1989
+ {
1990
+ key: RelationFilters.SOME,
1991
+ wrap: (c, j) => `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias}${j} ${SQL_TEMPLATES.WHERE} ${join3} ${SQL_TEMPLATES.AND} ${c})`
1992
+ },
1993
+ {
1994
+ key: RelationFilters.EVERY,
1995
+ wrap: (c, j) => `${SQL_TEMPLATES.NOT} EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias}${j} ${SQL_TEMPLATES.WHERE} ${join3} ${SQL_TEMPLATES.AND} ${SQL_TEMPLATES.NOT} (${c}))`
1996
+ },
1997
+ {
1998
+ key: RelationFilters.NONE,
1999
+ wrap: (c, j) => {
2000
+ const condition = c === DEFAULT_WHERE_CLAUSE ? "" : ` ${SQL_TEMPLATES.AND} ${c}`;
2001
+ return `${SQL_TEMPLATES.NOT} EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias}${j} ${SQL_TEMPLATES.WHERE} ${join3}${condition})`;
2002
+ }
2003
+ }
2004
+ ];
2005
+ const clauses = [];
2006
+ for (const { key, wrap } of filters) {
2007
+ const raw = value[key];
2008
+ if (raw === void 0 || raw === null) continue;
2009
+ const sub = whereBuilder.build(raw, __spreadProps(__spreadValues({}, ctx), {
2010
+ alias: relAlias,
2011
+ model: relModel,
2012
+ path: [...ctx.path, fieldName, key],
2013
+ isSubquery: true,
2014
+ depth: ctx.depth + 1
2015
+ }));
2016
+ const j = sub.joins.length > 0 ? ` ${sub.joins.join(" ")}` : "";
2017
+ clauses.push(wrap(sub.clause, j));
2018
+ }
2019
+ if (clauses.length === 0) {
2020
+ throw createError(
2021
+ `List relation '${fieldName}' requires one of { some, every, none }`,
2022
+ { field: fieldName, path: ctx.path, modelName: ctx.model.name }
2023
+ );
2024
+ }
2221
2025
  return Object.freeze({
2222
- clause: clause || DEFAULT_WHERE_CLAUSE,
2223
- joins: Object.freeze([...joins]),
2224
- params: snapshot.params,
2225
- paramMappings: snapshot.mappings,
2226
- nextParamIndex: snapshot.index
2026
+ clause: clauses.join(SQL_SEPARATORS.CONDITION_AND),
2027
+ joins: NO_JOINS
2227
2028
  });
2228
2029
  }
2229
-
2230
- // src/builder/where.ts
2231
- function buildWhereClause(where, options) {
2232
- var _a, _b, _c, _d, _e;
2233
- const dialect = options.dialect || getGlobalDialect();
2234
- const params = (_a = options.params) != null ? _a : createParamStore();
2235
- const ctx = {
2236
- alias: options.alias,
2237
- model: options.model,
2238
- schemaModels: (_b = options.schemaModels) != null ? _b : [],
2239
- path: (_c = options.path) != null ? _c : [],
2240
- isSubquery: (_d = options.isSubquery) != null ? _d : false,
2241
- aliasGen: (_e = options.aliasGen) != null ? _e : createAliasGenerator(),
2242
- dialect,
2243
- params,
2244
- depth: 0
2245
- };
2246
- const result = whereBuilderInstance.build(where, ctx);
2247
- const publicResult = toPublicResult(result.clause, result.joins, params);
2248
- if (!options.isSubquery) {
2249
- const nums = [...publicResult.clause.matchAll(/\$(\d+)/g)].map(
2250
- (m) => parseInt(m[1], 10)
2030
+ function buildToOneRelationFilters(args) {
2031
+ const {
2032
+ fieldName,
2033
+ value,
2034
+ ctx,
2035
+ whereBuilder,
2036
+ field,
2037
+ relModel,
2038
+ relTable,
2039
+ relAlias,
2040
+ join: join3
2041
+ } = args;
2042
+ const hasSomeEveryNone = isNotNullish(value[RelationFilters.SOME]) || isNotNullish(value[RelationFilters.EVERY]) || isNotNullish(value[RelationFilters.NONE]);
2043
+ if (hasSomeEveryNone) {
2044
+ throw createError(
2045
+ `To-one relation '${fieldName}' does not support { some, every, none }; use { is, isNot }`,
2046
+ { field: fieldName, path: ctx.path, modelName: ctx.model.name }
2251
2047
  );
2252
- if (nums.length > 0) {
2253
- const min = Math.min(...nums);
2254
- if (min === 1) {
2255
- validateParamConsistency(publicResult.clause, publicResult.params);
2256
- } else {
2257
- validateParamConsistencyFragment(
2258
- publicResult.clause,
2259
- publicResult.params
2260
- );
2048
+ }
2049
+ const hasIs = Object.prototype.hasOwnProperty.call(value, "is");
2050
+ const hasIsNot = Object.prototype.hasOwnProperty.call(value, "isNot");
2051
+ let filterKey;
2052
+ let filterVal;
2053
+ if (hasIs) {
2054
+ filterKey = "is";
2055
+ filterVal = value.is;
2056
+ } else if (hasIsNot) {
2057
+ filterKey = "isNot";
2058
+ filterVal = value.isNot;
2059
+ } else {
2060
+ filterKey = "is";
2061
+ filterVal = value;
2062
+ }
2063
+ if (filterVal === void 0) {
2064
+ return Object.freeze({
2065
+ clause: DEFAULT_WHERE_CLAUSE,
2066
+ joins: NO_JOINS
2067
+ });
2068
+ }
2069
+ if (filterVal === null) {
2070
+ const wantNull = filterKey === "is";
2071
+ const clause2 = buildToOneNullCheck(
2072
+ field,
2073
+ ctx.alias,
2074
+ relTable,
2075
+ relAlias,
2076
+ join3,
2077
+ wantNull
2078
+ );
2079
+ return Object.freeze({
2080
+ clause: clause2,
2081
+ joins: NO_JOINS
2082
+ });
2083
+ }
2084
+ if (!isPlainObject(filterVal)) {
2085
+ throw createError(
2086
+ `Relation '${fieldName}' filter must be an object or null`,
2087
+ {
2088
+ field: fieldName,
2089
+ path: ctx.path,
2090
+ modelName: ctx.model.name,
2091
+ value: filterVal
2092
+ }
2093
+ );
2094
+ }
2095
+ const sub = whereBuilder.build(filterVal, __spreadProps(__spreadValues({}, ctx), {
2096
+ alias: relAlias,
2097
+ model: relModel,
2098
+ path: [...ctx.path, fieldName, filterKey],
2099
+ isSubquery: true,
2100
+ depth: ctx.depth + 1
2101
+ }));
2102
+ const clause = filterKey === "is" ? buildToOneExistsMatch(relTable, relAlias, join3, sub) : buildToOneNotExistsMatch(relTable, relAlias, join3, sub);
2103
+ return Object.freeze({
2104
+ clause,
2105
+ joins: NO_JOINS
2106
+ });
2107
+ }
2108
+ function ensureRelationFilterObject(fieldName, value, ctx) {
2109
+ if (!isPlainObject(value)) {
2110
+ throw createError(`Relation filter '${fieldName}' must be an object`, {
2111
+ path: [...ctx.path, fieldName],
2112
+ field: fieldName,
2113
+ modelName: ctx.model.name,
2114
+ value
2115
+ });
2116
+ }
2117
+ }
2118
+ function buildRelation(fieldName, value, ctx, whereBuilder) {
2119
+ const field = ctx.model.fields.find((f) => f.name === fieldName);
2120
+ if (!isValidRelationField(field)) {
2121
+ throw createError(`Invalid relation '${fieldName}'`, {
2122
+ field: fieldName,
2123
+ path: ctx.path,
2124
+ modelName: ctx.model.name
2125
+ });
2126
+ }
2127
+ const relModel = ctx.schemaModels.find((m) => m.name === field.relatedModel);
2128
+ if (!isNotNullish(relModel)) {
2129
+ throw createError(
2130
+ `Related model '${field.relatedModel}' not found in schema. Available models: ${ctx.schemaModels.map((m) => m.name).join(", ")}`,
2131
+ {
2132
+ field: fieldName,
2133
+ path: ctx.path,
2134
+ modelName: ctx.model.name
2261
2135
  }
2136
+ );
2137
+ }
2138
+ const relTable = buildTableReference(
2139
+ SQL_TEMPLATES.PUBLIC_SCHEMA,
2140
+ relModel.tableName,
2141
+ ctx.dialect
2142
+ );
2143
+ const relAlias = ctx.aliasGen.next(fieldName);
2144
+ const join3 = joinCondition(field, ctx.model, relModel, ctx.alias, relAlias);
2145
+ const args = {
2146
+ fieldName,
2147
+ value,
2148
+ ctx,
2149
+ whereBuilder,
2150
+ field,
2151
+ relModel,
2152
+ relTable,
2153
+ relAlias,
2154
+ join: join3
2155
+ };
2156
+ if (isListRelation(field.type)) return buildListRelationFilters(args);
2157
+ return buildToOneRelationFilters(args);
2158
+ }
2159
+ function buildTopLevelRelation(fieldName, value, ctx, whereBuilder) {
2160
+ ensureRelationFilterObject(fieldName, value, ctx);
2161
+ return buildRelation(fieldName, value, ctx, whereBuilder);
2162
+ }
2163
+ function buildNestedRelation(fieldName, value, ctx, whereBuilder) {
2164
+ return buildTopLevelRelation(fieldName, value, ctx, whereBuilder);
2165
+ }
2166
+
2167
+ // src/builder/shared/validators/field-validators.ts
2168
+ function assertFieldExists(name, model, path) {
2169
+ const field = model.fields.find((f) => f.name === name);
2170
+ if (!isNotNullish(field)) {
2171
+ throw createError(`Field '${name}' does not exist on '${model.name}'`, {
2172
+ field: name,
2173
+ path,
2174
+ modelName: model.name,
2175
+ availableFields: model.fields.map((f) => f.name)
2176
+ });
2177
+ }
2178
+ return field;
2179
+ }
2180
+ function assertValidOperator(fieldName, op, fieldType, path, modelName) {
2181
+ if (!isNotNullish(fieldType)) return;
2182
+ const ARRAY_OPS = /* @__PURE__ */ new Set([
2183
+ Ops.HAS,
2184
+ Ops.HAS_SOME,
2185
+ Ops.HAS_EVERY,
2186
+ Ops.IS_EMPTY
2187
+ ]);
2188
+ const JSON_OPS = /* @__PURE__ */ new Set([
2189
+ Ops.PATH,
2190
+ Ops.STRING_CONTAINS,
2191
+ Ops.STRING_STARTS_WITH,
2192
+ Ops.STRING_ENDS_WITH
2193
+ ]);
2194
+ const isArrayOp = ARRAY_OPS.has(op);
2195
+ const isFieldArray = isArrayType(fieldType);
2196
+ const arrayOpMismatch = isArrayOp && !isFieldArray;
2197
+ if (arrayOpMismatch) {
2198
+ throw createError(`'${op}' requires array field, got '${fieldType}'`, {
2199
+ operator: op,
2200
+ field: fieldName,
2201
+ path,
2202
+ modelName
2203
+ });
2204
+ }
2205
+ const isJsonOp = JSON_OPS.has(op);
2206
+ const isFieldJson = isJsonType(fieldType);
2207
+ const jsonOpMismatch = isJsonOp && !isFieldJson;
2208
+ if (jsonOpMismatch) {
2209
+ throw createError(`'${op}' requires JSON field, got '${fieldType}'`, {
2210
+ operator: op,
2211
+ field: fieldName,
2212
+ path,
2213
+ modelName
2214
+ });
2215
+ }
2216
+ }
2217
+
2218
+ // src/builder/where/builder.ts
2219
+ var WhereBuilder = class {
2220
+ build(where, ctx) {
2221
+ if (!isPlainObject(where)) {
2222
+ throw createError("where must be an object", {
2223
+ path: ctx.path,
2224
+ modelName: ctx.model.name
2225
+ });
2226
+ }
2227
+ return buildWhereInternal(where, ctx, this);
2228
+ }
2229
+ };
2230
+ var MAX_QUERY_DEPTH = 50;
2231
+ var EMPTY_JOINS = Object.freeze([]);
2232
+ var whereBuilderInstance = new WhereBuilder();
2233
+ function freezeResult(clause, joins = EMPTY_JOINS) {
2234
+ return Object.freeze({ clause, joins });
2235
+ }
2236
+ function dedupePreserveOrder(items) {
2237
+ if (items.length <= 1) return Object.freeze([...items]);
2238
+ const seen = /* @__PURE__ */ new Set();
2239
+ const out = [];
2240
+ for (const s of items) {
2241
+ if (!seen.has(s)) {
2242
+ seen.add(s);
2243
+ out.push(s);
2244
+ }
2245
+ }
2246
+ return Object.freeze(out);
2247
+ }
2248
+ function appendResult(result, clauses, allJoins) {
2249
+ if (isValidWhereClause(result.clause)) clauses.push(result.clause);
2250
+ if (isNonEmptyArray(result.joins)) allJoins.push(...result.joins);
2251
+ }
2252
+ function asLogicalOperator(key) {
2253
+ if (key === LogicalOps.AND) return "AND";
2254
+ if (key === LogicalOps.OR) return "OR";
2255
+ if (key === LogicalOps.NOT) return "NOT";
2256
+ return null;
2257
+ }
2258
+ function nextContext(ctx) {
2259
+ return __spreadProps(__spreadValues({}, ctx), { depth: ctx.depth + 1 });
2260
+ }
2261
+ function buildRelationFilter(fieldName, value, ctx, builder) {
2262
+ const ctx2 = nextContext(ctx);
2263
+ if (ctx.isSubquery) {
2264
+ return buildNestedRelation(fieldName, value, ctx2, builder);
2265
+ }
2266
+ return buildTopLevelRelation(fieldName, value, ctx2, builder);
2267
+ }
2268
+ function buildWhereEntry(key, value, ctx, builder) {
2269
+ const op = asLogicalOperator(key);
2270
+ if (op) return buildLogical(op, value, ctx, builder);
2271
+ if (isRelationField(key, ctx.model)) {
2272
+ if (!isPlainObject(value)) {
2273
+ throw createError(`Relation filter '${key}' must be an object`, {
2274
+ path: [...ctx.path, key],
2275
+ field: key,
2276
+ modelName: ctx.model.name,
2277
+ value
2278
+ });
2262
2279
  }
2280
+ return buildRelationFilter(key, value, ctx, builder);
2263
2281
  }
2264
- return publicResult;
2282
+ return buildScalarField(key, value, ctx);
2265
2283
  }
2266
- function normalizeIntLike(name, v, opts = {}) {
2267
- var _a, _b;
2268
- if (!isNotNullish(v)) return void 0;
2269
- if (schemaParser.isDynamicParameter(v)) return v;
2270
- if (typeof v !== "number" || !Number.isFinite(v) || !Number.isInteger(v)) {
2271
- throw new Error(`${name} must be an integer`);
2272
- }
2273
- const min = (_a = opts.min) != null ? _a : 0;
2274
- const allowZero = (_b = opts.allowZero) != null ? _b : true;
2275
- if (!allowZero && v === 0) {
2276
- throw new Error(`${name} must be > 0`);
2277
- }
2278
- if (v < min) {
2279
- throw new Error(`${name} must be >= ${min}`);
2284
+ function buildWhereInternal(where, ctx, builder) {
2285
+ if (ctx.depth > MAX_QUERY_DEPTH) {
2286
+ throw createError(
2287
+ `Query nesting too deep (max ${MAX_QUERY_DEPTH} levels). This usually indicates a circular reference.`,
2288
+ { path: ctx.path, modelName: ctx.model.name }
2289
+ );
2280
2290
  }
2281
- if (typeof opts.max === "number" && v > opts.max) {
2282
- throw new Error(`${name} must be <= ${opts.max}`);
2291
+ if (isEmptyWhere(where)) {
2292
+ return freezeResult(DEFAULT_WHERE_CLAUSE, EMPTY_JOINS);
2283
2293
  }
2284
- return v;
2285
- }
2286
- function scopeName(scope, dynamicName) {
2287
- const s = String(scope).trim();
2288
- const dn = String(dynamicName).trim();
2289
- if (s.length === 0) return dn;
2290
- return `${s}:${dn}`;
2291
- }
2292
- function addAutoScoped(params, value, scope) {
2293
- if (schemaParser.isDynamicParameter(value)) {
2294
- const dn = schemaParser.extractDynamicName(value);
2295
- return params.add(void 0, scopeName(scope, dn));
2294
+ const allJoins = [];
2295
+ const clauses = [];
2296
+ for (const [key, value] of Object.entries(where)) {
2297
+ if (value === void 0) continue;
2298
+ const result = buildWhereEntry(key, value, ctx, builder);
2299
+ appendResult(result, clauses, allJoins);
2296
2300
  }
2297
- return params.add(value);
2301
+ const finalClause = clauses.length > 0 ? clauses.join(SQL_SEPARATORS.CONDITION_AND) : DEFAULT_WHERE_CLAUSE;
2302
+ return freezeResult(finalClause, dedupePreserveOrder(allJoins));
2298
2303
  }
2299
-
2300
- // src/builder/shared/order-by-utils.ts
2301
- var flipNulls = (v) => {
2302
- const s = String(v).toLowerCase();
2303
- if (s === "first") return "last";
2304
- if (s === "last") return "first";
2305
- return v;
2306
- };
2307
- var flipSortString = (v) => {
2308
- if (typeof v !== "string") return v;
2309
- const s = v.toLowerCase();
2310
- if (s === "asc") return "desc";
2311
- if (s === "desc") return "asc";
2312
- return v;
2313
- };
2314
- var getNextSort = (sortRaw) => {
2315
- if (typeof sortRaw !== "string") return sortRaw;
2316
- const s = sortRaw.toLowerCase();
2317
- if (s === "asc") return "desc";
2318
- if (s === "desc") return "asc";
2319
- return sortRaw;
2320
- };
2321
- var flipObjectSort = (obj) => {
2322
- const sortRaw = obj.sort;
2323
- const out = __spreadProps(__spreadValues({}, obj), { sort: getNextSort(sortRaw) });
2324
- const nullsRaw = obj.nulls;
2325
- if (typeof nullsRaw === "string") {
2326
- out.nulls = flipNulls(nullsRaw);
2327
- }
2328
- return out;
2329
- };
2330
- var flipValue = (v) => {
2331
- if (typeof v === "string") return flipSortString(v);
2332
- if (isPlainObject(v)) return flipObjectSort(v);
2333
- return v;
2334
- };
2335
- var assertSingleFieldObject = (item) => {
2336
- if (!isPlainObject(item)) {
2337
- throw new Error("orderBy array entries must be objects");
2304
+ function normalizeLogicalValue(operator, value, ctx) {
2305
+ if (Array.isArray(value)) {
2306
+ const out = [];
2307
+ for (let i = 0; i < value.length; i++) {
2308
+ const v = value[i];
2309
+ if (v === void 0) continue;
2310
+ if (!isPlainObject(v)) {
2311
+ throw createError(`${operator} entries must be objects`, {
2312
+ path: [...ctx.path, operator, String(i)],
2313
+ modelName: ctx.model.name,
2314
+ value: v
2315
+ });
2316
+ }
2317
+ out.push(v);
2318
+ }
2319
+ return out;
2338
2320
  }
2339
- const entries = Object.entries(item);
2340
- if (entries.length !== 1) {
2341
- throw new Error("orderBy array entries must have exactly one field");
2321
+ if (isPlainObject(value)) {
2322
+ return [value];
2342
2323
  }
2343
- return entries[0];
2344
- };
2345
- var flipOrderByArray = (orderBy) => {
2346
- return orderBy.map((item) => {
2347
- const [k, v] = assertSingleFieldObject(item);
2348
- return { [k]: flipValue(v) };
2324
+ throw createError(`${operator} must be an object or array of objects`, {
2325
+ path: [...ctx.path, operator],
2326
+ modelName: ctx.model.name,
2327
+ value
2349
2328
  });
2350
- };
2351
- var flipOrderByObject = (orderBy) => {
2352
- const out = {};
2353
- for (const [k, v] of Object.entries(orderBy)) {
2354
- out[k] = flipValue(v);
2329
+ }
2330
+ function collectLogicalParts(operator, conditions, ctx, builder) {
2331
+ const allJoins = [];
2332
+ const clauses = [];
2333
+ for (let i = 0; i < conditions.length; i++) {
2334
+ const result = builder.build(conditions[i], __spreadProps(__spreadValues({}, ctx), {
2335
+ path: [...ctx.path, operator, String(i)],
2336
+ depth: ctx.depth + 1
2337
+ }));
2338
+ if (isNonEmptyArray(result.joins)) allJoins.push(...result.joins);
2339
+ if (result.clause && result.clause !== DEFAULT_WHERE_CLAUSE) {
2340
+ clauses.push(`(${result.clause})`);
2341
+ }
2355
2342
  }
2356
- return out;
2357
- };
2358
- function reverseOrderByInput(orderBy) {
2359
- if (!isNotNullish(orderBy)) return orderBy;
2360
- if (Array.isArray(orderBy)) {
2361
- return flipOrderByArray(orderBy);
2343
+ return {
2344
+ joins: dedupePreserveOrder(allJoins),
2345
+ clauses: Object.freeze(clauses)
2346
+ };
2347
+ }
2348
+ function buildLogicalClause(operator, clauses) {
2349
+ if (clauses.length === 0) return DEFAULT_WHERE_CLAUSE;
2350
+ if (operator === "NOT") {
2351
+ if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
2352
+ return `${SQL_TEMPLATES.NOT} (${clauses.join(SQL_SEPARATORS.CONDITION_AND)})`;
2362
2353
  }
2363
- if (isPlainObject(orderBy)) {
2364
- return flipOrderByObject(orderBy);
2354
+ return clauses.join(` ${operator} `);
2355
+ }
2356
+ function buildLogical(operator, value, ctx, builder) {
2357
+ const conditions = normalizeLogicalValue(operator, value, ctx);
2358
+ if (conditions.length === 0) {
2359
+ const clause2 = operator === "OR" ? "0=1" : DEFAULT_WHERE_CLAUSE;
2360
+ return freezeResult(clause2, EMPTY_JOINS);
2365
2361
  }
2366
- throw new Error("orderBy must be an object or array of objects");
2362
+ const { joins, clauses } = collectLogicalParts(
2363
+ operator,
2364
+ conditions,
2365
+ ctx,
2366
+ builder
2367
+ );
2368
+ const clause = buildLogicalClause(operator, clauses);
2369
+ return freezeResult(clause, joins);
2367
2370
  }
2368
- var normalizePairs = (pairs, parseValue) => {
2369
- return pairs.map(([field, rawValue]) => {
2370
- const parsed = parseValue(rawValue, field);
2371
- return {
2372
- [field]: parsed.nulls !== void 0 ? { sort: parsed.direction, nulls: parsed.nulls } : parsed.direction
2373
- };
2374
- });
2375
- };
2376
- function normalizeOrderByInput(orderBy, parseValue) {
2377
- if (!isNotNullish(orderBy)) return [];
2378
- if (Array.isArray(orderBy)) {
2379
- const pairs = orderBy.map(assertSingleFieldObject);
2380
- return normalizePairs(pairs, parseValue);
2371
+ function buildScalarField(fieldName, value, ctx) {
2372
+ const field = assertFieldExists(fieldName, ctx.model, ctx.path);
2373
+ const expr = col(ctx.alias, fieldName, ctx.model);
2374
+ if (value === null) {
2375
+ return freezeResult(`${expr} ${SQL_TEMPLATES.IS_NULL}`, EMPTY_JOINS);
2381
2376
  }
2382
- if (isPlainObject(orderBy)) {
2383
- return normalizePairs(Object.entries(orderBy), parseValue);
2377
+ if (isPlainObject(value)) {
2378
+ const mode = value.mode;
2379
+ const ops = Object.entries(value).filter(
2380
+ ([k, v]) => k !== "mode" && v !== void 0
2381
+ );
2382
+ if (ops.length === 0) {
2383
+ return freezeResult(DEFAULT_WHERE_CLAUSE, EMPTY_JOINS);
2384
+ }
2385
+ const parts = [];
2386
+ for (const [op, val] of ops) {
2387
+ assertValidOperator(fieldName, op, field.type, ctx.path, ctx.model.name);
2388
+ const clause3 = buildOperator(expr, op, val, ctx, mode, field.type);
2389
+ if (isValidWhereClause(clause3)) parts.push(clause3);
2390
+ }
2391
+ const clause2 = parts.length > 0 ? parts.join(SQL_SEPARATORS.CONDITION_AND) : DEFAULT_WHERE_CLAUSE;
2392
+ return freezeResult(clause2, EMPTY_JOINS);
2384
2393
  }
2385
- throw new Error("orderBy must be an object or array of objects");
2386
- }
2387
-
2388
- // src/builder/pagination.ts
2389
- var MAX_LIMIT_OFFSET = 2147483647;
2390
- function parseDirectionRaw(raw, errorLabel) {
2391
- const s = String(raw).toLowerCase();
2392
- if (s === "asc" || s === "desc") return s;
2393
- throw new Error(`Invalid ${errorLabel}: ${raw}`);
2394
- }
2395
- function parseNullsRaw(raw, errorLabel) {
2396
- if (!isNotNullish(raw)) return void 0;
2397
- const s = String(raw).toLowerCase();
2398
- if (s === "first" || s === "last") return s;
2399
- throw new Error(`Invalid ${errorLabel}: ${raw}`);
2394
+ const clause = buildOperator(
2395
+ expr,
2396
+ Ops.EQUALS,
2397
+ value,
2398
+ ctx,
2399
+ void 0,
2400
+ field.type
2401
+ );
2402
+ return freezeResult(clause || DEFAULT_WHERE_CLAUSE, EMPTY_JOINS);
2400
2403
  }
2401
- function requireOrderByObject(v, errorPrefix) {
2402
- if (!isPlainObject(v) || !("sort" in v)) {
2403
- throw new Error(`${errorPrefix} must be 'asc' | 'desc' or { sort, nulls? }`);
2404
+ function buildOperator(expr, op, val, ctx, mode, fieldType) {
2405
+ if (fieldType && isArrayType(fieldType)) {
2406
+ return buildArrayOperator(expr, op, val, ctx.params, fieldType, ctx.dialect);
2404
2407
  }
2405
- return v;
2406
- }
2407
- function assertAllowedOrderByKeys(obj, fieldName) {
2408
- const allowed = /* @__PURE__ */ new Set(["sort", "nulls"]);
2409
- for (const k of Object.keys(obj)) {
2410
- if (!allowed.has(k)) {
2411
- throw new Error(
2412
- fieldName ? `Unsupported orderBy key '${k}' for field '${fieldName}'` : `Unsupported orderBy key '${k}'`
2413
- );
2408
+ if (fieldType && isJsonType(fieldType)) {
2409
+ const JSON_OPS = /* @__PURE__ */ new Set([
2410
+ Ops.PATH,
2411
+ Ops.STRING_CONTAINS,
2412
+ Ops.STRING_STARTS_WITH,
2413
+ Ops.STRING_ENDS_WITH
2414
+ ]);
2415
+ if (JSON_OPS.has(op)) {
2416
+ return buildJsonOperator(expr, op, val, ctx.params, ctx.dialect);
2414
2417
  }
2415
2418
  }
2419
+ return buildScalarOperator(
2420
+ expr,
2421
+ op,
2422
+ val,
2423
+ ctx.params,
2424
+ mode,
2425
+ fieldType,
2426
+ ctx.dialect
2427
+ );
2416
2428
  }
2417
- function parseOrderByValue(v, fieldName) {
2418
- const errorPrefix = fieldName ? `orderBy for '${fieldName}'` : "orderBy value";
2419
- if (typeof v === "string") {
2420
- return { direction: parseDirectionRaw(v, `${errorPrefix} direction`) };
2421
- }
2422
- const obj = requireOrderByObject(v, errorPrefix);
2423
- const direction = parseDirectionRaw(obj.sort, `${errorPrefix}.sort`);
2424
- const nulls = parseNullsRaw(obj.nulls, `${errorPrefix}.nulls`);
2425
- assertAllowedOrderByKeys(obj, fieldName);
2426
- return { direction, nulls };
2429
+
2430
+ // src/builder/shared/alias-generator.ts
2431
+ function toSafeSqlIdentifier(input) {
2432
+ const raw = String(input);
2433
+ const cleaned = raw.replace(/\W/g, "_");
2434
+ const startsOk = /^[a-zA-Z_]/.test(cleaned);
2435
+ const base = startsOk ? cleaned : `_${cleaned}`;
2436
+ const fallback = base.length > 0 ? base : "_t";
2437
+ const lowered = fallback.toLowerCase();
2438
+ return SQL_KEYWORDS.has(lowered) ? `_${lowered}` : lowered;
2427
2439
  }
2428
- function normalizeFiniteInteger(name, v) {
2429
- if (typeof v !== "number" || !Number.isFinite(v) || !Number.isInteger(v)) {
2430
- throw new Error(`${name} must be an integer`);
2431
- }
2432
- return v;
2440
+ function createAliasGenerator(maxAliases = 1e4) {
2441
+ let counter = 0;
2442
+ const usedAliases = /* @__PURE__ */ new Set();
2443
+ return {
2444
+ next(baseName) {
2445
+ if (usedAliases.size >= maxAliases) {
2446
+ throw new Error(
2447
+ `Alias generator exceeded maximum of ${maxAliases} aliases. This indicates a query complexity issue or potential infinite loop.`
2448
+ );
2449
+ }
2450
+ const base = toSafeSqlIdentifier(baseName);
2451
+ const suffix = `_${counter}`;
2452
+ const maxLen = 63;
2453
+ const baseMax = Math.max(1, maxLen - suffix.length);
2454
+ const trimmedBase = base.length > baseMax ? base.slice(0, baseMax) : base;
2455
+ const alias = `${trimmedBase}${suffix}`;
2456
+ counter += 1;
2457
+ if (usedAliases.has(alias)) {
2458
+ throw new Error(
2459
+ `CRITICAL: Duplicate alias '${alias}' at counter=${counter}. This indicates a bug in alias generation logic.`
2460
+ );
2461
+ }
2462
+ usedAliases.add(alias);
2463
+ return alias;
2464
+ }
2465
+ };
2433
2466
  }
2434
- function normalizeNonNegativeInt(name, v) {
2435
- if (schemaParser.isDynamicParameter(v)) return v;
2436
- const n = normalizeFiniteInteger(name, v);
2437
- if (n < 0) {
2438
- throw new Error(`${name} must be >= 0`);
2439
- }
2440
- if (n > MAX_LIMIT_OFFSET) {
2441
- throw new Error(`${name} must be <= ${MAX_LIMIT_OFFSET}`);
2467
+ var MAX_PARAM_INDEX = Number.MAX_SAFE_INTEGER - 1e3;
2468
+ function assertSameLength(params, mappings) {
2469
+ if (params.length !== mappings.length) {
2470
+ throw new Error(
2471
+ `CRITICAL: State corruption - params=${params.length}, mappings=${mappings.length}`
2472
+ );
2442
2473
  }
2443
- return n;
2444
- }
2445
- function hasNonNullishProp(v, key) {
2446
- return isPlainObject(v) && key in v && isNotNullish(v[key]);
2447
- }
2448
- function normalizeIntegerOrDynamic(name, v) {
2449
- if (schemaParser.isDynamicParameter(v)) return v;
2450
- return normalizeFiniteInteger(name, v);
2451
2474
  }
2452
- function readSkipTake(relArgs) {
2453
- const hasSkip = hasNonNullishProp(relArgs, "skip");
2454
- const hasTake = hasNonNullishProp(relArgs, "take");
2455
- if (!hasSkip && !hasTake) {
2456
- return {
2457
- hasSkip: false,
2458
- hasTake: false,
2459
- skipVal: void 0,
2460
- takeVal: void 0
2461
- };
2475
+ function assertValidNextIndex(index) {
2476
+ if (!Number.isInteger(index) || index < 1) {
2477
+ throw new Error(`CRITICAL: Index must be integer >= 1, got ${index}`);
2462
2478
  }
2463
- const obj = relArgs;
2464
- const skipVal = hasSkip ? normalizeNonNegativeInt("skip", obj.skip) : void 0;
2465
- const takeVal = hasTake ? normalizeIntegerOrDynamic("take", obj.take) : void 0;
2466
- return { hasSkip, hasTake, skipVal, takeVal };
2467
2479
  }
2468
- function buildOrderByFragment(entries, alias, dialect, model) {
2469
- if (entries.length === 0) return "";
2470
- const out = [];
2471
- for (const e of entries) {
2472
- const dir = e.direction.toUpperCase();
2473
- const c = col(alias, e.field, model);
2474
- if (dialect === "postgres") {
2475
- const nulls = isNotNullish(e.nulls) ? ` NULLS ${e.nulls.toUpperCase()}` : "";
2476
- out.push(`${c} ${dir}${nulls}`);
2477
- continue;
2478
- }
2479
- if (isNotNullish(e.nulls)) {
2480
- const isNullExpr = `(${c} IS NULL)`;
2481
- const nullRankDir = e.nulls === "first" ? "DESC" : "ASC";
2482
- out.push(`${isNullExpr} ${nullRankDir}`);
2483
- out.push(`${c} ${dir}`);
2484
- continue;
2485
- }
2486
- out.push(`${c} ${dir}`);
2480
+ function assertNextIndexMatches(mappingsLength, nextIndex) {
2481
+ const expected = mappingsLength + 1;
2482
+ if (nextIndex !== expected) {
2483
+ throw new Error(
2484
+ `CRITICAL: Next index mismatch - expected ${expected}, got ${nextIndex}`
2485
+ );
2487
2486
  }
2488
- return out.join(SQL_SEPARATORS.ORDER_BY);
2489
2487
  }
2490
- function defaultNullsFor(dialect, direction) {
2491
- if (dialect === "postgres") {
2492
- return direction === "asc" ? "last" : "first";
2488
+ function assertSequentialIndex(actual, expected) {
2489
+ if (actual !== expected) {
2490
+ throw new Error(
2491
+ `CRITICAL: Indices must be sequential from 1..N. Expected ${expected}, got ${actual}`
2492
+ );
2493
2493
  }
2494
- return direction === "asc" ? "first" : "last";
2495
2494
  }
2496
- function ensureCursorFieldsInOrder(orderEntries, cursorEntries) {
2497
- const existing = /* @__PURE__ */ new Map();
2498
- for (const e of orderEntries) existing.set(e.field, e);
2499
- const out = [...orderEntries];
2500
- for (const [field] of cursorEntries) {
2501
- if (!existing.has(field)) {
2502
- out.push({ field, direction: "asc" });
2503
- existing.set(field, out[out.length - 1]);
2504
- }
2495
+ function assertExactlyOneOfDynamicOrValue(m) {
2496
+ const hasDynamic = typeof m.dynamicName === "string";
2497
+ const hasStatic = m.value !== void 0;
2498
+ if (hasDynamic === hasStatic) {
2499
+ throw new Error(
2500
+ `CRITICAL: ParamMap ${m.index} must have exactly one of dynamicName or value`
2501
+ );
2505
2502
  }
2506
- return out;
2507
2503
  }
2508
- function buildCursorFilterParts(cursor, cursorAlias, params, model) {
2509
- const entries = Object.entries(cursor);
2510
- if (entries.length === 0) {
2511
- throw new Error("cursor must have at least one field");
2504
+ function normalizeDynamicNameOrThrow(dynamicName, index) {
2505
+ const dn = dynamicName.trim();
2506
+ if (dn.length === 0) {
2507
+ throw new Error(`CRITICAL: dynamicName cannot be empty (index=${index})`);
2512
2508
  }
2513
- const placeholdersByField = /* @__PURE__ */ new Map();
2514
- const parts = [];
2515
- for (const [field, value] of entries) {
2516
- const c = `${cursorAlias}.${quote(field)}`;
2517
- if (value === null) {
2518
- parts.push(`${c} IS NULL`);
2519
- continue;
2520
- }
2521
- const ph = addAutoScoped(params, value, `cursor.filter.${field}`);
2522
- placeholdersByField.set(field, ph);
2523
- parts.push(`${c} = ${ph}`);
2509
+ return dn;
2510
+ }
2511
+ function assertUniqueDynamicName(dn, seen) {
2512
+ if (seen.has(dn)) {
2513
+ throw new Error(`CRITICAL: Duplicate dynamic param name in mappings: ${dn}`);
2524
2514
  }
2525
- return {
2526
- whereSql: parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`,
2527
- placeholdersByField
2528
- };
2515
+ seen.add(dn);
2529
2516
  }
2530
- function cursorValueExpr(tableName, cursorAlias, cursorWhereSql, field, model) {
2531
- const colName = quote(field);
2532
- return `(SELECT ${cursorAlias}.${colName} ${SQL_TEMPLATES.FROM} ${tableName} ${cursorAlias} ${SQL_TEMPLATES.WHERE} ${cursorWhereSql} ${SQL_TEMPLATES.LIMIT} 1)`;
2517
+ function validateMappingEntry(m, expectedIndex, seenDynamic) {
2518
+ assertSequentialIndex(m.index, expectedIndex);
2519
+ assertExactlyOneOfDynamicOrValue(m);
2520
+ if (typeof m.dynamicName === "string") {
2521
+ const dn = normalizeDynamicNameOrThrow(m.dynamicName, m.index);
2522
+ assertUniqueDynamicName(dn, seenDynamic);
2523
+ }
2533
2524
  }
2534
- function buildCursorRowExistsExpr(tableName, cursorAlias, cursorWhereSql) {
2535
- return `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${tableName} ${cursorAlias} ${SQL_TEMPLATES.WHERE} ${cursorWhereSql} ${SQL_TEMPLATES.LIMIT} 1)`;
2525
+ function validateMappings(mappings) {
2526
+ const seenDynamic = /* @__PURE__ */ new Set();
2527
+ for (let i = 0; i < mappings.length; i++) {
2528
+ validateMappingEntry(mappings[i], i + 1, seenDynamic);
2529
+ }
2536
2530
  }
2537
- function buildCursorEqualityExpr(columnExpr, valueExpr) {
2538
- return `((${valueExpr} IS NULL AND ${columnExpr} IS NULL) OR (${valueExpr} IS NOT NULL AND ${columnExpr} = ${valueExpr}))`;
2531
+ function validateState(params, mappings, index) {
2532
+ assertSameLength(params, mappings);
2533
+ assertValidNextIndex(index);
2534
+ if (mappings.length === 0) return;
2535
+ validateMappings(mappings);
2536
+ assertNextIndexMatches(mappings.length, index);
2539
2537
  }
2540
- function buildCursorInequalityExpr(columnExpr, direction, nulls, valueExpr) {
2541
- const op = direction === "asc" ? ">" : "<";
2542
- if (nulls === "first") {
2543
- return `(CASE WHEN ${valueExpr} IS NULL THEN (${columnExpr} IS NOT NULL) ELSE (${columnExpr} ${op} ${valueExpr}) END)`;
2538
+ function normalizeValue(value) {
2539
+ if (value instanceof Date) {
2540
+ return value.toISOString();
2544
2541
  }
2545
- return `(CASE WHEN ${valueExpr} IS NULL THEN 0=1 ELSE ((${columnExpr} ${op} ${valueExpr}) OR (${columnExpr} IS NULL)) END)`;
2542
+ return value;
2546
2543
  }
2547
- function buildOuterCursorMatch(cursor, outerAlias, placeholdersByField, params, model) {
2548
- const parts = [];
2549
- for (const [field, value] of Object.entries(cursor)) {
2550
- const c = col(outerAlias, field, model);
2551
- if (value === null) {
2552
- parts.push(`${c} IS NULL`);
2553
- continue;
2554
- }
2555
- const existing = placeholdersByField.get(field);
2556
- if (typeof existing === "string" && existing.length > 0) {
2557
- parts.push(`${c} = ${existing}`);
2558
- continue;
2544
+ function createStoreInternal(startIndex, initialParams = [], initialMappings = []) {
2545
+ let index = startIndex;
2546
+ const params = [...initialParams];
2547
+ const mappings = [...initialMappings];
2548
+ const dynamicNameToIndex = /* @__PURE__ */ new Map();
2549
+ for (const m of initialMappings) {
2550
+ if (typeof m.dynamicName === "string") {
2551
+ dynamicNameToIndex.set(m.dynamicName.trim(), m.index);
2559
2552
  }
2560
- const ph = addAutoScoped(params, value, `cursor.outerMatch.${field}`);
2561
- parts.push(`${c} = ${ph}`);
2562
2553
  }
2563
- return parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`;
2564
- }
2565
- function buildOrderEntries(orderBy) {
2566
- const normalized = normalizeOrderByInput(orderBy, parseOrderByValue);
2567
- const entries = [];
2568
- for (const item of normalized) {
2569
- for (const [field, value] of Object.entries(item)) {
2570
- if (typeof value === "string") {
2571
- entries.push({ field, direction: value });
2572
- } else {
2573
- entries.push({
2574
- field,
2575
- direction: value.sort,
2576
- nulls: value.nulls
2577
- });
2578
- }
2554
+ function assertCanAdd() {
2555
+ if (index > MAX_PARAM_INDEX) {
2556
+ throw new Error(
2557
+ `CRITICAL: Cannot add param - would overflow MAX_SAFE_INTEGER. Current index: ${index}`
2558
+ );
2579
2559
  }
2580
2560
  }
2581
- return entries;
2582
- }
2583
- function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect, model) {
2584
- var _a;
2585
- const d = dialect != null ? dialect : getGlobalDialect();
2586
- const cursorEntries = Object.entries(cursor);
2587
- if (cursorEntries.length === 0) {
2588
- throw new Error("cursor must have at least one field");
2561
+ function normalizeDynamicName(dynamicName) {
2562
+ const dn = dynamicName.trim();
2563
+ if (dn.length === 0) {
2564
+ throw new Error("CRITICAL: dynamicName cannot be empty");
2565
+ }
2566
+ return dn;
2589
2567
  }
2590
- const cursorAlias = "__tp_cursor_src";
2591
- const { whereSql: cursorWhereSql, placeholdersByField } = buildCursorFilterParts(cursor, cursorAlias, params);
2592
- let orderEntries = buildOrderEntries(orderBy);
2593
- if (orderEntries.length === 0) {
2594
- orderEntries = cursorEntries.map(([field]) => ({
2595
- field,
2596
- direction: "asc"
2597
- }));
2598
- } else {
2599
- orderEntries = ensureCursorFieldsInOrder(orderEntries, cursorEntries);
2568
+ function format(position) {
2569
+ return `$${position}`;
2600
2570
  }
2601
- const existsExpr = buildCursorRowExistsExpr(
2602
- tableName,
2603
- cursorAlias,
2604
- cursorWhereSql
2605
- );
2606
- const outerCursorMatch = buildOuterCursorMatch(
2607
- cursor,
2608
- alias,
2609
- placeholdersByField,
2610
- params,
2611
- model
2612
- );
2613
- const orClauses = [];
2614
- for (let level = 0; level < orderEntries.length; level++) {
2615
- const andParts = [];
2616
- for (let i = 0; i < level; i++) {
2617
- const e2 = orderEntries[i];
2618
- const c2 = col(alias, e2.field, model);
2619
- const v2 = cursorValueExpr(
2620
- tableName,
2621
- cursorAlias,
2622
- cursorWhereSql,
2623
- e2.field);
2624
- andParts.push(buildCursorEqualityExpr(c2, v2));
2571
+ function addDynamic(dynamicName) {
2572
+ const dn = normalizeDynamicName(dynamicName);
2573
+ const existing = dynamicNameToIndex.get(dn);
2574
+ if (existing !== void 0) {
2575
+ return format(existing);
2625
2576
  }
2626
- const e = orderEntries[level];
2627
- const c = col(alias, e.field, model);
2628
- const v = cursorValueExpr(
2629
- tableName,
2630
- cursorAlias,
2631
- cursorWhereSql,
2632
- e.field);
2633
- const nulls = (_a = e.nulls) != null ? _a : defaultNullsFor(d, e.direction);
2634
- andParts.push(buildCursorInequalityExpr(c, e.direction, nulls, v));
2635
- orClauses.push(`(${andParts.join(SQL_SEPARATORS.CONDITION_AND)})`);
2577
+ const position = index;
2578
+ dynamicNameToIndex.set(dn, position);
2579
+ params.push(void 0);
2580
+ mappings.push({ index: position, dynamicName: dn });
2581
+ index++;
2582
+ return format(position);
2636
2583
  }
2637
- const exclusive = orClauses.join(SQL_SEPARATORS.CONDITION_OR);
2638
- return `(${existsExpr} ${SQL_SEPARATORS.CONDITION_AND} ((${exclusive})${SQL_SEPARATORS.CONDITION_OR}(${outerCursorMatch})))`;
2639
- }
2640
- function buildOrderBy(orderBy, alias, dialect, model) {
2641
- const entries = buildOrderEntries(orderBy);
2642
- if (entries.length === 0) return "";
2643
- const d = dialect != null ? dialect : getGlobalDialect();
2644
- return buildOrderByFragment(entries, alias, d, model);
2584
+ function addStatic(value) {
2585
+ const position = index;
2586
+ const normalizedValue = normalizeValue(value);
2587
+ params.push(normalizedValue);
2588
+ mappings.push({ index: position, value: normalizedValue });
2589
+ index++;
2590
+ return format(position);
2591
+ }
2592
+ function add(value, dynamicName) {
2593
+ assertCanAdd();
2594
+ return dynamicName === void 0 ? addStatic(value) : addDynamic(dynamicName);
2595
+ }
2596
+ function addAuto(value) {
2597
+ if (schemaParser.isDynamicParameter(value)) {
2598
+ const dynamicName = schemaParser.extractDynamicName(value);
2599
+ return add(void 0, dynamicName);
2600
+ }
2601
+ return add(value);
2602
+ }
2603
+ function snapshot() {
2604
+ return Object.freeze({
2605
+ index,
2606
+ params: Object.freeze([...params]),
2607
+ mappings: Object.freeze([...mappings])
2608
+ });
2609
+ }
2610
+ return {
2611
+ add,
2612
+ addAuto,
2613
+ snapshot,
2614
+ get index() {
2615
+ return index;
2616
+ }
2617
+ };
2645
2618
  }
2646
- function buildOrderByClause(args, alias, dialect, model) {
2647
- if (!isNotNullish(args.orderBy)) return "";
2648
- const result = buildOrderBy(args.orderBy, alias, dialect, model);
2649
- if (!isNonEmptyString(result)) {
2619
+ function createParamStore(startIndex = 1) {
2620
+ if (!Number.isInteger(startIndex) || startIndex < 1) {
2621
+ throw new Error(`Start index must be integer >= 1, got ${startIndex}`);
2622
+ }
2623
+ if (startIndex > MAX_PARAM_INDEX) {
2650
2624
  throw new Error(
2651
- "buildOrderByClause: orderBy specified but produced empty result"
2625
+ `Start index too high (${startIndex}), risk of overflow at MAX_SAFE_INTEGER`
2652
2626
  );
2653
2627
  }
2654
- return result;
2628
+ return createStoreInternal(startIndex);
2655
2629
  }
2656
- function normalizeTakeLike(v) {
2657
- const n = normalizeIntLike("take", v, {
2658
- min: Number.MIN_SAFE_INTEGER,
2659
- max: MAX_LIMIT_OFFSET,
2660
- allowZero: true
2661
- });
2662
- if (typeof n === "number") {
2663
- if (n === 0) return 0;
2664
- }
2665
- return n;
2630
+ function createParamStoreFrom(existingParams, existingMappings, nextIndex) {
2631
+ validateState([...existingParams], [...existingMappings], nextIndex);
2632
+ return createStoreInternal(
2633
+ nextIndex,
2634
+ [...existingParams],
2635
+ [...existingMappings]
2636
+ );
2666
2637
  }
2667
- function normalizeSkipLike(v) {
2668
- return normalizeIntLike("skip", v, {
2669
- min: 0,
2670
- max: MAX_LIMIT_OFFSET,
2671
- allowZero: true
2638
+
2639
+ // src/builder/shared/state.ts
2640
+ function toPublicResult(clause, joins, params) {
2641
+ const snapshot = params.snapshot();
2642
+ return Object.freeze({
2643
+ clause: clause || DEFAULT_WHERE_CLAUSE,
2644
+ joins: Object.freeze([...joins]),
2645
+ params: snapshot.params,
2646
+ paramMappings: snapshot.mappings,
2647
+ nextParamIndex: snapshot.index
2672
2648
  });
2673
2649
  }
2674
- function getPaginationParams(method, args) {
2675
- if (method === "findMany") {
2676
- return {
2677
- take: normalizeTakeLike(args.take),
2678
- skip: normalizeSkipLike(args.skip),
2679
- cursor: args.cursor
2680
- };
2681
- }
2682
- if (method === "findFirst") {
2683
- const skip = normalizeSkipLike(args.skip);
2684
- return { take: 1, skip: skip != null ? skip : 0 };
2685
- }
2686
- if (method === "findUnique") {
2687
- return { take: 1, skip: 0 };
2650
+
2651
+ // src/builder/where.ts
2652
+ function buildWhereClause(where, options) {
2653
+ var _a, _b, _c, _d, _e;
2654
+ const dialect = options.dialect || getGlobalDialect();
2655
+ const params = (_a = options.params) != null ? _a : createParamStore();
2656
+ const ctx = {
2657
+ alias: options.alias,
2658
+ model: options.model,
2659
+ schemaModels: (_b = options.schemaModels) != null ? _b : [],
2660
+ path: (_c = options.path) != null ? _c : [],
2661
+ isSubquery: (_d = options.isSubquery) != null ? _d : false,
2662
+ aliasGen: (_e = options.aliasGen) != null ? _e : createAliasGenerator(),
2663
+ dialect,
2664
+ params,
2665
+ depth: 0
2666
+ };
2667
+ const result = whereBuilderInstance.build(where, ctx);
2668
+ const publicResult = toPublicResult(result.clause, result.joins, params);
2669
+ if (!options.isSubquery) {
2670
+ const nums = [...publicResult.clause.matchAll(/\$(\d+)/g)].map(
2671
+ (m) => parseInt(m[1], 10)
2672
+ );
2673
+ if (nums.length > 0) {
2674
+ const min = Math.min(...nums);
2675
+ if (min === 1) {
2676
+ validateParamConsistency(publicResult.clause, publicResult.params);
2677
+ } else {
2678
+ validateParamConsistencyFragment(
2679
+ publicResult.clause,
2680
+ publicResult.params
2681
+ );
2682
+ }
2683
+ }
2688
2684
  }
2689
- return {};
2685
+ return publicResult;
2690
2686
  }
2691
2687
 
2692
2688
  // src/builder/select/fields.ts
@@ -3437,8 +3433,8 @@ function replaceOrderByAlias(orderBy, fromAlias, outerAlias) {
3437
3433
  const replacement = `${outerAlias}.`;
3438
3434
  return orderBy.split(needle).join(replacement);
3439
3435
  }
3440
- function buildDistinctColumns(distinct, fromAlias) {
3441
- return distinct.map((f) => col(fromAlias, f)).join(SQL_SEPARATORS.FIELD_LIST);
3436
+ function buildDistinctColumns(distinct, fromAlias, model) {
3437
+ return distinct.map((f) => col(fromAlias, f, model)).join(SQL_SEPARATORS.FIELD_LIST);
3442
3438
  }
3443
3439
  function buildOutputColumns(scalarNames, includeNames, hasCount) {
3444
3440
  const outputCols = [...scalarNames, ...includeNames];
@@ -3452,13 +3448,13 @@ function buildOutputColumns(scalarNames, includeNames, hasCount) {
3452
3448
  return formatted;
3453
3449
  }
3454
3450
  function buildWindowOrder(args) {
3455
- const { baseOrder, idField, fromAlias } = args;
3451
+ const { baseOrder, idField, fromAlias, model } = args;
3456
3452
  const orderFields = baseOrder.split(SQL_SEPARATORS.ORDER_BY).map((s) => s.trim().toLowerCase());
3457
3453
  const hasIdInOrder = orderFields.some(
3458
3454
  (f) => f.startsWith(`${fromAlias}.id `) || f.startsWith(`${fromAlias}."id" `)
3459
3455
  );
3460
3456
  if (hasIdInOrder) return baseOrder;
3461
- const idTiebreaker = idField ? `, ${col(fromAlias, "id")} ASC` : "";
3457
+ const idTiebreaker = idField ? `, ${col(fromAlias, "id", model)} ASC` : "";
3462
3458
  return `${baseOrder}${idTiebreaker}`;
3463
3459
  }
3464
3460
  function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
@@ -3475,14 +3471,17 @@ function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
3475
3471
  includeNames,
3476
3472
  hasCount
3477
3473
  );
3478
- const distinctCols = buildDistinctColumns([...distinct], from.alias);
3479
- const fallbackOrder = [...distinct].map((f) => `${col(from.alias, f)} ASC`).join(SQL_SEPARATORS.FIELD_LIST);
3480
- const idField = model.fields.find((f) => f.name === "id" && !f.isRelation);
3474
+ const distinctCols = buildDistinctColumns([...distinct], from.alias, model);
3475
+ const fallbackOrder = [...distinct].map((f) => `${col(from.alias, f, model)} ASC`).join(SQL_SEPARATORS.FIELD_LIST);
3476
+ const idField = model.fields.find(
3477
+ (f) => f.name === "id" && !f.isRelation
3478
+ );
3481
3479
  const baseOrder = isNonEmptyString(orderBy) ? orderBy : fallbackOrder;
3482
3480
  const windowOrder = buildWindowOrder({
3483
3481
  baseOrder,
3484
3482
  idField,
3485
- fromAlias: from.alias
3483
+ fromAlias: from.alias,
3484
+ model
3486
3485
  });
3487
3486
  const outerOrder = isNonEmptyString(orderBy) ? replaceOrderByAlias(orderBy, from.alias, `"__tp_distinct"`) : replaceOrderByAlias(fallbackOrder, from.alias, `"__tp_distinct"`);
3488
3487
  const joins = buildJoinsSql(whereJoins, countJoins);
@@ -3593,9 +3592,9 @@ function withCountJoins(spec, countJoins, whereJoins) {
3593
3592
  whereJoins: [...whereJoins || [], ...countJoins || []]
3594
3593
  });
3595
3594
  }
3596
- function buildPostgresDistinctOnClause(fromAlias, distinct) {
3595
+ function buildPostgresDistinctOnClause(fromAlias, distinct, model) {
3597
3596
  if (!isNonEmptyArray(distinct)) return null;
3598
- const distinctCols = buildDistinctColumns([...distinct], fromAlias);
3597
+ const distinctCols = buildDistinctColumns([...distinct], fromAlias, model);
3599
3598
  return `${SQL_TEMPLATES.DISTINCT_ON} (${distinctCols})`;
3600
3599
  }
3601
3600
  function pushJoinGroups(parts, ...groups) {
@@ -3625,7 +3624,8 @@ function constructFinalSql(spec) {
3625
3624
  method,
3626
3625
  cursorClause,
3627
3626
  params,
3628
- dialect
3627
+ dialect,
3628
+ model
3629
3629
  } = spec;
3630
3630
  const useWindowDistinct = hasWindowDistinct(spec);
3631
3631
  assertDistinctAllowed(method, useWindowDistinct);
@@ -3639,7 +3639,7 @@ function constructFinalSql(spec) {
3639
3639
  return finalizeSql(sql2, params);
3640
3640
  }
3641
3641
  const parts = [SQL_TEMPLATES.SELECT];
3642
- const distinctOn = dialect === "postgres" ? buildPostgresDistinctOnClause(from.alias, distinct) : null;
3642
+ const distinctOn = dialect === "postgres" ? buildPostgresDistinctOnClause(from.alias, distinct, model) : null;
3643
3643
  if (distinctOn) parts.push(distinctOn);
3644
3644
  const baseSelect = (select != null ? select : "").trim();
3645
3645
  const fullSelectList = buildSelectList(baseSelect, includeCols);
@@ -3899,17 +3899,17 @@ function getModelFieldMap(model) {
3899
3899
  function isTruthySelection(v) {
3900
3900
  return v === true;
3901
3901
  }
3902
- function aggExprForField(aggKey, field, alias) {
3902
+ function aggExprForField(aggKey, field, alias, model) {
3903
3903
  if (aggKey === "_count") {
3904
- return field === "_all" ? `COUNT(*)` : `COUNT(${col(alias, field)})`;
3904
+ return field === "_all" ? `COUNT(*)` : `COUNT(${col(alias, field, model)})`;
3905
3905
  }
3906
3906
  if (field === "_all") {
3907
3907
  throw new Error(`'${aggKey}' does not support '_all'`);
3908
3908
  }
3909
- if (aggKey === "_sum") return `SUM(${col(alias, field)})`;
3910
- if (aggKey === "_avg") return `AVG(${col(alias, field)})`;
3911
- if (aggKey === "_min") return `MIN(${col(alias, field)})`;
3912
- return `MAX(${col(alias, field)})`;
3909
+ if (aggKey === "_sum") return `SUM(${col(alias, field, model)})`;
3910
+ if (aggKey === "_avg") return `AVG(${col(alias, field, model)})`;
3911
+ if (aggKey === "_min") return `MIN(${col(alias, field, model)})`;
3912
+ return `MAX(${col(alias, field, model)})`;
3913
3913
  }
3914
3914
  function buildComparisonOp(op) {
3915
3915
  const sqlOp = COMPARISON_OPS[op];
@@ -4090,7 +4090,7 @@ function buildHavingForAggregateFirstShape(aggKey, target, alias, params, dialec
4090
4090
  for (const [field, filter] of Object.entries(target)) {
4091
4091
  assertHavingAggTarget(aggKey, field, model);
4092
4092
  if (!isPlainObject(filter) || Object.keys(filter).length === 0) continue;
4093
- const expr = aggExprForField(aggKey, field, alias);
4093
+ const expr = aggExprForField(aggKey, field, alias, model);
4094
4094
  out.push(...buildHavingOpsForExpr(expr, filter, params, dialect));
4095
4095
  }
4096
4096
  return out;
@@ -4107,7 +4107,7 @@ function buildHavingForFieldFirstShape(fieldName, target, alias, params, dialect
4107
4107
  assertAggregateFieldType(aggKey, field.type, field.name, model.name);
4108
4108
  const entries = Object.entries(aggFilter);
4109
4109
  if (entries.length === 0) continue;
4110
- const expr = aggExprForField(aggKey, fieldName, alias);
4110
+ const expr = aggExprForField(aggKey, fieldName, alias, model);
4111
4111
  for (const [op, val] of entries) {
4112
4112
  if (op === "mode") continue;
4113
4113
  const built = buildSimpleComparison(expr, op, val, params, dialect);
@@ -4146,10 +4146,10 @@ function assertCountableScalarField(fieldMap, model, fieldName) {
4146
4146
  );
4147
4147
  }
4148
4148
  }
4149
- function pushCountField(fields, alias, fieldName) {
4149
+ function pushCountField(fields, alias, fieldName, model) {
4150
4150
  const outAlias = `_count.${fieldName}`;
4151
4151
  fields.push(
4152
- `COUNT(${col(alias, fieldName)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4152
+ `COUNT(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4153
4153
  );
4154
4154
  }
4155
4155
  function addCountFields(fields, countArg, alias, model, fieldMap) {
@@ -4167,7 +4167,7 @@ function addCountFields(fields, countArg, alias, model, fieldMap) {
4167
4167
  );
4168
4168
  for (const [f] of selected) {
4169
4169
  assertCountableScalarField(fieldMap, model, f);
4170
- pushCountField(fields, alias, f);
4170
+ pushCountField(fields, alias, f, model);
4171
4171
  }
4172
4172
  }
4173
4173
  function getAggregateSelectionObject(args, agg) {
@@ -4188,10 +4188,10 @@ function assertAggregatableScalarField(fieldMap, model, agg, fieldName) {
4188
4188
  }
4189
4189
  return field;
4190
4190
  }
4191
- function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName) {
4191
+ function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
4192
4192
  const outAlias = `${agg}.${fieldName}`;
4193
4193
  fields.push(
4194
- `${aggFn}(${col(alias, fieldName)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4194
+ `${aggFn}(${col(alias, fieldName, model)}) ${SQL_TEMPLATES.AS} ${quote(outAlias)}`
4195
4195
  );
4196
4196
  }
4197
4197
  function addAggregateFields(fields, args, alias, model, fieldMap) {
@@ -4209,7 +4209,7 @@ function addAggregateFields(fields, args, alias, model, fieldMap) {
4209
4209
  fieldName
4210
4210
  );
4211
4211
  assertAggregateFieldType(agg, field.type, fieldName, model.name);
4212
- pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName);
4212
+ pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model);
4213
4213
  }
4214
4214
  }
4215
4215
  }
@@ -4272,7 +4272,7 @@ function assertGroupByBy(args, model) {
4272
4272
  return byFields;
4273
4273
  }
4274
4274
  function buildGroupBySelectParts(args, alias, model, byFields) {
4275
- const groupCols = byFields.map((f) => col(alias, f));
4275
+ const groupCols = byFields.map((f) => col(alias, f, model));
4276
4276
  const groupFields = groupCols.join(SQL_SEPARATORS.FIELD_LIST);
4277
4277
  const aggFields = buildAggregateFields(args, alias, model);
4278
4278
  const selectFields = isNonEmptyArray(aggFields) ? groupCols.concat(aggFields).join(SQL_SEPARATORS.FIELD_LIST) : groupCols.join(SQL_SEPARATORS.FIELD_LIST);
@@ -4565,6 +4565,29 @@ function generateSQL2(directive) {
4565
4565
  }
4566
4566
 
4567
4567
  // src/code-emitter.ts
4568
+ function extractEnumMappings(datamodel) {
4569
+ const mappings = {};
4570
+ const fieldTypes = {};
4571
+ for (const enumDef of datamodel.enums) {
4572
+ const enumMapping = {};
4573
+ for (const value of enumDef.values) {
4574
+ enumMapping[value.name] = value.dbName || value.name;
4575
+ }
4576
+ if (Object.keys(enumMapping).length > 0) {
4577
+ mappings[enumDef.name] = enumMapping;
4578
+ }
4579
+ }
4580
+ for (const model of datamodel.models) {
4581
+ fieldTypes[model.name] = {};
4582
+ for (const field of model.fields) {
4583
+ const baseType = field.type.replace(/\[\]|\?/g, "");
4584
+ if (mappings[baseType]) {
4585
+ fieldTypes[model.name][field.name] = baseType;
4586
+ }
4587
+ }
4588
+ }
4589
+ return { mappings, fieldTypes };
4590
+ }
4568
4591
  function generateClient(options) {
4569
4592
  return __async(this, null, function* () {
4570
4593
  const { datamodel, outputDir, config } = options;
@@ -4606,7 +4629,7 @@ function generateClient(options) {
4606
4629
  }
4607
4630
  const absoluteOutputDir = path.resolve(process.cwd(), outputDir);
4608
4631
  yield promises.mkdir(absoluteOutputDir, { recursive: true });
4609
- const code = generateCode(models, queries, config.dialect);
4632
+ const code = generateCode(models, queries, config.dialect, datamodel);
4610
4633
  const outputPath = path.join(absoluteOutputDir, "index.ts");
4611
4634
  yield promises.writeFile(outputPath, code);
4612
4635
  const totalQueries = Array.from(queries.values()).reduce(
@@ -4634,15 +4657,20 @@ function createQueryKey(processedQuery) {
4634
4657
  return value;
4635
4658
  });
4636
4659
  }
4637
- function generateCode(models, queries, dialect) {
4660
+ function generateCode(models, queries, dialect, datamodel) {
4638
4661
  const cleanModels = models.map((model) => __spreadProps(__spreadValues({}, model), {
4639
4662
  fields: model.fields.filter((f) => f !== void 0 && f !== null)
4640
4663
  }));
4664
+ const { mappings, fieldTypes } = extractEnumMappings(datamodel);
4641
4665
  return `// Generated by @prisma-sql/generator - DO NOT EDIT
4642
4666
  import { buildSQL, transformQueryResults, type PrismaMethod, type Model } from 'prisma-sql'
4643
4667
 
4644
4668
  export const MODELS: Model[] = ${JSON.stringify(cleanModels, null, 2)}
4645
4669
 
4670
+ const ENUM_MAPPINGS: Record<string, Record<string, string>> = ${JSON.stringify(mappings, null, 2)}
4671
+
4672
+ const ENUM_FIELDS: Record<string, Record<string, string>> = ${JSON.stringify(fieldTypes, null, 2)}
4673
+
4646
4674
  const QUERIES: Record<string, Record<string, Record<string, {
4647
4675
  sql: string
4648
4676
  params: unknown[]
@@ -4656,6 +4684,79 @@ function isDynamicParam(key: string): boolean {
4656
4684
  return key === 'skip' || key === 'take' || key === 'cursor'
4657
4685
  }
4658
4686
 
4687
+ function transformEnumInValue(value: unknown, enumType: string | undefined): unknown {
4688
+ if (!enumType || value === null || value === undefined) {
4689
+ return value
4690
+ }
4691
+
4692
+ const mapping = ENUM_MAPPINGS[enumType]
4693
+ if (!mapping) {
4694
+ return value
4695
+ }
4696
+
4697
+ // Handle array of enum values
4698
+ if (Array.isArray(value)) {
4699
+ return value.map(v => {
4700
+ if (typeof v === 'string' && mapping[v] !== undefined) {
4701
+ return mapping[v]
4702
+ }
4703
+ return v
4704
+ })
4705
+ }
4706
+
4707
+ // Handle single enum value
4708
+ if (typeof value === 'string' && mapping[value] !== undefined) {
4709
+ return mapping[value]
4710
+ }
4711
+
4712
+ return value
4713
+ }
4714
+
4715
+ function transformEnumValues(modelName: string, obj: any, currentPath: string[] = []): any {
4716
+ if (obj === null || obj === undefined) {
4717
+ return obj
4718
+ }
4719
+
4720
+ if (Array.isArray(obj)) {
4721
+ return obj.map(item => transformEnumValues(modelName, item, currentPath))
4722
+ }
4723
+
4724
+ if (typeof obj === 'object') {
4725
+ const transformed: any = {}
4726
+ const modelFields = ENUM_FIELDS[modelName] || {}
4727
+
4728
+ for (const [key, value] of Object.entries(obj)) {
4729
+ const newPath = [...currentPath, key]
4730
+
4731
+ // Check if current key is an enum field at root level
4732
+ const enumType = modelFields[key]
4733
+
4734
+ if (enumType) {
4735
+ // This is an enum field - check if value is direct or has operators
4736
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
4737
+ // Has operators like { equals: "ACTIVE" }, { in: ["ACTIVE"] }, etc.
4738
+ const transformedOperators: any = {}
4739
+ for (const [op, opValue] of Object.entries(value)) {
4740
+ transformedOperators[op] = transformEnumInValue(opValue, enumType)
4741
+ }
4742
+ transformed[key] = transformedOperators
4743
+ } else {
4744
+ // Direct value like { status: "ACTIVE" }
4745
+ transformed[key] = transformEnumInValue(value, enumType)
4746
+ }
4747
+ } else if (typeof value === 'object' && value !== null) {
4748
+ // Recursively transform nested objects (relations, logical operators, etc)
4749
+ transformed[key] = transformEnumValues(modelName, value, newPath)
4750
+ } else {
4751
+ transformed[key] = value
4752
+ }
4753
+ }
4754
+ return transformed
4755
+ }
4756
+
4757
+ return obj
4758
+ }
4759
+
4659
4760
  function normalizeQuery(args: any): string {
4660
4761
  if (!args) return '{}'
4661
4762
 
@@ -4771,7 +4872,10 @@ export function speedExtension(config: {
4771
4872
  const modelName = this?.name || this?.$name
4772
4873
  const startTime = Date.now()
4773
4874
 
4774
- const queryKey = normalizeQuery(args)
4875
+ // Transform enum values before processing
4876
+ const transformedArgs = transformEnumValues(modelName, args || {})
4877
+
4878
+ const queryKey = normalizeQuery(transformedArgs)
4775
4879
  const prebakedQuery = QUERIES[modelName]?.[method]?.[queryKey]
4776
4880
 
4777
4881
  let sql: string
@@ -4780,7 +4884,7 @@ export function speedExtension(config: {
4780
4884
 
4781
4885
  if (prebakedQuery) {
4782
4886
  sql = prebakedQuery.sql
4783
- params = [...prebakedQuery.params, ...extractDynamicParams(args, prebakedQuery.dynamicKeys)]
4887
+ params = [...prebakedQuery.params, ...extractDynamicParams(transformedArgs, prebakedQuery.dynamicKeys)]
4784
4888
  prebaked = true
4785
4889
  } else {
4786
4890
  const model = MODELS.find((m) => m.name === modelName)
@@ -4789,7 +4893,7 @@ export function speedExtension(config: {
4789
4893
  return this.$parent[modelName][method](args)
4790
4894
  }
4791
4895
 
4792
- const result = buildSQL(model, MODELS, method, args || {}, DIALECT)
4896
+ const result = buildSQL(model, MODELS, method, transformedArgs, DIALECT)
4793
4897
  sql = result.sql
4794
4898
  params = result.params
4795
4899
  }