prisma-sql 1.59.0 → 1.60.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.
@@ -14,6 +14,9 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
14
14
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
15
15
  var __hasOwnProp = Object.prototype.hasOwnProperty;
16
16
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
17
+ var __typeError = (msg) => {
18
+ throw TypeError(msg);
19
+ };
17
20
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
18
21
  var __spreadValues = (a, b) => {
19
22
  for (var prop in b || (b = {}))
@@ -30,6 +33,18 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
30
33
  var __commonJS = (cb, mod) => function __require() {
31
34
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
32
35
  };
36
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
37
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
38
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
39
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
40
+ var __privateWrapper = (obj, member, setter, getter) => ({
41
+ set _(value) {
42
+ __privateSet(obj, member, value);
43
+ },
44
+ get _() {
45
+ return __privateGet(obj, member, getter);
46
+ }
47
+ });
33
48
  var __async = (__this, __arguments, generator) => {
34
49
  return new Promise((resolve3, reject) => {
35
50
  var fulfilled = (value) => {
@@ -56,7 +71,7 @@ var require_package = __commonJS({
56
71
  "package.json"(exports$1, module) {
57
72
  module.exports = {
58
73
  name: "prisma-sql",
59
- version: "1.59.0",
74
+ version: "1.60.0",
60
75
  description: "Convert Prisma queries to optimized SQL with type safety. 2-7x faster than Prisma Client.",
61
76
  main: "dist/index.cjs",
62
77
  module: "dist/index.js",
@@ -94,7 +109,7 @@ var require_package = __commonJS({
94
109
  "prisma:migrate": "npx prisma migrate dev --schema=tests/prisma/schema.prisma",
95
110
  "prisma:reset": "npx prisma db push --force-reset --skip-generate --schema=tests/prisma/schema.prisma",
96
111
  prepublishOnly: "npm run build",
97
- "sonar-cli": "sonar-scanner -Dsonar.projectKey=prisma-sql -Dsonar.sources=./src -Dsonar.host.url=http://localhost:9000 -Dsonar.login=sqp_9fe07460d0aa83f711d0edf4f317f05019d0613b",
112
+ "sonar-cli": "sonar-scanner -Dsonar.projectKey=b -Dsonar.sources=./src -Dsonar.host.url=http://localhost:9000 -Dsonar.login=sqp_0d5fbc16a275fceb6458d193a8aa8a975956edc1",
98
113
  sonar: "npm run sonar-cli && npx tsx scripts/sonar.ts"
99
114
  },
100
115
  keywords: [
@@ -152,155 +167,6 @@ var require_package = __commonJS({
152
167
  }
153
168
  });
154
169
 
155
- // src/builder/shared/constants.ts
156
- var IS_PRODUCTION = process.env.NODE_ENV === "production";
157
- var SQL_SEPARATORS = Object.freeze({
158
- FIELD_LIST: ", ",
159
- CONDITION_AND: " AND ",
160
- CONDITION_OR: " OR ",
161
- ORDER_BY: ", "
162
- });
163
- var ALIAS_FORBIDDEN_KEYWORDS = /* @__PURE__ */ new Set([
164
- "select",
165
- "from",
166
- "where",
167
- "having",
168
- "order",
169
- "group",
170
- "limit",
171
- "offset",
172
- "join",
173
- "inner",
174
- "left",
175
- "right",
176
- "outer",
177
- "cross",
178
- "full",
179
- "and",
180
- "or",
181
- "not",
182
- "by",
183
- "as",
184
- "on",
185
- "union",
186
- "intersect",
187
- "except",
188
- "case",
189
- "when",
190
- "then",
191
- "else",
192
- "end"
193
- ]);
194
- var SQL_KEYWORDS = /* @__PURE__ */ new Set([
195
- ...ALIAS_FORBIDDEN_KEYWORDS,
196
- "user",
197
- "users",
198
- "table",
199
- "column",
200
- "index",
201
- "values",
202
- "in",
203
- "like",
204
- "between",
205
- "is",
206
- "exists",
207
- "null",
208
- "true",
209
- "false",
210
- "all",
211
- "any",
212
- "some",
213
- "update",
214
- "insert",
215
- "delete",
216
- "create",
217
- "drop",
218
- "alter",
219
- "truncate",
220
- "grant",
221
- "revoke",
222
- "exec",
223
- "execute"
224
- ]);
225
- var SQL_RESERVED_WORDS = SQL_KEYWORDS;
226
- var DEFAULT_WHERE_CLAUSE = "1=1";
227
- var SPECIAL_FIELDS = Object.freeze({
228
- ID: "id"
229
- });
230
- var SQL_TEMPLATES = Object.freeze({
231
- PUBLIC_SCHEMA: "public",
232
- WHERE: "WHERE",
233
- SELECT: "SELECT",
234
- FROM: "FROM",
235
- ORDER_BY: "ORDER BY",
236
- GROUP_BY: "GROUP BY",
237
- HAVING: "HAVING",
238
- LIMIT: "LIMIT",
239
- OFFSET: "OFFSET",
240
- COUNT_ALL: "COUNT(*)",
241
- AS: "AS",
242
- DISTINCT_ON: "DISTINCT ON",
243
- IS_NULL: "IS NULL",
244
- IS_NOT_NULL: "IS NOT NULL",
245
- LIKE: "LIKE",
246
- AND: "AND",
247
- OR: "OR",
248
- NOT: "NOT"
249
- });
250
- var SCHEMA_PREFIXES = Object.freeze({
251
- INTERNAL: "@",
252
- COMMENT: "//"
253
- });
254
- var Ops = Object.freeze({
255
- EQUALS: "equals",
256
- NOT: "not",
257
- GT: "gt",
258
- GTE: "gte",
259
- LT: "lt",
260
- LTE: "lte",
261
- IN: "in",
262
- NOT_IN: "notIn",
263
- CONTAINS: "contains",
264
- STARTS_WITH: "startsWith",
265
- ENDS_WITH: "endsWith",
266
- HAS: "has",
267
- HAS_SOME: "hasSome",
268
- HAS_EVERY: "hasEvery",
269
- IS_EMPTY: "isEmpty",
270
- PATH: "path",
271
- STRING_CONTAINS: "string_contains",
272
- STRING_STARTS_WITH: "string_starts_with",
273
- STRING_ENDS_WITH: "string_ends_with"
274
- });
275
- var LogicalOps = Object.freeze({
276
- AND: "AND",
277
- OR: "OR",
278
- NOT: "NOT"
279
- });
280
- var RelationFilters = Object.freeze({
281
- SOME: "some",
282
- EVERY: "every",
283
- NONE: "none"
284
- });
285
- var Modes = Object.freeze({
286
- INSENSITIVE: "insensitive",
287
- DEFAULT: "default"
288
- });
289
- var Wildcards = Object.freeze({
290
- [Ops.CONTAINS]: (v) => `%${v}%`,
291
- [Ops.STARTS_WITH]: (v) => `${v}%`,
292
- [Ops.ENDS_WITH]: (v) => `%${v}`
293
- });
294
- var REGEX_CACHE = {
295
- VALID_IDENTIFIER: /^[a-z_][a-z0-9_]*$/
296
- };
297
- var LIMITS = Object.freeze({
298
- MAX_QUERY_DEPTH: 50,
299
- MAX_ARRAY_SIZE: 1e4,
300
- MAX_STRING_LENGTH: 1e4,
301
- MAX_HAVING_DEPTH: 50
302
- });
303
-
304
170
  // src/utils/normalize-value.ts
305
171
  var MAX_DEPTH = 20;
306
172
  function normalizeValue(value, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
@@ -308,45 +174,54 @@ function normalizeValue(value, seen = /* @__PURE__ */ new WeakSet(), depth = 0)
308
174
  throw new Error(`Max normalization depth exceeded (${MAX_DEPTH} levels)`);
309
175
  }
310
176
  if (value instanceof Date) {
311
- const t = value.getTime();
312
- if (!Number.isFinite(t)) {
313
- throw new Error("Invalid Date value in SQL params");
314
- }
315
- return value.toISOString();
177
+ return normalizeDateValue(value);
316
178
  }
317
179
  if (typeof value === "bigint") {
318
180
  return value.toString();
319
181
  }
320
182
  if (Array.isArray(value)) {
321
- const arrRef = value;
322
- if (seen.has(arrRef)) {
323
- throw new Error("Circular reference in SQL params");
324
- }
325
- seen.add(arrRef);
326
- const out = value.map((v) => normalizeValue(v, seen, depth + 1));
327
- seen.delete(arrRef);
328
- return out;
183
+ return normalizeArrayValue(value, seen, depth);
329
184
  }
330
185
  if (value && typeof value === "object") {
331
- if (value instanceof Uint8Array) return value;
332
- if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) return value;
333
- const proto = Object.getPrototypeOf(value);
334
- const isPlain = proto === Object.prototype || proto === null;
335
- if (!isPlain) return value;
336
- const obj = value;
337
- if (seen.has(obj)) {
338
- throw new Error("Circular reference in SQL params");
339
- }
340
- seen.add(obj);
341
- const out = {};
342
- for (const [k, v] of Object.entries(obj)) {
343
- out[k] = normalizeValue(v, seen, depth + 1);
344
- }
345
- seen.delete(obj);
346
- return out;
186
+ return normalizeObjectValue(value, seen, depth);
347
187
  }
348
188
  return value;
349
189
  }
190
+ function normalizeDateValue(date) {
191
+ const t = date.getTime();
192
+ if (!Number.isFinite(t)) {
193
+ throw new Error("Invalid Date value in SQL params");
194
+ }
195
+ return date.toISOString();
196
+ }
197
+ function normalizeArrayValue(value, seen, depth) {
198
+ const arrRef = value;
199
+ if (seen.has(arrRef)) {
200
+ throw new Error("Circular reference in SQL params");
201
+ }
202
+ seen.add(arrRef);
203
+ const out = value.map((v) => normalizeValue(v, seen, depth + 1));
204
+ seen.delete(arrRef);
205
+ return out;
206
+ }
207
+ function normalizeObjectValue(value, seen, depth) {
208
+ if (value instanceof Uint8Array) return value;
209
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) return value;
210
+ const proto = Object.getPrototypeOf(value);
211
+ const isPlain = proto === Object.prototype || proto === null;
212
+ if (!isPlain) return value;
213
+ const obj = value;
214
+ if (seen.has(obj)) {
215
+ throw new Error("Circular reference in SQL params");
216
+ }
217
+ seen.add(obj);
218
+ const out = {};
219
+ for (const [k, v] of Object.entries(obj)) {
220
+ out[k] = normalizeValue(v, seen, depth + 1);
221
+ }
222
+ seen.delete(obj);
223
+ return out;
224
+ }
350
225
 
351
226
  // src/sql-builder-dialect.ts
352
227
  var globalDialect = "postgres";
@@ -526,9 +401,158 @@ function prepareArrayParam(value, dialect) {
526
401
  if (dialect === "postgres") {
527
402
  return value.map((v) => normalizeValue(v));
528
403
  }
529
- return JSON.stringify(value);
404
+ return JSON.stringify(value.map((v) => normalizeValue(v)));
530
405
  }
531
406
 
407
+ // src/builder/shared/constants.ts
408
+ var IS_PRODUCTION = process.env.NODE_ENV === "production";
409
+ var SQL_SEPARATORS = Object.freeze({
410
+ FIELD_LIST: ", ",
411
+ CONDITION_AND: " AND ",
412
+ CONDITION_OR: " OR ",
413
+ ORDER_BY: ", "
414
+ });
415
+ var ALIAS_FORBIDDEN_KEYWORDS = /* @__PURE__ */ new Set([
416
+ "select",
417
+ "from",
418
+ "where",
419
+ "having",
420
+ "order",
421
+ "group",
422
+ "limit",
423
+ "offset",
424
+ "join",
425
+ "inner",
426
+ "left",
427
+ "right",
428
+ "outer",
429
+ "cross",
430
+ "full",
431
+ "and",
432
+ "or",
433
+ "not",
434
+ "by",
435
+ "as",
436
+ "on",
437
+ "union",
438
+ "intersect",
439
+ "except",
440
+ "case",
441
+ "when",
442
+ "then",
443
+ "else",
444
+ "end"
445
+ ]);
446
+ var SQL_KEYWORDS = /* @__PURE__ */ new Set([
447
+ ...ALIAS_FORBIDDEN_KEYWORDS,
448
+ "user",
449
+ "users",
450
+ "table",
451
+ "column",
452
+ "index",
453
+ "values",
454
+ "in",
455
+ "like",
456
+ "between",
457
+ "is",
458
+ "exists",
459
+ "null",
460
+ "true",
461
+ "false",
462
+ "all",
463
+ "any",
464
+ "some",
465
+ "update",
466
+ "insert",
467
+ "delete",
468
+ "create",
469
+ "drop",
470
+ "alter",
471
+ "truncate",
472
+ "grant",
473
+ "revoke",
474
+ "exec",
475
+ "execute"
476
+ ]);
477
+ var SQL_RESERVED_WORDS = SQL_KEYWORDS;
478
+ var DEFAULT_WHERE_CLAUSE = "1=1";
479
+ var SPECIAL_FIELDS = Object.freeze({
480
+ ID: "id"
481
+ });
482
+ var SQL_TEMPLATES = Object.freeze({
483
+ PUBLIC_SCHEMA: "public",
484
+ WHERE: "WHERE",
485
+ SELECT: "SELECT",
486
+ FROM: "FROM",
487
+ ORDER_BY: "ORDER BY",
488
+ GROUP_BY: "GROUP BY",
489
+ HAVING: "HAVING",
490
+ LIMIT: "LIMIT",
491
+ OFFSET: "OFFSET",
492
+ COUNT_ALL: "COUNT(*)",
493
+ AS: "AS",
494
+ DISTINCT_ON: "DISTINCT ON",
495
+ IS_NULL: "IS NULL",
496
+ IS_NOT_NULL: "IS NOT NULL",
497
+ LIKE: "LIKE",
498
+ AND: "AND",
499
+ OR: "OR",
500
+ NOT: "NOT"
501
+ });
502
+ var SCHEMA_PREFIXES = Object.freeze({
503
+ INTERNAL: "@",
504
+ COMMENT: "//"
505
+ });
506
+ var Ops = Object.freeze({
507
+ EQUALS: "equals",
508
+ NOT: "not",
509
+ GT: "gt",
510
+ GTE: "gte",
511
+ LT: "lt",
512
+ LTE: "lte",
513
+ IN: "in",
514
+ NOT_IN: "notIn",
515
+ CONTAINS: "contains",
516
+ STARTS_WITH: "startsWith",
517
+ ENDS_WITH: "endsWith",
518
+ HAS: "has",
519
+ HAS_SOME: "hasSome",
520
+ HAS_EVERY: "hasEvery",
521
+ IS_EMPTY: "isEmpty",
522
+ PATH: "path",
523
+ STRING_CONTAINS: "string_contains",
524
+ STRING_STARTS_WITH: "string_starts_with",
525
+ STRING_ENDS_WITH: "string_ends_with"
526
+ });
527
+ var LogicalOps = Object.freeze({
528
+ AND: "AND",
529
+ OR: "OR",
530
+ NOT: "NOT"
531
+ });
532
+ var RelationFilters = Object.freeze({
533
+ SOME: "some",
534
+ EVERY: "every",
535
+ NONE: "none"
536
+ });
537
+ var Modes = Object.freeze({
538
+ INSENSITIVE: "insensitive",
539
+ DEFAULT: "default"
540
+ });
541
+ var Wildcards = Object.freeze({
542
+ [Ops.CONTAINS]: (v) => `%${v}%`,
543
+ [Ops.STARTS_WITH]: (v) => `${v}%`,
544
+ [Ops.ENDS_WITH]: (v) => `%${v}`
545
+ });
546
+ var REGEX_CACHE = {
547
+ VALID_IDENTIFIER: /^[a-z_][a-z0-9_]*$/
548
+ };
549
+ var LIMITS = Object.freeze({
550
+ MAX_QUERY_DEPTH: 50,
551
+ MAX_ARRAY_SIZE: 1e4,
552
+ MAX_STRING_LENGTH: 1e4,
553
+ MAX_HAVING_DEPTH: 50
554
+ });
555
+
532
556
  // src/builder/shared/validators/type-guards.ts
533
557
  function isNotNullish(value) {
534
558
  return value !== null && value !== void 0;
@@ -744,10 +768,13 @@ function quoteIdent(id) {
744
768
  if (typeof id !== "string" || id.trim().length === 0) {
745
769
  throw new Error("quoteIdent: identifier is required and cannot be empty");
746
770
  }
747
- if (/[\u0000-\u001F\u007F]/.test(id)) {
748
- throw new Error(
749
- `quoteIdent: identifier contains invalid characters: ${JSON.stringify(id)}`
750
- );
771
+ for (let i = 0; i < id.length; i++) {
772
+ const code = id.charCodeAt(i);
773
+ if (code >= 0 && code <= 31 || code === 127) {
774
+ throw new Error(
775
+ `quoteIdent: identifier contains invalid characters: ${JSON.stringify(id)}`
776
+ );
777
+ }
751
778
  }
752
779
  if (needsQuoting(id)) {
753
780
  return `"${id.replace(/"/g, '""')}"`;
@@ -812,7 +839,13 @@ function getQuotedColumn(model, fieldName) {
812
839
 
813
840
  // src/builder/shared/sql-utils.ts
814
841
  function containsControlChars(s) {
815
- return /[\u0000-\u001F\u007F]/.test(s);
842
+ for (let i = 0; i < s.length; i++) {
843
+ const code = s.charCodeAt(i);
844
+ if (code >= 0 && code <= 31 || code === 127) {
845
+ return true;
846
+ }
847
+ }
848
+ return false;
816
849
  }
817
850
  function assertNoControlChars(label, s) {
818
851
  if (containsControlChars(s)) {
@@ -887,20 +920,9 @@ function parseUnquotedPart(input, start) {
887
920
  }
888
921
  return i;
889
922
  }
890
- function assertSafeQualifiedName(input) {
891
- const raw = String(input);
892
- const trimmed = raw.trim();
893
- if (trimmed.length === 0) {
894
- throw new Error("tableName/tableRef is required and cannot be empty");
895
- }
896
- if (raw !== trimmed) {
897
- throw new Error(
898
- `tableName/tableRef must not contain leading/trailing whitespace: ${JSON.stringify(raw)}`
899
- );
900
- }
901
- assertNoControlChars("tableName/tableRef", trimmed);
902
- for (let i2 = 0; i2 < trimmed.length; i2++) {
903
- const c = trimmed.charCodeAt(i2);
923
+ function validateQualifiedNameFormat(trimmed) {
924
+ for (let i = 0; i < trimmed.length; i++) {
925
+ const c = trimmed.charCodeAt(i);
904
926
  if (c === 9 || c === 11 || c === 12 || c === 32) {
905
927
  throw new Error(
906
928
  `tableName/tableRef must not contain whitespace: ${JSON.stringify(trimmed)}`
@@ -917,6 +939,8 @@ function assertSafeQualifiedName(input) {
917
939
  );
918
940
  }
919
941
  }
942
+ }
943
+ function parseQualifiedNameParts(trimmed) {
920
944
  let i = 0;
921
945
  const n = trimmed.length;
922
946
  let parts = 0;
@@ -952,6 +976,21 @@ function assertSafeQualifiedName(input) {
952
976
  }
953
977
  }
954
978
  }
979
+ function assertSafeQualifiedName(input) {
980
+ const raw = String(input);
981
+ const trimmed = raw.trim();
982
+ if (trimmed.length === 0) {
983
+ throw new Error("tableName/tableRef is required and cannot be empty");
984
+ }
985
+ if (raw !== trimmed) {
986
+ throw new Error(
987
+ `tableName/tableRef must not contain leading/trailing whitespace: ${JSON.stringify(raw)}`
988
+ );
989
+ }
990
+ assertNoControlChars("tableName/tableRef", trimmed);
991
+ validateQualifiedNameFormat(trimmed);
992
+ parseQualifiedNameParts(trimmed);
993
+ }
955
994
  function quote(id) {
956
995
  if (isEmptyString(id)) {
957
996
  throw new Error("quote: identifier is required and cannot be empty");
@@ -1041,7 +1080,7 @@ function assertSafeAlias(alias) {
1041
1080
  if (a !== alias) {
1042
1081
  throw new Error("Invalid alias: leading/trailing whitespace");
1043
1082
  }
1044
- if (/[\u0000-\u001F\u007F]/.test(a)) {
1083
+ if (containsControlChars(a)) {
1045
1084
  throw new Error(
1046
1085
  "Invalid alias: contains unsafe characters (control characters)"
1047
1086
  );
@@ -1062,7 +1101,7 @@ function assertSafeAlias(alias) {
1062
1101
  "Invalid alias: must be a simple identifier without whitespace"
1063
1102
  );
1064
1103
  }
1065
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(a)) {
1104
+ if (!/^[A-Za-z_]\w*$/.test(a)) {
1066
1105
  throw new Error(
1067
1106
  `Invalid alias: must be a simple identifier (alphanumeric with underscores): "${alias}"`
1068
1107
  );
@@ -1110,8 +1149,7 @@ function isValidRelationField(field) {
1110
1149
  return false;
1111
1150
  const fk = normalizeKeyList(field.foreignKey);
1112
1151
  if (fk.length === 0) return false;
1113
- const refsRaw = field.references;
1114
- const refs = normalizeKeyList(refsRaw);
1152
+ const refs = normalizeKeyList(field.references);
1115
1153
  if (refs.length === 0) {
1116
1154
  return fk.length === 1;
1117
1155
  }
@@ -1119,8 +1157,7 @@ function isValidRelationField(field) {
1119
1157
  return true;
1120
1158
  }
1121
1159
  function getReferenceFieldNames(field, foreignKeyCount) {
1122
- const refsRaw = field.references;
1123
- const refs = normalizeKeyList(refsRaw);
1160
+ const refs = normalizeKeyList(field.references);
1124
1161
  if (refs.length === 0) {
1125
1162
  if (foreignKeyCount === 1) return [SPECIAL_FIELDS.ID];
1126
1163
  return [];
@@ -1281,29 +1318,35 @@ function normalizeOrderByInput(orderBy, parseValue) {
1281
1318
  }
1282
1319
 
1283
1320
  // src/builder/shared/order-by-determinism.ts
1284
- function modelHasScalarId(model) {
1285
- if (!model) return false;
1286
- return getScalarFieldSet(model).has("id");
1321
+ function findTiebreakerField(model) {
1322
+ if (!model) return null;
1323
+ const scalarSet = getScalarFieldSet(model);
1324
+ for (const f of model.fields) {
1325
+ if (f.isId && !f.isRelation && scalarSet.has(f.name)) return f.name;
1326
+ }
1327
+ if (scalarSet.has("id")) return "id";
1328
+ return null;
1287
1329
  }
1288
- function hasIdTiebreaker(orderBy, parse) {
1330
+ function hasTiebreaker(orderBy, parse, field) {
1289
1331
  if (!isNotNullish(orderBy)) return false;
1290
1332
  const normalized = normalizeOrderByInput(orderBy, parse);
1291
1333
  return normalized.some(
1292
- (obj) => Object.prototype.hasOwnProperty.call(obj, "id")
1334
+ (obj) => Object.prototype.hasOwnProperty.call(obj, field)
1293
1335
  );
1294
1336
  }
1295
- function addIdTiebreaker(orderBy) {
1296
- if (Array.isArray(orderBy)) return [...orderBy, { id: "asc" }];
1297
- return [orderBy, { id: "asc" }];
1337
+ function addTiebreaker(orderBy, field) {
1338
+ if (Array.isArray(orderBy)) return [...orderBy, { [field]: "asc" }];
1339
+ return [orderBy, { [field]: "asc" }];
1298
1340
  }
1299
1341
  function ensureDeterministicOrderByInput(args) {
1300
1342
  const { orderBy, model, parseValue } = args;
1301
- if (!modelHasScalarId(model)) return orderBy;
1343
+ const tiebreaker = findTiebreakerField(model);
1344
+ if (!tiebreaker) return orderBy;
1302
1345
  if (!isNotNullish(orderBy)) {
1303
- return { id: "asc" };
1346
+ return { [tiebreaker]: "asc" };
1304
1347
  }
1305
- if (hasIdTiebreaker(orderBy, parseValue)) return orderBy;
1306
- return addIdTiebreaker(orderBy);
1348
+ if (hasTiebreaker(orderBy, parseValue, tiebreaker)) return orderBy;
1349
+ return addTiebreaker(orderBy, tiebreaker);
1307
1350
  }
1308
1351
 
1309
1352
  // src/builder/shared/validators/field-assertions.ts
@@ -1450,11 +1493,9 @@ function defaultNullsFor(dialect, direction) {
1450
1493
  function ensureCursorFieldsInOrder(orderEntries, cursorEntries) {
1451
1494
  if (cursorEntries.length === 0) return orderEntries;
1452
1495
  const existing = /* @__PURE__ */ new Set();
1453
- for (let i = 0; i < orderEntries.length; i++)
1454
- existing.add(orderEntries[i].field);
1496
+ for (const entry of orderEntries) existing.add(entry.field);
1455
1497
  let out = null;
1456
- for (let i = 0; i < cursorEntries.length; i++) {
1457
- const field = cursorEntries[i][0];
1498
+ for (const [field] of cursorEntries) {
1458
1499
  if (!existing.has(field)) {
1459
1500
  if (!out) out = orderEntries.slice();
1460
1501
  out.push({ field, direction: "asc" });
@@ -1694,7 +1735,44 @@ function buildNotComposite(expr, val, params, dialect, buildOp, separator) {
1694
1735
  if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
1695
1736
  return `${SQL_TEMPLATES.NOT} (${clauses.join(separator)})`;
1696
1737
  }
1697
- function buildScalarOperator(expr, op, val, params, mode, fieldType, dialect, depth = 0) {
1738
+ function validateOperatorRequirements(op, mode, dialect) {
1739
+ const STRING_LIKE_OPS = /* @__PURE__ */ new Set([
1740
+ Ops.CONTAINS,
1741
+ Ops.STARTS_WITH,
1742
+ Ops.ENDS_WITH
1743
+ ]);
1744
+ if (STRING_LIKE_OPS.has(op) && !isNotNullish(dialect)) {
1745
+ throw createError(`Like operators require a SQL dialect`, { operator: op });
1746
+ }
1747
+ if ((op === Ops.IN || op === Ops.NOT_IN) && !isNotNullish(dialect)) {
1748
+ throw createError(`IN operators require a SQL dialect`, { operator: op });
1749
+ }
1750
+ if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && !isNotNullish(dialect)) {
1751
+ throw createError(`Insensitive equals requires a SQL dialect`, {
1752
+ operator: op
1753
+ });
1754
+ }
1755
+ }
1756
+ function routeOperatorHandler(expr, op, val, params, mode, dialect) {
1757
+ const STRING_LIKE_OPS = /* @__PURE__ */ new Set([
1758
+ Ops.CONTAINS,
1759
+ Ops.STARTS_WITH,
1760
+ Ops.ENDS_WITH
1761
+ ]);
1762
+ if (STRING_LIKE_OPS.has(op)) {
1763
+ return handleLikeOperator(expr, op, val, params, mode, dialect);
1764
+ }
1765
+ if (op === Ops.IN || op === Ops.NOT_IN) {
1766
+ return handleInOperator(expr, op, val, params, dialect);
1767
+ }
1768
+ if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && isNotNullish(dialect)) {
1769
+ const placeholder = params.addAuto(val);
1770
+ return caseInsensitiveEquals(expr, placeholder);
1771
+ }
1772
+ return null;
1773
+ }
1774
+ function buildScalarOperator(expr, op, val, params, options = {}) {
1775
+ const { mode, fieldType, dialect, depth = 0 } = options;
1698
1776
  if (val === void 0) return "";
1699
1777
  if (depth > MAX_NOT_DEPTH) {
1700
1778
  throw new Error(
@@ -1711,33 +1789,17 @@ function buildScalarOperator(expr, op, val, params, mode, fieldType, dialect, de
1711
1789
  const placeholder = params.addAuto(val);
1712
1790
  return `${expr} <> ${placeholder}`;
1713
1791
  }
1714
- if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && isNotNullish(dialect)) {
1715
- const placeholder = params.addAuto(val);
1716
- return caseInsensitiveEquals(expr, placeholder);
1717
- }
1718
- const STRING_LIKE_OPS = /* @__PURE__ */ new Set([
1719
- Ops.CONTAINS,
1720
- Ops.STARTS_WITH,
1721
- Ops.ENDS_WITH
1722
- ]);
1723
- if (STRING_LIKE_OPS.has(op)) {
1724
- if (!isNotNullish(dialect)) {
1725
- throw createError(`Like operators require a SQL dialect`, {
1726
- operator: op
1727
- });
1728
- }
1729
- return handleLikeOperator(expr, op, val, params, mode, dialect);
1730
- }
1731
- if (op === Ops.IN || op === Ops.NOT_IN) {
1732
- if (!isNotNullish(dialect)) {
1733
- throw createError(`IN operators require a SQL dialect`, { operator: op });
1734
- }
1735
- return handleInOperator(expr, op, val, params, dialect);
1736
- }
1737
- if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && !isNotNullish(dialect)) {
1738
- throw createError(`Insensitive equals requires a SQL dialect`, {
1739
- operator: op
1740
- });
1792
+ validateOperatorRequirements(op, mode, dialect);
1793
+ const specialHandler = routeOperatorHandler(
1794
+ expr,
1795
+ op,
1796
+ val,
1797
+ params,
1798
+ mode,
1799
+ dialect
1800
+ );
1801
+ if (specialHandler !== null) {
1802
+ return specialHandler;
1741
1803
  }
1742
1804
  return handleComparisonOperator(expr, op, val, params);
1743
1805
  }
@@ -1761,38 +1823,32 @@ function handleNotOperator(expr, val, params, outerMode, fieldType, dialect, dep
1761
1823
  if (!isNotNullish(dialect)) {
1762
1824
  const clauses = [];
1763
1825
  for (const [subOp, subVal] of entries) {
1764
- const sub = buildScalarOperator(
1765
- expr,
1766
- subOp,
1767
- subVal,
1768
- params,
1769
- effectiveMode,
1826
+ const sub = buildScalarOperator(expr, subOp, subVal, params, {
1827
+ mode: effectiveMode,
1770
1828
  fieldType,
1771
- void 0,
1772
- depth + 1
1773
- );
1829
+ dialect: void 0,
1830
+ depth: depth + 1
1831
+ });
1774
1832
  if (sub && sub.trim().length > 0) clauses.push(`(${sub})`);
1775
1833
  }
1776
1834
  if (clauses.length === 0) return "";
1777
1835
  if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
1778
- return `${SQL_TEMPLATES.NOT} (${clauses.join(` ${SQL_TEMPLATES.AND} `)})`;
1836
+ const separator2 = ` ${SQL_TEMPLATES.AND} `;
1837
+ return `${SQL_TEMPLATES.NOT} (${clauses.join(separator2)})`;
1779
1838
  }
1839
+ const separator = ` ${SQL_TEMPLATES.AND} `;
1780
1840
  return buildNotComposite(
1781
1841
  expr,
1782
1842
  val,
1783
1843
  params,
1784
1844
  dialect,
1785
- (e, subOp, subVal, p, d) => buildScalarOperator(
1786
- e,
1787
- subOp,
1788
- subVal,
1789
- p,
1790
- effectiveMode,
1845
+ (e, subOp, subVal, p, d) => buildScalarOperator(e, subOp, subVal, p, {
1846
+ mode: effectiveMode,
1791
1847
  fieldType,
1792
- d,
1793
- depth + 1
1794
- ),
1795
- ` ${SQL_TEMPLATES.AND} `
1848
+ dialect: d,
1849
+ depth: depth + 1
1850
+ }),
1851
+ separator
1796
1852
  );
1797
1853
  }
1798
1854
  function buildDynamicLikePattern(op, placeholder) {
@@ -1989,24 +2045,42 @@ function handleArrayIsEmpty(expr, val, dialect) {
1989
2045
  // src/builder/where/operators-json.ts
1990
2046
  var SAFE_JSON_PATH_SEGMENT = /^[a-zA-Z_]\w*$/;
1991
2047
  var MAX_PATH_SEGMENT_LENGTH = 255;
2048
+ var MAX_PATH_SEGMENTS = 100;
2049
+ function sanitizeForError(s) {
2050
+ let result = "";
2051
+ for (let i = 0; i < s.length; i++) {
2052
+ const code = s.charCodeAt(i);
2053
+ if (code >= 0 && code <= 31 || code === 127) {
2054
+ result += `\\x${code.toString(16).padStart(2, "0")}`;
2055
+ } else {
2056
+ result += s[i];
2057
+ }
2058
+ }
2059
+ return result;
2060
+ }
1992
2061
  function validateJsonPathSegments(segments) {
1993
- for (const segment of segments) {
2062
+ if (segments.length > MAX_PATH_SEGMENTS) {
2063
+ throw createError(`JSON path too long: max ${MAX_PATH_SEGMENTS} segments`, {
2064
+ operator: Ops.PATH
2065
+ });
2066
+ }
2067
+ for (let i = 0; i < segments.length; i++) {
2068
+ const segment = segments[i];
1994
2069
  if (typeof segment !== "string") {
1995
- throw createError("JSON path segments must be strings", {
1996
- operator: Ops.PATH,
1997
- value: segment
2070
+ throw createError(`JSON path segment at index ${i} must be string`, {
2071
+ operator: Ops.PATH
1998
2072
  });
1999
2073
  }
2000
2074
  if (segment.length > MAX_PATH_SEGMENT_LENGTH) {
2001
2075
  throw createError(
2002
- `JSON path segment too long: max ${MAX_PATH_SEGMENT_LENGTH} characters`,
2003
- { operator: Ops.PATH, value: `[${segment.length} chars]` }
2076
+ `JSON path segment at index ${i} too long: max ${MAX_PATH_SEGMENT_LENGTH} characters`,
2077
+ { operator: Ops.PATH }
2004
2078
  );
2005
2079
  }
2006
2080
  if (!SAFE_JSON_PATH_SEGMENT.test(segment)) {
2007
2081
  throw createError(
2008
- `Invalid JSON path segment: '${segment}'. Must be alphanumeric with underscores, starting with letter or underscore.`,
2009
- { operator: Ops.PATH, value: segment }
2082
+ `Invalid JSON path segment at index ${i}: '${sanitizeForError(segment)}'`,
2083
+ { operator: Ops.PATH }
2010
2084
  );
2011
2085
  }
2012
2086
  }
@@ -2124,6 +2198,35 @@ function buildToOneNotExistsMatch(relTable, relAlias, join3, sub) {
2124
2198
  const joins = sub.joins.length > 0 ? ` ${sub.joins.join(" ")}` : "";
2125
2199
  return `${SQL_TEMPLATES.NOT} EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias}${joins} ${SQL_TEMPLATES.WHERE} ${join3} ${SQL_TEMPLATES.AND} ${sub.clause})`;
2126
2200
  }
2201
+ function tryOptimizeNoneFilter(noneValue, ctx, relModel, relTable, relAlias, join3, sub) {
2202
+ const isEmptyFilter = isPlainObject(noneValue) && Object.keys(noneValue).length === 0;
2203
+ const canOptimize = !ctx.isSubquery && isEmptyFilter && sub.clause === DEFAULT_WHERE_CLAUSE && sub.joins.length === 0;
2204
+ if (!canOptimize) return null;
2205
+ const checkField = relModel.fields.find(
2206
+ (f) => !f.isRelation && f.isRequired && f.name !== "id"
2207
+ ) || relModel.fields.find((f) => !f.isRelation && f.name === "id");
2208
+ if (!checkField) return null;
2209
+ const leftJoinSql = `LEFT JOIN ${relTable} ${relAlias} ON ${join3}`;
2210
+ const whereClause = `${relAlias}.${quoteColumn(relModel, checkField.name)} IS NULL`;
2211
+ return Object.freeze({
2212
+ clause: whereClause,
2213
+ joins: freezeJoins([leftJoinSql])
2214
+ });
2215
+ }
2216
+ function processRelationFilter(key, wrap, args) {
2217
+ const { value, fieldName, ctx, relAlias, relModel, whereBuilder } = args;
2218
+ const raw = value[key];
2219
+ if (raw === void 0 || raw === null) return null;
2220
+ const sub = whereBuilder.build(raw, __spreadProps(__spreadValues({}, ctx), {
2221
+ alias: relAlias,
2222
+ model: relModel,
2223
+ path: [...ctx.path, fieldName, key],
2224
+ isSubquery: true,
2225
+ depth: ctx.depth + 1
2226
+ }));
2227
+ const j = sub.joins.length > 0 ? ` ${sub.joins.join(" ")}` : "";
2228
+ return wrap(sub.clause, j);
2229
+ }
2127
2230
  function buildListRelationFilters(args) {
2128
2231
  const {
2129
2232
  fieldName,
@@ -2144,21 +2247,16 @@ function buildListRelationFilters(args) {
2144
2247
  isSubquery: true,
2145
2248
  depth: ctx.depth + 1
2146
2249
  }));
2147
- const isEmptyFilter = isPlainObject(noneValue) && Object.keys(noneValue).length === 0;
2148
- const canOptimize = !ctx.isSubquery && isEmptyFilter && sub.clause === DEFAULT_WHERE_CLAUSE && sub.joins.length === 0;
2149
- if (canOptimize) {
2150
- const checkField = relModel.fields.find(
2151
- (f) => !f.isRelation && f.isRequired && f.name !== "id"
2152
- ) || relModel.fields.find((f) => !f.isRelation && f.name === "id");
2153
- if (checkField) {
2154
- const leftJoinSql = `LEFT JOIN ${relTable} ${relAlias} ON ${join3}`;
2155
- const whereClause = `${relAlias}.${quote(checkField.name)} IS NULL`;
2156
- return Object.freeze({
2157
- clause: whereClause,
2158
- joins: freezeJoins([leftJoinSql])
2159
- });
2160
- }
2161
- }
2250
+ const optimized = tryOptimizeNoneFilter(
2251
+ noneValue,
2252
+ ctx,
2253
+ relModel,
2254
+ relTable,
2255
+ relAlias,
2256
+ join3,
2257
+ sub
2258
+ );
2259
+ if (optimized) return optimized;
2162
2260
  }
2163
2261
  const filters = [
2164
2262
  {
@@ -2179,17 +2277,8 @@ function buildListRelationFilters(args) {
2179
2277
  ];
2180
2278
  const clauses = [];
2181
2279
  for (const { key, wrap } of filters) {
2182
- const raw = value[key];
2183
- if (raw === void 0 || raw === null) continue;
2184
- const sub = whereBuilder.build(raw, __spreadProps(__spreadValues({}, ctx), {
2185
- alias: relAlias,
2186
- model: relModel,
2187
- path: [...ctx.path, fieldName, key],
2188
- isSubquery: true,
2189
- depth: ctx.depth + 1
2190
- }));
2191
- const j = sub.joins.length > 0 ? ` ${sub.joins.join(" ")}` : "";
2192
- clauses.push(wrap(sub.clause, j));
2280
+ const clause = processRelationFilter(key, wrap, args);
2281
+ if (clause) clauses.push(clause);
2193
2282
  }
2194
2283
  if (clauses.length === 0) {
2195
2284
  throw createError(
@@ -2591,15 +2680,11 @@ function buildOperator(expr, op, val, ctx, mode, fieldType) {
2591
2680
  if (fieldType && isJsonType(fieldType) && JSON_OPS.has(op)) {
2592
2681
  return buildJsonOperator(expr, op, val, ctx.params, ctx.dialect);
2593
2682
  }
2594
- return buildScalarOperator(
2595
- expr,
2596
- op,
2597
- val,
2598
- ctx.params,
2683
+ return buildScalarOperator(expr, op, val, ctx.params, {
2599
2684
  mode,
2600
2685
  fieldType,
2601
- ctx.dialect
2602
- );
2686
+ dialect: ctx.dialect
2687
+ });
2603
2688
  }
2604
2689
 
2605
2690
  // src/builder/shared/alias-generator.ts
@@ -2713,47 +2798,50 @@ function validateState(params, mappings, index) {
2713
2798
  validateMappings(mappings);
2714
2799
  assertNextIndexMatches(mappings.length, index);
2715
2800
  }
2716
- function createStoreInternal(startIndex, initialParams = [], initialMappings = []) {
2717
- let index = startIndex;
2718
- const params = initialParams.length > 0 ? initialParams.slice() : [];
2719
- const mappings = initialMappings.length > 0 ? initialMappings.slice() : [];
2801
+ function buildDynamicNameIndex(mappings) {
2720
2802
  const dynamicNameToIndex = /* @__PURE__ */ new Map();
2721
- for (let i = 0; i < mappings.length; i++) {
2722
- const m = mappings[i];
2803
+ for (const m of mappings) {
2723
2804
  if (typeof m.dynamicName === "string") {
2724
2805
  dynamicNameToIndex.set(m.dynamicName.trim(), m.index);
2725
2806
  }
2726
2807
  }
2727
- let dirty = true;
2728
- let cachedSnapshot = null;
2729
- function assertCanAdd() {
2730
- if (index > MAX_PARAM_INDEX) {
2731
- throw new Error(
2732
- `CRITICAL: Cannot add param - would overflow MAX_SAFE_INTEGER. Current index: ${index}`
2733
- );
2734
- }
2735
- }
2736
- function format(position) {
2737
- return `$${position}`;
2808
+ return dynamicNameToIndex;
2809
+ }
2810
+ function assertCanAddParam(currentIndex) {
2811
+ if (currentIndex > MAX_PARAM_INDEX) {
2812
+ throw new Error(
2813
+ `CRITICAL: Cannot add param - would overflow MAX_SAFE_INTEGER. Current index: ${currentIndex}`
2814
+ );
2738
2815
  }
2739
- function normalizeDynamicName(dynamicName) {
2740
- const dn = dynamicName.trim();
2741
- if (dn.length === 0) {
2742
- throw new Error("CRITICAL: dynamicName cannot be empty");
2743
- }
2744
- return dn;
2816
+ }
2817
+ function formatPosition(position) {
2818
+ return `$${position}`;
2819
+ }
2820
+ function validateDynamicName(dynamicName) {
2821
+ const dn = dynamicName.trim();
2822
+ if (dn.length === 0) {
2823
+ throw new Error("CRITICAL: dynamicName cannot be empty");
2745
2824
  }
2825
+ return dn;
2826
+ }
2827
+ function createStoreInternal(startIndex, initialParams = [], initialMappings = []) {
2828
+ let index = startIndex;
2829
+ const params = initialParams.length > 0 ? initialParams.slice() : [];
2830
+ const mappings = initialMappings.length > 0 ? initialMappings.slice() : [];
2831
+ const dynamicNameToIndex = buildDynamicNameIndex(mappings);
2832
+ let dirty = true;
2833
+ let cachedSnapshot = null;
2746
2834
  function addDynamic(dynamicName) {
2747
- const dn = normalizeDynamicName(dynamicName);
2835
+ const dn = validateDynamicName(dynamicName);
2748
2836
  const existing = dynamicNameToIndex.get(dn);
2749
- if (existing !== void 0) return format(existing);
2837
+ if (existing !== void 0) return formatPosition(existing);
2750
2838
  const position = index;
2751
2839
  dynamicNameToIndex.set(dn, position);
2752
2840
  params.push(void 0);
2753
2841
  mappings.push({ index: position, dynamicName: dn });
2754
2842
  index++;
2755
2843
  dirty = true;
2756
- return format(position);
2844
+ return formatPosition(position);
2757
2845
  }
2758
2846
  function addStatic(value) {
2759
2847
  const position = index;
@@ -2762,10 +2850,10 @@ function createStoreInternal(startIndex, initialParams = [], initialMappings = [
2762
2850
  mappings.push({ index: position, value: normalizedValue });
2763
2851
  index++;
2764
2852
  dirty = true;
2765
- return format(position);
2853
+ return formatPosition(position);
2766
2854
  }
2767
2855
  function add(value, dynamicName) {
2768
- assertCanAdd();
2856
+ assertCanAddParam(index);
2769
2857
  return dynamicName === void 0 ? addStatic(value) : addDynamic(dynamicName);
2770
2858
  }
2771
2859
  function addAuto(value) {
@@ -2986,6 +3074,17 @@ function buildRelationSelect(relArgs, relModel, relAlias) {
2986
3074
  var MAX_INCLUDE_DEPTH = 10;
2987
3075
  var MAX_TOTAL_SUBQUERIES = 100;
2988
3076
  var MAX_TOTAL_INCLUDES = 50;
3077
+ function buildIncludeScope(includePath) {
3078
+ if (includePath.length === 0) return "include";
3079
+ let scope = "include";
3080
+ for (let i = 0; i < includePath.length; i++) {
3081
+ scope += `.${includePath[i]}`;
3082
+ if (i < includePath.length - 1) {
3083
+ scope += ".include";
3084
+ }
3085
+ }
3086
+ return scope;
3087
+ }
2989
3088
  function getRelationTableReference(relModel, dialect) {
2990
3089
  return buildTableReference(
2991
3090
  SQL_TEMPLATES.PUBLIC_SCHEMA,
@@ -3129,19 +3228,11 @@ function finalizeOrderByForInclude(args) {
3129
3228
  }
3130
3229
  function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
3131
3230
  let relSelect = buildRelationSelect(relArgs, relModel, relAlias);
3132
- const nestedIncludes = isPlainObject(relArgs) ? buildIncludeSqlInternal(
3133
- relArgs,
3134
- relModel,
3135
- ctx.schemas,
3136
- ctx.schemaByName,
3137
- relAlias,
3138
- ctx.aliasGen,
3139
- ctx.params,
3140
- ctx.dialect,
3141
- ctx.visitPath || [],
3142
- (ctx.depth || 0) + 1,
3143
- ctx.stats
3144
- ) : [];
3231
+ const nestedIncludes = isPlainObject(relArgs) ? buildIncludeSqlInternal(relArgs, __spreadProps(__spreadValues({}, ctx), {
3232
+ model: relModel,
3233
+ parentAlias: relAlias,
3234
+ depth: (ctx.depth || 0) + 1
3235
+ })) : [];
3145
3236
  if (isNonEmptyArray(nestedIncludes)) {
3146
3237
  const emptyJson = ctx.dialect === "postgres" ? `'[]'::json` : `json('[]')`;
3147
3238
  const nestedSelects = nestedIncludes.map(
@@ -3196,6 +3287,7 @@ function buildOneToOneIncludeSql(args) {
3196
3287
  whereClause: args.whereClause
3197
3288
  });
3198
3289
  if (args.orderBySql) sql += ` ${SQL_TEMPLATES.ORDER_BY} ${args.orderBySql}`;
3290
+ const scopeBase = buildIncludeScope(args.ctx.includePath);
3199
3291
  if (isNotNullish(args.takeVal)) {
3200
3292
  return appendLimitOffset(
3201
3293
  sql,
@@ -3203,15 +3295,10 @@ function buildOneToOneIncludeSql(args) {
3203
3295
  args.ctx.params,
3204
3296
  args.takeVal,
3205
3297
  args.skipVal,
3206
- `include.${args.relName}`
3298
+ scopeBase
3207
3299
  );
3208
3300
  }
3209
- return limitOneSql(
3210
- sql,
3211
- args.ctx.params,
3212
- args.skipVal,
3213
- `include.${args.relName}`
3214
- );
3301
+ return limitOneSql(sql, args.ctx.params, args.skipVal, scopeBase);
3215
3302
  }
3216
3303
  function buildListIncludeSpec(args) {
3217
3304
  const rowExpr = jsonBuildObject(args.relSelect, args.ctx.dialect);
@@ -3239,13 +3326,14 @@ function buildListIncludeSpec(args) {
3239
3326
  whereClause: args.whereClause
3240
3327
  });
3241
3328
  if (args.orderBySql) base += ` ${SQL_TEMPLATES.ORDER_BY} ${args.orderBySql}`;
3329
+ const scopeBase = buildIncludeScope(args.ctx.includePath);
3242
3330
  base = appendLimitOffset(
3243
3331
  base,
3244
3332
  args.ctx.dialect,
3245
3333
  args.ctx.params,
3246
3334
  args.takeVal,
3247
3335
  args.skipVal,
3248
- `include.${args.relName}`
3336
+ scopeBase
3249
3337
  );
3250
3338
  const selectExpr = jsonAgg("row", args.ctx.dialect);
3251
3339
  const sql = `${SQL_TEMPLATES.SELECT} ${selectExpr} ${SQL_TEMPLATES.FROM} (${base}) ${SQL_TEMPLATES.AS} ${rowAlias}`;
@@ -3294,7 +3382,6 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3294
3382
  );
3295
3383
  if (!isList) {
3296
3384
  const sql = buildOneToOneIncludeSql({
3297
- relName,
3298
3385
  relTable,
3299
3386
  relAlias,
3300
3387
  joins: whereParts.joins,
@@ -3322,8 +3409,14 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3322
3409
  ctx
3323
3410
  });
3324
3411
  }
3325
- function buildIncludeSqlInternal(args, model, schemas, schemaByName, parentAlias, aliasGen, params, dialect, visitPath = [], depth = 0, stats) {
3326
- if (!stats) stats = { totalIncludes: 0, totalSubqueries: 0, maxDepth: 0 };
3412
+ function buildIncludeSqlInternal(args, ctx) {
3413
+ const stats = ctx.stats || {
3414
+ totalIncludes: 0,
3415
+ totalSubqueries: 0,
3416
+ maxDepth: 0
3417
+ };
3418
+ const depth = ctx.depth || 0;
3419
+ const visitPath = ctx.visitPath || [];
3327
3420
  if (depth > MAX_INCLUDE_DEPTH) {
3328
3421
  throw new Error(
3329
3422
  `Maximum include depth of ${MAX_INCLUDE_DEPTH} exceeded. Path: ${visitPath.join(" -> ")}. Deep includes cause exponential SQL complexity and performance issues.`
@@ -3331,7 +3424,7 @@ function buildIncludeSqlInternal(args, model, schemas, schemaByName, parentAlias
3331
3424
  }
3332
3425
  stats.maxDepth = Math.max(stats.maxDepth, depth);
3333
3426
  const includes = [];
3334
- const entries = relationEntriesFromArgs(args, model);
3427
+ const entries = relationEntriesFromArgs(args, ctx.model);
3335
3428
  for (const [relName, relArgs] of entries) {
3336
3429
  if (relArgs === false) continue;
3337
3430
  stats.totalIncludes++;
@@ -3346,8 +3439,12 @@ function buildIncludeSqlInternal(args, model, schemas, schemaByName, parentAlias
3346
3439
  `Query complexity limit exceeded: ${stats.totalSubqueries} subqueries generated. Maximum allowed: ${MAX_TOTAL_SUBQUERIES}. This indicates exponential include nesting. Stats: depth=${stats.maxDepth}, includes=${stats.totalIncludes}. Path: ${visitPath.join(" -> ")}. Simplify your include structure or split into multiple queries.`
3347
3440
  );
3348
3441
  }
3349
- const resolved = resolveRelationOrThrow(model, schemaByName, relName);
3350
- const relationPath = `${model.name}.${relName}`;
3442
+ const resolved = resolveRelationOrThrow(
3443
+ ctx.model,
3444
+ ctx.schemaByName,
3445
+ relName
3446
+ );
3447
+ const relationPath = `${ctx.model.name}.${relName}`;
3351
3448
  const currentPath = [...visitPath, relationPath];
3352
3449
  if (visitPath.includes(relationPath)) {
3353
3450
  throw new Error(
@@ -3362,19 +3459,14 @@ function buildIncludeSqlInternal(args, model, schemas, schemaByName, parentAlias
3362
3459
  `Include too deeply nested: model '${resolved.relModel.name}' appears ${modelOccurrences} times in path: ${currentPath.join(" -> ")}`
3363
3460
  );
3364
3461
  }
3462
+ const nextIncludePath = [...ctx.includePath, relName];
3365
3463
  includes.push(
3366
- buildSingleInclude(relName, relArgs, resolved.field, resolved.relModel, {
3367
- model,
3368
- schemas,
3369
- schemaByName,
3370
- parentAlias,
3371
- aliasGen,
3372
- dialect,
3373
- params,
3464
+ buildSingleInclude(relName, relArgs, resolved.field, resolved.relModel, __spreadProps(__spreadValues({}, ctx), {
3465
+ includePath: nextIncludePath,
3374
3466
  visitPath: currentPath,
3375
3467
  depth: depth + 1,
3376
3468
  stats
3377
- })
3469
+ }))
3378
3470
  );
3379
3471
  }
3380
3472
  return includes;
@@ -3388,8 +3480,7 @@ function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
3388
3480
  };
3389
3481
  const schemaByName = /* @__PURE__ */ new Map();
3390
3482
  for (const m of schemas) schemaByName.set(m.name, m);
3391
- return buildIncludeSqlInternal(
3392
- args,
3483
+ return buildIncludeSqlInternal(args, {
3393
3484
  model,
3394
3485
  schemas,
3395
3486
  schemaByName,
@@ -3397,10 +3488,11 @@ function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
3397
3488
  aliasGen,
3398
3489
  params,
3399
3490
  dialect,
3400
- [],
3401
- 0,
3491
+ includePath: [],
3492
+ visitPath: [],
3493
+ depth: 0,
3402
3494
  stats
3403
- );
3495
+ });
3404
3496
  }
3405
3497
  function resolveCountRelationOrThrow(relName, model, schemaByName) {
3406
3498
  const relationSet = getRelationFieldSet(model);
@@ -3445,8 +3537,7 @@ function resolveCountKeyPairs(field) {
3445
3537
  if (fkFields.length === 0) {
3446
3538
  throw new Error("Relation count requires foreignKey");
3447
3539
  }
3448
- const refsRaw = field.references;
3449
- const refs = normalizeKeyList(refsRaw);
3540
+ const refs = normalizeKeyList(field.references);
3450
3541
  const refFields = refs.length > 0 ? refs : defaultReferencesForCount(fkFields.length);
3451
3542
  if (refFields.length !== fkFields.length) {
3452
3543
  throw new Error(
@@ -3581,133 +3672,140 @@ function finalizeSql(sql, params, dialect) {
3581
3672
  paramMappings: snapshot.mappings
3582
3673
  };
3583
3674
  }
3675
+ function isIdent(s) {
3676
+ return /^[A-Za-z_]\w*$/.test(s);
3677
+ }
3678
+ function skipSpaces(s, i) {
3679
+ while (i < s.length) {
3680
+ const c = s.charCodeAt(i);
3681
+ if (c !== 32 && c !== 9) break;
3682
+ i++;
3683
+ }
3684
+ return i;
3685
+ }
3686
+ function parseQuotedIdentifier(s, start) {
3687
+ const n = s.length;
3688
+ let i = start + 1;
3689
+ let out = "";
3690
+ let saw = false;
3691
+ while (i < n) {
3692
+ const c = s.charCodeAt(i);
3693
+ if (c !== 34) {
3694
+ out += s[i];
3695
+ saw = true;
3696
+ i++;
3697
+ continue;
3698
+ }
3699
+ const next = i + 1;
3700
+ if (next < n && s.charCodeAt(next) === 34) {
3701
+ out += '"';
3702
+ saw = true;
3703
+ i += 2;
3704
+ continue;
3705
+ }
3706
+ if (!saw) {
3707
+ throw new Error(
3708
+ `sqlite distinct emulation: empty quoted identifier in: ${s}`
3709
+ );
3710
+ }
3711
+ return { text: out, next: i + 1, quoted: true };
3712
+ }
3713
+ throw new Error(
3714
+ `sqlite distinct emulation: unterminated quoted identifier in: ${s}`
3715
+ );
3716
+ }
3717
+ function parseUnquotedIdentifier(s, start) {
3718
+ const n = s.length;
3719
+ let i = start;
3720
+ while (i < n) {
3721
+ const c = s.charCodeAt(i);
3722
+ if (c === 32 || c === 9 || c === 46) break;
3723
+ i++;
3724
+ }
3725
+ return { text: s.slice(start, i), next: i, quoted: false };
3726
+ }
3727
+ function readIdentOrQuoted(s, start) {
3728
+ const n = s.length;
3729
+ if (start >= n) return { text: "", next: start, quoted: false };
3730
+ if (s.charCodeAt(start) === 34) {
3731
+ return parseQuotedIdentifier(s, start);
3732
+ }
3733
+ return parseUnquotedIdentifier(s, start);
3734
+ }
3735
+ function parseOutputAlias(p, i) {
3736
+ if (i >= p.length) return "";
3737
+ const rest = p.slice(i).trim();
3738
+ if (rest.length === 0) return "";
3739
+ const m = rest.match(/^AS\s+/i);
3740
+ if (!m) {
3741
+ throw new Error(
3742
+ `sqlite distinct emulation requires scalar select fields to be simple columns (optionally with AS). Got: ${p}`
3743
+ );
3744
+ }
3745
+ let j = i;
3746
+ j = skipSpaces(p, j);
3747
+ if (!/^AS\b/i.test(p.slice(j))) {
3748
+ throw new Error(`Failed to parse AS in: ${p}`);
3749
+ }
3750
+ j += 2;
3751
+ j = skipSpaces(p, j);
3752
+ const out = readIdentOrQuoted(p, j);
3753
+ const outAlias = out.text.trim();
3754
+ if (outAlias.length === 0) {
3755
+ throw new Error(`Failed to parse output alias from: ${p}`);
3756
+ }
3757
+ j = skipSpaces(p, out.next);
3758
+ if (j !== p.length) {
3759
+ throw new Error(
3760
+ `sqlite distinct emulation requires scalar select fields to be simple columns (optionally with AS). Got: ${p}`
3761
+ );
3762
+ }
3763
+ return outAlias;
3764
+ }
3765
+ function parseSelectField(p, fromAlias) {
3766
+ const fromLower = fromAlias.toLowerCase();
3767
+ let i = 0;
3768
+ i = skipSpaces(p, i);
3769
+ const a = readIdentOrQuoted(p, i);
3770
+ const actualAlias = a.text.toLowerCase();
3771
+ if (!isIdent(a.text)) {
3772
+ throw new Error(
3773
+ `sqlite distinct emulation requires scalar select fields to be simple columns (alias.column). Got: ${p}`
3774
+ );
3775
+ }
3776
+ if (actualAlias !== fromLower) {
3777
+ throw new Error(`Expected alias '${fromAlias}', got '${a.text}' in: ${p}`);
3778
+ }
3779
+ i = a.next;
3780
+ if (i >= p.length || p.charCodeAt(i) !== 46) {
3781
+ throw new Error(
3782
+ `sqlite distinct emulation requires scalar select fields to be simple columns (alias.column). Got: ${p}`
3783
+ );
3784
+ }
3785
+ i++;
3786
+ i = skipSpaces(p, i);
3787
+ const colPart = readIdentOrQuoted(p, i);
3788
+ const columnName = colPart.text.trim();
3789
+ if (columnName.length === 0) {
3790
+ throw new Error(`Failed to parse selected column name from: ${p}`);
3791
+ }
3792
+ i = colPart.next;
3793
+ i = skipSpaces(p, i);
3794
+ const outAlias = parseOutputAlias(p, i);
3795
+ return outAlias.length > 0 ? outAlias : columnName;
3796
+ }
3584
3797
  function parseSimpleScalarSelect(select, fromAlias) {
3585
3798
  const raw = select.trim();
3586
3799
  if (raw.length === 0) return [];
3587
- const fromLower = fromAlias.toLowerCase();
3588
3800
  const parts = raw.split(SQL_SEPARATORS.FIELD_LIST);
3589
3801
  const names = [];
3590
- const isIdent = (s) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(s);
3591
- const readIdentOrQuoted = (s, start) => {
3592
- const n = s.length;
3593
- if (start >= n) return { text: "", next: start, quoted: false };
3594
- if (s.charCodeAt(start) === 34) {
3595
- let i2 = start + 1;
3596
- let out = "";
3597
- let saw = false;
3598
- while (i2 < n) {
3599
- const c = s.charCodeAt(i2);
3600
- if (c === 34) {
3601
- const next = i2 + 1;
3602
- if (next < n && s.charCodeAt(next) === 34) {
3603
- out += '"';
3604
- saw = true;
3605
- i2 += 2;
3606
- continue;
3607
- }
3608
- if (!saw)
3609
- throw new Error(
3610
- `sqlite distinct emulation: empty quoted identifier in: ${s}`
3611
- );
3612
- return { text: out, next: i2 + 1, quoted: true };
3613
- }
3614
- out += s[i2];
3615
- saw = true;
3616
- i2++;
3617
- }
3618
- throw new Error(
3619
- `sqlite distinct emulation: unterminated quoted identifier in: ${s}`
3620
- );
3621
- }
3622
- let i = start;
3623
- while (i < n) {
3624
- const c = s.charCodeAt(i);
3625
- if (c === 32 || c === 9) break;
3626
- if (c === 46) break;
3627
- i++;
3628
- }
3629
- return { text: s.slice(start, i), next: i, quoted: false };
3630
- };
3631
- const skipSpaces = (s, i) => {
3632
- while (i < s.length) {
3633
- const c = s.charCodeAt(i);
3634
- if (c !== 32 && c !== 9) break;
3635
- i++;
3636
- }
3637
- return i;
3638
- };
3639
- for (let idx = 0; idx < parts.length; idx++) {
3640
- const p = parts[idx].trim();
3641
- if (p.length === 0) continue;
3642
- let i = 0;
3643
- i = skipSpaces(p, i);
3644
- const a = readIdentOrQuoted(p, i);
3645
- const actualAlias = a.text.toLowerCase();
3646
- if (!isIdent(a.text)) {
3647
- throw new Error(
3648
- `sqlite distinct emulation requires scalar select fields to be simple columns (alias.column). Got: ${p}`
3649
- );
3650
- }
3651
- if (actualAlias !== fromLower) {
3652
- throw new Error(`Expected alias '${fromAlias}', got '${a.text}' in: ${p}`);
3653
- }
3654
- i = a.next;
3655
- if (i >= p.length || p.charCodeAt(i) !== 46) {
3656
- throw new Error(
3657
- `sqlite distinct emulation requires scalar select fields to be simple columns (alias.column). Got: ${p}`
3658
- );
3659
- }
3660
- i++;
3661
- i = skipSpaces(p, i);
3662
- const colPart = readIdentOrQuoted(p, i);
3663
- const columnName = colPart.text.trim();
3664
- if (columnName.length === 0) {
3665
- throw new Error(`Failed to parse selected column name from: ${p}`);
3666
- }
3667
- i = colPart.next;
3668
- i = skipSpaces(p, i);
3669
- let outAlias = "";
3670
- if (i < p.length) {
3671
- const rest = p.slice(i).trim();
3672
- if (rest.length > 0) {
3673
- const m = rest.match(/^AS\s+/i);
3674
- if (!m) {
3675
- throw new Error(
3676
- `sqlite distinct emulation requires scalar select fields to be simple columns (optionally with AS). Got: ${p}`
3677
- );
3678
- }
3679
- let j = i;
3680
- j = skipSpaces(p, j);
3681
- if (!/^AS\b/i.test(p.slice(j))) {
3682
- throw new Error(`Failed to parse AS in: ${p}`);
3683
- }
3684
- j += 2;
3685
- j = skipSpaces(p, j);
3686
- const out = readIdentOrQuoted(p, j);
3687
- outAlias = out.text.trim();
3688
- if (outAlias.length === 0) {
3689
- throw new Error(`Failed to parse output alias from: ${p}`);
3690
- }
3691
- j = skipSpaces(p, out.next);
3692
- if (j !== p.length) {
3693
- throw new Error(
3694
- `sqlite distinct emulation requires scalar select fields to be simple columns (optionally with AS). Got: ${p}`
3695
- );
3696
- }
3697
- }
3698
- }
3699
- const name = outAlias.length > 0 ? outAlias : columnName;
3700
- names.push(name);
3802
+ for (const p of parts) {
3803
+ const trimmed = p.trim();
3804
+ if (trimmed.length === 0) continue;
3805
+ names.push(parseSelectField(trimmed, fromAlias));
3701
3806
  }
3702
3807
  return names;
3703
3808
  }
3704
- function replaceOrderByAlias(orderBy, fromAlias, outerAlias) {
3705
- const src = String(fromAlias);
3706
- if (src.length === 0) return orderBy;
3707
- const escaped = src.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3708
- const re = new RegExp(`\\b${escaped}\\.`, "gi");
3709
- return orderBy.replace(re, outerAlias + ".");
3710
- }
3711
3809
  function buildDistinctColumns(distinct, fromAlias, model) {
3712
3810
  return distinct.map((f) => col(fromAlias, f, model)).join(SQL_SEPARATORS.FIELD_LIST);
3713
3811
  }
@@ -3730,6 +3828,52 @@ function buildWindowOrder(args) {
3730
3828
  const idTiebreaker = idField ? ", " + col(fromAlias, "id", model) + " ASC" : "";
3731
3829
  return baseOrder + idTiebreaker;
3732
3830
  }
3831
+ function extractDistinctOrderEntries(spec) {
3832
+ if (isNotNullish(spec.args.orderBy)) {
3833
+ const normalized = normalizeOrderByInput(
3834
+ spec.args.orderBy,
3835
+ parseOrderByValue
3836
+ );
3837
+ const entries = [];
3838
+ for (const item of normalized) {
3839
+ for (const field in item) {
3840
+ if (!Object.prototype.hasOwnProperty.call(item, field)) continue;
3841
+ const value = item[field];
3842
+ if (typeof value === "string") {
3843
+ entries.push({ field, direction: value });
3844
+ } else {
3845
+ const obj = value;
3846
+ entries.push({ field, direction: obj.sort, nulls: obj.nulls });
3847
+ }
3848
+ }
3849
+ }
3850
+ if (entries.length > 0) return entries;
3851
+ }
3852
+ if (isNotNullish(spec.distinct) && isNonEmptyArray(spec.distinct)) {
3853
+ return [...spec.distinct].map((f) => ({
3854
+ field: f,
3855
+ direction: "asc"
3856
+ }));
3857
+ }
3858
+ return [];
3859
+ }
3860
+ function buildFieldNameOrderBy(entries, alias) {
3861
+ if (entries.length === 0) return "";
3862
+ const out = [];
3863
+ for (const e of entries) {
3864
+ const dir = e.direction.toUpperCase();
3865
+ const c = `${alias}.${quote(e.field)}`;
3866
+ if (isNotNullish(e.nulls)) {
3867
+ const isNullExpr = `(${c} IS NULL)`;
3868
+ const nullRankDir = e.nulls === "first" ? "DESC" : "ASC";
3869
+ out.push(isNullExpr + " " + nullRankDir);
3870
+ out.push(c + " " + dir);
3871
+ continue;
3872
+ }
3873
+ out.push(c + " " + dir);
3874
+ }
3875
+ return out.join(SQL_SEPARATORS.ORDER_BY);
3876
+ }
3733
3877
  function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
3734
3878
  var _a, _b;
3735
3879
  const { includes, from, whereClause, whereJoins, orderBy, distinct, model } = spec;
@@ -3756,7 +3900,8 @@ function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
3756
3900
  fromAlias: from.alias,
3757
3901
  model
3758
3902
  });
3759
- const outerOrder = isNonEmptyString(orderBy) ? replaceOrderByAlias(orderBy, from.alias, '"__tp_distinct"') : replaceOrderByAlias(fallbackOrder, from.alias, '"__tp_distinct"');
3903
+ const outerEntries = extractDistinctOrderEntries(spec);
3904
+ const outerOrder = buildFieldNameOrderBy(outerEntries, '"__tp_distinct"');
3760
3905
  const joins = buildJoinsSql(whereJoins, countJoins);
3761
3906
  const conditions = [];
3762
3907
  if (whereClause && whereClause !== "1=1") conditions.push(whereClause);
@@ -3791,17 +3936,33 @@ function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
3791
3936
  }
3792
3937
  return outerParts.join(" ");
3793
3938
  }
3939
+ function resolveCountSelect(countSelectRaw, model) {
3940
+ if (countSelectRaw === true) {
3941
+ const relationSet = getRelationFieldSet(model);
3942
+ if (relationSet.size === 0) return null;
3943
+ const allRelations = {};
3944
+ for (const name of relationSet) {
3945
+ allRelations[name] = true;
3946
+ }
3947
+ return allRelations;
3948
+ }
3949
+ if (isPlainObject(countSelectRaw) && "select" in countSelectRaw) {
3950
+ return countSelectRaw.select;
3951
+ }
3952
+ return null;
3953
+ }
3794
3954
  function buildIncludeColumns(spec) {
3795
3955
  var _a, _b;
3796
3956
  const { select, includes, dialect, model, schemas, from, params } = spec;
3797
3957
  const baseSelect = (select != null ? select : "").trim();
3798
3958
  let countCols = "";
3799
3959
  let countJoins = [];
3800
- const countSelect = (_b = (_a = spec.args) == null ? void 0 : _a.select) == null ? void 0 : _b._count;
3801
- if (countSelect) {
3802
- if (isPlainObject(countSelect) && "select" in countSelect) {
3960
+ const countSelectRaw = (_b = (_a = spec.args) == null ? void 0 : _a.select) == null ? void 0 : _b._count;
3961
+ if (countSelectRaw) {
3962
+ const resolvedCountSelect = resolveCountSelect(countSelectRaw, model);
3963
+ if (resolvedCountSelect && Object.keys(resolvedCountSelect).length > 0) {
3803
3964
  const countBuild = buildRelationCountSql(
3804
- countSelect.select,
3965
+ resolvedCountSelect,
3805
3966
  model,
3806
3967
  schemas,
3807
3968
  from.alias,
@@ -4626,32 +4787,32 @@ function buildGroupBySql(args, whereResult, tableName, alias, model, dialect) {
4626
4787
  paramMappings: allMappings
4627
4788
  };
4628
4789
  }
4790
+ function isPositiveInteger(value) {
4791
+ return Number.isFinite(value) && Number.isInteger(value) && value > 0;
4792
+ }
4793
+ function parseSkipValue(skip) {
4794
+ return typeof skip === "string" ? Number(skip.trim()) : skip;
4795
+ }
4796
+ function validateSkipParameter(skip) {
4797
+ if (skip === void 0 || skip === null) {
4798
+ return;
4799
+ }
4800
+ if (schemaParser.isDynamicParameter(skip)) {
4801
+ throw new Error(
4802
+ "count() with skip is not supported because it produces nondeterministic results. Dynamic skip cannot be validated at build time. Use findMany().length or add explicit orderBy + cursor/skip logic in a deterministic query."
4803
+ );
4804
+ }
4805
+ const skipValue = parseSkipValue(skip);
4806
+ if (isPositiveInteger(skipValue)) {
4807
+ throw new Error(
4808
+ "count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
4809
+ );
4810
+ }
4811
+ }
4629
4812
  function buildCountSql(whereResult, tableName, alias, skip, _dialect) {
4630
4813
  assertSafeAlias(alias);
4631
4814
  assertSafeTableRef(tableName);
4632
- if (skip !== void 0 && skip !== null) {
4633
- if (schemaParser.isDynamicParameter(skip)) {
4634
- throw new Error(
4635
- "count() with skip is not supported because it produces nondeterministic results. Dynamic skip cannot be validated at build time. Use findMany().length or add explicit orderBy + cursor/skip logic in a deterministic query."
4636
- );
4637
- }
4638
- if (typeof skip === "string") {
4639
- const s = skip.trim();
4640
- if (s.length > 0) {
4641
- const n = Number(s);
4642
- if (Number.isFinite(n) && Number.isInteger(n) && n > 0) {
4643
- throw new Error(
4644
- "count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
4645
- );
4646
- }
4647
- }
4648
- }
4649
- if (typeof skip === "number" && Number.isFinite(skip) && Number.isInteger(skip) && skip > 0) {
4650
- throw new Error(
4651
- "count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
4652
- );
4653
- }
4654
- }
4815
+ validateSkipParameter(skip);
4655
4816
  const whereClause = isValidWhereClause(whereResult.clause) ? SQL_TEMPLATES.WHERE + " " + whereResult.clause : "";
4656
4817
  const parts = [
4657
4818
  SQL_TEMPLATES.SELECT,
@@ -4875,6 +5036,257 @@ function generateSQL(directive) {
4875
5036
  dialect
4876
5037
  });
4877
5038
  }
5039
+
5040
+ // src/utils/s3-fifo.ts
5041
+ function withDispose(it) {
5042
+ const anyIt = it;
5043
+ if (anyIt[Symbol.dispose] === void 0) {
5044
+ anyIt[Symbol.dispose] = () => {
5045
+ };
5046
+ }
5047
+ return it;
5048
+ }
5049
+ var BoundedCache = class {
5050
+ constructor(maxSize) {
5051
+ this.map = /* @__PURE__ */ new Map();
5052
+ this.ghost = /* @__PURE__ */ new Set();
5053
+ this.smallHead = null;
5054
+ this.smallTail = null;
5055
+ this.smallSize = 0;
5056
+ this.mainHead = null;
5057
+ this.mainTail = null;
5058
+ this.mainSize = 0;
5059
+ this.maxSize = maxSize;
5060
+ this.smallLimit = Math.max(1, Math.floor(maxSize * 0.1));
5061
+ this.mainLimit = maxSize - this.smallLimit;
5062
+ this.ghostLimit = this.mainLimit;
5063
+ }
5064
+ get size() {
5065
+ return this.map.size;
5066
+ }
5067
+ get(key) {
5068
+ const node = this.map.get(key);
5069
+ if (!node) return void 0;
5070
+ node.freq = Math.min(node.freq + 1, 3);
5071
+ return node.value;
5072
+ }
5073
+ set(key, value) {
5074
+ const existing = this.map.get(key);
5075
+ if (existing) {
5076
+ existing.value = value;
5077
+ return this;
5078
+ }
5079
+ if (this.ghost.has(key)) {
5080
+ this.ghost.delete(key);
5081
+ const node2 = this.createNode(key, value, "main");
5082
+ this.map.set(key, node2);
5083
+ this.pushMain(node2);
5084
+ if (this.mainSize > this.mainLimit) this.evictMain();
5085
+ return this;
5086
+ }
5087
+ const node = this.createNode(key, value, "small");
5088
+ this.map.set(key, node);
5089
+ this.pushSmall(node);
5090
+ if (this.size > this.maxSize) {
5091
+ if (this.smallSize > this.smallLimit) this.evictSmall();
5092
+ else this.evictMain();
5093
+ }
5094
+ return this;
5095
+ }
5096
+ has(key) {
5097
+ return this.map.has(key);
5098
+ }
5099
+ delete(key) {
5100
+ const node = this.map.get(key);
5101
+ if (!node) return false;
5102
+ this.map.delete(key);
5103
+ this.removeNode(node);
5104
+ return true;
5105
+ }
5106
+ clear() {
5107
+ this.map.clear();
5108
+ this.ghost.clear();
5109
+ this.smallHead = this.smallTail = null;
5110
+ this.mainHead = this.mainTail = null;
5111
+ this.smallSize = this.mainSize = 0;
5112
+ }
5113
+ keys() {
5114
+ return withDispose(
5115
+ (function* (self) {
5116
+ for (const key of self.map.keys()) yield key;
5117
+ })(this)
5118
+ );
5119
+ }
5120
+ values() {
5121
+ return withDispose(
5122
+ (function* (self) {
5123
+ for (const node of self.map.values()) yield node.value;
5124
+ })(this)
5125
+ );
5126
+ }
5127
+ entries() {
5128
+ return withDispose(
5129
+ (function* (self) {
5130
+ for (const [key, node] of self.map.entries())
5131
+ yield [key, node.value];
5132
+ })(this)
5133
+ );
5134
+ }
5135
+ forEach(callbackfn, thisArg) {
5136
+ for (const [key, node] of this.map.entries()) {
5137
+ callbackfn.call(thisArg, node.value, key, this);
5138
+ }
5139
+ }
5140
+ [Symbol.iterator]() {
5141
+ return this.entries();
5142
+ }
5143
+ get [Symbol.toStringTag]() {
5144
+ return "BoundedCache";
5145
+ }
5146
+ createNode(key, value, queue) {
5147
+ return { key, value, freq: 0, queue, prev: null, next: null };
5148
+ }
5149
+ pushSmall(node) {
5150
+ node.next = this.smallHead;
5151
+ node.prev = null;
5152
+ if (this.smallHead) this.smallHead.prev = node;
5153
+ else this.smallTail = node;
5154
+ this.smallHead = node;
5155
+ this.smallSize++;
5156
+ }
5157
+ pushMain(node) {
5158
+ node.next = this.mainHead;
5159
+ node.prev = null;
5160
+ if (this.mainHead) this.mainHead.prev = node;
5161
+ else this.mainTail = node;
5162
+ this.mainHead = node;
5163
+ this.mainSize++;
5164
+ }
5165
+ popSmall() {
5166
+ if (!this.smallTail) return null;
5167
+ const node = this.smallTail;
5168
+ this.smallTail = node.prev;
5169
+ if (this.smallTail) this.smallTail.next = null;
5170
+ else this.smallHead = null;
5171
+ node.prev = null;
5172
+ node.next = null;
5173
+ this.smallSize--;
5174
+ return node;
5175
+ }
5176
+ popMain() {
5177
+ if (!this.mainTail) return null;
5178
+ const node = this.mainTail;
5179
+ this.mainTail = node.prev;
5180
+ if (this.mainTail) this.mainTail.next = null;
5181
+ else this.mainHead = null;
5182
+ node.prev = null;
5183
+ node.next = null;
5184
+ this.mainSize--;
5185
+ return node;
5186
+ }
5187
+ removeNode(node) {
5188
+ this.unlinkNode(node);
5189
+ if (node.queue === "small") {
5190
+ if (node === this.smallHead) this.smallHead = node.next;
5191
+ if (node === this.smallTail) this.smallTail = node.prev;
5192
+ this.smallSize--;
5193
+ } else {
5194
+ if (node === this.mainHead) this.mainHead = node.next;
5195
+ if (node === this.mainTail) this.mainTail = node.prev;
5196
+ this.mainSize--;
5197
+ }
5198
+ node.prev = null;
5199
+ node.next = null;
5200
+ }
5201
+ unlinkNode(node) {
5202
+ if (node.prev) node.prev.next = node.next;
5203
+ if (node.next) node.next.prev = node.prev;
5204
+ }
5205
+ shouldPromoteFromSmall(node) {
5206
+ return node.freq > 1;
5207
+ }
5208
+ shouldRetryInMain(node) {
5209
+ return node.freq >= 1;
5210
+ }
5211
+ promoteToMain(node) {
5212
+ node.queue = "main";
5213
+ this.pushMain(node);
5214
+ }
5215
+ addToGhost(key) {
5216
+ this.ghost.add(key);
5217
+ if (this.ghost.size <= this.ghostLimit) return;
5218
+ const firstGhost = this.ghost.values().next().value;
5219
+ if (firstGhost !== void 0) this.ghost.delete(firstGhost);
5220
+ }
5221
+ evictFromCache(node) {
5222
+ this.map.delete(node.key);
5223
+ }
5224
+ evictSmall() {
5225
+ while (this.smallSize > 0) {
5226
+ const node = this.popSmall();
5227
+ if (!node) return;
5228
+ if (this.shouldPromoteFromSmall(node)) {
5229
+ this.promoteToMain(node);
5230
+ if (this.mainSize > this.mainLimit) {
5231
+ this.evictMain();
5232
+ return;
5233
+ }
5234
+ continue;
5235
+ }
5236
+ this.evictFromCache(node);
5237
+ this.addToGhost(node.key);
5238
+ return;
5239
+ }
5240
+ }
5241
+ evictMain() {
5242
+ while (this.mainSize > 0) {
5243
+ const node = this.popMain();
5244
+ if (!node) return;
5245
+ if (this.shouldRetryInMain(node)) {
5246
+ node.freq--;
5247
+ this.pushMain(node);
5248
+ continue;
5249
+ }
5250
+ this.evictFromCache(node);
5251
+ return;
5252
+ }
5253
+ }
5254
+ };
5255
+ function createBoundedCache(maxSize) {
5256
+ return new BoundedCache(maxSize);
5257
+ }
5258
+
5259
+ // src/query-cache.ts
5260
+ var _hits, _misses;
5261
+ var QueryCacheStats = class {
5262
+ constructor() {
5263
+ __privateAdd(this, _hits, 0);
5264
+ __privateAdd(this, _misses, 0);
5265
+ }
5266
+ hit() {
5267
+ __privateWrapper(this, _hits)._++;
5268
+ }
5269
+ miss() {
5270
+ __privateWrapper(this, _misses)._++;
5271
+ }
5272
+ reset() {
5273
+ __privateSet(this, _hits, 0);
5274
+ __privateSet(this, _misses, 0);
5275
+ }
5276
+ get snapshot() {
5277
+ return Object.freeze({
5278
+ hits: __privateGet(this, _hits),
5279
+ misses: __privateGet(this, _misses),
5280
+ size: queryCache.size
5281
+ });
5282
+ }
5283
+ };
5284
+ _hits = new WeakMap();
5285
+ _misses = new WeakMap();
5286
+ var queryCache = createBoundedCache(1e3);
5287
+ new QueryCacheStats();
5288
+
5289
+ // src/index.ts
4878
5290
  function generateSQL2(directive) {
4879
5291
  return generateSQL(directive);
4880
5292
  }
@@ -4903,6 +5315,57 @@ function extractEnumMappings(datamodel) {
4903
5315
  }
4904
5316
  return { mappings, fieldTypes };
4905
5317
  }
5318
+ function processModelDirectives(modelName, result, config) {
5319
+ const modelQueries = /* @__PURE__ */ new Map();
5320
+ let skipped = 0;
5321
+ for (const directive of result.directives) {
5322
+ try {
5323
+ const method = directive.method;
5324
+ const sqlDirective = generateSQL2(directive);
5325
+ if (!modelQueries.has(method)) {
5326
+ modelQueries.set(method, /* @__PURE__ */ new Map());
5327
+ }
5328
+ const methodQueriesMap = modelQueries.get(method);
5329
+ const queryKey = createQueryKey(directive.query.processed);
5330
+ methodQueriesMap.set(queryKey, {
5331
+ sql: sqlDirective.sql,
5332
+ params: sqlDirective.staticParams,
5333
+ dynamicKeys: sqlDirective.dynamicKeys,
5334
+ paramMappings: sqlDirective.paramMappings
5335
+ });
5336
+ } catch (error) {
5337
+ if (!config.skipInvalid) throw error;
5338
+ skipped++;
5339
+ const errMsg = error instanceof Error ? error.message : String(error);
5340
+ console.warn(` \u26A0 Skipped ${modelName}.${directive.method}: ${errMsg}`);
5341
+ }
5342
+ }
5343
+ return { modelQueries, skipped };
5344
+ }
5345
+ function processAllModelDirectives(directiveResults, config) {
5346
+ const queries = /* @__PURE__ */ new Map();
5347
+ let skippedCount = 0;
5348
+ for (const [modelName, result] of directiveResults) {
5349
+ if (result.directives.length === 0) continue;
5350
+ const { modelQueries, skipped } = processModelDirectives(
5351
+ modelName,
5352
+ result,
5353
+ config
5354
+ );
5355
+ queries.set(modelName, modelQueries);
5356
+ skippedCount += skipped;
5357
+ }
5358
+ return { queries, skippedCount };
5359
+ }
5360
+ function countTotalQueries(queries) {
5361
+ return Array.from(queries.values()).reduce(
5362
+ (sum, methodMap) => sum + Array.from(methodMap.values()).reduce(
5363
+ (s, queryMap) => s + queryMap.size,
5364
+ 0
5365
+ ),
5366
+ 0
5367
+ );
5368
+ }
4906
5369
  function generateClient(options) {
4907
5370
  return __async(this, null, function* () {
4908
5371
  const { datamodel, outputDir, config } = options;
@@ -4915,48 +5378,22 @@ function generateClient(options) {
4915
5378
  skipInvalid: config.skipInvalid
4916
5379
  }
4917
5380
  );
4918
- const queries = /* @__PURE__ */ new Map();
4919
- for (const [modelName, result] of directiveResults) {
4920
- if (result.directives.length === 0) continue;
4921
- if (!queries.has(modelName)) {
4922
- queries.set(modelName, /* @__PURE__ */ new Map());
4923
- }
4924
- const modelQueries = queries.get(modelName);
4925
- for (const directive of result.directives) {
4926
- try {
4927
- const method = directive.method;
4928
- const sqlDirective = generateSQL2(directive);
4929
- if (!modelQueries.has(method)) {
4930
- modelQueries.set(method, /* @__PURE__ */ new Map());
4931
- }
4932
- const methodQueriesMap = modelQueries.get(method);
4933
- const queryKey = createQueryKey(directive.query.processed);
4934
- methodQueriesMap.set(queryKey, {
4935
- sql: sqlDirective.sql,
4936
- params: sqlDirective.staticParams,
4937
- dynamicKeys: sqlDirective.dynamicKeys,
4938
- paramMappings: sqlDirective.paramMappings
4939
- });
4940
- } catch (error) {
4941
- if (!config.skipInvalid) throw error;
4942
- }
4943
- }
4944
- }
5381
+ const { queries, skippedCount } = processAllModelDirectives(
5382
+ directiveResults,
5383
+ config
5384
+ );
4945
5385
  const absoluteOutputDir = path.resolve(process.cwd(), outputDir);
4946
5386
  yield promises.mkdir(absoluteOutputDir, { recursive: true });
4947
5387
  const code = generateCode(models, queries, config.dialect, datamodel);
4948
5388
  const outputPath = path.join(absoluteOutputDir, "index.ts");
4949
5389
  yield promises.writeFile(outputPath, code);
4950
- const totalQueries = Array.from(queries.values()).reduce(
4951
- (sum, methodMap) => sum + Array.from(methodMap.values()).reduce(
4952
- (s, queryMap) => s + queryMap.size,
4953
- 0
4954
- ),
4955
- 0
4956
- );
5390
+ const totalQueries = countTotalQueries(queries);
4957
5391
  console.log(
4958
5392
  `\u2713 Generated ${queries.size} models, ${totalQueries} prebaked queries`
4959
5393
  );
5394
+ if (skippedCount > 0) {
5395
+ console.log(`\u26A0 Skipped ${skippedCount} directive(s) due to errors`);
5396
+ }
4960
5397
  console.log(`\u2713 Output: ${outputPath}`);
4961
5398
  });
4962
5399
  }
@@ -4972,16 +5409,13 @@ function createQueryKey(processedQuery) {
4972
5409
  return value;
4973
5410
  });
4974
5411
  }
4975
- function generateCode(models, queries, dialect, datamodel) {
4976
- const cleanModels = models.map((model) => __spreadProps(__spreadValues({}, model), {
4977
- fields: model.fields.filter((f) => f !== void 0 && f !== null)
4978
- }));
4979
- const { mappings, fieldTypes } = extractEnumMappings(datamodel);
5412
+ function generateImports() {
4980
5413
  return `// Generated by @prisma-sql/generator - DO NOT EDIT
4981
5414
  import { buildSQL, buildBatchSql, parseBatchResults, buildBatchCountSql, parseBatchCountResults, createTransactionExecutor, transformQueryResults, type PrismaMethod, type Model, type BatchQuery, type BatchCountQuery, type TransactionQuery, type TransactionOptions } from 'prisma-sql'
4982
- import { normalizeValue } from 'prisma-sql/utils/normalize-value'
4983
-
4984
- class DeferredQuery {
5415
+ import { normalizeValue } from 'prisma-sql/utils/normalize-value'`;
5416
+ }
5417
+ function generateCoreTypes() {
5418
+ return `class DeferredQuery {
4985
5419
  constructor(
4986
5420
  public readonly model: string,
4987
5421
  public readonly method: PrismaMethod,
@@ -5013,9 +5447,10 @@ const ACCELERATED_METHODS = new Set<PrismaMethod>([
5013
5447
  'count',
5014
5448
  'aggregate',
5015
5449
  'groupBy',
5016
- ])
5017
-
5018
- function createBatchProxy(): BatchProxy {
5450
+ ])`;
5451
+ }
5452
+ function generateHelpers() {
5453
+ return `function createBatchProxy(): BatchProxy {
5019
5454
  return new Proxy(
5020
5455
  {},
5021
5456
  {
@@ -5066,7 +5501,68 @@ function getByPath(obj: any, path: string): unknown {
5066
5501
  return result
5067
5502
  }
5068
5503
 
5069
- export const MODELS: Model[] = ${JSON.stringify(cleanModels, null, 2)}
5504
+ function resolveParamsFromMappings(args: any, paramMappings: any[]): unknown[] {
5505
+ const params: unknown[] = []
5506
+ for (let i = 0; i < paramMappings.length; i++) {
5507
+ const m = paramMappings[i]
5508
+ if (m.value !== undefined) {
5509
+ params.push(m.value)
5510
+ continue
5511
+ }
5512
+ if (m.dynamicName === undefined) {
5513
+ throw new Error(\`CRITICAL: ParamMap \${m.index} has neither dynamicName nor value\`)
5514
+ }
5515
+ const colonIdx = m.dynamicName.indexOf(':')
5516
+ const scopePath = colonIdx !== -1 ? m.dynamicName.slice(0, colonIdx) : null
5517
+ const name = colonIdx !== -1 ? m.dynamicName.slice(colonIdx + 1) : m.dynamicName
5518
+ let value: unknown
5519
+ if (!scopePath || scopePath.startsWith('root.')) {
5520
+ value = name.includes('.')
5521
+ ? getByPath(args, name)
5522
+ : args?.[name]
5523
+ } else {
5524
+ value = getByPath(args, scopePath)
5525
+ }
5526
+ if (value === undefined) {
5527
+ throw new Error(\`Missing required parameter: \${m.dynamicName}\`)
5528
+ }
5529
+ params.push(normalizeValue(value))
5530
+ }
5531
+ return params
5532
+ }
5533
+
5534
+ function shouldSqliteUseGet(method: string): boolean {
5535
+ return (
5536
+ method === 'count' ||
5537
+ method === 'findFirst' ||
5538
+ method === 'findUnique' ||
5539
+ method === 'aggregate'
5540
+ )
5541
+ }
5542
+
5543
+ async function executeQuery(client: any, method: string, sql: string, params: unknown[]): Promise<unknown[]> {
5544
+ const normalizedParams = normalizeParams(params)
5545
+ if (DIALECT === 'postgres') {
5546
+ return await client.unsafe(sql, normalizedParams)
5547
+ }
5548
+ const stmt = client.prepare(sql)
5549
+ if (shouldSqliteUseGet(method)) {
5550
+ const row = stmt.get(...normalizedParams)
5551
+ if (row === undefined) return []
5552
+ return [row]
5553
+ }
5554
+ return stmt.all(...normalizedParams)
5555
+ }
5556
+
5557
+ async function executeRaw(client: any, sql: string, params?: unknown[]): Promise<unknown[]> {
5558
+ if (DIALECT === 'postgres') {
5559
+ return await client.unsafe(sql, (params || []) as any[])
5560
+ }
5561
+ throw new Error('Raw execution for sqlite not supported in transactions')
5562
+ }`;
5563
+ }
5564
+ function generateDataConstants(cleanModels, mappings, fieldTypes, queries, dialect) {
5565
+ return `export const MODELS: Model[] = ${JSON.stringify(cleanModels, null, 2)}
5070
5566
 
5071
5567
  const ENUM_MAPPINGS: Record<string, Record<string, string>> = ${JSON.stringify(mappings, null, 2)}
5072
5568
 
@@ -5081,12 +5577,19 @@ const QUERIES: Record<string, Record<string, Record<string, {
5081
5577
 
5082
5578
  const DIALECT = ${JSON.stringify(dialect)}
5083
5579
 
5084
- const MODEL_MAP = new Map(MODELS.map(m => [m.name, m]))
5085
-
5086
- function isDynamicKeyForQueryKey(path: string[], key: string): boolean {
5580
+ const MODEL_MAP = new Map(MODELS.map(m => [m.name, m]))`;
5581
+ }
5582
+ function generateTransformLogic() {
5583
+ return `function isDynamicKeyForQueryKey(path: string[], key: string): boolean {
5087
5584
  if (key !== 'skip' && key !== 'take' && key !== 'cursor') return false
5585
+ const parent = path.length > 0 ? path[path.length - 1] : null
5586
+ if (!parent) return true
5587
+ if (parent === 'include' || parent === 'select') return false
5088
5588
  if (path.includes('where')) return false
5089
5589
  if (path.includes('data')) return false
5590
+ if (path.includes('orderBy')) return false
5591
+ if (path.includes('having')) return false
5592
+ if (path.includes('by')) return false
5090
5593
  return true
5091
5594
  }
5092
5595
 
@@ -5113,6 +5616,14 @@ function transformEnumInValue(value: unknown, enumType: string | undefined): unk
5113
5616
  return mapping[value]
5114
5617
  }
5115
5618
 
5619
+ if (typeof value === 'object' && !(value instanceof Date)) {
5620
+ const result: Record<string, unknown> = {}
5621
+ for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
5622
+ result[k] = transformEnumInValue(v, enumType)
5623
+ }
5624
+ return result
5625
+ }
5626
+
5116
5627
  return value
5117
5628
  }
5118
5629
 
@@ -5202,21 +5713,7 @@ function normalizeQuery(args: any): string {
5202
5713
 
5203
5714
  const withMarkers = replaceDynamicParams(normalized)
5204
5715
 
5205
- function removeEmptyObjects(obj: any): any {
5206
- if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return obj
5207
- const result: any = {}
5208
- for (const [key, value] of Object.entries(obj)) {
5209
- if (value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0) {
5210
- continue
5211
- }
5212
- result[key] = removeEmptyObjects(value)
5213
- }
5214
- return result
5215
- }
5216
-
5217
- const cleaned = removeEmptyObjects(withMarkers)
5218
-
5219
- return JSON.stringify(cleaned, (key, value) => {
5716
+ return JSON.stringify(withMarkers, (key, value) => {
5220
5717
  if (typeof value === 'bigint') return '__bigint__' + value.toString()
5221
5718
  if (value && typeof value === 'object' && !Array.isArray(value)) {
5222
5719
  const sorted: Record<string, unknown> = {}
@@ -5227,43 +5724,10 @@ function normalizeQuery(args: any): string {
5227
5724
  }
5228
5725
  return value
5229
5726
  })
5727
+ }`;
5230
5728
  }
5231
-
5232
- function extractDynamicParams(args: any, dynamicKeys: string[]): unknown[] {
5233
- const params: unknown[] = []
5234
- for (const key of dynamicKeys) {
5235
- const parts = key.split(':')
5236
- const lookupKey = parts.length === 2 ? parts[1] : key
5237
- const value =
5238
- lookupKey.includes('.') ? getByPath(args, lookupKey) : args?.[lookupKey]
5239
- if (value === undefined) {
5240
- throw new Error(\`Missing required parameter: \${key}\`)
5241
- }
5242
- params.push(normalizeValue(value))
5243
- }
5244
- return params
5245
- }
5246
-
5247
- async function executeQuery(client: any, sql: string, params: unknown[]): Promise<unknown[]> {
5248
- const normalizedParams = normalizeParams(params)
5249
- if (DIALECT === 'postgres') {
5250
- return await client.unsafe(sql, normalizedParams)
5251
- }
5252
- const stmt = client.prepare(sql)
5253
- if (sql.toUpperCase().includes('COUNT(*) AS')) {
5254
- return [stmt.get(...normalizedParams)]
5255
- }
5256
- return stmt.all(...normalizedParams)
5257
- }
5258
-
5259
- async function executeRaw(client: any, sql: string, params?: unknown[]): Promise<unknown[]> {
5260
- if (DIALECT === 'postgres') {
5261
- return await client.unsafe(sql, (params || []) as any[])
5262
- }
5263
- throw new Error('Raw execution for sqlite not supported in transactions')
5264
- }
5265
-
5266
- export function speedExtension(config: {
5729
+ function generateExtension() {
5730
+ return `export function speedExtension(config: {
5267
5731
  postgres?: any
5268
5732
  sqlite?: any
5269
5733
  debug?: boolean
@@ -5298,49 +5762,56 @@ export function speedExtension(config: {
5298
5762
  const handleMethod = async function(this: any, method: PrismaMethod, args: any) {
5299
5763
  const modelName = this?.name || this?.$name
5300
5764
  const startTime = Date.now()
5301
- const transformedArgs = transformEnumValuesByModel(modelName, args || {})
5302
- const queryKey = normalizeQuery(transformedArgs)
5303
- const prebakedQuery = QUERIES[modelName]?.[method]?.[queryKey]
5304
- let sql: string
5305
- let params: unknown[]
5306
- let prebaked = false
5307
5765
 
5308
- if (prebakedQuery) {
5309
- sql = prebakedQuery.sql
5310
- params = normalizeParams([
5311
- ...prebakedQuery.params,
5312
- ...extractDynamicParams(transformedArgs, prebakedQuery.dynamicKeys),
5313
- ])
5314
- prebaked = true
5315
- } else {
5316
- const model = MODELS.find((m) => m.name === modelName)
5317
- if (!model) {
5318
- return this.$parent[modelName][method](args)
5766
+ try {
5767
+ const transformedArgs = transformEnumValuesByModel(modelName, args || {})
5768
+ const queryKey = normalizeQuery(transformedArgs)
5769
+ const prebakedQuery = QUERIES[modelName]?.[method]?.[queryKey]
5770
+ let sql: string
5771
+ let params: unknown[]
5772
+ let prebaked = false
5773
+
5774
+ if (prebakedQuery) {
5775
+ sql = prebakedQuery.sql
5776
+ params = resolveParamsFromMappings(transformedArgs, prebakedQuery.paramMappings)
5777
+ prebaked = true
5778
+ } else {
5779
+ const model = MODELS.find((m) => m.name === modelName)
5780
+ if (!model) {
5781
+ return this.$parent[modelName][method](args)
5782
+ }
5783
+ const result = buildSQL(model, MODELS, method, transformedArgs, DIALECT)
5784
+ sql = result.sql
5785
+ params = result.params as unknown[]
5319
5786
  }
5320
- const result = buildSQL(model, MODELS, method, transformedArgs, DIALECT)
5321
- sql = result.sql
5322
- params = normalizeParams(result.params as unknown[])
5323
- }
5324
5787
 
5325
- if (debug) {
5326
- console.log(\`[\${DIALECT}] \${modelName}.\${method} \${prebaked ? '\u26A1 PREBAKED' : '\u{1F528} RUNTIME'}\`)
5327
- console.log('SQL:', sql)
5328
- console.log('Params:', params)
5329
- }
5788
+ if (debug) {
5789
+ console.log(\`[\${DIALECT}] \${modelName}.\${method} \${prebaked ? '\u26A1 PREBAKED' : '\u{1F528} RUNTIME'}\`)
5790
+ console.log('SQL:', sql)
5791
+ console.log('Params:', params)
5792
+ }
5330
5793
 
5331
- const results = await executeQuery(client, sql, params)
5332
- const duration = Date.now() - startTime
5794
+ const results = await executeQuery(client, method, sql, params)
5795
+ const duration = Date.now() - startTime
5333
5796
 
5334
- onQuery?.({
5335
- model: modelName,
5336
- method,
5337
- sql,
5338
- params,
5339
- duration,
5340
- prebaked,
5341
- })
5797
+ onQuery?.({
5798
+ model: modelName,
5799
+ method,
5800
+ sql,
5801
+ params,
5802
+ duration,
5803
+ prebaked,
5804
+ })
5342
5805
 
5343
- return transformQueryResults(method, results)
5806
+ return transformQueryResults(method, results)
5807
+ } catch (error) {
5808
+ const msg = error instanceof Error ? error.message : String(error)
5809
+ console.warn(\`[prisma-sql] \${modelName}.\${method} acceleration failed, falling back to Prisma: \${msg}\`)
5810
+ if (debug && error instanceof Error && error.stack) {
5811
+ console.warn(error.stack)
5812
+ }
5813
+ return this.$parent[modelName][method](args)
5814
+ }
5344
5815
  }
5345
5816
 
5346
5817
  async function batch<T extends Record<string, DeferredQuery>>(
@@ -5358,7 +5829,7 @@ export function speedExtension(config: {
5358
5829
  batchQueries[key] = {
5359
5830
  model: deferred.model,
5360
5831
  method: deferred.method,
5361
- args: deferred.args || {},
5832
+ args: transformEnumValuesByModel(deferred.model, deferred.args || {}),
5362
5833
  }
5363
5834
  }
5364
5835
 
@@ -5379,7 +5850,7 @@ export function speedExtension(config: {
5379
5850
  const normalizedParams = normalizeParams(params)
5380
5851
  const rows = await client.unsafe(sql, normalizedParams as any[])
5381
5852
  const row = rows[0] as Record<string, unknown>
5382
- const results = parseBatchResults(row, keys, batchQueries, aliases)
5853
+ const results = parseBatchResults(row, keys, batchQueries, aliases, MODEL_MAP)
5383
5854
 
5384
5855
  const duration = Date.now() - startTime
5385
5856
  onQuery?.({
@@ -5436,7 +5907,11 @@ export function speedExtension(config: {
5436
5907
  if (debug) {
5437
5908
  console.log(\`[\${DIALECT}] $transaction (\${queries.length} queries)\`)
5438
5909
  }
5439
- const results = await txExecutor.execute(queries, options)
5910
+ const transformedQueries = queries.map(q => ({
5911
+ ...q,
5912
+ args: transformEnumValuesByModel(q.model, q.args || {}),
5913
+ }))
5914
+ const results = await txExecutor.execute(transformedQueries, options)
5440
5915
  const duration = Date.now() - startTime
5441
5916
  onQuery?.({
5442
5917
  model: '_transaction',
@@ -5483,9 +5958,10 @@ export function speedExtension(config: {
5483
5958
  },
5484
5959
  })
5485
5960
  }
5961
+ }`;
5486
5962
  }
5487
-
5488
- type SpeedExtensionReturn = ReturnType<ReturnType<typeof speedExtension>>
5963
+ function generateTypeExports() {
5964
+ return `type SpeedExtensionReturn = ReturnType<ReturnType<typeof speedExtension>>
5489
5965
 
5490
5966
  export type SpeedClient<T> = T & {
5491
5967
  $batch<T extends Record<string, DeferredQuery>>(
@@ -5495,8 +5971,22 @@ export type SpeedClient<T> = T & {
5495
5971
  $transaction(queries: TransactionQuery[], options?: TransactionOptions): Promise<unknown[]>
5496
5972
  }
5497
5973
 
5498
- export type { BatchCountQuery, TransactionQuery, TransactionOptions }
5499
- `;
5974
+ export type { BatchCountQuery, TransactionQuery, TransactionOptions }`;
5975
+ }
5976
+ function generateCode(models, queries, dialect, datamodel) {
5977
+ const cleanModels = models.map((model) => __spreadProps(__spreadValues({}, model), {
5978
+ fields: model.fields.filter((f) => f !== void 0 && f !== null)
5979
+ }));
5980
+ const { mappings, fieldTypes } = extractEnumMappings(datamodel);
5981
+ return [
5982
+ generateImports(),
5983
+ generateCoreTypes(),
5984
+ generateHelpers(),
5985
+ generateDataConstants(cleanModels, mappings, fieldTypes, queries, dialect),
5986
+ generateTransformLogic(),
5987
+ generateExtension(),
5988
+ generateTypeExports()
5989
+ ].join("\n\n");
5500
5990
  }
5501
5991
  function formatQueries(queries) {
5502
5992
  if (queries.size === 0) {