prisma-sql 1.58.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.58.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,154 +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
- });
302
-
303
170
  // src/utils/normalize-value.ts
304
171
  var MAX_DEPTH = 20;
305
172
  function normalizeValue(value, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
@@ -307,45 +174,54 @@ function normalizeValue(value, seen = /* @__PURE__ */ new WeakSet(), depth = 0)
307
174
  throw new Error(`Max normalization depth exceeded (${MAX_DEPTH} levels)`);
308
175
  }
309
176
  if (value instanceof Date) {
310
- const t = value.getTime();
311
- if (!Number.isFinite(t)) {
312
- throw new Error("Invalid Date value in SQL params");
313
- }
314
- return value.toISOString();
177
+ return normalizeDateValue(value);
315
178
  }
316
179
  if (typeof value === "bigint") {
317
180
  return value.toString();
318
181
  }
319
182
  if (Array.isArray(value)) {
320
- const arrRef = value;
321
- if (seen.has(arrRef)) {
322
- throw new Error("Circular reference in SQL params");
323
- }
324
- seen.add(arrRef);
325
- const out = value.map((v) => normalizeValue(v, seen, depth + 1));
326
- seen.delete(arrRef);
327
- return out;
183
+ return normalizeArrayValue(value, seen, depth);
328
184
  }
329
185
  if (value && typeof value === "object") {
330
- if (value instanceof Uint8Array) return value;
331
- if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) return value;
332
- const proto = Object.getPrototypeOf(value);
333
- const isPlain = proto === Object.prototype || proto === null;
334
- if (!isPlain) return value;
335
- const obj = value;
336
- if (seen.has(obj)) {
337
- throw new Error("Circular reference in SQL params");
338
- }
339
- seen.add(obj);
340
- const out = {};
341
- for (const [k, v] of Object.entries(obj)) {
342
- out[k] = normalizeValue(v, seen, depth + 1);
343
- }
344
- seen.delete(obj);
345
- return out;
186
+ return normalizeObjectValue(value, seen, depth);
346
187
  }
347
188
  return value;
348
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
+ }
349
225
 
350
226
  // src/sql-builder-dialect.ts
351
227
  var globalDialect = "postgres";
@@ -525,9 +401,158 @@ function prepareArrayParam(value, dialect) {
525
401
  if (dialect === "postgres") {
526
402
  return value.map((v) => normalizeValue(v));
527
403
  }
528
- return JSON.stringify(value);
404
+ return JSON.stringify(value.map((v) => normalizeValue(v)));
529
405
  }
530
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
+
531
556
  // src/builder/shared/validators/type-guards.ts
532
557
  function isNotNullish(value) {
533
558
  return value !== null && value !== void 0;
@@ -594,73 +619,6 @@ function createError(message, ctx, code = "VALIDATION_ERROR") {
594
619
  return new SqlBuilderError(parts.join("\n"), code, ctx);
595
620
  }
596
621
 
597
- // src/builder/shared/model-field-cache.ts
598
- var MODEL_CACHE = /* @__PURE__ */ new WeakMap();
599
- function quote(id) {
600
- const needsQuoting2 = !/^[a-z_][a-z0-9_]*$/.test(id) || /^(select|from|where|having|order|group|limit|offset|join|inner|left|right|outer|cross|full|and|or|not|by|as|on|union|intersect|except|case|when|then|else|end|user|users|table|column|index|values|in|like|between|is|exists|null|true|false|all|any|some|update|insert|delete|create|drop|alter|truncate|grant|revoke|exec|execute)$/i.test(
601
- id
602
- );
603
- if (needsQuoting2) {
604
- return `"${id.replace(/"/g, '""')}"`;
605
- }
606
- return id;
607
- }
608
- function ensureFullCache(model) {
609
- let cache = MODEL_CACHE.get(model);
610
- if (!cache) {
611
- const fieldInfo = /* @__PURE__ */ new Map();
612
- const scalarFields = /* @__PURE__ */ new Set();
613
- const relationFields = /* @__PURE__ */ new Set();
614
- const columnMap = /* @__PURE__ */ new Map();
615
- const fieldByName = /* @__PURE__ */ new Map();
616
- const quotedColumns = /* @__PURE__ */ new Map();
617
- for (const f of model.fields) {
618
- const info = {
619
- name: f.name,
620
- dbName: f.dbName || f.name,
621
- type: f.type,
622
- isRelation: !!f.isRelation,
623
- isRequired: !!f.isRequired
624
- };
625
- fieldInfo.set(f.name, info);
626
- fieldByName.set(f.name, f);
627
- if (info.isRelation) {
628
- relationFields.add(f.name);
629
- } else {
630
- scalarFields.add(f.name);
631
- const dbName = info.dbName;
632
- columnMap.set(f.name, dbName);
633
- quotedColumns.set(f.name, quote(dbName));
634
- }
635
- }
636
- cache = {
637
- fieldInfo,
638
- scalarFields,
639
- relationFields,
640
- columnMap,
641
- fieldByName,
642
- quotedColumns
643
- };
644
- MODEL_CACHE.set(model, cache);
645
- }
646
- return cache;
647
- }
648
- function getFieldInfo(model, fieldName) {
649
- return ensureFullCache(model).fieldInfo.get(fieldName);
650
- }
651
- function getScalarFieldSet(model) {
652
- return ensureFullCache(model).scalarFields;
653
- }
654
- function getRelationFieldSet(model) {
655
- return ensureFullCache(model).relationFields;
656
- }
657
- function getColumnMap(model) {
658
- return ensureFullCache(model).columnMap;
659
- }
660
- function getQuotedColumn(model, fieldName) {
661
- return ensureFullCache(model).quotedColumns.get(fieldName);
662
- }
663
-
664
622
  // src/builder/shared/validators/sql-validators.ts
665
623
  function isValidWhereClause(clause) {
666
624
  return isNotNullish(clause) && clause.trim().length > 0 && clause !== DEFAULT_WHERE_CLAUSE;
@@ -804,9 +762,90 @@ function needsQuoting(identifier) {
804
762
  return false;
805
763
  }
806
764
 
765
+ // src/builder/shared/model-field-cache.ts
766
+ var MODEL_CACHE = /* @__PURE__ */ new WeakMap();
767
+ function quoteIdent(id) {
768
+ if (typeof id !== "string" || id.trim().length === 0) {
769
+ throw new Error("quoteIdent: identifier is required and cannot be empty");
770
+ }
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
+ }
778
+ }
779
+ if (needsQuoting(id)) {
780
+ return `"${id.replace(/"/g, '""')}"`;
781
+ }
782
+ return id;
783
+ }
784
+ function ensureFullCache(model) {
785
+ let cache = MODEL_CACHE.get(model);
786
+ if (!cache) {
787
+ const fieldInfo = /* @__PURE__ */ new Map();
788
+ const scalarFields = /* @__PURE__ */ new Set();
789
+ const relationFields = /* @__PURE__ */ new Set();
790
+ const columnMap = /* @__PURE__ */ new Map();
791
+ const fieldByName = /* @__PURE__ */ new Map();
792
+ const quotedColumns = /* @__PURE__ */ new Map();
793
+ for (const f of model.fields) {
794
+ const info = {
795
+ name: f.name,
796
+ dbName: f.dbName || f.name,
797
+ type: f.type,
798
+ isRelation: !!f.isRelation,
799
+ isRequired: !!f.isRequired
800
+ };
801
+ fieldInfo.set(f.name, info);
802
+ fieldByName.set(f.name, f);
803
+ if (info.isRelation) {
804
+ relationFields.add(f.name);
805
+ } else {
806
+ scalarFields.add(f.name);
807
+ const dbName = info.dbName;
808
+ columnMap.set(f.name, dbName);
809
+ quotedColumns.set(f.name, quoteIdent(dbName));
810
+ }
811
+ }
812
+ cache = {
813
+ fieldInfo,
814
+ scalarFields,
815
+ relationFields,
816
+ columnMap,
817
+ fieldByName,
818
+ quotedColumns
819
+ };
820
+ MODEL_CACHE.set(model, cache);
821
+ }
822
+ return cache;
823
+ }
824
+ function getFieldInfo(model, fieldName) {
825
+ return ensureFullCache(model).fieldInfo.get(fieldName);
826
+ }
827
+ function getScalarFieldSet(model) {
828
+ return ensureFullCache(model).scalarFields;
829
+ }
830
+ function getRelationFieldSet(model) {
831
+ return ensureFullCache(model).relationFields;
832
+ }
833
+ function getColumnMap(model) {
834
+ return ensureFullCache(model).columnMap;
835
+ }
836
+ function getQuotedColumn(model, fieldName) {
837
+ return ensureFullCache(model).quotedColumns.get(fieldName);
838
+ }
839
+
807
840
  // src/builder/shared/sql-utils.ts
808
841
  function containsControlChars(s) {
809
- 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;
810
849
  }
811
850
  function assertNoControlChars(label, s) {
812
851
  if (containsControlChars(s)) {
@@ -881,20 +920,9 @@ function parseUnquotedPart(input, start) {
881
920
  }
882
921
  return i;
883
922
  }
884
- function assertSafeQualifiedName(input) {
885
- const raw = String(input);
886
- const trimmed = raw.trim();
887
- if (trimmed.length === 0) {
888
- throw new Error("tableName/tableRef is required and cannot be empty");
889
- }
890
- if (raw !== trimmed) {
891
- throw new Error(
892
- `tableName/tableRef must not contain leading/trailing whitespace: ${JSON.stringify(raw)}`
893
- );
894
- }
895
- assertNoControlChars("tableName/tableRef", trimmed);
896
- for (let i2 = 0; i2 < trimmed.length; i2++) {
897
- const c = trimmed.charCodeAt(i2);
923
+ function validateQualifiedNameFormat(trimmed) {
924
+ for (let i = 0; i < trimmed.length; i++) {
925
+ const c = trimmed.charCodeAt(i);
898
926
  if (c === 9 || c === 11 || c === 12 || c === 32) {
899
927
  throw new Error(
900
928
  `tableName/tableRef must not contain whitespace: ${JSON.stringify(trimmed)}`
@@ -911,6 +939,8 @@ function assertSafeQualifiedName(input) {
911
939
  );
912
940
  }
913
941
  }
942
+ }
943
+ function parseQualifiedNameParts(trimmed) {
914
944
  let i = 0;
915
945
  const n = trimmed.length;
916
946
  let parts = 0;
@@ -946,7 +976,22 @@ function assertSafeQualifiedName(input) {
946
976
  }
947
977
  }
948
978
  }
949
- function quote2(id) {
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
+ }
994
+ function quote(id) {
950
995
  if (isEmptyString(id)) {
951
996
  throw new Error("quote: identifier is required and cannot be empty");
952
997
  }
@@ -966,13 +1011,13 @@ function resolveColumnName(model, fieldName) {
966
1011
  return columnMap.get(fieldName) || fieldName;
967
1012
  }
968
1013
  function quoteColumn(model, fieldName) {
969
- if (!model) return quote2(fieldName);
1014
+ if (!model) return quote(fieldName);
970
1015
  const cached = getQuotedColumn(model, fieldName);
971
- return cached || quote2(fieldName);
1016
+ return cached || quote(fieldName);
972
1017
  }
973
1018
  function col(alias, field, model) {
974
1019
  const columnName = model ? getColumnMap(model).get(field) || field : field;
975
- const quoted = model ? getQuotedColumn(model, field) || quote2(columnName) : quote2(columnName);
1020
+ const quoted = model ? getQuotedColumn(model, field) || quote(columnName) : quote(columnName);
976
1021
  return alias + "." + quoted;
977
1022
  }
978
1023
  function colWithAlias(alias, field, model) {
@@ -983,9 +1028,9 @@ function colWithAlias(alias, field, model) {
983
1028
  throw new Error("colWithAlias: field is required and cannot be empty");
984
1029
  }
985
1030
  const columnName = resolveColumnName(model, field);
986
- const columnRef = alias + "." + (model ? getQuotedColumn(model, field) || quote2(columnName) : quote2(columnName));
1031
+ const columnRef = alias + "." + (model ? getQuotedColumn(model, field) || quote(columnName) : quote(columnName));
987
1032
  if (columnName !== field) {
988
- return columnRef + " AS " + quote2(field);
1033
+ return columnRef + " AS " + quote(field);
989
1034
  }
990
1035
  return columnRef;
991
1036
  }
@@ -1008,7 +1053,7 @@ function buildTableReference(schemaName, tableName, dialect) {
1008
1053
  }
1009
1054
  const d = dialect != null ? dialect : "postgres";
1010
1055
  if (d === "sqlite") {
1011
- return quote2(tableName);
1056
+ return quote(tableName);
1012
1057
  }
1013
1058
  if (isEmptyString(schemaName)) {
1014
1059
  throw new Error(
@@ -1035,7 +1080,7 @@ function assertSafeAlias(alias) {
1035
1080
  if (a !== alias) {
1036
1081
  throw new Error("Invalid alias: leading/trailing whitespace");
1037
1082
  }
1038
- if (/[\u0000-\u001F\u007F]/.test(a)) {
1083
+ if (containsControlChars(a)) {
1039
1084
  throw new Error(
1040
1085
  "Invalid alias: contains unsafe characters (control characters)"
1041
1086
  );
@@ -1056,7 +1101,7 @@ function assertSafeAlias(alias) {
1056
1101
  "Invalid alias: must be a simple identifier without whitespace"
1057
1102
  );
1058
1103
  }
1059
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(a)) {
1104
+ if (!/^[A-Za-z_]\w*$/.test(a)) {
1060
1105
  throw new Error(
1061
1106
  `Invalid alias: must be a simple identifier (alphanumeric with underscores): "${alias}"`
1062
1107
  );
@@ -1104,8 +1149,7 @@ function isValidRelationField(field) {
1104
1149
  return false;
1105
1150
  const fk = normalizeKeyList(field.foreignKey);
1106
1151
  if (fk.length === 0) return false;
1107
- const refsRaw = field.references;
1108
- const refs = normalizeKeyList(refsRaw);
1152
+ const refs = normalizeKeyList(field.references);
1109
1153
  if (refs.length === 0) {
1110
1154
  return fk.length === 1;
1111
1155
  }
@@ -1113,8 +1157,7 @@ function isValidRelationField(field) {
1113
1157
  return true;
1114
1158
  }
1115
1159
  function getReferenceFieldNames(field, foreignKeyCount) {
1116
- const refsRaw = field.references;
1117
- const refs = normalizeKeyList(refsRaw);
1160
+ const refs = normalizeKeyList(field.references);
1118
1161
  if (refs.length === 0) {
1119
1162
  if (foreignKeyCount === 1) return [SPECIAL_FIELDS.ID];
1120
1163
  return [];
@@ -1275,29 +1318,35 @@ function normalizeOrderByInput(orderBy, parseValue) {
1275
1318
  }
1276
1319
 
1277
1320
  // src/builder/shared/order-by-determinism.ts
1278
- function modelHasScalarId(model) {
1279
- if (!model) return false;
1280
- 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;
1281
1329
  }
1282
- function hasIdTiebreaker(orderBy, parse) {
1330
+ function hasTiebreaker(orderBy, parse, field) {
1283
1331
  if (!isNotNullish(orderBy)) return false;
1284
1332
  const normalized = normalizeOrderByInput(orderBy, parse);
1285
1333
  return normalized.some(
1286
- (obj) => Object.prototype.hasOwnProperty.call(obj, "id")
1334
+ (obj) => Object.prototype.hasOwnProperty.call(obj, field)
1287
1335
  );
1288
1336
  }
1289
- function addIdTiebreaker(orderBy) {
1290
- if (Array.isArray(orderBy)) return [...orderBy, { id: "asc" }];
1291
- return [orderBy, { id: "asc" }];
1337
+ function addTiebreaker(orderBy, field) {
1338
+ if (Array.isArray(orderBy)) return [...orderBy, { [field]: "asc" }];
1339
+ return [orderBy, { [field]: "asc" }];
1292
1340
  }
1293
1341
  function ensureDeterministicOrderByInput(args) {
1294
1342
  const { orderBy, model, parseValue } = args;
1295
- if (!modelHasScalarId(model)) return orderBy;
1343
+ const tiebreaker = findTiebreakerField(model);
1344
+ if (!tiebreaker) return orderBy;
1296
1345
  if (!isNotNullish(orderBy)) {
1297
- return { id: "asc" };
1346
+ return { [tiebreaker]: "asc" };
1298
1347
  }
1299
- if (hasIdTiebreaker(orderBy, parseValue)) return orderBy;
1300
- return addIdTiebreaker(orderBy);
1348
+ if (hasTiebreaker(orderBy, parseValue, tiebreaker)) return orderBy;
1349
+ return addTiebreaker(orderBy, tiebreaker);
1301
1350
  }
1302
1351
 
1303
1352
  // src/builder/shared/validators/field-assertions.ts
@@ -1444,11 +1493,9 @@ function defaultNullsFor(dialect, direction) {
1444
1493
  function ensureCursorFieldsInOrder(orderEntries, cursorEntries) {
1445
1494
  if (cursorEntries.length === 0) return orderEntries;
1446
1495
  const existing = /* @__PURE__ */ new Set();
1447
- for (let i = 0; i < orderEntries.length; i++)
1448
- existing.add(orderEntries[i].field);
1496
+ for (const entry of orderEntries) existing.add(entry.field);
1449
1497
  let out = null;
1450
- for (let i = 0; i < cursorEntries.length; i++) {
1451
- const field = cursorEntries[i][0];
1498
+ for (const [field] of cursorEntries) {
1452
1499
  if (!existing.has(field)) {
1453
1500
  if (!out) out = orderEntries.slice();
1454
1501
  out.push({ field, direction: "asc" });
@@ -1673,6 +1720,7 @@ function buildOrderEntries(orderBy) {
1673
1720
  }
1674
1721
  return entries;
1675
1722
  }
1723
+ var MAX_NOT_DEPTH = 50;
1676
1724
  function buildNotComposite(expr, val, params, dialect, buildOp, separator) {
1677
1725
  const entries = Object.entries(val).filter(
1678
1726
  ([k, v]) => k !== "mode" && v !== void 0
@@ -1687,45 +1735,71 @@ function buildNotComposite(expr, val, params, dialect, buildOp, separator) {
1687
1735
  if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
1688
1736
  return `${SQL_TEMPLATES.NOT} (${clauses.join(separator)})`;
1689
1737
  }
1690
- function buildScalarOperator(expr, op, val, params, mode, fieldType, dialect) {
1691
- if (val === void 0) return "";
1692
- if (val === null) {
1693
- return handleNullValue(expr, op);
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 });
1694
1746
  }
1695
- if (op === Ops.NOT && isPlainObject(val)) {
1696
- return handleNotOperator(expr, val, params, mode, fieldType, dialect);
1747
+ if ((op === Ops.IN || op === Ops.NOT_IN) && !isNotNullish(dialect)) {
1748
+ throw createError(`IN operators require a SQL dialect`, { operator: op });
1697
1749
  }
1698
- if (op === Ops.NOT) {
1699
- const placeholder = params.addAuto(val);
1700
- return `${expr} <> ${placeholder}`;
1701
- }
1702
- if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && isNotNullish(dialect)) {
1703
- const placeholder = params.addAuto(val);
1704
- return caseInsensitiveEquals(expr, placeholder);
1750
+ if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && !isNotNullish(dialect)) {
1751
+ throw createError(`Insensitive equals requires a SQL dialect`, {
1752
+ operator: op
1753
+ });
1705
1754
  }
1755
+ }
1756
+ function routeOperatorHandler(expr, op, val, params, mode, dialect) {
1706
1757
  const STRING_LIKE_OPS = /* @__PURE__ */ new Set([
1707
1758
  Ops.CONTAINS,
1708
1759
  Ops.STARTS_WITH,
1709
1760
  Ops.ENDS_WITH
1710
1761
  ]);
1711
1762
  if (STRING_LIKE_OPS.has(op)) {
1712
- if (!isNotNullish(dialect)) {
1713
- throw createError(`Like operators require a SQL dialect`, {
1714
- operator: op
1715
- });
1716
- }
1717
1763
  return handleLikeOperator(expr, op, val, params, mode, dialect);
1718
1764
  }
1719
1765
  if (op === Ops.IN || op === Ops.NOT_IN) {
1720
- if (!isNotNullish(dialect)) {
1721
- throw createError(`IN operators require a SQL dialect`, { operator: op });
1722
- }
1723
1766
  return handleInOperator(expr, op, val, params, dialect);
1724
1767
  }
1725
- if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && !isNotNullish(dialect)) {
1726
- throw createError(`Insensitive equals requires a SQL dialect`, {
1727
- operator: op
1728
- });
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;
1776
+ if (val === void 0) return "";
1777
+ if (depth > MAX_NOT_DEPTH) {
1778
+ throw new Error(
1779
+ `NOT operator nesting too deep (max ${MAX_NOT_DEPTH} levels). This usually indicates a circular reference or adversarial input.`
1780
+ );
1781
+ }
1782
+ if (val === null) {
1783
+ return handleNullValue(expr, op);
1784
+ }
1785
+ if (op === Ops.NOT && isPlainObject(val)) {
1786
+ return handleNotOperator(expr, val, params, mode, fieldType, dialect, depth);
1787
+ }
1788
+ if (op === Ops.NOT) {
1789
+ const placeholder = params.addAuto(val);
1790
+ return `${expr} <> ${placeholder}`;
1791
+ }
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;
1729
1803
  }
1730
1804
  return handleComparisonOperator(expr, op, val, params);
1731
1805
  }
@@ -1739,7 +1813,7 @@ function normalizeMode(v) {
1739
1813
  if (v === Modes.DEFAULT) return Modes.DEFAULT;
1740
1814
  return void 0;
1741
1815
  }
1742
- function handleNotOperator(expr, val, params, outerMode, fieldType, dialect) {
1816
+ function handleNotOperator(expr, val, params, outerMode, fieldType, dialect, depth = 0) {
1743
1817
  const innerMode = normalizeMode(val.mode);
1744
1818
  const effectiveMode = innerMode != null ? innerMode : outerMode;
1745
1819
  const entries = Object.entries(val).filter(
@@ -1749,43 +1823,35 @@ function handleNotOperator(expr, val, params, outerMode, fieldType, dialect) {
1749
1823
  if (!isNotNullish(dialect)) {
1750
1824
  const clauses = [];
1751
1825
  for (const [subOp, subVal] of entries) {
1752
- const sub = buildScalarOperator(
1753
- expr,
1754
- subOp,
1755
- subVal,
1756
- params,
1757
- effectiveMode,
1826
+ const sub = buildScalarOperator(expr, subOp, subVal, params, {
1827
+ mode: effectiveMode,
1758
1828
  fieldType,
1759
- void 0
1760
- );
1829
+ dialect: void 0,
1830
+ depth: depth + 1
1831
+ });
1761
1832
  if (sub && sub.trim().length > 0) clauses.push(`(${sub})`);
1762
1833
  }
1763
1834
  if (clauses.length === 0) return "";
1764
1835
  if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
1765
- return `${SQL_TEMPLATES.NOT} (${clauses.join(` ${SQL_TEMPLATES.AND} `)})`;
1836
+ const separator2 = ` ${SQL_TEMPLATES.AND} `;
1837
+ return `${SQL_TEMPLATES.NOT} (${clauses.join(separator2)})`;
1766
1838
  }
1839
+ const separator = ` ${SQL_TEMPLATES.AND} `;
1767
1840
  return buildNotComposite(
1768
1841
  expr,
1769
1842
  val,
1770
1843
  params,
1771
1844
  dialect,
1772
- (e, subOp, subVal, p, d) => buildScalarOperator(e, subOp, subVal, p, effectiveMode, fieldType, d),
1773
- ` ${SQL_TEMPLATES.AND} `
1845
+ (e, subOp, subVal, p, d) => buildScalarOperator(e, subOp, subVal, p, {
1846
+ mode: effectiveMode,
1847
+ fieldType,
1848
+ dialect: d,
1849
+ depth: depth + 1
1850
+ }),
1851
+ separator
1774
1852
  );
1775
1853
  }
1776
- function buildDynamicLikePattern(op, placeholder, dialect) {
1777
- if (dialect === "postgres") {
1778
- switch (op) {
1779
- case Ops.CONTAINS:
1780
- return `('%' || ${placeholder} || '%')`;
1781
- case Ops.STARTS_WITH:
1782
- return `(${placeholder} || '%')`;
1783
- case Ops.ENDS_WITH:
1784
- return `('%' || ${placeholder})`;
1785
- default:
1786
- return placeholder;
1787
- }
1788
- }
1854
+ function buildDynamicLikePattern(op, placeholder) {
1789
1855
  switch (op) {
1790
1856
  case Ops.CONTAINS:
1791
1857
  return `('%' || ${placeholder} || '%')`;
@@ -1801,7 +1867,7 @@ function handleLikeOperator(expr, op, val, params, mode, dialect) {
1801
1867
  if (val === void 0) return "";
1802
1868
  if (schemaParser.isDynamicParameter(val)) {
1803
1869
  const placeholder2 = params.addAuto(val);
1804
- const patternExpr = buildDynamicLikePattern(op, placeholder2, dialect);
1870
+ const patternExpr = buildDynamicLikePattern(op, placeholder2);
1805
1871
  if (mode === Modes.INSENSITIVE) {
1806
1872
  return caseInsensitiveLike(expr, patternExpr, dialect);
1807
1873
  }
@@ -1979,24 +2045,42 @@ function handleArrayIsEmpty(expr, val, dialect) {
1979
2045
  // src/builder/where/operators-json.ts
1980
2046
  var SAFE_JSON_PATH_SEGMENT = /^[a-zA-Z_]\w*$/;
1981
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
+ }
1982
2061
  function validateJsonPathSegments(segments) {
1983
- 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];
1984
2069
  if (typeof segment !== "string") {
1985
- throw createError("JSON path segments must be strings", {
1986
- operator: Ops.PATH,
1987
- value: segment
2070
+ throw createError(`JSON path segment at index ${i} must be string`, {
2071
+ operator: Ops.PATH
1988
2072
  });
1989
2073
  }
1990
2074
  if (segment.length > MAX_PATH_SEGMENT_LENGTH) {
1991
2075
  throw createError(
1992
- `JSON path segment too long: max ${MAX_PATH_SEGMENT_LENGTH} characters`,
1993
- { 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 }
1994
2078
  );
1995
2079
  }
1996
2080
  if (!SAFE_JSON_PATH_SEGMENT.test(segment)) {
1997
2081
  throw createError(
1998
- `Invalid JSON path segment: '${segment}'. Must be alphanumeric with underscores, starting with letter or underscore.`,
1999
- { operator: Ops.PATH, value: segment }
2082
+ `Invalid JSON path segment at index ${i}: '${sanitizeForError(segment)}'`,
2083
+ { operator: Ops.PATH }
2000
2084
  );
2001
2085
  }
2002
2086
  }
@@ -2087,7 +2171,7 @@ function freezeJoins(items) {
2087
2171
  function isListRelation(fieldType) {
2088
2172
  return typeof fieldType === "string" && fieldType.endsWith("[]");
2089
2173
  }
2090
- function buildToOneNullCheck(field, parentAlias, relTable, relAlias, join3, wantNull) {
2174
+ function buildToOneNullCheck(field, parentModel, parentAlias, relTable, relAlias, join3, wantNull) {
2091
2175
  const isLocal = field.isForeignKeyLocal === true;
2092
2176
  const fkFields = normalizeKeyList(field.foreignKey);
2093
2177
  if (isLocal) {
@@ -2097,8 +2181,7 @@ function buildToOneNullCheck(field, parentAlias, relTable, relAlias, join3, want
2097
2181
  });
2098
2182
  }
2099
2183
  const parts = fkFields.map((fk) => {
2100
- const safe = fk.replace(/"/g, '""');
2101
- const expr = `${parentAlias}."${safe}"`;
2184
+ const expr = `${parentAlias}.${quoteColumn(parentModel, fk)}`;
2102
2185
  return wantNull ? `${expr} ${SQL_TEMPLATES.IS_NULL}` : `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
2103
2186
  });
2104
2187
  if (parts.length === 1) return parts[0];
@@ -2115,6 +2198,35 @@ function buildToOneNotExistsMatch(relTable, relAlias, join3, sub) {
2115
2198
  const joins = sub.joins.length > 0 ? ` ${sub.joins.join(" ")}` : "";
2116
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})`;
2117
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
+ }
2118
2230
  function buildListRelationFilters(args) {
2119
2231
  const {
2120
2232
  fieldName,
@@ -2135,21 +2247,16 @@ function buildListRelationFilters(args) {
2135
2247
  isSubquery: true,
2136
2248
  depth: ctx.depth + 1
2137
2249
  }));
2138
- const isEmptyFilter = isPlainObject(noneValue) && Object.keys(noneValue).length === 0;
2139
- const canOptimize = !ctx.isSubquery && isEmptyFilter && sub.clause === DEFAULT_WHERE_CLAUSE && sub.joins.length === 0;
2140
- if (canOptimize) {
2141
- const checkField = relModel.fields.find(
2142
- (f) => !f.isRelation && f.isRequired && f.name !== "id"
2143
- ) || relModel.fields.find((f) => !f.isRelation && f.name === "id");
2144
- if (checkField) {
2145
- const leftJoinSql = `LEFT JOIN ${relTable} ${relAlias} ON ${join3}`;
2146
- const whereClause = `${relAlias}.${quote2(checkField.name)} IS NULL`;
2147
- return Object.freeze({
2148
- clause: whereClause,
2149
- joins: freezeJoins([leftJoinSql])
2150
- });
2151
- }
2152
- }
2250
+ const optimized = tryOptimizeNoneFilter(
2251
+ noneValue,
2252
+ ctx,
2253
+ relModel,
2254
+ relTable,
2255
+ relAlias,
2256
+ join3,
2257
+ sub
2258
+ );
2259
+ if (optimized) return optimized;
2153
2260
  }
2154
2261
  const filters = [
2155
2262
  {
@@ -2170,17 +2277,8 @@ function buildListRelationFilters(args) {
2170
2277
  ];
2171
2278
  const clauses = [];
2172
2279
  for (const { key, wrap } of filters) {
2173
- const raw = value[key];
2174
- if (raw === void 0 || raw === null) continue;
2175
- const sub = whereBuilder.build(raw, __spreadProps(__spreadValues({}, ctx), {
2176
- alias: relAlias,
2177
- model: relModel,
2178
- path: [...ctx.path, fieldName, key],
2179
- isSubquery: true,
2180
- depth: ctx.depth + 1
2181
- }));
2182
- const j = sub.joins.length > 0 ? ` ${sub.joins.join(" ")}` : "";
2183
- clauses.push(wrap(sub.clause, j));
2280
+ const clause = processRelationFilter(key, wrap, args);
2281
+ if (clause) clauses.push(clause);
2184
2282
  }
2185
2283
  if (clauses.length === 0) {
2186
2284
  throw createError(
@@ -2236,6 +2334,7 @@ function buildToOneRelationFilters(args) {
2236
2334
  const wantNull = filterKey === "is";
2237
2335
  const clause2 = buildToOneNullCheck(
2238
2336
  field,
2337
+ ctx.model,
2239
2338
  ctx.alias,
2240
2339
  relTable,
2241
2340
  relAlias,
@@ -2465,7 +2564,8 @@ function buildWhereInternal(where, ctx, builder) {
2465
2564
  }
2466
2565
  const allJoins = [];
2467
2566
  const clauses = [];
2468
- for (const [key, value] of Object.entries(where)) {
2567
+ for (const key in where) {
2568
+ const value = where[key];
2469
2569
  if (value === void 0) continue;
2470
2570
  const result = buildWhereEntry(key, value, ctx, builder);
2471
2571
  appendResult(result, clauses, allJoins);
@@ -2580,15 +2680,11 @@ function buildOperator(expr, op, val, ctx, mode, fieldType) {
2580
2680
  if (fieldType && isJsonType(fieldType) && JSON_OPS.has(op)) {
2581
2681
  return buildJsonOperator(expr, op, val, ctx.params, ctx.dialect);
2582
2682
  }
2583
- return buildScalarOperator(
2584
- expr,
2585
- op,
2586
- val,
2587
- ctx.params,
2683
+ return buildScalarOperator(expr, op, val, ctx.params, {
2588
2684
  mode,
2589
2685
  fieldType,
2590
- ctx.dialect
2591
- );
2686
+ dialect: ctx.dialect
2687
+ });
2592
2688
  }
2593
2689
 
2594
2690
  // src/builder/shared/alias-generator.ts
@@ -2702,49 +2798,50 @@ function validateState(params, mappings, index) {
2702
2798
  validateMappings(mappings);
2703
2799
  assertNextIndexMatches(mappings.length, index);
2704
2800
  }
2705
- function createStoreInternal(startIndex, initialParams = [], initialMappings = []) {
2706
- let index = startIndex;
2707
- const params = initialParams.length > 0 ? initialParams.slice() : [];
2708
- const mappings = initialMappings.length > 0 ? initialMappings.slice() : [];
2801
+ function buildDynamicNameIndex(mappings) {
2709
2802
  const dynamicNameToIndex = /* @__PURE__ */ new Map();
2710
- for (let i = 0; i < mappings.length; i++) {
2711
- const m = mappings[i];
2803
+ for (const m of mappings) {
2712
2804
  if (typeof m.dynamicName === "string") {
2713
2805
  dynamicNameToIndex.set(m.dynamicName.trim(), m.index);
2714
2806
  }
2715
2807
  }
2716
- let dirty = true;
2717
- let cachedSnapshot = null;
2718
- let frozenParams = null;
2719
- let frozenMappings = null;
2720
- function assertCanAdd() {
2721
- if (index > MAX_PARAM_INDEX) {
2722
- throw new Error(
2723
- `CRITICAL: Cannot add param - would overflow MAX_SAFE_INTEGER. Current index: ${index}`
2724
- );
2725
- }
2726
- }
2727
- function format(position) {
2728
- 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
+ );
2729
2815
  }
2730
- function normalizeDynamicName(dynamicName) {
2731
- const dn = dynamicName.trim();
2732
- if (dn.length === 0) {
2733
- throw new Error("CRITICAL: dynamicName cannot be empty");
2734
- }
2735
- 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");
2736
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;
2737
2834
  function addDynamic(dynamicName) {
2738
- const dn = normalizeDynamicName(dynamicName);
2835
+ const dn = validateDynamicName(dynamicName);
2739
2836
  const existing = dynamicNameToIndex.get(dn);
2740
- if (existing !== void 0) return format(existing);
2837
+ if (existing !== void 0) return formatPosition(existing);
2741
2838
  const position = index;
2742
2839
  dynamicNameToIndex.set(dn, position);
2743
2840
  params.push(void 0);
2744
2841
  mappings.push({ index: position, dynamicName: dn });
2745
2842
  index++;
2746
2843
  dirty = true;
2747
- return format(position);
2844
+ return formatPosition(position);
2748
2845
  }
2749
2846
  function addStatic(value) {
2750
2847
  const position = index;
@@ -2753,10 +2850,10 @@ function createStoreInternal(startIndex, initialParams = [], initialMappings = [
2753
2850
  mappings.push({ index: position, value: normalizedValue });
2754
2851
  index++;
2755
2852
  dirty = true;
2756
- return format(position);
2853
+ return formatPosition(position);
2757
2854
  }
2758
2855
  function add(value, dynamicName) {
2759
- assertCanAdd();
2856
+ assertCanAddParam(index);
2760
2857
  return dynamicName === void 0 ? addStatic(value) : addDynamic(dynamicName);
2761
2858
  }
2762
2859
  function addAuto(value) {
@@ -2768,17 +2865,13 @@ function createStoreInternal(startIndex, initialParams = [], initialMappings = [
2768
2865
  }
2769
2866
  function snapshot() {
2770
2867
  if (!dirty && cachedSnapshot) return cachedSnapshot;
2771
- if (!frozenParams) frozenParams = Object.freeze(params.slice());
2772
- if (!frozenMappings) frozenMappings = Object.freeze(mappings.slice());
2773
2868
  const snap = {
2774
2869
  index,
2775
- params: frozenParams,
2776
- mappings: frozenMappings
2870
+ params: params.slice(),
2871
+ mappings: mappings.slice()
2777
2872
  };
2778
2873
  cachedSnapshot = snap;
2779
2874
  dirty = false;
2780
- frozenParams = null;
2781
- frozenMappings = null;
2782
2875
  return snap;
2783
2876
  }
2784
2877
  return {
@@ -2981,6 +3074,17 @@ function buildRelationSelect(relArgs, relModel, relAlias) {
2981
3074
  var MAX_INCLUDE_DEPTH = 10;
2982
3075
  var MAX_TOTAL_SUBQUERIES = 100;
2983
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
+ }
2984
3088
  function getRelationTableReference(relModel, dialect) {
2985
3089
  return buildTableReference(
2986
3090
  SQL_TEMPLATES.PUBLIC_SCHEMA,
@@ -3124,19 +3228,11 @@ function finalizeOrderByForInclude(args) {
3124
3228
  }
3125
3229
  function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
3126
3230
  let relSelect = buildRelationSelect(relArgs, relModel, relAlias);
3127
- const nestedIncludes = isPlainObject(relArgs) ? buildIncludeSqlInternal(
3128
- relArgs,
3129
- relModel,
3130
- ctx.schemas,
3131
- ctx.schemaByName,
3132
- relAlias,
3133
- ctx.aliasGen,
3134
- ctx.params,
3135
- ctx.dialect,
3136
- ctx.visitPath || [],
3137
- (ctx.depth || 0) + 1,
3138
- ctx.stats
3139
- ) : [];
3231
+ const nestedIncludes = isPlainObject(relArgs) ? buildIncludeSqlInternal(relArgs, __spreadProps(__spreadValues({}, ctx), {
3232
+ model: relModel,
3233
+ parentAlias: relAlias,
3234
+ depth: (ctx.depth || 0) + 1
3235
+ })) : [];
3140
3236
  if (isNonEmptyArray(nestedIncludes)) {
3141
3237
  const emptyJson = ctx.dialect === "postgres" ? `'[]'::json` : `json('[]')`;
3142
3238
  const nestedSelects = nestedIncludes.map(
@@ -3191,6 +3287,7 @@ function buildOneToOneIncludeSql(args) {
3191
3287
  whereClause: args.whereClause
3192
3288
  });
3193
3289
  if (args.orderBySql) sql += ` ${SQL_TEMPLATES.ORDER_BY} ${args.orderBySql}`;
3290
+ const scopeBase = buildIncludeScope(args.ctx.includePath);
3194
3291
  if (isNotNullish(args.takeVal)) {
3195
3292
  return appendLimitOffset(
3196
3293
  sql,
@@ -3198,15 +3295,10 @@ function buildOneToOneIncludeSql(args) {
3198
3295
  args.ctx.params,
3199
3296
  args.takeVal,
3200
3297
  args.skipVal,
3201
- `include.${args.relName}`
3298
+ scopeBase
3202
3299
  );
3203
3300
  }
3204
- return limitOneSql(
3205
- sql,
3206
- args.ctx.params,
3207
- args.skipVal,
3208
- `include.${args.relName}`
3209
- );
3301
+ return limitOneSql(sql, args.ctx.params, args.skipVal, scopeBase);
3210
3302
  }
3211
3303
  function buildListIncludeSpec(args) {
3212
3304
  const rowExpr = jsonBuildObject(args.relSelect, args.ctx.dialect);
@@ -3234,13 +3326,14 @@ function buildListIncludeSpec(args) {
3234
3326
  whereClause: args.whereClause
3235
3327
  });
3236
3328
  if (args.orderBySql) base += ` ${SQL_TEMPLATES.ORDER_BY} ${args.orderBySql}`;
3329
+ const scopeBase = buildIncludeScope(args.ctx.includePath);
3237
3330
  base = appendLimitOffset(
3238
3331
  base,
3239
3332
  args.ctx.dialect,
3240
3333
  args.ctx.params,
3241
3334
  args.takeVal,
3242
3335
  args.skipVal,
3243
- `include.${args.relName}`
3336
+ scopeBase
3244
3337
  );
3245
3338
  const selectExpr = jsonAgg("row", args.ctx.dialect);
3246
3339
  const sql = `${SQL_TEMPLATES.SELECT} ${selectExpr} ${SQL_TEMPLATES.FROM} (${base}) ${SQL_TEMPLATES.AS} ${rowAlias}`;
@@ -3289,7 +3382,6 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3289
3382
  );
3290
3383
  if (!isList) {
3291
3384
  const sql = buildOneToOneIncludeSql({
3292
- relName,
3293
3385
  relTable,
3294
3386
  relAlias,
3295
3387
  joins: whereParts.joins,
@@ -3317,8 +3409,14 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3317
3409
  ctx
3318
3410
  });
3319
3411
  }
3320
- function buildIncludeSqlInternal(args, model, schemas, schemaByName, parentAlias, aliasGen, params, dialect, visitPath = [], depth = 0, stats) {
3321
- 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 || [];
3322
3420
  if (depth > MAX_INCLUDE_DEPTH) {
3323
3421
  throw new Error(
3324
3422
  `Maximum include depth of ${MAX_INCLUDE_DEPTH} exceeded. Path: ${visitPath.join(" -> ")}. Deep includes cause exponential SQL complexity and performance issues.`
@@ -3326,7 +3424,7 @@ function buildIncludeSqlInternal(args, model, schemas, schemaByName, parentAlias
3326
3424
  }
3327
3425
  stats.maxDepth = Math.max(stats.maxDepth, depth);
3328
3426
  const includes = [];
3329
- const entries = relationEntriesFromArgs(args, model);
3427
+ const entries = relationEntriesFromArgs(args, ctx.model);
3330
3428
  for (const [relName, relArgs] of entries) {
3331
3429
  if (relArgs === false) continue;
3332
3430
  stats.totalIncludes++;
@@ -3341,8 +3439,12 @@ function buildIncludeSqlInternal(args, model, schemas, schemaByName, parentAlias
3341
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.`
3342
3440
  );
3343
3441
  }
3344
- const resolved = resolveRelationOrThrow(model, schemaByName, relName);
3345
- 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}`;
3346
3448
  const currentPath = [...visitPath, relationPath];
3347
3449
  if (visitPath.includes(relationPath)) {
3348
3450
  throw new Error(
@@ -3357,19 +3459,14 @@ function buildIncludeSqlInternal(args, model, schemas, schemaByName, parentAlias
3357
3459
  `Include too deeply nested: model '${resolved.relModel.name}' appears ${modelOccurrences} times in path: ${currentPath.join(" -> ")}`
3358
3460
  );
3359
3461
  }
3462
+ const nextIncludePath = [...ctx.includePath, relName];
3360
3463
  includes.push(
3361
- buildSingleInclude(relName, relArgs, resolved.field, resolved.relModel, {
3362
- model,
3363
- schemas,
3364
- schemaByName,
3365
- parentAlias,
3366
- aliasGen,
3367
- dialect,
3368
- params,
3464
+ buildSingleInclude(relName, relArgs, resolved.field, resolved.relModel, __spreadProps(__spreadValues({}, ctx), {
3465
+ includePath: nextIncludePath,
3369
3466
  visitPath: currentPath,
3370
3467
  depth: depth + 1,
3371
3468
  stats
3372
- })
3469
+ }))
3373
3470
  );
3374
3471
  }
3375
3472
  return includes;
@@ -3383,8 +3480,7 @@ function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
3383
3480
  };
3384
3481
  const schemaByName = /* @__PURE__ */ new Map();
3385
3482
  for (const m of schemas) schemaByName.set(m.name, m);
3386
- return buildIncludeSqlInternal(
3387
- args,
3483
+ return buildIncludeSqlInternal(args, {
3388
3484
  model,
3389
3485
  schemas,
3390
3486
  schemaByName,
@@ -3392,10 +3488,11 @@ function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
3392
3488
  aliasGen,
3393
3489
  params,
3394
3490
  dialect,
3395
- [],
3396
- 0,
3491
+ includePath: [],
3492
+ visitPath: [],
3493
+ depth: 0,
3397
3494
  stats
3398
- );
3495
+ });
3399
3496
  }
3400
3497
  function resolveCountRelationOrThrow(relName, model, schemaByName) {
3401
3498
  const relationSet = getRelationFieldSet(model);
@@ -3440,8 +3537,7 @@ function resolveCountKeyPairs(field) {
3440
3537
  if (fkFields.length === 0) {
3441
3538
  throw new Error("Relation count requires foreignKey");
3442
3539
  }
3443
- const refsRaw = field.references;
3444
- const refs = normalizeKeyList(refsRaw);
3540
+ const refs = normalizeKeyList(field.references);
3445
3541
  const refFields = refs.length > 0 ? refs : defaultReferencesForCount(fkFields.length);
3446
3542
  if (refFields.length !== fkFields.length) {
3447
3543
  throw new Error(
@@ -3576,139 +3672,146 @@ function finalizeSql(sql, params, dialect) {
3576
3672
  paramMappings: snapshot.mappings
3577
3673
  };
3578
3674
  }
3579
- function parseSimpleScalarSelect(select, fromAlias) {
3580
- const raw = select.trim();
3581
- if (raw.length === 0) return [];
3582
- const fromLower = fromAlias.toLowerCase();
3583
- const parts = raw.split(SQL_SEPARATORS.FIELD_LIST);
3584
- const names = [];
3585
- const isIdent = (s) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(s);
3586
- const readIdentOrQuoted = (s, start) => {
3587
- const n = s.length;
3588
- if (start >= n) return { text: "", next: start, quoted: false };
3589
- if (s.charCodeAt(start) === 34) {
3590
- let i2 = start + 1;
3591
- let out = "";
3592
- let saw = false;
3593
- while (i2 < n) {
3594
- const c = s.charCodeAt(i2);
3595
- if (c === 34) {
3596
- const next = i2 + 1;
3597
- if (next < n && s.charCodeAt(next) === 34) {
3598
- out += '"';
3599
- saw = true;
3600
- i2 += 2;
3601
- continue;
3602
- }
3603
- if (!saw)
3604
- throw new Error(
3605
- `sqlite distinct emulation: empty quoted identifier in: ${s}`
3606
- );
3607
- return { text: out, next: i2 + 1, quoted: true };
3608
- }
3609
- out += s[i2];
3610
- saw = true;
3611
- i2++;
3612
- }
3613
- throw new Error(
3614
- `sqlite distinct emulation: unterminated quoted identifier in: ${s}`
3615
- );
3616
- }
3617
- let i = start;
3618
- while (i < n) {
3619
- const c = s.charCodeAt(i);
3620
- if (c === 32 || c === 9) break;
3621
- if (c === 46) break;
3622
- i++;
3623
- }
3624
- return { text: s.slice(start, i), next: i, quoted: false };
3625
- };
3626
- const skipSpaces = (s, i) => {
3627
- while (i < s.length) {
3628
- const c = s.charCodeAt(i);
3629
- if (c !== 32 && c !== 9) break;
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;
3630
3696
  i++;
3697
+ continue;
3631
3698
  }
3632
- return i;
3633
- };
3634
- for (let idx = 0; idx < parts.length; idx++) {
3635
- const p = parts[idx].trim();
3636
- if (p.length === 0) continue;
3637
- let i = 0;
3638
- i = skipSpaces(p, i);
3639
- const a = readIdentOrQuoted(p, i);
3640
- const actualAlias = a.text.toLowerCase();
3641
- if (!isIdent(a.text)) {
3642
- throw new Error(
3643
- `sqlite distinct emulation requires scalar select fields to be simple columns (alias.column). Got: ${p}`
3644
- );
3645
- }
3646
- if (actualAlias !== fromLower) {
3647
- throw new Error(`Expected alias '${fromAlias}', got '${a.text}' in: ${p}`);
3699
+ const next = i + 1;
3700
+ if (next < n && s.charCodeAt(next) === 34) {
3701
+ out += '"';
3702
+ saw = true;
3703
+ i += 2;
3704
+ continue;
3648
3705
  }
3649
- i = a.next;
3650
- if (i >= p.length || p.charCodeAt(i) !== 46) {
3706
+ if (!saw) {
3651
3707
  throw new Error(
3652
- `sqlite distinct emulation requires scalar select fields to be simple columns (alias.column). Got: ${p}`
3708
+ `sqlite distinct emulation: empty quoted identifier in: ${s}`
3653
3709
  );
3654
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;
3655
3723
  i++;
3656
- i = skipSpaces(p, i);
3657
- const colPart = readIdentOrQuoted(p, i);
3658
- const columnName = colPart.text.trim();
3659
- if (columnName.length === 0) {
3660
- throw new Error(`Failed to parse selected column name from: ${p}`);
3661
- }
3662
- i = colPart.next;
3663
- i = skipSpaces(p, i);
3664
- let outAlias = "";
3665
- if (i < p.length) {
3666
- const rest = p.slice(i).trim();
3667
- if (rest.length > 0) {
3668
- const m = rest.match(/^AS\s+/i);
3669
- if (!m) {
3670
- throw new Error(
3671
- `sqlite distinct emulation requires scalar select fields to be simple columns (optionally with AS). Got: ${p}`
3672
- );
3673
- }
3674
- let j = i;
3675
- j = skipSpaces(p, j);
3676
- if (!/^AS\b/i.test(p.slice(j))) {
3677
- throw new Error(`Failed to parse AS in: ${p}`);
3678
- }
3679
- j += 2;
3680
- j = skipSpaces(p, j);
3681
- const out = readIdentOrQuoted(p, j);
3682
- outAlias = out.text.trim();
3683
- if (outAlias.length === 0) {
3684
- throw new Error(`Failed to parse output alias from: ${p}`);
3685
- }
3686
- j = skipSpaces(p, out.next);
3687
- if (j !== p.length) {
3688
- throw new Error(
3689
- `sqlite distinct emulation requires scalar select fields to be simple columns (optionally with AS). Got: ${p}`
3690
- );
3691
- }
3692
- }
3693
- }
3694
- const name = outAlias.length > 0 ? outAlias : columnName;
3695
- names.push(name);
3696
3724
  }
3697
- return names;
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;
3698
3796
  }
3699
- function replaceOrderByAlias(orderBy, fromAlias, outerAlias) {
3700
- const src = String(fromAlias);
3701
- if (src.length === 0) return orderBy;
3702
- const escaped = src.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3703
- const re = new RegExp(`\\b${escaped}\\.`, "gi");
3704
- return orderBy.replace(re, outerAlias + ".");
3797
+ function parseSimpleScalarSelect(select, fromAlias) {
3798
+ const raw = select.trim();
3799
+ if (raw.length === 0) return [];
3800
+ const parts = raw.split(SQL_SEPARATORS.FIELD_LIST);
3801
+ const names = [];
3802
+ for (const p of parts) {
3803
+ const trimmed = p.trim();
3804
+ if (trimmed.length === 0) continue;
3805
+ names.push(parseSelectField(trimmed, fromAlias));
3806
+ }
3807
+ return names;
3705
3808
  }
3706
3809
  function buildDistinctColumns(distinct, fromAlias, model) {
3707
3810
  return distinct.map((f) => col(fromAlias, f, model)).join(SQL_SEPARATORS.FIELD_LIST);
3708
3811
  }
3709
3812
  function buildOutputColumns(scalarNames, includeNames, hasCount) {
3710
3813
  const outputCols = hasCount ? [...scalarNames, ...includeNames, "_count"] : [...scalarNames, ...includeNames];
3711
- const formatted = outputCols.map((n) => quote2(n)).join(SQL_SEPARATORS.FIELD_LIST);
3814
+ const formatted = outputCols.map((n) => quote(n)).join(SQL_SEPARATORS.FIELD_LIST);
3712
3815
  if (!isNonEmptyString(formatted)) {
3713
3816
  throw new Error("distinct emulation requires at least one output column");
3714
3817
  }
@@ -3725,6 +3828,52 @@ function buildWindowOrder(args) {
3725
3828
  const idTiebreaker = idField ? ", " + col(fromAlias, "id", model) + " ASC" : "";
3726
3829
  return baseOrder + idTiebreaker;
3727
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
+ }
3728
3877
  function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
3729
3878
  var _a, _b;
3730
3879
  const { includes, from, whereClause, whereJoins, orderBy, distinct, model } = spec;
@@ -3751,7 +3900,8 @@ function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
3751
3900
  fromAlias: from.alias,
3752
3901
  model
3753
3902
  });
3754
- 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"');
3755
3905
  const joins = buildJoinsSql(whereJoins, countJoins);
3756
3906
  const conditions = [];
3757
3907
  if (whereClause && whereClause !== "1=1") conditions.push(whereClause);
@@ -3786,17 +3936,33 @@ function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
3786
3936
  }
3787
3937
  return outerParts.join(" ");
3788
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
+ }
3789
3954
  function buildIncludeColumns(spec) {
3790
3955
  var _a, _b;
3791
3956
  const { select, includes, dialect, model, schemas, from, params } = spec;
3792
3957
  const baseSelect = (select != null ? select : "").trim();
3793
3958
  let countCols = "";
3794
3959
  let countJoins = [];
3795
- const countSelect = (_b = (_a = spec.args) == null ? void 0 : _a.select) == null ? void 0 : _b._count;
3796
- if (countSelect) {
3797
- 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) {
3798
3964
  const countBuild = buildRelationCountSql(
3799
- countSelect.select,
3965
+ resolvedCountSelect,
3800
3966
  model,
3801
3967
  schemas,
3802
3968
  from.alias,
@@ -3804,7 +3970,7 @@ function buildIncludeColumns(spec) {
3804
3970
  dialect
3805
3971
  );
3806
3972
  if (countBuild.jsonPairs) {
3807
- countCols = jsonBuildObject(countBuild.jsonPairs, dialect) + " " + SQL_TEMPLATES.AS + " " + quote2("_count");
3973
+ countCols = jsonBuildObject(countBuild.jsonPairs, dialect) + " " + SQL_TEMPLATES.AS + " " + quote("_count");
3808
3974
  }
3809
3975
  countJoins = countBuild.joins;
3810
3976
  }
@@ -3817,7 +3983,7 @@ function buildIncludeColumns(spec) {
3817
3983
  const emptyJson = dialect === "postgres" ? `'[]'::json` : `json('[]')`;
3818
3984
  const includeCols = hasIncludes ? includes.map((inc) => {
3819
3985
  const expr = inc.isOneToOne ? "(" + inc.sql + ")" : "COALESCE((" + inc.sql + "), " + emptyJson + ")";
3820
- return expr + " " + SQL_TEMPLATES.AS + " " + quote2(inc.name);
3986
+ return expr + " " + SQL_TEMPLATES.AS + " " + quote(inc.name);
3821
3987
  }).join(SQL_SEPARATORS.FIELD_LIST) : "";
3822
3988
  const allCols = joinNonEmpty(
3823
3989
  [includeCols, countCols],
@@ -4182,6 +4348,7 @@ function buildComparisons(expr, filter, params, dialect, builder, excludeKeys =
4182
4348
  }
4183
4349
 
4184
4350
  // src/builder/aggregates.ts
4351
+ var MAX_NOT_DEPTH2 = 50;
4185
4352
  var AGGREGATES = [
4186
4353
  ["_sum", "SUM"],
4187
4354
  ["_avg", "AVG"],
@@ -4296,8 +4463,13 @@ function buildBinaryComparison(expr, op, val, params) {
4296
4463
  const placeholder = addHavingParam(params, op, val);
4297
4464
  return expr + " " + sqlOp + " " + placeholder;
4298
4465
  }
4299
- function buildSimpleComparison(expr, op, val, params, dialect) {
4466
+ function buildSimpleComparison(expr, op, val, params, dialect, depth = 0) {
4300
4467
  assertHavingOp(op);
4468
+ if (depth > MAX_NOT_DEPTH2) {
4469
+ throw new Error(
4470
+ `NOT operator nesting too deep in HAVING (max ${MAX_NOT_DEPTH2} levels).`
4471
+ );
4472
+ }
4301
4473
  if (val === null) return buildNullComparison(expr, op);
4302
4474
  if (op === Ops.NOT && isPlainObject(val)) {
4303
4475
  return buildNotComposite(
@@ -4305,7 +4477,7 @@ function buildSimpleComparison(expr, op, val, params, dialect) {
4305
4477
  val,
4306
4478
  params,
4307
4479
  dialect,
4308
- buildSimpleComparison,
4480
+ (e, subOp, subVal, p, d) => buildSimpleComparison(e, subOp, subVal, p, d, depth + 1),
4309
4481
  SQL_SEPARATORS.CONDITION_AND
4310
4482
  );
4311
4483
  }
@@ -4322,23 +4494,36 @@ function combineLogical(key, subClauses) {
4322
4494
  if (key === LogicalOps.NOT) return negateClauses(subClauses);
4323
4495
  return subClauses.join(" " + key + " ");
4324
4496
  }
4325
- function buildHavingNode(node, alias, params, dialect, model) {
4497
+ function buildHavingNode(node, alias, params, dialect, model, depth = 0) {
4498
+ if (depth > LIMITS.MAX_HAVING_DEPTH) {
4499
+ throw new Error(
4500
+ `HAVING clause nesting too deep (max ${LIMITS.MAX_HAVING_DEPTH} levels). This usually indicates a circular reference.`
4501
+ );
4502
+ }
4326
4503
  const clauses = [];
4327
4504
  for (const key in node) {
4328
4505
  if (!Object.prototype.hasOwnProperty.call(node, key)) continue;
4329
4506
  const value = node[key];
4330
- const built = buildHavingEntry(key, value, alias, params, dialect, model);
4507
+ const built = buildHavingEntry(
4508
+ key,
4509
+ value,
4510
+ alias,
4511
+ params,
4512
+ dialect,
4513
+ model,
4514
+ depth
4515
+ );
4331
4516
  for (const c of built) {
4332
4517
  if (c && c.length > 0) clauses.push(c);
4333
4518
  }
4334
4519
  }
4335
4520
  return clauses.join(SQL_SEPARATORS.CONDITION_AND);
4336
4521
  }
4337
- function buildLogicalClause2(key, value, alias, params, dialect, model) {
4522
+ function buildLogicalClause2(key, value, alias, params, dialect, model, depth = 0) {
4338
4523
  const items = normalizeLogicalValue2(key, value);
4339
4524
  const subClauses = [];
4340
4525
  for (const it of items) {
4341
- const c = buildHavingNode(it, alias, params, dialect, model);
4526
+ const c = buildHavingNode(it, alias, params, dialect, model, depth + 1);
4342
4527
  if (c && c.length > 0) subClauses.push("(" + c + ")");
4343
4528
  }
4344
4529
  if (subClauses.length === 0) return "";
@@ -4397,7 +4582,7 @@ function buildHavingForFieldFirstShape(fieldName, target, alias, params, dialect
4397
4582
  }
4398
4583
  return out;
4399
4584
  }
4400
- function buildHavingEntry(key, value, alias, params, dialect, model) {
4585
+ function buildHavingEntry(key, value, alias, params, dialect, model, depth = 0) {
4401
4586
  if (isLogicalKey(key)) {
4402
4587
  const logical = buildLogicalClause2(
4403
4588
  key,
@@ -4405,7 +4590,8 @@ function buildHavingEntry(key, value, alias, params, dialect, model) {
4405
4590
  alias,
4406
4591
  params,
4407
4592
  dialect,
4408
- model
4593
+ model,
4594
+ depth
4409
4595
  );
4410
4596
  return logical ? [logical] : [];
4411
4597
  }
@@ -4432,7 +4618,7 @@ function buildHavingClause(having, alias, params, model, dialect) {
4432
4618
  if (!isNotNullish(having)) return "";
4433
4619
  const d = dialect != null ? dialect : getGlobalDialect();
4434
4620
  if (!isPlainObject(having)) throw new Error("having must be an object");
4435
- return buildHavingNode(having, alias, params, d, model);
4621
+ return buildHavingNode(having, alias, params, d, model, 0);
4436
4622
  }
4437
4623
  function normalizeCountArg(v) {
4438
4624
  if (!isNotNullish(v)) return void 0;
@@ -4442,13 +4628,13 @@ function normalizeCountArg(v) {
4442
4628
  }
4443
4629
  function pushCountAllField(fields) {
4444
4630
  fields.push(
4445
- SQL_TEMPLATES.COUNT_ALL + " " + SQL_TEMPLATES.AS + " " + quote2("_count._all")
4631
+ SQL_TEMPLATES.COUNT_ALL + " " + SQL_TEMPLATES.AS + " " + quote("_count._all")
4446
4632
  );
4447
4633
  }
4448
4634
  function pushCountField(fields, alias, fieldName, model) {
4449
4635
  const outAlias = "_count." + fieldName;
4450
4636
  fields.push(
4451
- "COUNT(" + col(alias, fieldName, model) + ") " + SQL_TEMPLATES.AS + " " + quote2(outAlias)
4637
+ "COUNT(" + col(alias, fieldName, model) + ") " + SQL_TEMPLATES.AS + " " + quote(outAlias)
4452
4638
  );
4453
4639
  }
4454
4640
  function addCountFields(fields, countArg, alias, model) {
@@ -4485,7 +4671,7 @@ function assertAggregatableScalarField(model, agg, fieldName) {
4485
4671
  function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
4486
4672
  const outAlias = agg + "." + fieldName;
4487
4673
  fields.push(
4488
- aggFn + "(" + col(alias, fieldName, model) + ") " + SQL_TEMPLATES.AS + " " + quote2(outAlias)
4674
+ aggFn + "(" + col(alias, fieldName, model) + ") " + SQL_TEMPLATES.AS + " " + quote(outAlias)
4489
4675
  );
4490
4676
  }
4491
4677
  function addAggregateFields(fields, args, alias, model) {
@@ -4569,7 +4755,7 @@ function buildGroupBySql(args, whereResult, tableName, alias, model, dialect) {
4569
4755
  assertSafeAlias(alias);
4570
4756
  assertSafeTableRef(tableName);
4571
4757
  const byFields = assertGroupByBy(args, model);
4572
- const d = getGlobalDialect();
4758
+ const d = dialect != null ? dialect : getGlobalDialect();
4573
4759
  const params = createParamStore(whereResult.nextParamIndex);
4574
4760
  const { groupFields, selectFields } = buildGroupBySelectParts(
4575
4761
  args,
@@ -4601,38 +4787,38 @@ function buildGroupBySql(args, whereResult, tableName, alias, model, dialect) {
4601
4787
  paramMappings: allMappings
4602
4788
  };
4603
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
+ }
4604
4812
  function buildCountSql(whereResult, tableName, alias, skip, _dialect) {
4605
4813
  assertSafeAlias(alias);
4606
4814
  assertSafeTableRef(tableName);
4607
- if (skip !== void 0 && skip !== null) {
4608
- if (schemaParser.isDynamicParameter(skip)) {
4609
- throw new Error(
4610
- "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."
4611
- );
4612
- }
4613
- if (typeof skip === "string") {
4614
- const s = skip.trim();
4615
- if (s.length > 0) {
4616
- const n = Number(s);
4617
- if (Number.isFinite(n) && Number.isInteger(n) && n > 0) {
4618
- throw new Error(
4619
- "count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
4620
- );
4621
- }
4622
- }
4623
- }
4624
- if (typeof skip === "number" && Number.isFinite(skip) && Number.isInteger(skip) && skip > 0) {
4625
- throw new Error(
4626
- "count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
4627
- );
4628
- }
4629
- }
4815
+ validateSkipParameter(skip);
4630
4816
  const whereClause = isValidWhereClause(whereResult.clause) ? SQL_TEMPLATES.WHERE + " " + whereResult.clause : "";
4631
4817
  const parts = [
4632
4818
  SQL_TEMPLATES.SELECT,
4633
4819
  SQL_TEMPLATES.COUNT_ALL,
4634
4820
  SQL_TEMPLATES.AS,
4635
- quote2("_count._all"),
4821
+ quote("_count._all"),
4636
4822
  SQL_TEMPLATES.FROM,
4637
4823
  tableName,
4638
4824
  alias
@@ -4673,15 +4859,21 @@ function buildSqlResult(args) {
4673
4859
  return buildAggregateSql(processed, whereResult, tableName, alias, modelDef);
4674
4860
  }
4675
4861
  if (method === "groupBy") {
4676
- return buildGroupBySql(processed, whereResult, tableName, alias, modelDef);
4862
+ return buildGroupBySql(
4863
+ processed,
4864
+ whereResult,
4865
+ tableName,
4866
+ alias,
4867
+ modelDef,
4868
+ dialect
4869
+ );
4677
4870
  }
4678
4871
  if (method === "count") {
4679
4872
  return buildCountSql(
4680
4873
  whereResult,
4681
4874
  tableName,
4682
4875
  alias,
4683
- processed.skip
4684
- );
4876
+ processed.skip);
4685
4877
  }
4686
4878
  return buildSelectSql({
4687
4879
  method,
@@ -4721,22 +4913,23 @@ function normalizeSqlAndMappingsForDialect(sql, paramMappings, dialect) {
4721
4913
  }
4722
4914
  function buildParamsFromMappings(mappings) {
4723
4915
  const sorted = [...mappings].sort((a, b) => a.index - b.index);
4724
- return sorted.reduce(
4725
- (acc, m) => {
4726
- if (m.dynamicName !== void 0) {
4727
- acc.dynamicKeys.push(m.dynamicName);
4728
- return acc;
4729
- }
4730
- if (m.value !== void 0) {
4731
- acc.staticParams.push(m.value);
4732
- return acc;
4733
- }
4916
+ const staticParams = [];
4917
+ const dynamicKeys = [];
4918
+ let paramOrder = "";
4919
+ for (const m of sorted) {
4920
+ if (m.dynamicName !== void 0) {
4921
+ dynamicKeys.push(m.dynamicName);
4922
+ paramOrder += "d";
4923
+ } else if (m.value !== void 0) {
4924
+ staticParams.push(m.value);
4925
+ paramOrder += "s";
4926
+ } else {
4734
4927
  throw new Error(
4735
4928
  `CRITICAL: ParamMap ${m.index} has neither dynamicName nor value`
4736
4929
  );
4737
- },
4738
- { staticParams: [], dynamicKeys: [] }
4739
- );
4930
+ }
4931
+ }
4932
+ return { staticParams, dynamicKeys, paramOrder };
4740
4933
  }
4741
4934
  function resolveModelContext(directive) {
4742
4935
  const { model, datamodel } = directive.context;
@@ -4802,12 +4995,13 @@ function finalizeDirective(args) {
4802
4995
  return (_a = m.value) != null ? _a : void 0;
4803
4996
  });
4804
4997
  validateParamConsistencyByDialect(normalizedSql, params, dialect);
4805
- const { staticParams, dynamicKeys } = buildParamsFromMappings(normalizedMappings);
4998
+ const { staticParams, dynamicKeys, paramOrder } = buildParamsFromMappings(normalizedMappings);
4806
4999
  return {
4807
5000
  method: directive.method,
4808
5001
  sql: normalizedSql,
4809
5002
  staticParams,
4810
5003
  dynamicKeys,
5004
+ paramOrder,
4811
5005
  paramMappings: normalizedMappings,
4812
5006
  originalDirective: directive
4813
5007
  };
@@ -4842,6 +5036,257 @@ function generateSQL(directive) {
4842
5036
  dialect
4843
5037
  });
4844
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
4845
5290
  function generateSQL2(directive) {
4846
5291
  return generateSQL(directive);
4847
5292
  }
@@ -4870,6 +5315,57 @@ function extractEnumMappings(datamodel) {
4870
5315
  }
4871
5316
  return { mappings, fieldTypes };
4872
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
+ }
4873
5369
  function generateClient(options) {
4874
5370
  return __async(this, null, function* () {
4875
5371
  const { datamodel, outputDir, config } = options;
@@ -4882,48 +5378,22 @@ function generateClient(options) {
4882
5378
  skipInvalid: config.skipInvalid
4883
5379
  }
4884
5380
  );
4885
- const queries = /* @__PURE__ */ new Map();
4886
- for (const [modelName, result] of directiveResults) {
4887
- if (result.directives.length === 0) continue;
4888
- if (!queries.has(modelName)) {
4889
- queries.set(modelName, /* @__PURE__ */ new Map());
4890
- }
4891
- const modelQueries = queries.get(modelName);
4892
- for (const directive of result.directives) {
4893
- try {
4894
- const method = directive.method;
4895
- const sqlDirective = generateSQL2(directive);
4896
- if (!modelQueries.has(method)) {
4897
- modelQueries.set(method, /* @__PURE__ */ new Map());
4898
- }
4899
- const methodQueriesMap = modelQueries.get(method);
4900
- const queryKey = createQueryKey(directive.query.processed);
4901
- methodQueriesMap.set(queryKey, {
4902
- sql: sqlDirective.sql,
4903
- params: sqlDirective.staticParams,
4904
- dynamicKeys: sqlDirective.dynamicKeys,
4905
- paramMappings: sqlDirective.paramMappings
4906
- });
4907
- } catch (error) {
4908
- if (!config.skipInvalid) throw error;
4909
- }
4910
- }
4911
- }
5381
+ const { queries, skippedCount } = processAllModelDirectives(
5382
+ directiveResults,
5383
+ config
5384
+ );
4912
5385
  const absoluteOutputDir = path.resolve(process.cwd(), outputDir);
4913
5386
  yield promises.mkdir(absoluteOutputDir, { recursive: true });
4914
5387
  const code = generateCode(models, queries, config.dialect, datamodel);
4915
5388
  const outputPath = path.join(absoluteOutputDir, "index.ts");
4916
5389
  yield promises.writeFile(outputPath, code);
4917
- const totalQueries = Array.from(queries.values()).reduce(
4918
- (sum, methodMap) => sum + Array.from(methodMap.values()).reduce(
4919
- (s, queryMap) => s + queryMap.size,
4920
- 0
4921
- ),
4922
- 0
4923
- );
5390
+ const totalQueries = countTotalQueries(queries);
4924
5391
  console.log(
4925
5392
  `\u2713 Generated ${queries.size} models, ${totalQueries} prebaked queries`
4926
5393
  );
5394
+ if (skippedCount > 0) {
5395
+ console.log(`\u26A0 Skipped ${skippedCount} directive(s) due to errors`);
5396
+ }
4927
5397
  console.log(`\u2713 Output: ${outputPath}`);
4928
5398
  });
4929
5399
  }
@@ -4939,15 +5409,13 @@ function createQueryKey(processedQuery) {
4939
5409
  return value;
4940
5410
  });
4941
5411
  }
4942
- function generateCode(models, queries, dialect, datamodel) {
4943
- const cleanModels = models.map((model) => __spreadProps(__spreadValues({}, model), {
4944
- fields: model.fields.filter((f) => f !== void 0 && f !== null)
4945
- }));
4946
- const { mappings, fieldTypes } = extractEnumMappings(datamodel);
5412
+ function generateImports() {
4947
5413
  return `// Generated by @prisma-sql/generator - DO NOT EDIT
4948
5414
  import { buildSQL, buildBatchSql, parseBatchResults, buildBatchCountSql, parseBatchCountResults, createTransactionExecutor, transformQueryResults, type PrismaMethod, type Model, type BatchQuery, type BatchCountQuery, type TransactionQuery, type TransactionOptions } from 'prisma-sql'
4949
-
4950
- class DeferredQuery {
5415
+ import { normalizeValue } from 'prisma-sql/utils/normalize-value'`;
5416
+ }
5417
+ function generateCoreTypes() {
5418
+ return `class DeferredQuery {
4951
5419
  constructor(
4952
5420
  public readonly model: string,
4953
5421
  public readonly method: PrismaMethod,
@@ -4979,22 +5447,21 @@ const ACCELERATED_METHODS = new Set<PrismaMethod>([
4979
5447
  'count',
4980
5448
  'aggregate',
4981
5449
  'groupBy',
4982
- ])
4983
-
4984
- function createBatchProxy(): BatchProxy {
5450
+ ])`;
5451
+ }
5452
+ function generateHelpers() {
5453
+ return `function createBatchProxy(): BatchProxy {
4985
5454
  return new Proxy(
4986
5455
  {},
4987
5456
  {
4988
5457
  get(_target, modelName: string): any {
4989
5458
  if (typeof modelName === 'symbol') return undefined
4990
-
4991
5459
  const model = MODEL_MAP.get(modelName)
4992
5460
  if (!model) {
4993
5461
  throw new Error(
4994
5462
  \`Model '\${modelName}' not found. Available: \${[...MODEL_MAP.keys()].join(', ')}\`,
4995
5463
  )
4996
5464
  }
4997
-
4998
5465
  return new Proxy(
4999
5466
  {},
5000
5467
  {
@@ -5004,7 +5471,6 @@ function createBatchProxy(): BatchProxy {
5004
5471
  \`Method '\${method}' not supported in batch. Supported: \${[...ACCELERATED_METHODS].join(', ')}\`,
5005
5472
  )
5006
5473
  }
5007
-
5008
5474
  return (args?: any): DeferredQuery => {
5009
5475
  return new DeferredQuery(
5010
5476
  modelName,
@@ -5020,52 +5486,6 @@ function createBatchProxy(): BatchProxy {
5020
5486
  ) as BatchProxy
5021
5487
  }
5022
5488
 
5023
- function normalizeValue(value: unknown, seen = new WeakSet<object>(), depth = 0): unknown {
5024
- const MAX_DEPTH = 20
5025
- if (depth > MAX_DEPTH) {
5026
- throw new Error(\`Max normalization depth exceeded (\${MAX_DEPTH} levels)\`)
5027
- }
5028
- if (value instanceof Date) {
5029
- const t = value.getTime()
5030
- if (!Number.isFinite(t)) {
5031
- throw new Error('Invalid Date value in SQL params')
5032
- }
5033
- return value.toISOString()
5034
- }
5035
- if (typeof value === 'bigint') {
5036
- return value.toString()
5037
- }
5038
- if (Array.isArray(value)) {
5039
- const arrRef = value as unknown as object
5040
- if (seen.has(arrRef)) {
5041
- throw new Error('Circular reference in SQL params')
5042
- }
5043
- seen.add(arrRef)
5044
- const out = value.map((v) => normalizeValue(v, seen, depth + 1))
5045
- seen.delete(arrRef)
5046
- return out
5047
- }
5048
- if (value && typeof value === 'object') {
5049
- if (value instanceof Uint8Array) return value
5050
- if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) return value
5051
- const proto = Object.getPrototypeOf(value)
5052
- const isPlain = proto === Object.prototype || proto === null
5053
- if (!isPlain) return value
5054
- const obj = value as Record<string, unknown>
5055
- if (seen.has(obj)) {
5056
- throw new Error('Circular reference in SQL params')
5057
- }
5058
- seen.add(obj)
5059
- const out: Record<string, unknown> = {}
5060
- for (const [k, v] of Object.entries(obj)) {
5061
- out[k] = normalizeValue(v, seen, depth + 1)
5062
- }
5063
- seen.delete(obj)
5064
- return out
5065
- }
5066
- return value
5067
- }
5068
-
5069
5489
  function normalizeParams(params: unknown[]): unknown[] {
5070
5490
  return params.map(p => normalizeValue(p))
5071
5491
  }
@@ -5081,7 +5501,68 @@ function getByPath(obj: any, path: string): unknown {
5081
5501
  return result
5082
5502
  }
5083
5503
 
5084
- 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)}
5085
5566
 
5086
5567
  const ENUM_MAPPINGS: Record<string, Record<string, string>> = ${JSON.stringify(mappings, null, 2)}
5087
5568
 
@@ -5096,12 +5577,19 @@ const QUERIES: Record<string, Record<string, Record<string, {
5096
5577
 
5097
5578
  const DIALECT = ${JSON.stringify(dialect)}
5098
5579
 
5099
- const MODEL_MAP = new Map(MODELS.map(m => [m.name, m]))
5100
-
5101
- 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 {
5102
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
5103
5588
  if (path.includes('where')) return false
5104
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
5105
5593
  return true
5106
5594
  }
5107
5595
 
@@ -5128,6 +5616,14 @@ function transformEnumInValue(value: unknown, enumType: string | undefined): unk
5128
5616
  return mapping[value]
5129
5617
  }
5130
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
+
5131
5627
  return value
5132
5628
  }
5133
5629
 
@@ -5155,19 +5651,16 @@ function transformEnumValuesByModel(modelName: string, obj: any, path: string[]
5155
5651
  if (typeof obj === 'object') {
5156
5652
  const transformed: any = {}
5157
5653
  const modelFields = ENUM_FIELDS[modelName] || {}
5158
-
5159
5654
  for (const [key, value] of Object.entries(obj)) {
5160
5655
  const nextPath = [...path, key]
5161
5656
 
5162
5657
  const relModel = getRelatedModelName(modelName, key)
5163
-
5164
5658
  if (relModel && value && typeof value === 'object') {
5165
5659
  transformed[key] = transformEnumValuesByModel(relModel, value, nextPath)
5166
5660
  continue
5167
5661
  }
5168
5662
 
5169
5663
  const enumType = modelFields[key]
5170
-
5171
5664
  if (enumType) {
5172
5665
  if (value && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)) {
5173
5666
  const transformedOperators: any = {}
@@ -5186,25 +5679,27 @@ function transformEnumValuesByModel(modelName: string, obj: any, path: string[]
5186
5679
  transformed[key] = value
5187
5680
  }
5188
5681
  }
5189
-
5190
5682
  return transformed
5191
5683
  }
5192
5684
 
5193
5685
  return obj
5194
5686
  }
5195
5687
 
5688
+ function bigIntSafeReplacer(_key: string, value: unknown): unknown {
5689
+ if (typeof value === 'bigint') return '__bigint__' + value.toString()
5690
+ return value
5691
+ }
5692
+
5196
5693
  function normalizeQuery(args: any): string {
5197
5694
  if (!args) return '{}'
5198
-
5199
- const normalized = JSON.parse(JSON.stringify(args))
5695
+ const jsonStr = JSON.stringify(args, bigIntSafeReplacer)
5696
+ const normalized = JSON.parse(jsonStr)
5200
5697
 
5201
5698
  function replaceDynamicParams(obj: any, path: string[] = []): any {
5202
5699
  if (!obj || typeof obj !== 'object') return obj
5203
-
5204
5700
  if (Array.isArray(obj)) {
5205
5701
  return obj.map((v) => replaceDynamicParams(v, path))
5206
5702
  }
5207
-
5208
5703
  const result: any = {}
5209
5704
  for (const [key, value] of Object.entries(obj)) {
5210
5705
  if (isDynamicKeyForQueryKey(path, key)) {
@@ -5218,22 +5713,8 @@ function normalizeQuery(args: any): string {
5218
5713
 
5219
5714
  const withMarkers = replaceDynamicParams(normalized)
5220
5715
 
5221
- function removeEmptyObjects(obj: any): any {
5222
- if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return obj
5223
-
5224
- const result: any = {}
5225
- for (const [key, value] of Object.entries(obj)) {
5226
- if (value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0) {
5227
- continue
5228
- }
5229
- result[key] = removeEmptyObjects(value)
5230
- }
5231
- return result
5232
- }
5233
-
5234
- const cleaned = removeEmptyObjects(withMarkers)
5235
-
5236
- return JSON.stringify(cleaned, (key, value) => {
5716
+ return JSON.stringify(withMarkers, (key, value) => {
5717
+ if (typeof value === 'bigint') return '__bigint__' + value.toString()
5237
5718
  if (value && typeof value === 'object' && !Array.isArray(value)) {
5238
5719
  const sorted: Record<string, unknown> = {}
5239
5720
  for (const k of Object.keys(value).sort()) {
@@ -5243,52 +5724,10 @@ function normalizeQuery(args: any): string {
5243
5724
  }
5244
5725
  return value
5245
5726
  })
5727
+ }`;
5246
5728
  }
5247
-
5248
- function extractDynamicParams(args: any, dynamicKeys: string[]): unknown[] {
5249
- const params: unknown[] = []
5250
-
5251
- for (const key of dynamicKeys) {
5252
- const parts = key.split(':')
5253
- const lookupKey = parts.length === 2 ? parts[1] : key
5254
-
5255
- const value =
5256
- lookupKey.includes('.') ? getByPath(args, lookupKey) : args?.[lookupKey]
5257
-
5258
- if (value === undefined) {
5259
- throw new Error(\`Missing required parameter: \${key}\`)
5260
- }
5261
-
5262
- params.push(normalizeValue(value))
5263
- }
5264
-
5265
- return params
5266
- }
5267
-
5268
- async function executeQuery(client: any, sql: string, params: unknown[]): Promise<unknown[]> {
5269
- const normalizedParams = normalizeParams(params)
5270
-
5271
- if (DIALECT === 'postgres') {
5272
- return await client.unsafe(sql, normalizedParams)
5273
- }
5274
-
5275
- const stmt = client.prepare(sql)
5276
-
5277
- if (sql.toUpperCase().includes('COUNT(*) AS')) {
5278
- return [stmt.get(...normalizedParams)]
5279
- }
5280
-
5281
- return stmt.all(...normalizedParams)
5282
- }
5283
-
5284
- async function executeRaw(client: any, sql: string, params?: unknown[]): Promise<unknown[]> {
5285
- if (DIALECT === 'postgres') {
5286
- return await client.unsafe(sql, (params || []) as any[])
5287
- }
5288
- throw new Error('Raw execution for sqlite not supported in transactions')
5289
- }
5290
-
5291
- export function speedExtension(config: {
5729
+ function generateExtension() {
5730
+ return `export function speedExtension(config: {
5292
5731
  postgres?: any
5293
5732
  sqlite?: any
5294
5733
  debug?: boolean
@@ -5302,14 +5741,11 @@ export function speedExtension(config: {
5302
5741
  }) => void
5303
5742
  }) {
5304
5743
  const { postgres, sqlite, debug, onQuery } = config
5305
-
5306
5744
  if (!postgres && !sqlite) {
5307
5745
  throw new Error('speedExtension requires postgres or sqlite client')
5308
5746
  }
5309
-
5310
5747
  const client = postgres || sqlite
5311
5748
  const actualDialect = postgres ? 'postgres' : 'sqlite'
5312
-
5313
5749
  if (actualDialect !== DIALECT) {
5314
5750
  throw new Error(\`Generated code is for \${DIALECT}, but you provided \${actualDialect}\`)
5315
5751
  }
@@ -5327,53 +5763,55 @@ export function speedExtension(config: {
5327
5763
  const modelName = this?.name || this?.$name
5328
5764
  const startTime = Date.now()
5329
5765
 
5330
- const transformedArgs = transformEnumValuesByModel(modelName, args || {})
5331
-
5332
- const queryKey = normalizeQuery(transformedArgs)
5333
- const prebakedQuery = QUERIES[modelName]?.[method]?.[queryKey]
5334
-
5335
- let sql: string
5336
- let params: unknown[]
5337
- let prebaked = false
5338
-
5339
- if (prebakedQuery) {
5340
- sql = prebakedQuery.sql
5341
- params = normalizeParams([
5342
- ...prebakedQuery.params,
5343
- ...extractDynamicParams(transformedArgs, prebakedQuery.dynamicKeys),
5344
- ])
5345
- prebaked = true
5346
- } else {
5347
- const model = MODELS.find((m) => m.name === modelName)
5348
-
5349
- if (!model) {
5350
- 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[]
5351
5786
  }
5352
5787
 
5353
- const result = buildSQL(model, MODELS, method, transformedArgs, DIALECT)
5354
- sql = result.sql
5355
- params = normalizeParams(result.params as unknown[])
5356
- }
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
+ }
5357
5793
 
5358
- if (debug) {
5359
- console.log(\`[\${DIALECT}] \${modelName}.\${method} \${prebaked ? '\u26A1 PREBAKED' : '\u{1F528} RUNTIME'}\`)
5360
- console.log('SQL:', sql)
5361
- console.log('Params:', params)
5794
+ const results = await executeQuery(client, method, sql, params)
5795
+ const duration = Date.now() - startTime
5796
+
5797
+ onQuery?.({
5798
+ model: modelName,
5799
+ method,
5800
+ sql,
5801
+ params,
5802
+ duration,
5803
+ prebaked,
5804
+ })
5805
+
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)
5362
5814
  }
5363
-
5364
- const results = await executeQuery(client, sql, params)
5365
- const duration = Date.now() - startTime
5366
-
5367
- onQuery?.({
5368
- model: modelName,
5369
- method,
5370
- sql,
5371
- params,
5372
- duration,
5373
- prebaked,
5374
- })
5375
-
5376
- return transformQueryResults(method, results)
5377
5815
  }
5378
5816
 
5379
5817
  async function batch<T extends Record<string, DeferredQuery>>(
@@ -5381,20 +5819,17 @@ export function speedExtension(config: {
5381
5819
  ): Promise<{ [K in keyof T]: any }> {
5382
5820
  const batchProxy = createBatchProxy()
5383
5821
  const queries = await callback(batchProxy)
5384
-
5385
5822
  const batchQueries: Record<string, BatchQuery> = {}
5386
-
5387
5823
  for (const [key, deferred] of Object.entries(queries)) {
5388
5824
  if (!(deferred instanceof DeferredQuery)) {
5389
5825
  throw new Error(
5390
5826
  \`Batch query '\${key}' must be a deferred query. Did you await it?\`,
5391
5827
  )
5392
5828
  }
5393
-
5394
5829
  batchQueries[key] = {
5395
5830
  model: deferred.model,
5396
5831
  method: deferred.method,
5397
- args: deferred.args || {},
5832
+ args: transformEnumValuesByModel(deferred.model, deferred.args || {}),
5398
5833
  }
5399
5834
  }
5400
5835
 
@@ -5415,9 +5850,9 @@ export function speedExtension(config: {
5415
5850
  const normalizedParams = normalizeParams(params)
5416
5851
  const rows = await client.unsafe(sql, normalizedParams as any[])
5417
5852
  const row = rows[0] as Record<string, unknown>
5418
- const results = parseBatchResults(row, keys, batchQueries, aliases)
5419
- const duration = Date.now() - startTime
5853
+ const results = parseBatchResults(row, keys, batchQueries, aliases, MODEL_MAP)
5420
5854
 
5855
+ const duration = Date.now() - startTime
5421
5856
  onQuery?.({
5422
5857
  model: '_batch',
5423
5858
  method: 'batch',
@@ -5432,7 +5867,6 @@ export function speedExtension(config: {
5432
5867
 
5433
5868
  async function batchCount(queries: BatchCountQuery[]): Promise<number[]> {
5434
5869
  if (queries.length === 0) return []
5435
-
5436
5870
  const startTime = Date.now()
5437
5871
  const { sql, params } = buildBatchCountSql(
5438
5872
  queries,
@@ -5451,8 +5885,8 @@ export function speedExtension(config: {
5451
5885
  const rows = await client.unsafe(sql, normalizedParams as any[])
5452
5886
  const row = rows[0] as Record<string, unknown>
5453
5887
  const results = parseBatchCountResults(row, queries.length)
5454
- const duration = Date.now() - startTime
5455
5888
 
5889
+ const duration = Date.now() - startTime
5456
5890
  onQuery?.({
5457
5891
  model: '_batch',
5458
5892
  method: 'count',
@@ -5470,14 +5904,15 @@ export function speedExtension(config: {
5470
5904
  options?: TransactionOptions,
5471
5905
  ): Promise<unknown[]> {
5472
5906
  const startTime = Date.now()
5473
-
5474
5907
  if (debug) {
5475
5908
  console.log(\`[\${DIALECT}] $transaction (\${queries.length} queries)\`)
5476
5909
  }
5477
-
5478
- 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)
5479
5915
  const duration = Date.now() - startTime
5480
-
5481
5916
  onQuery?.({
5482
5917
  model: '_transaction',
5483
5918
  method: 'transaction',
@@ -5486,13 +5921,11 @@ export function speedExtension(config: {
5486
5921
  duration,
5487
5922
  prebaked: false,
5488
5923
  })
5489
-
5490
5924
  return results
5491
5925
  }
5492
5926
 
5493
5927
  return prisma.$extends({
5494
5928
  name: 'prisma-sql-generated',
5495
-
5496
5929
  client: {
5497
5930
  $original: prisma,
5498
5931
  $batch: batch as <T extends Record<string, DeferredQuery>>(
@@ -5501,7 +5934,6 @@ export function speedExtension(config: {
5501
5934
  $batchCount: batchCount as (...args: any[]) => Promise<number[]>,
5502
5935
  $transaction: transaction as (...args: any[]) => Promise<unknown[]>,
5503
5936
  },
5504
-
5505
5937
  model: {
5506
5938
  $allModels: {
5507
5939
  async findMany(args: any) {
@@ -5526,9 +5958,10 @@ export function speedExtension(config: {
5526
5958
  },
5527
5959
  })
5528
5960
  }
5961
+ }`;
5529
5962
  }
5530
-
5531
- type SpeedExtensionReturn = ReturnType<ReturnType<typeof speedExtension>>
5963
+ function generateTypeExports() {
5964
+ return `type SpeedExtensionReturn = ReturnType<ReturnType<typeof speedExtension>>
5532
5965
 
5533
5966
  export type SpeedClient<T> = T & {
5534
5967
  $batch<T extends Record<string, DeferredQuery>>(
@@ -5538,8 +5971,22 @@ export type SpeedClient<T> = T & {
5538
5971
  $transaction(queries: TransactionQuery[], options?: TransactionOptions): Promise<unknown[]>
5539
5972
  }
5540
5973
 
5541
- export type { BatchCountQuery, TransactionQuery, TransactionOptions }
5542
- `;
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");
5543
5990
  }
5544
5991
  function formatQueries(queries) {
5545
5992
  if (queries.size === 0) {