prisma-sql 1.38.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.38.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",
@@ -97,17 +97,6 @@ var require_package = __commonJS({
97
97
  "sonar-cli": "sonar-scanner -Dsonar.projectKey=prisma-sql -Dsonar.sources=./src -Dsonar.host.url=http://localhost:9000 -Dsonar.login=sqp_9fe07460d0aa83f711d0edf4f317f05019d0613b",
98
98
  sonar: "yarn sonar-cli && npx tsx scripts/sonar.ts"
99
99
  },
100
- workspaces: {
101
- packages: [
102
- "demo"
103
- ],
104
- nohoist: [
105
- "**/prisma",
106
- "**/prisma/**",
107
- "**/@prisma/**",
108
- "**/prisma-*"
109
- ]
110
- },
111
100
  keywords: [
112
101
  "prisma",
113
102
  "sql",
@@ -140,7 +129,6 @@ var require_package = __commonJS({
140
129
  "@faker-js/faker": "^10.2.0",
141
130
  "@prisma/adapter-better-sqlite3": "^7.2.0",
142
131
  "@prisma/adapter-pg": "^7.2.0",
143
- "@prisma/client": "7.2.0",
144
132
  "@types/better-sqlite3": "^7.6.13",
145
133
  "@types/node": "^25.0.10",
146
134
  "@vitest/coverage-v8": "4.0.18",
@@ -148,11 +136,12 @@ var require_package = __commonJS({
148
136
  "drizzle-kit": "^0.31.8",
149
137
  "drizzle-orm": "^0.45.1",
150
138
  postgres: "^3.4.8",
151
- prisma: "7.2.0",
152
139
  tsup: "^8.5.1",
153
140
  tsx: "^4.21.0",
154
141
  typescript: "^5.9.3",
155
- vitest: "^4.0.18"
142
+ vitest: "^4.0.18",
143
+ "@prisma/client": "7.2.0",
144
+ prisma: "7.2.0"
156
145
  },
157
146
  engines: {
158
147
  node: ">=16.0.0"
@@ -161,6 +150,148 @@ var require_package = __commonJS({
161
150
  }
162
151
  });
163
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
+
164
295
  // src/sql-builder-dialect.ts
165
296
  var globalDialect = "postgres";
166
297
  function setGlobalDialect(dialect) {
@@ -342,148 +473,6 @@ function prepareArrayParam(value, dialect) {
342
473
  return JSON.stringify(value);
343
474
  }
344
475
 
345
- // src/builder/shared/constants.ts
346
- var SQL_SEPARATORS = Object.freeze({
347
- FIELD_LIST: ", ",
348
- CONDITION_AND: " AND ",
349
- CONDITION_OR: " OR ",
350
- ORDER_BY: ", "
351
- });
352
- var SQL_RESERVED_WORDS = /* @__PURE__ */ new Set([
353
- "select",
354
- "from",
355
- "where",
356
- "and",
357
- "or",
358
- "not",
359
- "in",
360
- "like",
361
- "between",
362
- "order",
363
- "by",
364
- "group",
365
- "having",
366
- "limit",
367
- "offset",
368
- "join",
369
- "inner",
370
- "left",
371
- "right",
372
- "outer",
373
- "on",
374
- "as",
375
- "table",
376
- "column",
377
- "index",
378
- "user",
379
- "users",
380
- "values",
381
- "update",
382
- "insert",
383
- "delete",
384
- "create",
385
- "drop",
386
- "alter",
387
- "truncate",
388
- "grant",
389
- "revoke",
390
- "exec",
391
- "execute",
392
- "union",
393
- "intersect",
394
- "except",
395
- "case",
396
- "when",
397
- "then",
398
- "else",
399
- "end",
400
- "null",
401
- "true",
402
- "false",
403
- "is",
404
- "exists",
405
- "all",
406
- "any",
407
- "some"
408
- ]);
409
- var SQL_KEYWORDS = SQL_RESERVED_WORDS;
410
- var DEFAULT_WHERE_CLAUSE = "1=1";
411
- var SPECIAL_FIELDS = Object.freeze({
412
- ID: "id"
413
- });
414
- var SQL_TEMPLATES = Object.freeze({
415
- PUBLIC_SCHEMA: "public",
416
- WHERE: "WHERE",
417
- SELECT: "SELECT",
418
- FROM: "FROM",
419
- ORDER_BY: "ORDER BY",
420
- GROUP_BY: "GROUP BY",
421
- HAVING: "HAVING",
422
- LIMIT: "LIMIT",
423
- OFFSET: "OFFSET",
424
- COUNT_ALL: "COUNT(*)",
425
- AS: "AS",
426
- DISTINCT_ON: "DISTINCT ON",
427
- IS_NULL: "IS NULL",
428
- IS_NOT_NULL: "IS NOT NULL",
429
- LIKE: "LIKE",
430
- AND: "AND",
431
- OR: "OR",
432
- NOT: "NOT"
433
- });
434
- var SCHEMA_PREFIXES = Object.freeze({
435
- INTERNAL: "@",
436
- COMMENT: "//"
437
- });
438
- var Ops = Object.freeze({
439
- EQUALS: "equals",
440
- NOT: "not",
441
- GT: "gt",
442
- GTE: "gte",
443
- LT: "lt",
444
- LTE: "lte",
445
- IN: "in",
446
- NOT_IN: "notIn",
447
- CONTAINS: "contains",
448
- STARTS_WITH: "startsWith",
449
- ENDS_WITH: "endsWith",
450
- HAS: "has",
451
- HAS_SOME: "hasSome",
452
- HAS_EVERY: "hasEvery",
453
- IS_EMPTY: "isEmpty",
454
- PATH: "path",
455
- STRING_CONTAINS: "string_contains",
456
- STRING_STARTS_WITH: "string_starts_with",
457
- STRING_ENDS_WITH: "string_ends_with"
458
- });
459
- var LogicalOps = Object.freeze({
460
- AND: "AND",
461
- OR: "OR",
462
- NOT: "NOT"
463
- });
464
- var RelationFilters = Object.freeze({
465
- SOME: "some",
466
- EVERY: "every",
467
- NONE: "none"
468
- });
469
- var Modes = Object.freeze({
470
- INSENSITIVE: "insensitive",
471
- DEFAULT: "default"
472
- });
473
- var Wildcards = Object.freeze({
474
- [Ops.CONTAINS]: (v) => `%${v}%`,
475
- [Ops.STARTS_WITH]: (v) => `${v}%`,
476
- [Ops.ENDS_WITH]: (v) => `%${v}`
477
- });
478
- var REGEX_CACHE = {
479
- VALID_IDENTIFIER: /^[a-z_][a-z0-9_]*$/
480
- };
481
- var LIMITS = Object.freeze({
482
- MAX_QUERY_DEPTH: 50,
483
- MAX_ARRAY_SIZE: 1e4,
484
- MAX_STRING_LENGTH: 1e4
485
- });
486
-
487
476
  // src/builder/shared/validators/type-guards.ts
488
477
  function isNotNullish(value) {
489
478
  return value !== null && value !== void 0;
@@ -569,6 +558,19 @@ function getRelationFieldSet(model) {
569
558
  RELATION_SET_CACHE.set(model, s);
570
559
  return s;
571
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
+ }
572
574
 
573
575
  // src/builder/shared/validators/sql-validators.ts
574
576
  function isValidWhereClause(clause) {
@@ -956,27 +958,10 @@ function quote(id) {
956
958
  }
957
959
  return id;
958
960
  }
959
- function pickDbFieldName(field) {
960
- const candidates = [
961
- field == null ? void 0 : field.dbName,
962
- field == null ? void 0 : field.columnName,
963
- field == null ? void 0 : field.databaseName,
964
- field == null ? void 0 : field.mappedName
965
- ];
966
- for (const c of candidates) {
967
- if (typeof c === "string") {
968
- const s = c.trim();
969
- if (s.length > 0) return s;
970
- }
971
- }
972
- return void 0;
973
- }
974
961
  function resolveColumnName(model, fieldName) {
975
- var _a;
976
962
  if (!model) return fieldName;
977
- const f = model.fields.find((x) => (x == null ? void 0 : x.name) === fieldName);
978
- if (!f) return fieldName;
979
- return (_a = pickDbFieldName(f)) != null ? _a : fieldName;
963
+ const columnMap = getColumnMap(model);
964
+ return columnMap.get(fieldName) || fieldName;
980
965
  }
981
966
  function quoteColumn(model, fieldName) {
982
967
  return quote(resolveColumnName(model, fieldName));
@@ -1134,1570 +1119,1570 @@ function joinCondition(field, parentModel, childModel, parentAlias, childAlias)
1134
1119
  function getModelByName(schemas, name) {
1135
1120
  return schemas.find((m) => m.name === name);
1136
1121
  }
1137
- function buildNotComposite(expr, val, params, dialect, buildOp, separator) {
1138
- const entries = Object.entries(val).filter(
1139
- ([k, v]) => k !== "mode" && v !== void 0
1140
- );
1141
- if (entries.length === 0) return "";
1142
- const clauses = [];
1143
- for (const [subOp, subVal] of entries) {
1144
- const sub = buildOp(expr, subOp, subVal, params, dialect);
1145
- if (sub && sub.trim().length > 0) clauses.push(`(${sub})`);
1146
- }
1147
- if (clauses.length === 0) return "";
1148
- if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
1149
- return `${SQL_TEMPLATES.NOT} (${clauses.join(separator)})`;
1150
- }
1151
- function buildScalarOperator(expr, op, val, params, mode, fieldType, dialect) {
1152
- if (val === void 0) return "";
1153
- if (val === null) {
1154
- return handleNullValue(expr, op);
1155
- }
1156
- if (op === Ops.NOT && isPlainObject(val)) {
1157
- return handleNotOperator(expr, val, params, mode, fieldType, dialect);
1158
- }
1159
- if (op === Ops.NOT) {
1160
- const placeholder = params.addAuto(val);
1161
- 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`);
1162
1128
  }
1163
- if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && isNotNullish(dialect)) {
1164
- const placeholder = params.addAuto(val);
1165
- 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`);
1166
1133
  }
1167
- const STRING_LIKE_OPS = /* @__PURE__ */ new Set([
1168
- Ops.CONTAINS,
1169
- Ops.STARTS_WITH,
1170
- Ops.ENDS_WITH
1171
- ]);
1172
- if (STRING_LIKE_OPS.has(op)) {
1173
- if (!isNotNullish(dialect)) {
1174
- throw createError(`Like operators require a SQL dialect`, {
1175
- operator: op
1176
- });
1177
- }
1178
- return handleLikeOperator(expr, op, val, params, mode, dialect);
1134
+ if (v < min) {
1135
+ throw new Error(`${name} must be >= ${min}`);
1179
1136
  }
1180
- if (op === Ops.IN || op === Ops.NOT_IN) {
1181
- if (!isNotNullish(dialect)) {
1182
- throw createError(`IN operators require a SQL dialect`, { operator: op });
1183
- }
1184
- 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}`);
1185
1139
  }
1186
- return handleComparisonOperator(expr, op, val, params);
1187
- }
1188
- function handleNullValue(expr, op) {
1189
- if (op === Ops.EQUALS) return `${expr} ${SQL_TEMPLATES.IS_NULL}`;
1190
- if (op === Ops.NOT) return `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
1191
- throw createError(`Operator '${op}' doesn't support null`, { operator: op });
1192
- }
1193
- function normalizeMode(v) {
1194
- if (v === Modes.INSENSITIVE) return Modes.INSENSITIVE;
1195
- if (v === Modes.DEFAULT) return Modes.DEFAULT;
1196
- return void 0;
1140
+ return v;
1197
1141
  }
1198
- function handleNotOperator(expr, val, params, outerMode, fieldType, dialect) {
1199
- const innerMode = normalizeMode(val.mode);
1200
- const effectiveMode = innerMode != null ? innerMode : outerMode;
1201
- return buildNotComposite(
1202
- expr,
1203
- val,
1204
- params,
1205
- dialect,
1206
- (e, subOp, subVal, p, d) => buildScalarOperator(e, subOp, subVal, p, effectiveMode, fieldType, d),
1207
- ` ${SQL_TEMPLATES.AND} `
1208
- );
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}`;
1209
1147
  }
1210
- function buildDynamicLikePattern(op, placeholder, dialect) {
1211
- if (dialect === "postgres") {
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;
1221
- }
1222
- }
1223
- switch (op) {
1224
- case Ops.CONTAINS:
1225
- return `('%' || ${placeholder} || '%')`;
1226
- case Ops.STARTS_WITH:
1227
- return `(${placeholder} || '%')`;
1228
- case Ops.ENDS_WITH:
1229
- return `('%' || ${placeholder})`;
1230
- default:
1231
- 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));
1232
1152
  }
1153
+ return params.add(value);
1233
1154
  }
1234
- function handleLikeOperator(expr, op, val, params, mode, dialect) {
1235
- if (val === void 0) return "";
1236
- if (schemaParser.isDynamicParameter(val)) {
1237
- const placeholder2 = params.addAuto(val);
1238
- const patternExpr = buildDynamicLikePattern(op, placeholder2, dialect);
1239
- if (mode === Modes.INSENSITIVE) {
1240
- return caseInsensitiveLike(expr, patternExpr, dialect);
1241
- }
1242
- 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);
1243
1183
  }
1244
- const placeholder = params.add(Wildcards[op](String(val)));
1245
- if (mode === Modes.INSENSITIVE) {
1246
- 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");
1247
1194
  }
1248
- return `${expr} ${SQL_TEMPLATES.LIKE} ${placeholder}`;
1249
- }
1250
- function handleInOperator(expr, op, val, params, dialect) {
1251
- if (val === void 0) return "";
1252
- if (schemaParser.isDynamicParameter(val)) {
1253
- const placeholder2 = params.addAuto(val);
1254
- 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");
1255
1198
  }
1256
- if (!Array.isArray(val)) {
1257
- throw createError(`IN operators require array value`, {
1258
- operator: op,
1259
- value: val
1260
- });
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);
1261
1211
  }
1262
- if (val.length === 0) {
1263
- 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);
1264
1218
  }
1265
- const paramValue = prepareArrayParam(val, dialect);
1266
- const placeholder = params.add(paramValue);
1267
- 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");
1268
1223
  }
1269
- function handleComparisonOperator(expr, op, val, params) {
1270
- if (val === void 0) return "";
1271
- const COMPARISON_OPS2 = {
1272
- [Ops.EQUALS]: "=",
1273
- [Ops.GT]: ">",
1274
- [Ops.GTE]: ">=",
1275
- [Ops.LT]: "<",
1276
- [Ops.LTE]: "<="
1277
- };
1278
- const sqlOp = COMPARISON_OPS2[op];
1279
- if (!sqlOp) {
1280
- throw createError(`Unsupported scalar operator: ${op}`, { operator: op });
1281
- }
1282
- const placeholder = params.addAuto(val);
1283
- return `${expr} ${sqlOp} ${placeholder}`;
1284
- }
1285
- function buildArrayParam(val, params, dialect) {
1286
- if (schemaParser.isDynamicParameter(val)) {
1287
- 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);
1288
1237
  }
1289
- if (!Array.isArray(val)) {
1290
- throw createError(`Array operation requires array value`, { value: val });
1238
+ if (isPlainObject(orderBy)) {
1239
+ return normalizePairs(Object.entries(orderBy), parseValue);
1291
1240
  }
1292
- const paramValue = prepareArrayParam(val, dialect);
1293
- return params.add(paramValue);
1241
+ throw new Error("orderBy must be an object or array of objects");
1294
1242
  }
1295
- function buildArrayOperator(expr, op, val, params, fieldType, dialect) {
1296
- if (val === void 0) return "";
1297
- if (val === null) {
1298
- if (op === Ops.EQUALS) return `${expr} ${SQL_TEMPLATES.IS_NULL}`;
1299
- if (op === Ops.NOT) return `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
1300
- }
1301
- const cast = getArrayType(fieldType, dialect);
1302
- if (op === Ops.EQUALS) {
1303
- return handleArrayEquals(expr, val, params, cast, dialect);
1304
- }
1305
- if (op === Ops.NOT) {
1306
- return handleArrayNot(expr, val, params, cast, dialect);
1307
- }
1308
- switch (op) {
1309
- case Ops.HAS:
1310
- return handleArrayHas(expr, val, params, cast, dialect);
1311
- case Ops.HAS_SOME:
1312
- return handleArrayHasSome(expr, val, params, cast, dialect);
1313
- case Ops.HAS_EVERY:
1314
- return handleArrayHasEvery(expr, val, params, cast, dialect);
1315
- case Ops.IS_EMPTY:
1316
- return handleArrayIsEmpty(expr, val, dialect);
1317
- default:
1318
- throw createError(`Unknown array operator: ${op}`, { operator: op });
1319
- }
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}`);
1320
1250
  }
1321
- function handleArrayEquals(expr, val, params, cast, dialect) {
1322
- if (val === void 0) return "";
1323
- if (isEmptyArray(val)) {
1324
- 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? }`);
1325
1260
  }
1326
- const placeholder = buildArrayParam(val, params, dialect);
1327
- return arrayEquals(expr, placeholder, cast, dialect);
1261
+ return v;
1328
1262
  }
1329
- function handleArrayNot(expr, val, params, cast, dialect) {
1330
- if (val === void 0) return "";
1331
- let target = val;
1332
- if (isPlainObject(val)) {
1333
- const entries = Object.entries(val).filter(([, v]) => v !== void 0);
1334
- if (entries.length === 1 && entries[0][0] === Ops.EQUALS) {
1335
- target = entries[0][1];
1336
- } else {
1337
- throw createError(`Array NOT only supports { equals: ... } shape`, {
1338
- operator: Ops.NOT,
1339
- value: val
1340
- });
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
+ );
1341
1270
  }
1342
1271
  }
1343
- if (target === null) {
1344
- return `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
1345
- }
1346
- if (isEmptyArray(target)) {
1347
- return arrayIsNotEmpty(expr, dialect);
1348
- }
1349
- const placeholder = buildArrayParam(target, params, dialect);
1350
- return `${SQL_TEMPLATES.NOT} (${arrayEquals(expr, placeholder, cast, dialect)})`;
1351
1272
  }
1352
- function handleArrayHas(expr, val, params, cast, dialect) {
1353
- if (val === void 0) return "";
1354
- if (val === null) {
1355
- throw createError(`has requires scalar value`, {
1356
- operator: Ops.HAS,
1357
- value: val
1358
- });
1359
- }
1360
- if (!schemaParser.isDynamicParameter(val) && Array.isArray(val)) {
1361
- throw createError(`has requires scalar value (single element), not array`, {
1362
- operator: Ops.HAS,
1363
- value: val
1364
- });
1365
- }
1366
- if (isPlainObject(val)) {
1367
- throw createError(`has requires scalar value`, {
1368
- operator: Ops.HAS,
1369
- value: val
1370
- });
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`) };
1371
1277
  }
1372
- const placeholder = params.addAuto(val);
1373
- 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 };
1374
1283
  }
1375
- function handleArrayHasSome(expr, val, params, cast, dialect) {
1376
- if (val === void 0) return "";
1377
- if (schemaParser.isDynamicParameter(val)) {
1378
- const placeholder2 = params.addAuto(val);
1379
- 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`);
1380
1287
  }
1381
- if (!Array.isArray(val)) {
1382
- throw createError(`hasSome requires array value`, {
1383
- operator: Ops.HAS_SOME,
1384
- value: val
1385
- });
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`);
1386
1295
  }
1387
- if (val.length > LIMITS.MAX_ARRAY_SIZE) {
1388
- throw createError(
1389
- `Array too large (${val.length} elements, max ${LIMITS.MAX_ARRAY_SIZE})`,
1390
- { operator: Ops.HAS_SOME, value: `[${val.length} items]` }
1391
- );
1296
+ if (n > MAX_LIMIT_OFFSET) {
1297
+ throw new Error(`${name} must be <= ${MAX_LIMIT_OFFSET}`);
1392
1298
  }
1393
- if (val.length === 0) return "0=1";
1394
- const paramValue = prepareArrayParam(val, dialect);
1395
- const placeholder = params.add(paramValue);
1396
- return arrayOverlaps(expr, placeholder, cast, dialect);
1299
+ return n;
1397
1300
  }
1398
- function handleArrayHasEvery(expr, val, params, cast, dialect) {
1399
- if (val === void 0) return "";
1400
- const placeholder = buildArrayParam(val, params, dialect);
1401
- return arrayContainsAll(expr, placeholder, cast, dialect);
1301
+ function hasNonNullishProp(v, key) {
1302
+ return isPlainObject(v) && key in v && isNotNullish(v[key]);
1402
1303
  }
1403
- function handleArrayIsEmpty(expr, val, dialect) {
1404
- if (typeof val !== "boolean") {
1405
- throw createError(`isEmpty requires boolean value`, {
1406
- operator: Ops.IS_EMPTY,
1407
- value: val
1408
- });
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
+ };
1409
1318
  }
1410
- 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 };
1411
1323
  }
1412
-
1413
- // src/builder/where/operators-json.ts
1414
- var SAFE_JSON_PATH_SEGMENT = /^[a-zA-Z_]\w*$/;
1415
- function validateJsonPathSegments(segments) {
1416
- for (const segment of segments) {
1417
- if (typeof segment !== "string") {
1418
- throw createError("JSON path segments must be strings", {
1419
- operator: Ops.PATH,
1420
- value: segment
1421
- });
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;
1422
1334
  }
1423
- if (!SAFE_JSON_PATH_SEGMENT.test(segment)) {
1424
- throw createError(
1425
- `Invalid JSON path segment: '${segment}'. Must be alphanumeric with underscores, starting with letter or underscore.`,
1426
- { operator: Ops.PATH, value: segment }
1427
- );
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;
1428
1341
  }
1342
+ out.push(`${c} ${dir}`);
1429
1343
  }
1344
+ return out.join(SQL_SEPARATORS.ORDER_BY);
1430
1345
  }
1431
- function buildJsonOperator(expr, op, val, params, dialect) {
1432
- if (val === void 0) return "";
1433
- if (op === Ops.PATH && isPlainObject(val) && "path" in val) {
1434
- return handleJsonPath(expr, val, params, dialect);
1435
- }
1436
- const jsonWildcards = {
1437
- [Ops.STRING_CONTAINS]: (v) => `%${v}%`,
1438
- [Ops.STRING_STARTS_WITH]: (v) => `${v}%`,
1439
- [Ops.STRING_ENDS_WITH]: (v) => `%${v}`
1440
- };
1441
- if (op in jsonWildcards) {
1442
- return handleJsonWildcard(expr, op, val, params, jsonWildcards, dialect);
1346
+ function defaultNullsFor(dialect, direction) {
1347
+ if (dialect === "postgres") {
1348
+ return direction === "asc" ? "last" : "first";
1443
1349
  }
1444
- throw createError(`Unsupported JSON operator: ${op}`, { operator: op });
1350
+ return direction === "asc" ? "first" : "last";
1445
1351
  }
1446
- function handleJsonPath(expr, val, params, dialect) {
1447
- const v = val;
1448
- if (!Array.isArray(v.path)) {
1449
- throw createError("JSON path must be an array", { operator: Ops.PATH });
1450
- }
1451
- if (v.path.length === 0) {
1452
- 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
+ }
1453
1361
  }
1454
- validateJsonPathSegments(v.path);
1455
- const pathExpr = dialect === "sqlite" ? params.add(`$.${v.path.join(".")}`) : params.add(v.path);
1456
- const rawOps = [
1457
- ["=", v.equals],
1458
- [">", v.gt],
1459
- [">=", v.gte],
1460
- ["<", v.lt],
1461
- ["<=", v.lte]
1462
- ];
1463
- const ops = rawOps.filter(
1464
- ([, value]) => value !== void 0
1465
- );
1466
- if (ops.length === 0) {
1467
- throw createError("JSON path query missing comparison operator", {
1468
- operator: Ops.PATH
1469
- });
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");
1470
1368
  }
1369
+ const placeholdersByField = /* @__PURE__ */ new Map();
1471
1370
  const parts = [];
1472
- for (const [sqlOp, value] of ops) {
1371
+ for (const [field, value] of entries) {
1372
+ const c = `${cursorAlias}.${quote(field)}`;
1473
1373
  if (value === null) {
1474
- const base2 = jsonExtractText(expr, pathExpr, dialect);
1475
- parts.push(`${base2} ${SQL_TEMPLATES.IS_NULL}`);
1374
+ parts.push(`${c} IS NULL`);
1476
1375
  continue;
1477
1376
  }
1478
- const valPh = params.add(value);
1479
- const base = typeof value === "number" ? jsonExtractNumeric(expr, pathExpr, dialect) : jsonExtractText(expr, pathExpr, dialect);
1480
- parts.push(`${base} ${sqlOp} ${valPh}`);
1481
- }
1482
- return parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`;
1483
- }
1484
- function handleJsonWildcard(expr, op, val, params, wildcards, dialect) {
1485
- if (!isNotNullish(val)) {
1486
- throw createError(`JSON string operator requires non-null value`, {
1487
- operator: op,
1488
- value: val
1489
- });
1490
- }
1491
- if (isPlainObject(val) || Array.isArray(val)) {
1492
- throw createError(`JSON string operator requires scalar value`, {
1493
- operator: op,
1494
- value: val
1495
- });
1496
- }
1497
- const strVal = String(val);
1498
- if (strVal.length > LIMITS.MAX_STRING_LENGTH) {
1499
- throw createError(
1500
- `String too long (${strVal.length} chars, max ${LIMITS.MAX_STRING_LENGTH})`,
1501
- { operator: op }
1502
- );
1377
+ const ph = addAutoScoped(params, value, `cursor.filter.${field}`);
1378
+ placeholdersByField.set(field, ph);
1379
+ parts.push(`${c} = ${ph}`);
1503
1380
  }
1504
- const placeholder = params.add(wildcards[op](strVal));
1505
- const jsonText = jsonToText(expr, dialect);
1506
- return caseInsensitiveLike(jsonText, placeholder, dialect);
1381
+ return {
1382
+ whereSql: parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`,
1383
+ placeholdersByField
1384
+ };
1507
1385
  }
1508
-
1509
- // src/builder/where/relations.ts
1510
- var NO_JOINS = Object.freeze([]);
1511
- function isListRelation(fieldType) {
1512
- 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)`;
1513
1389
  }
1514
- function buildToOneNullCheck(field, parentAlias, relTable, relAlias, join3, wantNull) {
1515
- const isLocal = field.isForeignKeyLocal === true;
1516
- const fkFields = normalizeKeyList(field.foreignKey);
1517
- if (isLocal) {
1518
- if (fkFields.length === 0) {
1519
- throw createError(`Relation '${field.name}' is missing foreignKey`, {
1520
- field: field.name
1521
- });
1522
- }
1523
- const parts = fkFields.map((fk) => {
1524
- const safe = fk.replace(/"/g, '""');
1525
- const expr = `${parentAlias}."${safe}"`;
1526
- return wantNull ? `${expr} ${SQL_TEMPLATES.IS_NULL}` : `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
1527
- });
1528
- if (parts.length === 1) return parts[0];
1529
- return wantNull ? `(${parts.join(" OR ")})` : `(${parts.join(" AND ")})`;
1530
- }
1531
- const existsSql = `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias} ${SQL_TEMPLATES.WHERE} ${join3})`;
1532
- 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)`;
1533
1392
  }
1534
- function buildToOneExistsMatch(relTable, relAlias, join3, sub) {
1535
- const joins = sub.joins.length > 0 ? ` ${sub.joins.join(" ")}` : "";
1536
- 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}))`;
1537
1395
  }
1538
- function buildToOneNotExistsMatch(relTable, relAlias, join3, sub) {
1539
- const joins = sub.joins.length > 0 ? ` ${sub.joins.join(" ")}` : "";
1540
- 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)`;
1541
1402
  }
1542
- function buildListRelationFilters(args) {
1543
- const {
1544
- fieldName,
1545
- value,
1546
- ctx,
1547
- whereBuilder,
1548
- relModel,
1549
- relTable,
1550
- relAlias,
1551
- join: join3
1552
- } = args;
1553
- const noneValue = value[RelationFilters.NONE];
1554
- if (noneValue !== void 0 && noneValue !== null) {
1555
- const sub = whereBuilder.build(noneValue, __spreadProps(__spreadValues({}, ctx), {
1556
- alias: relAlias,
1557
- model: relModel,
1558
- path: [...ctx.path, fieldName, RelationFilters.NONE],
1559
- isSubquery: true,
1560
- depth: ctx.depth + 1
1561
- }));
1562
- const isEmptyFilter = isPlainObject(noneValue) && Object.keys(noneValue).length === 0;
1563
- const canOptimize = !ctx.isSubquery && isEmptyFilter && sub.clause === DEFAULT_WHERE_CLAUSE && sub.joins.length === 0;
1564
- if (canOptimize) {
1565
- const checkField = relModel.fields.find(
1566
- (f) => !f.isRelation && f.isRequired && f.name !== "id"
1567
- ) || relModel.fields.find((f) => !f.isRelation && f.name === "id");
1568
- if (checkField) {
1569
- const leftJoinSql = `LEFT JOIN ${relTable} ${relAlias} ON ${join3}`;
1570
- const whereClause = `${relAlias}.${quote(checkField.name)} IS NULL`;
1571
- return Object.freeze({
1572
- clause: whereClause,
1573
- joins: [leftJoinSql]
1574
- });
1575
- }
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;
1576
1415
  }
1416
+ const ph = addAutoScoped(params, value, `cursor.outerMatch.${field}`);
1417
+ parts.push(`${c} = ${ph}`);
1577
1418
  }
1578
- const filters = [
1579
- {
1580
- key: RelationFilters.SOME,
1581
- wrap: (c, j) => `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias}${j} ${SQL_TEMPLATES.WHERE} ${join3} ${SQL_TEMPLATES.AND} ${c})`
1582
- },
1583
- {
1584
- key: RelationFilters.EVERY,
1585
- 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}))`
1586
- },
1587
- {
1588
- key: RelationFilters.NONE,
1589
- wrap: (c, j) => {
1590
- const condition = c === DEFAULT_WHERE_CLAUSE ? "" : ` ${SQL_TEMPLATES.AND} ${c}`;
1591
- 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
+ });
1592
1434
  }
1593
1435
  }
1594
- ];
1595
- const clauses = [];
1596
- for (const { key, wrap } of filters) {
1597
- const raw = value[key];
1598
- if (raw === void 0 || raw === null) continue;
1599
- const sub = whereBuilder.build(raw, __spreadProps(__spreadValues({}, ctx), {
1600
- alias: relAlias,
1601
- model: relModel,
1602
- path: [...ctx.path, fieldName, key],
1603
- isSubquery: true,
1604
- depth: ctx.depth + 1
1605
- }));
1606
- const j = sub.joins.length > 0 ? ` ${sub.joins.join(" ")}` : "";
1607
- clauses.push(wrap(sub.clause, j));
1608
- }
1609
- if (clauses.length === 0) {
1610
- throw createError(
1611
- `List relation '${fieldName}' requires one of { some, every, none }`,
1612
- { field: fieldName, path: ctx.path, modelName: ctx.model.name }
1613
- );
1614
1436
  }
1615
- return Object.freeze({
1616
- clause: clauses.join(SQL_SEPARATORS.CONDITION_AND),
1617
- joins: NO_JOINS
1618
- });
1437
+ return entries;
1619
1438
  }
1620
- function buildToOneRelationFilters(args) {
1621
- const {
1622
- fieldName,
1623
- value,
1624
- ctx,
1625
- whereBuilder,
1626
- field,
1627
- relModel,
1628
- relTable,
1629
- relAlias,
1630
- join: join3
1631
- } = args;
1632
- const hasSomeEveryNone = isNotNullish(value[RelationFilters.SOME]) || isNotNullish(value[RelationFilters.EVERY]) || isNotNullish(value[RelationFilters.NONE]);
1633
- if (hasSomeEveryNone) {
1634
- throw createError(
1635
- `To-one relation '${fieldName}' does not support { some, every, none }; use { is, isNot }`,
1636
- { field: fieldName, path: ctx.path, modelName: ctx.model.name }
1637
- );
1638
- }
1639
- const hasIs = Object.prototype.hasOwnProperty.call(value, "is");
1640
- const hasIsNot = Object.prototype.hasOwnProperty.call(value, "isNot");
1641
- let filterKey;
1642
- let filterVal;
1643
- if (hasIs) {
1644
- filterKey = "is";
1645
- filterVal = value.is;
1646
- } else if (hasIsNot) {
1647
- filterKey = "isNot";
1648
- filterVal = value.isNot;
1649
- } else {
1650
- filterKey = "is";
1651
- 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");
1652
1445
  }
1653
- if (filterVal === void 0) {
1654
- return Object.freeze({
1655
- clause: DEFAULT_WHERE_CLAUSE,
1656
- joins: NO_JOINS
1657
- });
1658
- }
1659
- if (filterVal === null) {
1660
- const wantNull = filterKey === "is";
1661
- 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]) => ({
1662
1451
  field,
1663
- ctx.alias,
1664
- relTable,
1665
- relAlias,
1666
- join3,
1667
- wantNull
1668
- );
1669
- return Object.freeze({
1670
- clause: clause2,
1671
- joins: NO_JOINS
1672
- });
1452
+ direction: "asc"
1453
+ }));
1454
+ } else {
1455
+ orderEntries = ensureCursorFieldsInOrder(orderEntries, cursorEntries);
1673
1456
  }
1674
- if (!isPlainObject(filterVal)) {
1675
- throw createError(
1676
- `Relation '${fieldName}' filter must be an object or null`,
1677
- {
1678
- field: fieldName,
1679
- path: ctx.path,
1680
- modelName: ctx.model.name,
1681
- value: filterVal
1682
- }
1683
- );
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)})`);
1684
1492
  }
1685
- const sub = whereBuilder.build(filterVal, __spreadProps(__spreadValues({}, ctx), {
1686
- alias: relAlias,
1687
- model: relModel,
1688
- path: [...ctx.path, fieldName, filterKey],
1689
- isSubquery: true,
1690
- depth: ctx.depth + 1
1691
- }));
1692
- const clause = filterKey === "is" ? buildToOneExistsMatch(relTable, relAlias, join3, sub) : buildToOneNotExistsMatch(relTable, relAlias, join3, sub);
1693
- return Object.freeze({
1694
- clause,
1695
- joins: NO_JOINS
1696
- });
1493
+ const exclusive = orClauses.join(SQL_SEPARATORS.CONDITION_OR);
1494
+ return `(${existsExpr} ${SQL_SEPARATORS.CONDITION_AND} ((${exclusive})${SQL_SEPARATORS.CONDITION_OR}(${outerCursorMatch})))`;
1697
1495
  }
1698
- function ensureRelationFilterObject(fieldName, value, ctx) {
1699
- if (!isPlainObject(value)) {
1700
- throw createError(`Relation filter '${fieldName}' must be an object`, {
1701
- path: [...ctx.path, fieldName],
1702
- field: fieldName,
1703
- modelName: ctx.model.name,
1704
- value
1705
- });
1706
- }
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);
1707
1501
  }
1708
- function buildRelation(fieldName, value, ctx, whereBuilder) {
1709
- const field = ctx.model.fields.find((f) => f.name === fieldName);
1710
- if (!isValidRelationField(field)) {
1711
- throw createError(`Invalid relation '${fieldName}'`, {
1712
- field: fieldName,
1713
- path: ctx.path,
1714
- modelName: ctx.model.name
1715
- });
1716
- }
1717
- const relModel = ctx.schemaModels.find((m) => m.name === field.relatedModel);
1718
- if (!isNotNullish(relModel)) {
1719
- throw createError(
1720
- `Related model '${field.relatedModel}' not found in schema. Available models: ${ctx.schemaModels.map((m) => m.name).join(", ")}`,
1721
- {
1722
- field: fieldName,
1723
- path: ctx.path,
1724
- modelName: ctx.model.name
1725
- }
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"
1726
1508
  );
1727
1509
  }
1728
- const relTable = buildTableReference(
1729
- SQL_TEMPLATES.PUBLIC_SCHEMA,
1730
- relModel.tableName,
1731
- ctx.dialect
1732
- );
1733
- const relAlias = ctx.aliasGen.next(fieldName);
1734
- const join3 = joinCondition(field, ctx.model, relModel, ctx.alias, relAlias);
1735
- const args = {
1736
- fieldName,
1737
- value,
1738
- ctx,
1739
- whereBuilder,
1740
- field,
1741
- relModel,
1742
- relTable,
1743
- relAlias,
1744
- join: join3
1745
- };
1746
- if (isListRelation(field.type)) return buildListRelationFilters(args);
1747
- return buildToOneRelationFilters(args);
1510
+ return result;
1748
1511
  }
1749
- function buildTopLevelRelation(fieldName, value, ctx, whereBuilder) {
1750
- ensureRelationFilterObject(fieldName, value, ctx);
1751
- 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;
1752
1522
  }
1753
- function buildNestedRelation(fieldName, value, ctx, whereBuilder) {
1754
- 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
+ });
1755
1529
  }
1756
-
1757
- // src/builder/shared/validators/field-validators.ts
1758
- function assertFieldExists(name, model, path) {
1759
- const field = model.fields.find((f) => f.name === name);
1760
- if (!isNotNullish(field)) {
1761
- throw createError(`Field '${name}' does not exist on '${model.name}'`, {
1762
- field: name,
1763
- path,
1764
- modelName: model.name,
1765
- availableFields: model.fields.map((f) => f.name)
1766
- });
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
+ };
1767
1537
  }
1768
- return field;
1538
+ if (method === "findFirst") {
1539
+ const skip = normalizeSkipLike(args.skip);
1540
+ return { take: 1, skip: skip != null ? skip : 0 };
1541
+ }
1542
+ if (method === "findUnique") {
1543
+ return { take: 1, skip: 0 };
1544
+ }
1545
+ return {};
1769
1546
  }
1770
- function assertValidOperator(fieldName, op, fieldType, path, modelName) {
1771
- if (!isNotNullish(fieldType)) return;
1772
- const ARRAY_OPS = /* @__PURE__ */ new Set([
1773
- Ops.HAS,
1774
- Ops.HAS_SOME,
1775
- Ops.HAS_EVERY,
1776
- Ops.IS_EMPTY
1777
- ]);
1778
- const JSON_OPS = /* @__PURE__ */ new Set([
1779
- Ops.PATH,
1780
- Ops.STRING_CONTAINS,
1781
- Ops.STRING_STARTS_WITH,
1782
- Ops.STRING_ENDS_WITH
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})`);
1556
+ }
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)})`;
1560
+ }
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
1783
1581
  ]);
1784
- const isArrayOp = ARRAY_OPS.has(op);
1785
- const isFieldArray = isArrayType(fieldType);
1786
- const arrayOpMismatch = isArrayOp && !isFieldArray;
1787
- if (arrayOpMismatch) {
1788
- throw createError(`'${op}' requires array field, got '${fieldType}'`, {
1789
- operator: op,
1790
- field: fieldName,
1791
- path,
1792
- modelName
1793
- });
1582
+ if (STRING_LIKE_OPS.has(op)) {
1583
+ if (!isNotNullish(dialect)) {
1584
+ throw createError(`Like operators require a SQL dialect`, {
1585
+ operator: op
1586
+ });
1587
+ }
1588
+ return handleLikeOperator(expr, op, val, params, mode, dialect);
1794
1589
  }
1795
- const isJsonOp = JSON_OPS.has(op);
1796
- const isFieldJson = isJsonType(fieldType);
1797
- const jsonOpMismatch = isJsonOp && !isFieldJson;
1798
- if (jsonOpMismatch) {
1799
- throw createError(`'${op}' requires JSON field, got '${fieldType}'`, {
1800
- operator: op,
1801
- field: fieldName,
1802
- path,
1803
- modelName
1804
- });
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);
1805
1595
  }
1596
+ return handleComparisonOperator(expr, op, val, params);
1806
1597
  }
1807
-
1808
- // src/builder/where/builder.ts
1809
- var WhereBuilder = class {
1810
- build(where, ctx) {
1811
- if (!isPlainObject(where)) {
1812
- throw createError("where must be an object", {
1813
- path: ctx.path,
1814
- modelName: ctx.model.name
1815
- });
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 });
1602
+ }
1603
+ function normalizeMode(v) {
1604
+ if (v === Modes.INSENSITIVE) return Modes.INSENSITIVE;
1605
+ if (v === Modes.DEFAULT) return Modes.DEFAULT;
1606
+ return void 0;
1607
+ }
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
+ );
1619
+ }
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;
1816
1631
  }
1817
- return buildWhereInternal(where, ctx, this);
1818
1632
  }
1819
- };
1820
- var MAX_QUERY_DEPTH = 50;
1821
- var EMPTY_JOINS = Object.freeze([]);
1822
- var whereBuilderInstance = new WhereBuilder();
1823
- function freezeResult(clause, joins = EMPTY_JOINS) {
1824
- return Object.freeze({ clause, joins });
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;
1642
+ }
1825
1643
  }
1826
- function dedupePreserveOrder(items) {
1827
- if (items.length <= 1) return Object.freeze([...items]);
1828
- const seen = /* @__PURE__ */ new Set();
1829
- const out = [];
1830
- for (const s of items) {
1831
- if (!seen.has(s)) {
1832
- seen.add(s);
1833
- out.push(s);
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);
1834
1651
  }
1652
+ return `${expr} ${SQL_TEMPLATES.LIKE} ${patternExpr}`;
1835
1653
  }
1836
- return Object.freeze(out);
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}`;
1837
1659
  }
1838
- function appendResult(result, clauses, allJoins) {
1839
- if (isValidWhereClause(result.clause)) clauses.push(result.clause);
1840
- if (isNonEmptyArray(result.joins)) allJoins.push(...result.joins);
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);
1665
+ }
1666
+ if (!Array.isArray(val)) {
1667
+ throw createError(`IN operators require array value`, {
1668
+ operator: op,
1669
+ value: val
1670
+ });
1671
+ }
1672
+ if (val.length === 0) {
1673
+ return op === Ops.IN ? "0=1" : "1=1";
1674
+ }
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);
1841
1678
  }
1842
- function asLogicalOperator(key) {
1843
- if (key === LogicalOps.AND) return "AND";
1844
- if (key === LogicalOps.OR) return "OR";
1845
- if (key === LogicalOps.NOT) return "NOT";
1846
- return null;
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 });
1691
+ }
1692
+ const placeholder = params.addAuto(val);
1693
+ return `${expr} ${sqlOp} ${placeholder}`;
1847
1694
  }
1848
- function nextContext(ctx) {
1849
- return __spreadProps(__spreadValues({}, ctx), { depth: ctx.depth + 1 });
1695
+ function buildArrayParam(val, params, dialect) {
1696
+ if (schemaParser.isDynamicParameter(val)) {
1697
+ return params.addAuto(val);
1698
+ }
1699
+ if (!Array.isArray(val)) {
1700
+ throw createError(`Array operation requires array value`, { value: val });
1701
+ }
1702
+ const paramValue = prepareArrayParam(val, dialect);
1703
+ return params.add(paramValue);
1850
1704
  }
1851
- function buildRelationFilter(fieldName, value, ctx, builder) {
1852
- const ctx2 = nextContext(ctx);
1853
- if (ctx.isSubquery) {
1854
- return buildNestedRelation(fieldName, value, ctx2, builder);
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}`;
1710
+ }
1711
+ const cast = getArrayType(fieldType, dialect);
1712
+ if (op === Ops.EQUALS) {
1713
+ return handleArrayEquals(expr, val, params, cast, dialect);
1714
+ }
1715
+ if (op === Ops.NOT) {
1716
+ return handleArrayNot(expr, val, params, cast, dialect);
1717
+ }
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 });
1855
1729
  }
1856
- return buildTopLevelRelation(fieldName, value, ctx2, builder);
1857
1730
  }
1858
- function buildWhereEntry(key, value, ctx, builder) {
1859
- const op = asLogicalOperator(key);
1860
- if (op) return buildLogical(op, value, ctx, builder);
1861
- if (isRelationField(key, ctx.model)) {
1862
- if (!isPlainObject(value)) {
1863
- throw createError(`Relation filter '${key}' must be an object`, {
1864
- path: [...ctx.path, key],
1865
- field: key,
1866
- modelName: ctx.model.name,
1867
- value
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);
1738
+ }
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
1868
1750
  });
1869
1751
  }
1870
- return buildRelationFilter(key, value, ctx, builder);
1871
- }
1872
- return buildScalarField(key, value, ctx);
1873
- }
1874
- function buildWhereInternal(where, ctx, builder) {
1875
- if (ctx.depth > MAX_QUERY_DEPTH) {
1876
- throw createError(
1877
- `Query nesting too deep (max ${MAX_QUERY_DEPTH} levels). This usually indicates a circular reference.`,
1878
- { path: ctx.path, modelName: ctx.model.name }
1879
- );
1880
1752
  }
1881
- if (isEmptyWhere(where)) {
1882
- return freezeResult(DEFAULT_WHERE_CLAUSE, EMPTY_JOINS);
1753
+ if (target === null) {
1754
+ return `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
1883
1755
  }
1884
- const allJoins = [];
1885
- const clauses = [];
1886
- for (const [key, value] of Object.entries(where)) {
1887
- if (value === void 0) continue;
1888
- const result = buildWhereEntry(key, value, ctx, builder);
1889
- appendResult(result, clauses, allJoins);
1756
+ if (isEmptyArray(target)) {
1757
+ return arrayIsNotEmpty(expr, dialect);
1890
1758
  }
1891
- const finalClause = clauses.length > 0 ? clauses.join(SQL_SEPARATORS.CONDITION_AND) : DEFAULT_WHERE_CLAUSE;
1892
- return freezeResult(finalClause, dedupePreserveOrder(allJoins));
1759
+ const placeholder = buildArrayParam(target, params, dialect);
1760
+ return `${SQL_TEMPLATES.NOT} (${arrayEquals(expr, placeholder, cast, dialect)})`;
1893
1761
  }
1894
- function normalizeLogicalValue(operator, value, ctx) {
1895
- if (Array.isArray(value)) {
1896
- const out = [];
1897
- for (let i = 0; i < value.length; i++) {
1898
- const v = value[i];
1899
- if (v === void 0) continue;
1900
- if (!isPlainObject(v)) {
1901
- throw createError(`${operator} entries must be objects`, {
1902
- path: [...ctx.path, operator, String(i)],
1903
- modelName: ctx.model.name,
1904
- value: v
1905
- });
1906
- }
1907
- out.push(v);
1908
- }
1909
- return out;
1910
- }
1911
- if (isPlainObject(value)) {
1912
- return [value];
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
+ });
1913
1769
  }
1914
- throw createError(`${operator} must be an object or array of objects`, {
1915
- path: [...ctx.path, operator],
1916
- modelName: ctx.model.name,
1917
- value
1918
- });
1919
- }
1920
- function collectLogicalParts(operator, conditions, ctx, builder) {
1921
- const allJoins = [];
1922
- const clauses = [];
1923
- for (let i = 0; i < conditions.length; i++) {
1924
- const result = builder.build(conditions[i], __spreadProps(__spreadValues({}, ctx), {
1925
- path: [...ctx.path, operator, String(i)],
1926
- depth: ctx.depth + 1
1927
- }));
1928
- if (isNonEmptyArray(result.joins)) allJoins.push(...result.joins);
1929
- if (result.clause && result.clause !== DEFAULT_WHERE_CLAUSE) {
1930
- clauses.push(`(${result.clause})`);
1931
- }
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
+ });
1932
1775
  }
1933
- return {
1934
- joins: dedupePreserveOrder(allJoins),
1935
- clauses: Object.freeze(clauses)
1936
- };
1937
- }
1938
- function buildLogicalClause(operator, clauses) {
1939
- if (clauses.length === 0) return DEFAULT_WHERE_CLAUSE;
1940
- if (operator === "NOT") {
1941
- if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
1942
- return `${SQL_TEMPLATES.NOT} (${clauses.join(SQL_SEPARATORS.CONDITION_AND)})`;
1776
+ if (isPlainObject(val)) {
1777
+ throw createError(`has requires scalar value`, {
1778
+ operator: Ops.HAS,
1779
+ value: val
1780
+ });
1943
1781
  }
1944
- return clauses.join(` ${operator} `);
1782
+ const placeholder = params.addAuto(val);
1783
+ return arrayContains(expr, placeholder, cast, dialect);
1945
1784
  }
1946
- function buildLogical(operator, value, ctx, builder) {
1947
- const conditions = normalizeLogicalValue(operator, value, ctx);
1948
- if (conditions.length === 0) {
1949
- const clause2 = operator === "OR" ? "0=1" : DEFAULT_WHERE_CLAUSE;
1950
- return freezeResult(clause2, EMPTY_JOINS);
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);
1951
1790
  }
1952
- const { joins, clauses } = collectLogicalParts(
1953
- operator,
1954
- conditions,
1955
- ctx,
1956
- builder
1957
- );
1958
- const clause = buildLogicalClause(operator, clauses);
1959
- return freezeResult(clause, joins);
1960
- }
1961
- function buildScalarField(fieldName, value, ctx) {
1962
- const field = assertFieldExists(fieldName, ctx.model, ctx.path);
1963
- const expr = col(ctx.alias, fieldName, ctx.model);
1964
- if (value === null) {
1965
- return freezeResult(`${expr} ${SQL_TEMPLATES.IS_NULL}`, EMPTY_JOINS);
1791
+ if (!Array.isArray(val)) {
1792
+ throw createError(`hasSome requires array value`, {
1793
+ operator: Ops.HAS_SOME,
1794
+ value: val
1795
+ });
1966
1796
  }
1967
- if (isPlainObject(value)) {
1968
- const mode = value.mode;
1969
- const ops = Object.entries(value).filter(
1970
- ([k, v]) => k !== "mode" && v !== void 0
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]` }
1971
1801
  );
1972
- if (ops.length === 0) {
1973
- return freezeResult(DEFAULT_WHERE_CLAUSE, EMPTY_JOINS);
1974
- }
1975
- const parts = [];
1976
- for (const [op, val] of ops) {
1977
- assertValidOperator(fieldName, op, field.type, ctx.path, ctx.model.name);
1978
- const clause3 = buildOperator(expr, op, val, ctx, mode, field.type);
1979
- if (isValidWhereClause(clause3)) parts.push(clause3);
1980
- }
1981
- const clause2 = parts.length > 0 ? parts.join(SQL_SEPARATORS.CONDITION_AND) : DEFAULT_WHERE_CLAUSE;
1982
- return freezeResult(clause2, EMPTY_JOINS);
1983
1802
  }
1984
- const clause = buildOperator(
1985
- expr,
1986
- Ops.EQUALS,
1987
- value,
1988
- ctx,
1989
- void 0,
1990
- field.type
1991
- );
1992
- return freezeResult(clause || DEFAULT_WHERE_CLAUSE, EMPTY_JOINS);
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);
1993
1807
  }
1994
- function buildOperator(expr, op, val, ctx, mode, fieldType) {
1995
- if (fieldType && isArrayType(fieldType)) {
1996
- return buildArrayOperator(expr, op, val, ctx.params, fieldType, ctx.dialect);
1997
- }
1998
- if (fieldType && isJsonType(fieldType)) {
1999
- const JSON_OPS = /* @__PURE__ */ new Set([
2000
- Ops.PATH,
2001
- Ops.STRING_CONTAINS,
2002
- Ops.STRING_STARTS_WITH,
2003
- Ops.STRING_ENDS_WITH
2004
- ]);
2005
- if (JSON_OPS.has(op)) {
2006
- return buildJsonOperator(expr, op, val, ctx.params, ctx.dialect);
2007
- }
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);
1812
+ }
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
+ });
2008
1819
  }
2009
- return buildScalarOperator(
2010
- expr,
2011
- op,
2012
- val,
2013
- ctx.params,
2014
- mode,
2015
- fieldType,
2016
- ctx.dialect
2017
- );
1820
+ return val === true ? arrayIsEmpty(expr, dialect) : arrayIsNotEmpty(expr, dialect);
2018
1821
  }
2019
1822
 
2020
- // src/builder/shared/alias-generator.ts
2021
- function toSafeSqlIdentifier(input) {
2022
- const raw = String(input);
2023
- const cleaned = raw.replace(/\W/g, "_");
2024
- const startsOk = /^[a-zA-Z_]/.test(cleaned);
2025
- const base = startsOk ? cleaned : `_${cleaned}`;
2026
- const fallback = base.length > 0 ? base : "_t";
2027
- const lowered = fallback.toLowerCase();
2028
- return SQL_KEYWORDS.has(lowered) ? `_${lowered}` : lowered;
2029
- }
2030
- function createAliasGenerator(maxAliases = 1e4) {
2031
- let counter = 0;
2032
- const usedAliases = /* @__PURE__ */ new Set();
2033
- return {
2034
- next(baseName) {
2035
- if (usedAliases.size >= maxAliases) {
2036
- throw new Error(
2037
- `Alias generator exceeded maximum of ${maxAliases} aliases. This indicates a query complexity issue or potential infinite loop.`
2038
- );
2039
- }
2040
- const base = toSafeSqlIdentifier(baseName);
2041
- const suffix = `_${counter}`;
2042
- const maxLen = 63;
2043
- const baseMax = Math.max(1, maxLen - suffix.length);
2044
- const trimmedBase = base.length > baseMax ? base.slice(0, baseMax) : base;
2045
- const alias = `${trimmedBase}${suffix}`;
2046
- counter += 1;
2047
- if (usedAliases.has(alias)) {
2048
- throw new Error(
2049
- `CRITICAL: Duplicate alias '${alias}' at counter=${counter}. This indicates a bug in alias generation logic.`
2050
- );
2051
- }
2052
- usedAliases.add(alias);
2053
- return alias;
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
+ });
2054
1832
  }
2055
- };
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 }
1837
+ );
1838
+ }
1839
+ }
2056
1840
  }
2057
- var MAX_PARAM_INDEX = Number.MAX_SAFE_INTEGER - 1e3;
2058
- function assertSameLength(params, mappings) {
2059
- if (params.length !== mappings.length) {
2060
- throw new Error(
2061
- `CRITICAL: State corruption - params=${params.length}, mappings=${mappings.length}`
2062
- );
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);
1845
+ }
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);
2063
1853
  }
1854
+ throw createError(`Unsupported JSON operator: ${op}`, { operator: op });
2064
1855
  }
2065
- function assertValidNextIndex(index) {
2066
- if (!Number.isInteger(index) || index < 1) {
2067
- throw new Error(`CRITICAL: Index must be integer >= 1, got ${index}`);
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 });
2068
1860
  }
2069
- }
2070
- function assertNextIndexMatches(mappingsLength, nextIndex) {
2071
- const expected = mappingsLength + 1;
2072
- if (nextIndex !== expected) {
2073
- throw new Error(
2074
- `CRITICAL: Next index mismatch - expected ${expected}, got ${nextIndex}`
2075
- );
1861
+ if (v.path.length === 0) {
1862
+ throw createError("JSON path cannot be empty", { operator: Ops.PATH });
2076
1863
  }
2077
- }
2078
- function assertSequentialIndex(actual, expected) {
2079
- if (actual !== expected) {
2080
- throw new Error(
2081
- `CRITICAL: Indices must be sequential from 1..N. Expected ${expected}, got ${actual}`
2082
- );
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
+ });
2083
1880
  }
2084
- }
2085
- function assertExactlyOneOfDynamicOrValue(m) {
2086
- const hasDynamic = typeof m.dynamicName === "string";
2087
- const hasStatic = m.value !== void 0;
2088
- if (hasDynamic === hasStatic) {
2089
- throw new Error(
2090
- `CRITICAL: ParamMap ${m.index} must have exactly one of dynamicName or value`
2091
- );
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;
1887
+ }
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}`);
2092
1891
  }
1892
+ return parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`;
2093
1893
  }
2094
- function normalizeDynamicNameOrThrow(dynamicName, index) {
2095
- const dn = dynamicName.trim();
2096
- if (dn.length === 0) {
2097
- throw new Error(`CRITICAL: dynamicName cannot be empty (index=${index})`);
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
1899
+ });
2098
1900
  }
2099
- return dn;
2100
- }
2101
- function assertUniqueDynamicName(dn, seen) {
2102
- if (seen.has(dn)) {
2103
- throw new Error(`CRITICAL: Duplicate dynamic param name in mappings: ${dn}`);
1901
+ if (isPlainObject(val) || Array.isArray(val)) {
1902
+ throw createError(`JSON string operator requires scalar value`, {
1903
+ operator: op,
1904
+ value: val
1905
+ });
2104
1906
  }
2105
- seen.add(dn);
2106
- }
2107
- function validateMappingEntry(m, expectedIndex, seenDynamic) {
2108
- assertSequentialIndex(m.index, expectedIndex);
2109
- assertExactlyOneOfDynamicOrValue(m);
2110
- if (typeof m.dynamicName === "string") {
2111
- const dn = normalizeDynamicNameOrThrow(m.dynamicName, m.index);
2112
- assertUniqueDynamicName(dn, seenDynamic);
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 }
1912
+ );
2113
1913
  }
1914
+ const placeholder = params.add(wildcards[op](strVal));
1915
+ const jsonText = jsonToText(expr, dialect);
1916
+ return caseInsensitiveLike(jsonText, placeholder, dialect);
2114
1917
  }
2115
- function validateMappings(mappings) {
2116
- const seenDynamic = /* @__PURE__ */ new Set();
2117
- for (let i = 0; i < mappings.length; i++) {
2118
- validateMappingEntry(mappings[i], i + 1, seenDynamic);
1918
+
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 ")})`;
2119
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;
2120
1943
  }
2121
- function validateState(params, mappings, index) {
2122
- assertSameLength(params, mappings);
2123
- assertValidNextIndex(index);
2124
- if (mappings.length === 0) return;
2125
- validateMappings(mappings);
2126
- assertNextIndexMatches(mappings.length, index);
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})`;
2127
1947
  }
2128
- function normalizeValue(value) {
2129
- if (value instanceof Date) {
2130
- return value.toISOString();
2131
- }
2132
- return value;
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})`;
2133
1951
  }
2134
- function createStoreInternal(startIndex, initialParams = [], initialMappings = []) {
2135
- let index = startIndex;
2136
- const params = [...initialParams];
2137
- const mappings = [...initialMappings];
2138
- const dynamicNameToIndex = /* @__PURE__ */ new Map();
2139
- for (const m of initialMappings) {
2140
- if (typeof m.dynamicName === "string") {
2141
- dynamicNameToIndex.set(m.dynamicName.trim(), m.index);
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
+ }
2142
1986
  }
2143
1987
  }
2144
- function assertCanAdd() {
2145
- if (index > MAX_PARAM_INDEX) {
2146
- throw new Error(
2147
- `CRITICAL: Cannot add param - would overflow MAX_SAFE_INTEGER. Current index: ${index}`
2148
- );
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
+ }
2149
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));
2150
2018
  }
2151
- function normalizeDynamicName(dynamicName) {
2152
- const dn = dynamicName.trim();
2153
- if (dn.length === 0) {
2154
- throw new Error("CRITICAL: dynamicName cannot be empty");
2155
- }
2156
- return dn;
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
+ );
2157
2024
  }
2158
- function format(position) {
2159
- return `$${position}`;
2025
+ return Object.freeze({
2026
+ clause: clauses.join(SQL_SEPARATORS.CONDITION_AND),
2027
+ joins: NO_JOINS
2028
+ });
2029
+ }
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 }
2047
+ );
2160
2048
  }
2161
- function addDynamic(dynamicName) {
2162
- const dn = normalizeDynamicName(dynamicName);
2163
- const existing = dynamicNameToIndex.get(dn);
2164
- if (existing !== void 0) {
2165
- return format(existing);
2166
- }
2167
- const position = index;
2168
- dynamicNameToIndex.set(dn, position);
2169
- params.push(void 0);
2170
- mappings.push({ index: position, dynamicName: dn });
2171
- index++;
2172
- return format(position);
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;
2173
2062
  }
2174
- function addStatic(value) {
2175
- const position = index;
2176
- const normalizedValue = normalizeValue(value);
2177
- params.push(normalizedValue);
2178
- mappings.push({ index: position, value: normalizedValue });
2179
- index++;
2180
- return format(position);
2063
+ if (filterVal === void 0) {
2064
+ return Object.freeze({
2065
+ clause: DEFAULT_WHERE_CLAUSE,
2066
+ joins: NO_JOINS
2067
+ });
2181
2068
  }
2182
- function add(value, dynamicName) {
2183
- assertCanAdd();
2184
- return dynamicName === void 0 ? addStatic(value) : addDynamic(dynamicName);
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
+ });
2185
2083
  }
2186
- function addAuto(value) {
2187
- if (schemaParser.isDynamicParameter(value)) {
2188
- const dynamicName = schemaParser.extractDynamicName(value);
2189
- return add(void 0, dynamicName);
2190
- }
2191
- return add(value);
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
+ );
2192
2094
  }
2193
- function snapshot() {
2194
- return Object.freeze({
2195
- index,
2196
- params: Object.freeze([...params]),
2197
- mappings: Object.freeze([...mappings])
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
2198
2115
  });
2199
2116
  }
2200
- return {
2201
- add,
2202
- addAuto,
2203
- snapshot,
2204
- get index() {
2205
- return index;
2206
- }
2207
- };
2208
2117
  }
2209
- function createParamStore(startIndex = 1) {
2210
- if (!Number.isInteger(startIndex) || startIndex < 1) {
2211
- throw new Error(`Start index must be integer >= 1, got ${startIndex}`);
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
+ });
2212
2126
  }
2213
- if (startIndex > MAX_PARAM_INDEX) {
2214
- throw new Error(
2215
- `Start index too high (${startIndex}), risk of overflow at MAX_SAFE_INTEGER`
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
2135
+ }
2216
2136
  );
2217
2137
  }
2218
- return createStoreInternal(startIndex);
2219
- }
2220
- function createParamStoreFrom(existingParams, existingMappings, nextIndex) {
2221
- validateState([...existingParams], [...existingMappings], nextIndex);
2222
- return createStoreInternal(
2223
- nextIndex,
2224
- [...existingParams],
2225
- [...existingMappings]
2138
+ const relTable = buildTableReference(
2139
+ SQL_TEMPLATES.PUBLIC_SCHEMA,
2140
+ relModel.tableName,
2141
+ ctx.dialect
2226
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);
2227
2165
  }
2228
2166
 
2229
- // src/builder/shared/state.ts
2230
- function toPublicResult(clause, joins, params) {
2231
- const snapshot = params.snapshot();
2232
- return Object.freeze({
2233
- clause: clause || DEFAULT_WHERE_CLAUSE,
2234
- joins: Object.freeze([...joins]),
2235
- params: snapshot.params,
2236
- paramMappings: snapshot.mappings,
2237
- nextParamIndex: snapshot.index
2238
- });
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
+ }
2239
2216
  }
2240
2217
 
2241
- // src/builder/where.ts
2242
- function buildWhereClause(where, options) {
2243
- var _a, _b, _c, _d, _e;
2244
- const dialect = options.dialect || getGlobalDialect();
2245
- const params = (_a = options.params) != null ? _a : createParamStore();
2246
- const ctx = {
2247
- alias: options.alias,
2248
- model: options.model,
2249
- schemaModels: (_b = options.schemaModels) != null ? _b : [],
2250
- path: (_c = options.path) != null ? _c : [],
2251
- isSubquery: (_d = options.isSubquery) != null ? _d : false,
2252
- aliasGen: (_e = options.aliasGen) != null ? _e : createAliasGenerator(),
2253
- dialect,
2254
- params,
2255
- depth: 0
2256
- };
2257
- const result = whereBuilderInstance.build(where, ctx);
2258
- const publicResult = toPublicResult(result.clause, result.joins, params);
2259
- if (!options.isSubquery) {
2260
- const nums = [...publicResult.clause.matchAll(/\$(\d+)/g)].map(
2261
- (m) => parseInt(m[1], 10)
2262
- );
2263
- if (nums.length > 0) {
2264
- const min = Math.min(...nums);
2265
- if (min === 1) {
2266
- validateParamConsistency(publicResult.clause, publicResult.params);
2267
- } else {
2268
- validateParamConsistencyFragment(
2269
- publicResult.clause,
2270
- publicResult.params
2271
- );
2272
- }
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
+ });
2273
2226
  }
2227
+ return buildWhereInternal(where, ctx, this);
2274
2228
  }
2275
- return publicResult;
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 });
2276
2235
  }
2277
- function normalizeIntLike(name, v, opts = {}) {
2278
- var _a, _b;
2279
- if (!isNotNullish(v)) return void 0;
2280
- if (schemaParser.isDynamicParameter(v)) return v;
2281
- if (typeof v !== "number" || !Number.isFinite(v) || !Number.isInteger(v)) {
2282
- throw new Error(`${name} must be an integer`);
2283
- }
2284
- const min = (_a = opts.min) != null ? _a : 0;
2285
- const allowZero = (_b = opts.allowZero) != null ? _b : true;
2286
- if (!allowZero && v === 0) {
2287
- throw new Error(`${name} must be > 0`);
2288
- }
2289
- if (v < min) {
2290
- throw new Error(`${name} must be >= ${min}`);
2291
- }
2292
- if (typeof opts.max === "number" && v > opts.max) {
2293
- throw new Error(`${name} must be <= ${opts.max}`);
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
+ }
2294
2245
  }
2295
- return v;
2246
+ return Object.freeze(out);
2296
2247
  }
2297
- function scopeName(scope, dynamicName) {
2298
- const s = String(scope).trim();
2299
- const dn = String(dynamicName).trim();
2300
- if (s.length === 0) return dn;
2301
- return `${s}:${dn}`;
2248
+ function appendResult(result, clauses, allJoins) {
2249
+ if (isValidWhereClause(result.clause)) clauses.push(result.clause);
2250
+ if (isNonEmptyArray(result.joins)) allJoins.push(...result.joins);
2302
2251
  }
2303
- function addAutoScoped(params, value, scope) {
2304
- if (schemaParser.isDynamicParameter(value)) {
2305
- const dn = schemaParser.extractDynamicName(value);
2306
- return params.add(void 0, scopeName(scope, dn));
2307
- }
2308
- return params.add(value);
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;
2309
2257
  }
2310
-
2311
- // src/builder/shared/order-by-utils.ts
2312
- var flipNulls = (v) => {
2313
- const s = String(v).toLowerCase();
2314
- if (s === "first") return "last";
2315
- if (s === "last") return "first";
2316
- return v;
2317
- };
2318
- var flipSortString = (v) => {
2319
- if (typeof v !== "string") return v;
2320
- const s = v.toLowerCase();
2321
- if (s === "asc") return "desc";
2322
- if (s === "desc") return "asc";
2323
- return v;
2324
- };
2325
- var getNextSort = (sortRaw) => {
2326
- if (typeof sortRaw !== "string") return sortRaw;
2327
- const s = sortRaw.toLowerCase();
2328
- if (s === "asc") return "desc";
2329
- if (s === "desc") return "asc";
2330
- return sortRaw;
2331
- };
2332
- var flipObjectSort = (obj) => {
2333
- const sortRaw = obj.sort;
2334
- const out = __spreadProps(__spreadValues({}, obj), { sort: getNextSort(sortRaw) });
2335
- const nullsRaw = obj.nulls;
2336
- if (typeof nullsRaw === "string") {
2337
- out.nulls = flipNulls(nullsRaw);
2338
- }
2339
- return out;
2340
- };
2341
- var flipValue = (v) => {
2342
- if (typeof v === "string") return flipSortString(v);
2343
- if (isPlainObject(v)) return flipObjectSort(v);
2344
- return v;
2345
- };
2346
- var assertSingleFieldObject = (item) => {
2347
- if (!isPlainObject(item)) {
2348
- throw new Error("orderBy array entries must be objects");
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);
2349
2265
  }
2350
- const entries = Object.entries(item);
2351
- if (entries.length !== 1) {
2352
- throw new Error("orderBy array entries must have exactly one field");
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
+ });
2279
+ }
2280
+ return buildRelationFilter(key, value, ctx, builder);
2353
2281
  }
2354
- return entries[0];
2355
- };
2356
- var flipOrderByArray = (orderBy) => {
2357
- return orderBy.map((item) => {
2358
- const [k, v] = assertSingleFieldObject(item);
2359
- return { [k]: flipValue(v) };
2360
- });
2361
- };
2362
- var flipOrderByObject = (orderBy) => {
2363
- const out = {};
2364
- for (const [k, v] of Object.entries(orderBy)) {
2365
- out[k] = flipValue(v);
2282
+ return buildScalarField(key, value, ctx);
2283
+ }
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
+ );
2366
2290
  }
2367
- return out;
2368
- };
2369
- function reverseOrderByInput(orderBy) {
2370
- if (!isNotNullish(orderBy)) return orderBy;
2371
- if (Array.isArray(orderBy)) {
2372
- return flipOrderByArray(orderBy);
2291
+ if (isEmptyWhere(where)) {
2292
+ return freezeResult(DEFAULT_WHERE_CLAUSE, EMPTY_JOINS);
2373
2293
  }
2374
- if (isPlainObject(orderBy)) {
2375
- return flipOrderByObject(orderBy);
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);
2376
2300
  }
2377
- throw new Error("orderBy must be an object or array of objects");
2301
+ const finalClause = clauses.length > 0 ? clauses.join(SQL_SEPARATORS.CONDITION_AND) : DEFAULT_WHERE_CLAUSE;
2302
+ return freezeResult(finalClause, dedupePreserveOrder(allJoins));
2378
2303
  }
2379
- var normalizePairs = (pairs, parseValue) => {
2380
- return pairs.map(([field, rawValue]) => {
2381
- const parsed = parseValue(rawValue, field);
2382
- return {
2383
- [field]: parsed.nulls !== void 0 ? { sort: parsed.direction, nulls: parsed.nulls } : parsed.direction
2384
- };
2385
- });
2386
- };
2387
- function normalizeOrderByInput(orderBy, parseValue) {
2388
- if (!isNotNullish(orderBy)) return [];
2389
- if (Array.isArray(orderBy)) {
2390
- const pairs = orderBy.map(assertSingleFieldObject);
2391
- return normalizePairs(pairs, parseValue);
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;
2392
2320
  }
2393
- if (isPlainObject(orderBy)) {
2394
- return normalizePairs(Object.entries(orderBy), parseValue);
2321
+ if (isPlainObject(value)) {
2322
+ return [value];
2395
2323
  }
2396
- throw new Error("orderBy must be an object or array of objects");
2397
- }
2398
-
2399
- // src/builder/pagination.ts
2400
- var MAX_LIMIT_OFFSET = 2147483647;
2401
- function parseDirectionRaw(raw, errorLabel) {
2402
- const s = String(raw).toLowerCase();
2403
- if (s === "asc" || s === "desc") return s;
2404
- throw new Error(`Invalid ${errorLabel}: ${raw}`);
2324
+ throw createError(`${operator} must be an object or array of objects`, {
2325
+ path: [...ctx.path, operator],
2326
+ modelName: ctx.model.name,
2327
+ value
2328
+ });
2405
2329
  }
2406
- function parseNullsRaw(raw, errorLabel) {
2407
- if (!isNotNullish(raw)) return void 0;
2408
- const s = String(raw).toLowerCase();
2409
- if (s === "first" || s === "last") return s;
2410
- throw new Error(`Invalid ${errorLabel}: ${raw}`);
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
+ }
2342
+ }
2343
+ return {
2344
+ joins: dedupePreserveOrder(allJoins),
2345
+ clauses: Object.freeze(clauses)
2346
+ };
2411
2347
  }
2412
- function requireOrderByObject(v, errorPrefix) {
2413
- if (!isPlainObject(v) || !("sort" in v)) {
2414
- throw new Error(`${errorPrefix} must be 'asc' | 'desc' or { sort, nulls? }`);
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)})`;
2415
2353
  }
2416
- return v;
2354
+ return clauses.join(` ${operator} `);
2417
2355
  }
2418
- function assertAllowedOrderByKeys(obj, fieldName) {
2419
- const allowed = /* @__PURE__ */ new Set(["sort", "nulls"]);
2420
- for (const k of Object.keys(obj)) {
2421
- if (!allowed.has(k)) {
2422
- throw new Error(
2423
- fieldName ? `Unsupported orderBy key '${k}' for field '${fieldName}'` : `Unsupported orderBy key '${k}'`
2424
- );
2425
- }
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);
2426
2361
  }
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);
2427
2370
  }
2428
- function parseOrderByValue(v, fieldName) {
2429
- const errorPrefix = fieldName ? `orderBy for '${fieldName}'` : "orderBy value";
2430
- if (typeof v === "string") {
2431
- return { direction: parseDirectionRaw(v, `${errorPrefix} direction`) };
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);
2432
2376
  }
2433
- const obj = requireOrderByObject(v, errorPrefix);
2434
- const direction = parseDirectionRaw(obj.sort, `${errorPrefix}.sort`);
2435
- const nulls = parseNullsRaw(obj.nulls, `${errorPrefix}.nulls`);
2436
- assertAllowedOrderByKeys(obj, fieldName);
2437
- return { direction, nulls };
2438
- }
2439
- function normalizeFiniteInteger(name, v) {
2440
- if (typeof v !== "number" || !Number.isFinite(v) || !Number.isInteger(v)) {
2441
- throw new Error(`${name} must be an integer`);
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);
2442
2393
  }
2443
- return v;
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);
2444
2403
  }
2445
- function normalizeNonNegativeInt(name, v) {
2446
- if (schemaParser.isDynamicParameter(v)) return v;
2447
- const n = normalizeFiniteInteger(name, v);
2448
- if (n < 0) {
2449
- throw new Error(`${name} must be >= 0`);
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);
2450
2407
  }
2451
- if (n > MAX_LIMIT_OFFSET) {
2452
- throw new Error(`${name} must be <= ${MAX_LIMIT_OFFSET}`);
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);
2417
+ }
2453
2418
  }
2454
- return n;
2419
+ return buildScalarOperator(
2420
+ expr,
2421
+ op,
2422
+ val,
2423
+ ctx.params,
2424
+ mode,
2425
+ fieldType,
2426
+ ctx.dialect
2427
+ );
2455
2428
  }
2456
- function hasNonNullishProp(v, key) {
2457
- return isPlainObject(v) && key in v && isNotNullish(v[key]);
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;
2458
2439
  }
2459
- function normalizeIntegerOrDynamic(name, v) {
2460
- if (schemaParser.isDynamicParameter(v)) return v;
2461
- return normalizeFiniteInteger(name, 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
+ };
2462
2466
  }
2463
- function readSkipTake(relArgs) {
2464
- const hasSkip = hasNonNullishProp(relArgs, "skip");
2465
- const hasTake = hasNonNullishProp(relArgs, "take");
2466
- if (!hasSkip && !hasTake) {
2467
- return {
2468
- hasSkip: false,
2469
- hasTake: false,
2470
- skipVal: void 0,
2471
- takeVal: void 0
2472
- };
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
+ );
2473
2473
  }
2474
- const obj = relArgs;
2475
- const skipVal = hasSkip ? normalizeNonNegativeInt("skip", obj.skip) : void 0;
2476
- const takeVal = hasTake ? normalizeIntegerOrDynamic("take", obj.take) : void 0;
2477
- return { hasSkip, hasTake, skipVal, takeVal };
2478
2474
  }
2479
- function buildOrderByFragment(entries, alias, dialect, model) {
2480
- if (entries.length === 0) return "";
2481
- const out = [];
2482
- for (const e of entries) {
2483
- const dir = e.direction.toUpperCase();
2484
- const c = col(alias, e.field, model);
2485
- if (dialect === "postgres") {
2486
- const nulls = isNotNullish(e.nulls) ? ` NULLS ${e.nulls.toUpperCase()}` : "";
2487
- out.push(`${c} ${dir}${nulls}`);
2488
- continue;
2489
- }
2490
- if (isNotNullish(e.nulls)) {
2491
- const isNullExpr = `(${c} IS NULL)`;
2492
- const nullRankDir = e.nulls === "first" ? "DESC" : "ASC";
2493
- out.push(`${isNullExpr} ${nullRankDir}`);
2494
- out.push(`${c} ${dir}`);
2495
- continue;
2496
- }
2497
- out.push(`${c} ${dir}`);
2475
+ function assertValidNextIndex(index) {
2476
+ if (!Number.isInteger(index) || index < 1) {
2477
+ throw new Error(`CRITICAL: Index must be integer >= 1, got ${index}`);
2498
2478
  }
2499
- return out.join(SQL_SEPARATORS.ORDER_BY);
2500
2479
  }
2501
- function defaultNullsFor(dialect, direction) {
2502
- if (dialect === "postgres") {
2503
- return direction === "asc" ? "last" : "first";
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
+ );
2504
2486
  }
2505
- return direction === "asc" ? "first" : "last";
2506
2487
  }
2507
- function ensureCursorFieldsInOrder(orderEntries, cursorEntries) {
2508
- const existing = /* @__PURE__ */ new Map();
2509
- for (const e of orderEntries) existing.set(e.field, e);
2510
- const out = [...orderEntries];
2511
- for (const [field] of cursorEntries) {
2512
- if (!existing.has(field)) {
2513
- out.push({ field, direction: "asc" });
2514
- existing.set(field, out[out.length - 1]);
2515
- }
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
+ );
2516
2493
  }
2517
- return out;
2518
2494
  }
2519
- function buildCursorFilterParts(cursor, cursorAlias, params, model) {
2520
- const entries = Object.entries(cursor);
2521
- if (entries.length === 0) {
2522
- throw new Error("cursor must have at least one field");
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
+ );
2523
2502
  }
2524
- const placeholdersByField = /* @__PURE__ */ new Map();
2525
- const parts = [];
2526
- for (const [field, value] of entries) {
2527
- const c = `${cursorAlias}.${quote(field)}`;
2528
- if (value === null) {
2529
- parts.push(`${c} IS NULL`);
2530
- continue;
2531
- }
2532
- const ph = addAutoScoped(params, value, `cursor.filter.${field}`);
2533
- placeholdersByField.set(field, ph);
2534
- parts.push(`${c} = ${ph}`);
2503
+ }
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})`);
2535
2508
  }
2536
- return {
2537
- whereSql: parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`,
2538
- placeholdersByField
2539
- };
2509
+ return dn;
2540
2510
  }
2541
- function cursorValueExpr(tableName, cursorAlias, cursorWhereSql, field, model) {
2542
- const colName = quote(field);
2543
- return `(SELECT ${cursorAlias}.${colName} ${SQL_TEMPLATES.FROM} ${tableName} ${cursorAlias} ${SQL_TEMPLATES.WHERE} ${cursorWhereSql} ${SQL_TEMPLATES.LIMIT} 1)`;
2511
+ function assertUniqueDynamicName(dn, seen) {
2512
+ if (seen.has(dn)) {
2513
+ throw new Error(`CRITICAL: Duplicate dynamic param name in mappings: ${dn}`);
2514
+ }
2515
+ seen.add(dn);
2544
2516
  }
2545
- function buildCursorRowExistsExpr(tableName, cursorAlias, cursorWhereSql) {
2546
- return `EXISTS (${SQL_TEMPLATES.SELECT} 1 ${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
+ }
2547
2524
  }
2548
- function buildCursorEqualityExpr(columnExpr, valueExpr) {
2549
- return `((${valueExpr} IS NULL AND ${columnExpr} IS NULL) OR (${valueExpr} IS NOT NULL AND ${columnExpr} = ${valueExpr}))`;
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
+ }
2550
2530
  }
2551
- function buildCursorInequalityExpr(columnExpr, direction, nulls, valueExpr) {
2552
- const op = direction === "asc" ? ">" : "<";
2553
- if (nulls === "first") {
2554
- return `(CASE WHEN ${valueExpr} IS NULL THEN (${columnExpr} IS NOT NULL) ELSE (${columnExpr} ${op} ${valueExpr}) END)`;
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);
2537
+ }
2538
+ function normalizeValue(value) {
2539
+ if (value instanceof Date) {
2540
+ return value.toISOString();
2555
2541
  }
2556
- return `(CASE WHEN ${valueExpr} IS NULL THEN 0=1 ELSE ((${columnExpr} ${op} ${valueExpr}) OR (${columnExpr} IS NULL)) END)`;
2542
+ return value;
2557
2543
  }
2558
- function buildOuterCursorMatch(cursor, outerAlias, placeholdersByField, params, model) {
2559
- const parts = [];
2560
- for (const [field, value] of Object.entries(cursor)) {
2561
- const c = col(outerAlias, field, model);
2562
- if (value === null) {
2563
- parts.push(`${c} IS NULL`);
2564
- 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);
2565
2552
  }
2566
- const existing = placeholdersByField.get(field);
2567
- if (typeof existing === "string" && existing.length > 0) {
2568
- parts.push(`${c} = ${existing}`);
2569
- continue;
2553
+ }
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
+ );
2570
2559
  }
2571
- const ph = addAutoScoped(params, value, `cursor.outerMatch.${field}`);
2572
- parts.push(`${c} = ${ph}`);
2573
2560
  }
2574
- return parts.length === 1 ? parts[0] : `(${parts.join(" AND ")})`;
2575
- }
2576
- function buildOrderEntries(orderBy) {
2577
- const normalized = normalizeOrderByInput(orderBy, parseOrderByValue);
2578
- const entries = [];
2579
- for (const item of normalized) {
2580
- for (const [field, value] of Object.entries(item)) {
2581
- if (typeof value === "string") {
2582
- entries.push({ field, direction: value });
2583
- } else {
2584
- entries.push({
2585
- field,
2586
- direction: value.sort,
2587
- nulls: value.nulls
2588
- });
2589
- }
2561
+ function normalizeDynamicName(dynamicName) {
2562
+ const dn = dynamicName.trim();
2563
+ if (dn.length === 0) {
2564
+ throw new Error("CRITICAL: dynamicName cannot be empty");
2590
2565
  }
2566
+ return dn;
2591
2567
  }
2592
- return entries;
2593
- }
2594
- function buildCursorCondition(cursor, orderBy, tableName, alias, params, dialect, model) {
2595
- var _a;
2596
- const d = dialect != null ? dialect : getGlobalDialect();
2597
- const cursorEntries = Object.entries(cursor);
2598
- if (cursorEntries.length === 0) {
2599
- throw new Error("cursor must have at least one field");
2568
+ function format(position) {
2569
+ return `$${position}`;
2600
2570
  }
2601
- const cursorAlias = "__tp_cursor_src";
2602
- const { whereSql: cursorWhereSql, placeholdersByField } = buildCursorFilterParts(cursor, cursorAlias, params);
2603
- let orderEntries = buildOrderEntries(orderBy);
2604
- if (orderEntries.length === 0) {
2605
- orderEntries = cursorEntries.map(([field]) => ({
2606
- field,
2607
- direction: "asc"
2608
- }));
2609
- } else {
2610
- orderEntries = ensureCursorFieldsInOrder(orderEntries, cursorEntries);
2571
+ function addDynamic(dynamicName) {
2572
+ const dn = normalizeDynamicName(dynamicName);
2573
+ const existing = dynamicNameToIndex.get(dn);
2574
+ if (existing !== void 0) {
2575
+ return format(existing);
2576
+ }
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);
2611
2583
  }
2612
- const existsExpr = buildCursorRowExistsExpr(
2613
- tableName,
2614
- cursorAlias,
2615
- cursorWhereSql
2616
- );
2617
- const outerCursorMatch = buildOuterCursorMatch(
2618
- cursor,
2619
- alias,
2620
- placeholdersByField,
2621
- params,
2622
- model
2623
- );
2624
- const orClauses = [];
2625
- for (let level = 0; level < orderEntries.length; level++) {
2626
- const andParts = [];
2627
- for (let i = 0; i < level; i++) {
2628
- const e2 = orderEntries[i];
2629
- const c2 = col(alias, e2.field, model);
2630
- const v2 = cursorValueExpr(
2631
- tableName,
2632
- cursorAlias,
2633
- cursorWhereSql,
2634
- e2.field);
2635
- andParts.push(buildCursorEqualityExpr(c2, v2));
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);
2636
2600
  }
2637
- const e = orderEntries[level];
2638
- const c = col(alias, e.field, model);
2639
- const v = cursorValueExpr(
2640
- tableName,
2641
- cursorAlias,
2642
- cursorWhereSql,
2643
- e.field);
2644
- const nulls = (_a = e.nulls) != null ? _a : defaultNullsFor(d, e.direction);
2645
- andParts.push(buildCursorInequalityExpr(c, e.direction, nulls, v));
2646
- orClauses.push(`(${andParts.join(SQL_SEPARATORS.CONDITION_AND)})`);
2601
+ return add(value);
2647
2602
  }
2648
- const exclusive = orClauses.join(SQL_SEPARATORS.CONDITION_OR);
2649
- return `(${existsExpr} ${SQL_SEPARATORS.CONDITION_AND} ((${exclusive})${SQL_SEPARATORS.CONDITION_OR}(${outerCursorMatch})))`;
2650
- }
2651
- function buildOrderBy(orderBy, alias, dialect, model) {
2652
- const entries = buildOrderEntries(orderBy);
2653
- if (entries.length === 0) return "";
2654
- const d = dialect != null ? dialect : getGlobalDialect();
2655
- return buildOrderByFragment(entries, alias, d, model);
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
+ };
2656
2618
  }
2657
- function buildOrderByClause(args, alias, dialect, model) {
2658
- if (!isNotNullish(args.orderBy)) return "";
2659
- const result = buildOrderBy(args.orderBy, alias, dialect, model);
2660
- 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) {
2661
2624
  throw new Error(
2662
- "buildOrderByClause: orderBy specified but produced empty result"
2625
+ `Start index too high (${startIndex}), risk of overflow at MAX_SAFE_INTEGER`
2663
2626
  );
2664
2627
  }
2665
- return result;
2628
+ return createStoreInternal(startIndex);
2666
2629
  }
2667
- function normalizeTakeLike(v) {
2668
- const n = normalizeIntLike("take", v, {
2669
- min: Number.MIN_SAFE_INTEGER,
2670
- max: MAX_LIMIT_OFFSET,
2671
- allowZero: true
2672
- });
2673
- if (typeof n === "number") {
2674
- if (n === 0) return 0;
2675
- }
2676
- return n;
2630
+ function createParamStoreFrom(existingParams, existingMappings, nextIndex) {
2631
+ validateState([...existingParams], [...existingMappings], nextIndex);
2632
+ return createStoreInternal(
2633
+ nextIndex,
2634
+ [...existingParams],
2635
+ [...existingMappings]
2636
+ );
2677
2637
  }
2678
- function normalizeSkipLike(v) {
2679
- return normalizeIntLike("skip", v, {
2680
- min: 0,
2681
- max: MAX_LIMIT_OFFSET,
2682
- 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
2683
2648
  });
2684
2649
  }
2685
- function getPaginationParams(method, args) {
2686
- if (method === "findMany") {
2687
- return {
2688
- take: normalizeTakeLike(args.take),
2689
- skip: normalizeSkipLike(args.skip),
2690
- cursor: args.cursor
2691
- };
2692
- }
2693
- if (method === "findFirst") {
2694
- const skip = normalizeSkipLike(args.skip);
2695
- return { take: 1, skip: skip != null ? skip : 0 };
2696
- }
2697
- if (method === "findUnique") {
2698
- 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
+ }
2699
2684
  }
2700
- return {};
2685
+ return publicResult;
2701
2686
  }
2702
2687
 
2703
2688
  // src/builder/select/fields.ts