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.
package/dist/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { convertDMMFToModels, isDynamicParameter, extractDynamicName } from '@dee-wan/schema-parser';
2
- export { convertDMMFToModels } from '@dee-wan/schema-parser';
3
2
 
4
3
  var __defProp = Object.defineProperty;
5
4
  var __defProps = Object.defineProperties;
@@ -7,6 +6,9 @@ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
7
6
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
8
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
8
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
9
+ var __typeError = (msg) => {
10
+ throw TypeError(msg);
11
+ };
10
12
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
11
13
  var __spreadValues = (a, b) => {
12
14
  for (var prop in b || (b = {}))
@@ -20,6 +22,18 @@ var __spreadValues = (a, b) => {
20
22
  return a;
21
23
  };
22
24
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
25
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
26
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
27
+ 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);
28
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
29
+ var __privateWrapper = (obj, member, setter, getter) => ({
30
+ set _(value) {
31
+ __privateSet(obj, member, value);
32
+ },
33
+ get _() {
34
+ return __privateGet(obj, member, getter);
35
+ }
36
+ });
23
37
  var __async = (__this, __arguments, generator) => {
24
38
  return new Promise((resolve, reject) => {
25
39
  var fulfilled = (value) => {
@@ -41,154 +55,6 @@ var __async = (__this, __arguments, generator) => {
41
55
  });
42
56
  };
43
57
 
44
- // src/builder/shared/constants.ts
45
- var IS_PRODUCTION = process.env.NODE_ENV === "production";
46
- var SQL_SEPARATORS = Object.freeze({
47
- FIELD_LIST: ", ",
48
- CONDITION_AND: " AND ",
49
- CONDITION_OR: " OR ",
50
- ORDER_BY: ", "
51
- });
52
- var ALIAS_FORBIDDEN_KEYWORDS = /* @__PURE__ */ new Set([
53
- "select",
54
- "from",
55
- "where",
56
- "having",
57
- "order",
58
- "group",
59
- "limit",
60
- "offset",
61
- "join",
62
- "inner",
63
- "left",
64
- "right",
65
- "outer",
66
- "cross",
67
- "full",
68
- "and",
69
- "or",
70
- "not",
71
- "by",
72
- "as",
73
- "on",
74
- "union",
75
- "intersect",
76
- "except",
77
- "case",
78
- "when",
79
- "then",
80
- "else",
81
- "end"
82
- ]);
83
- var SQL_KEYWORDS = /* @__PURE__ */ new Set([
84
- ...ALIAS_FORBIDDEN_KEYWORDS,
85
- "user",
86
- "users",
87
- "table",
88
- "column",
89
- "index",
90
- "values",
91
- "in",
92
- "like",
93
- "between",
94
- "is",
95
- "exists",
96
- "null",
97
- "true",
98
- "false",
99
- "all",
100
- "any",
101
- "some",
102
- "update",
103
- "insert",
104
- "delete",
105
- "create",
106
- "drop",
107
- "alter",
108
- "truncate",
109
- "grant",
110
- "revoke",
111
- "exec",
112
- "execute"
113
- ]);
114
- var SQL_RESERVED_WORDS = SQL_KEYWORDS;
115
- var DEFAULT_WHERE_CLAUSE = "1=1";
116
- var SPECIAL_FIELDS = Object.freeze({
117
- ID: "id"
118
- });
119
- var SQL_TEMPLATES = Object.freeze({
120
- PUBLIC_SCHEMA: "public",
121
- WHERE: "WHERE",
122
- SELECT: "SELECT",
123
- FROM: "FROM",
124
- ORDER_BY: "ORDER BY",
125
- GROUP_BY: "GROUP BY",
126
- HAVING: "HAVING",
127
- LIMIT: "LIMIT",
128
- OFFSET: "OFFSET",
129
- COUNT_ALL: "COUNT(*)",
130
- AS: "AS",
131
- DISTINCT_ON: "DISTINCT ON",
132
- IS_NULL: "IS NULL",
133
- IS_NOT_NULL: "IS NOT NULL",
134
- LIKE: "LIKE",
135
- AND: "AND",
136
- OR: "OR",
137
- NOT: "NOT"
138
- });
139
- var SCHEMA_PREFIXES = Object.freeze({
140
- INTERNAL: "@",
141
- COMMENT: "//"
142
- });
143
- var Ops = Object.freeze({
144
- EQUALS: "equals",
145
- NOT: "not",
146
- GT: "gt",
147
- GTE: "gte",
148
- LT: "lt",
149
- LTE: "lte",
150
- IN: "in",
151
- NOT_IN: "notIn",
152
- CONTAINS: "contains",
153
- STARTS_WITH: "startsWith",
154
- ENDS_WITH: "endsWith",
155
- HAS: "has",
156
- HAS_SOME: "hasSome",
157
- HAS_EVERY: "hasEvery",
158
- IS_EMPTY: "isEmpty",
159
- PATH: "path",
160
- STRING_CONTAINS: "string_contains",
161
- STRING_STARTS_WITH: "string_starts_with",
162
- STRING_ENDS_WITH: "string_ends_with"
163
- });
164
- var LogicalOps = Object.freeze({
165
- AND: "AND",
166
- OR: "OR",
167
- NOT: "NOT"
168
- });
169
- var RelationFilters = Object.freeze({
170
- SOME: "some",
171
- EVERY: "every",
172
- NONE: "none"
173
- });
174
- var Modes = Object.freeze({
175
- INSENSITIVE: "insensitive",
176
- DEFAULT: "default"
177
- });
178
- var Wildcards = Object.freeze({
179
- [Ops.CONTAINS]: (v) => `%${v}%`,
180
- [Ops.STARTS_WITH]: (v) => `${v}%`,
181
- [Ops.ENDS_WITH]: (v) => `%${v}`
182
- });
183
- var REGEX_CACHE = {
184
- VALID_IDENTIFIER: /^[a-z_][a-z0-9_]*$/
185
- };
186
- var LIMITS = Object.freeze({
187
- MAX_QUERY_DEPTH: 50,
188
- MAX_ARRAY_SIZE: 1e4,
189
- MAX_STRING_LENGTH: 1e4
190
- });
191
-
192
58
  // src/utils/normalize-value.ts
193
59
  var MAX_DEPTH = 20;
194
60
  function normalizeValue(value, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
@@ -196,45 +62,54 @@ function normalizeValue(value, seen = /* @__PURE__ */ new WeakSet(), depth = 0)
196
62
  throw new Error(`Max normalization depth exceeded (${MAX_DEPTH} levels)`);
197
63
  }
198
64
  if (value instanceof Date) {
199
- const t = value.getTime();
200
- if (!Number.isFinite(t)) {
201
- throw new Error("Invalid Date value in SQL params");
202
- }
203
- return value.toISOString();
65
+ return normalizeDateValue(value);
204
66
  }
205
67
  if (typeof value === "bigint") {
206
68
  return value.toString();
207
69
  }
208
70
  if (Array.isArray(value)) {
209
- const arrRef = value;
210
- if (seen.has(arrRef)) {
211
- throw new Error("Circular reference in SQL params");
212
- }
213
- seen.add(arrRef);
214
- const out = value.map((v) => normalizeValue(v, seen, depth + 1));
215
- seen.delete(arrRef);
216
- return out;
71
+ return normalizeArrayValue(value, seen, depth);
217
72
  }
218
73
  if (value && typeof value === "object") {
219
- if (value instanceof Uint8Array) return value;
220
- if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) return value;
221
- const proto = Object.getPrototypeOf(value);
222
- const isPlain = proto === Object.prototype || proto === null;
223
- if (!isPlain) return value;
224
- const obj = value;
225
- if (seen.has(obj)) {
226
- throw new Error("Circular reference in SQL params");
227
- }
228
- seen.add(obj);
229
- const out = {};
230
- for (const [k, v] of Object.entries(obj)) {
231
- out[k] = normalizeValue(v, seen, depth + 1);
232
- }
233
- seen.delete(obj);
234
- return out;
74
+ return normalizeObjectValue(value, seen, depth);
235
75
  }
236
76
  return value;
237
77
  }
78
+ function normalizeDateValue(date) {
79
+ const t = date.getTime();
80
+ if (!Number.isFinite(t)) {
81
+ throw new Error("Invalid Date value in SQL params");
82
+ }
83
+ return date.toISOString();
84
+ }
85
+ function normalizeArrayValue(value, seen, depth) {
86
+ const arrRef = value;
87
+ if (seen.has(arrRef)) {
88
+ throw new Error("Circular reference in SQL params");
89
+ }
90
+ seen.add(arrRef);
91
+ const out = value.map((v) => normalizeValue(v, seen, depth + 1));
92
+ seen.delete(arrRef);
93
+ return out;
94
+ }
95
+ function normalizeObjectValue(value, seen, depth) {
96
+ if (value instanceof Uint8Array) return value;
97
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) return value;
98
+ const proto = Object.getPrototypeOf(value);
99
+ const isPlain = proto === Object.prototype || proto === null;
100
+ if (!isPlain) return value;
101
+ const obj = value;
102
+ if (seen.has(obj)) {
103
+ throw new Error("Circular reference in SQL params");
104
+ }
105
+ seen.add(obj);
106
+ const out = {};
107
+ for (const [k, v] of Object.entries(obj)) {
108
+ out[k] = normalizeValue(v, seen, depth + 1);
109
+ }
110
+ seen.delete(obj);
111
+ return out;
112
+ }
238
113
 
239
114
  // src/sql-builder-dialect.ts
240
115
  var globalDialect = "postgres";
@@ -414,140 +289,222 @@ function prepareArrayParam(value, dialect) {
414
289
  if (dialect === "postgres") {
415
290
  return value.map((v) => normalizeValue(v));
416
291
  }
417
- return JSON.stringify(value);
418
- }
419
-
420
- // src/builder/shared/validators/type-guards.ts
421
- function isNotNullish(value) {
422
- return value !== null && value !== void 0;
423
- }
424
- function isNonEmptyString(value) {
425
- return typeof value === "string" && value.trim().length > 0;
426
- }
427
- function isEmptyString(value) {
428
- return typeof value === "string" && value.trim().length === 0;
429
- }
430
- function isNonEmptyArray(value) {
431
- return Array.isArray(value) && value.length > 0;
432
- }
433
- function isEmptyArray(value) {
434
- return Array.isArray(value) && value.length === 0;
435
- }
436
- function isPlainObject(val) {
437
- if (!isNotNullish(val)) return false;
438
- if (Array.isArray(val)) return false;
439
- if (typeof val !== "object") return false;
440
- return Object.prototype.toString.call(val) === "[object Object]";
441
- }
442
- function hasProperty(obj, key) {
443
- return isPlainObject(obj) && key in obj;
444
- }
445
- function isArrayType(t) {
446
- if (!isNotNullish(t)) return false;
447
- const normalized = t.replace(/\?$/, "");
448
- return normalized.endsWith("[]");
449
- }
450
- function isJsonType(t) {
451
- return isNotNullish(t) && (t === "Json" || t === "Json?");
452
- }
453
- function hasValidContent(sql) {
454
- return isNotNullish(sql) && sql.trim().length > 0;
455
- }
456
- function hasRequiredKeywords(sql) {
457
- const upper = sql.toUpperCase();
458
- const hasSelect = upper.includes("SELECT");
459
- const hasFrom = upper.includes("FROM");
460
- return hasSelect && hasFrom && upper.indexOf("SELECT") < upper.indexOf("FROM");
461
- }
462
-
463
- // src/builder/shared/errors.ts
464
- var SqlBuilderError = class extends Error {
465
- constructor(message, code, context) {
466
- super(message);
467
- this.name = "SqlBuilderError";
468
- this.code = code;
469
- this.context = context;
470
- }
471
- };
472
- function createError(message, ctx, code = "VALIDATION_ERROR") {
473
- const parts = [message];
474
- if (isNonEmptyArray(ctx.path)) {
475
- parts.push(`Path: ${ctx.path.join(".")}`);
476
- }
477
- if (isNotNullish(ctx.modelName)) {
478
- parts.push(`Model: ${ctx.modelName}`);
479
- }
480
- if (isNonEmptyArray(ctx.availableFields)) {
481
- parts.push(`Available fields: ${ctx.availableFields.join(", ")}`);
482
- }
483
- return new SqlBuilderError(parts.join("\n"), code, ctx);
292
+ return JSON.stringify(value.map((v) => normalizeValue(v)));
484
293
  }
485
294
 
486
- // src/builder/shared/model-field-cache.ts
487
- var MODEL_CACHE = /* @__PURE__ */ new WeakMap();
488
- function quote(id) {
489
- 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(
490
- id
491
- );
492
- if (needsQuoting2) {
493
- return `"${id.replace(/"/g, '""')}"`;
494
- }
495
- return id;
496
- }
497
- function ensureFullCache(model) {
498
- let cache = MODEL_CACHE.get(model);
499
- if (!cache) {
500
- const fieldInfo = /* @__PURE__ */ new Map();
501
- const scalarFields = /* @__PURE__ */ new Set();
502
- const relationFields = /* @__PURE__ */ new Set();
503
- const columnMap = /* @__PURE__ */ new Map();
504
- const fieldByName = /* @__PURE__ */ new Map();
505
- const quotedColumns = /* @__PURE__ */ new Map();
506
- for (const f of model.fields) {
507
- const info = {
508
- name: f.name,
509
- dbName: f.dbName || f.name,
510
- type: f.type,
511
- isRelation: !!f.isRelation,
512
- isRequired: !!f.isRequired
513
- };
514
- fieldInfo.set(f.name, info);
515
- fieldByName.set(f.name, f);
516
- if (info.isRelation) {
517
- relationFields.add(f.name);
518
- } else {
519
- scalarFields.add(f.name);
520
- const dbName = info.dbName;
521
- columnMap.set(f.name, dbName);
522
- quotedColumns.set(f.name, quote(dbName));
523
- }
524
- }
525
- cache = {
526
- fieldInfo,
527
- scalarFields,
528
- relationFields,
529
- columnMap,
530
- fieldByName,
531
- quotedColumns
532
- };
533
- MODEL_CACHE.set(model, cache);
534
- }
535
- return cache;
295
+ // src/builder/shared/constants.ts
296
+ var IS_PRODUCTION = process.env.NODE_ENV === "production";
297
+ var SQL_SEPARATORS = Object.freeze({
298
+ FIELD_LIST: ", ",
299
+ CONDITION_AND: " AND ",
300
+ CONDITION_OR: " OR ",
301
+ ORDER_BY: ", "
302
+ });
303
+ var ALIAS_FORBIDDEN_KEYWORDS = /* @__PURE__ */ new Set([
304
+ "select",
305
+ "from",
306
+ "where",
307
+ "having",
308
+ "order",
309
+ "group",
310
+ "limit",
311
+ "offset",
312
+ "join",
313
+ "inner",
314
+ "left",
315
+ "right",
316
+ "outer",
317
+ "cross",
318
+ "full",
319
+ "and",
320
+ "or",
321
+ "not",
322
+ "by",
323
+ "as",
324
+ "on",
325
+ "union",
326
+ "intersect",
327
+ "except",
328
+ "case",
329
+ "when",
330
+ "then",
331
+ "else",
332
+ "end"
333
+ ]);
334
+ var SQL_KEYWORDS = /* @__PURE__ */ new Set([
335
+ ...ALIAS_FORBIDDEN_KEYWORDS,
336
+ "user",
337
+ "users",
338
+ "table",
339
+ "column",
340
+ "index",
341
+ "values",
342
+ "in",
343
+ "like",
344
+ "between",
345
+ "is",
346
+ "exists",
347
+ "null",
348
+ "true",
349
+ "false",
350
+ "all",
351
+ "any",
352
+ "some",
353
+ "update",
354
+ "insert",
355
+ "delete",
356
+ "create",
357
+ "drop",
358
+ "alter",
359
+ "truncate",
360
+ "grant",
361
+ "revoke",
362
+ "exec",
363
+ "execute"
364
+ ]);
365
+ var SQL_RESERVED_WORDS = SQL_KEYWORDS;
366
+ var DEFAULT_WHERE_CLAUSE = "1=1";
367
+ var SPECIAL_FIELDS = Object.freeze({
368
+ ID: "id"
369
+ });
370
+ var SQL_TEMPLATES = Object.freeze({
371
+ PUBLIC_SCHEMA: "public",
372
+ WHERE: "WHERE",
373
+ SELECT: "SELECT",
374
+ FROM: "FROM",
375
+ ORDER_BY: "ORDER BY",
376
+ GROUP_BY: "GROUP BY",
377
+ HAVING: "HAVING",
378
+ LIMIT: "LIMIT",
379
+ OFFSET: "OFFSET",
380
+ COUNT_ALL: "COUNT(*)",
381
+ AS: "AS",
382
+ DISTINCT_ON: "DISTINCT ON",
383
+ IS_NULL: "IS NULL",
384
+ IS_NOT_NULL: "IS NOT NULL",
385
+ LIKE: "LIKE",
386
+ AND: "AND",
387
+ OR: "OR",
388
+ NOT: "NOT"
389
+ });
390
+ var SCHEMA_PREFIXES = Object.freeze({
391
+ INTERNAL: "@",
392
+ COMMENT: "//"
393
+ });
394
+ var Ops = Object.freeze({
395
+ EQUALS: "equals",
396
+ NOT: "not",
397
+ GT: "gt",
398
+ GTE: "gte",
399
+ LT: "lt",
400
+ LTE: "lte",
401
+ IN: "in",
402
+ NOT_IN: "notIn",
403
+ CONTAINS: "contains",
404
+ STARTS_WITH: "startsWith",
405
+ ENDS_WITH: "endsWith",
406
+ HAS: "has",
407
+ HAS_SOME: "hasSome",
408
+ HAS_EVERY: "hasEvery",
409
+ IS_EMPTY: "isEmpty",
410
+ PATH: "path",
411
+ STRING_CONTAINS: "string_contains",
412
+ STRING_STARTS_WITH: "string_starts_with",
413
+ STRING_ENDS_WITH: "string_ends_with"
414
+ });
415
+ var LogicalOps = Object.freeze({
416
+ AND: "AND",
417
+ OR: "OR",
418
+ NOT: "NOT"
419
+ });
420
+ var RelationFilters = Object.freeze({
421
+ SOME: "some",
422
+ EVERY: "every",
423
+ NONE: "none"
424
+ });
425
+ var Modes = Object.freeze({
426
+ INSENSITIVE: "insensitive",
427
+ DEFAULT: "default"
428
+ });
429
+ var Wildcards = Object.freeze({
430
+ [Ops.CONTAINS]: (v) => `%${v}%`,
431
+ [Ops.STARTS_WITH]: (v) => `${v}%`,
432
+ [Ops.ENDS_WITH]: (v) => `%${v}`
433
+ });
434
+ var REGEX_CACHE = {
435
+ VALID_IDENTIFIER: /^[a-z_][a-z0-9_]*$/
436
+ };
437
+ var LIMITS = Object.freeze({
438
+ MAX_QUERY_DEPTH: 50,
439
+ MAX_ARRAY_SIZE: 1e4,
440
+ MAX_STRING_LENGTH: 1e4,
441
+ MAX_HAVING_DEPTH: 50
442
+ });
443
+
444
+ // src/builder/shared/validators/type-guards.ts
445
+ function isNotNullish(value) {
446
+ return value !== null && value !== void 0;
536
447
  }
537
- function getFieldInfo(model, fieldName) {
538
- return ensureFullCache(model).fieldInfo.get(fieldName);
448
+ function isNonEmptyString(value) {
449
+ return typeof value === "string" && value.trim().length > 0;
539
450
  }
540
- function getScalarFieldSet(model) {
541
- return ensureFullCache(model).scalarFields;
451
+ function isEmptyString(value) {
452
+ return typeof value === "string" && value.trim().length === 0;
542
453
  }
543
- function getRelationFieldSet(model) {
544
- return ensureFullCache(model).relationFields;
454
+ function isNonEmptyArray(value) {
455
+ return Array.isArray(value) && value.length > 0;
545
456
  }
546
- function getColumnMap(model) {
547
- return ensureFullCache(model).columnMap;
457
+ function isEmptyArray(value) {
458
+ return Array.isArray(value) && value.length === 0;
548
459
  }
549
- function getQuotedColumn(model, fieldName) {
550
- return ensureFullCache(model).quotedColumns.get(fieldName);
460
+ function isPlainObject(val) {
461
+ if (!isNotNullish(val)) return false;
462
+ if (Array.isArray(val)) return false;
463
+ if (typeof val !== "object") return false;
464
+ return Object.prototype.toString.call(val) === "[object Object]";
465
+ }
466
+ function hasProperty(obj, key) {
467
+ return isPlainObject(obj) && key in obj;
468
+ }
469
+ function isArrayType(t) {
470
+ if (!isNotNullish(t)) return false;
471
+ const normalized = t.replace(/\?$/, "");
472
+ return normalized.endsWith("[]");
473
+ }
474
+ function isJsonType(t) {
475
+ return isNotNullish(t) && (t === "Json" || t === "Json?");
476
+ }
477
+ function hasValidContent(sql) {
478
+ return isNotNullish(sql) && sql.trim().length > 0;
479
+ }
480
+ function hasRequiredKeywords(sql) {
481
+ const upper = sql.toUpperCase();
482
+ const hasSelect = upper.includes("SELECT");
483
+ const hasFrom = upper.includes("FROM");
484
+ return hasSelect && hasFrom && upper.indexOf("SELECT") < upper.indexOf("FROM");
485
+ }
486
+
487
+ // src/builder/shared/errors.ts
488
+ var SqlBuilderError = class extends Error {
489
+ constructor(message, code, context) {
490
+ super(message);
491
+ this.name = "SqlBuilderError";
492
+ this.code = code;
493
+ this.context = context;
494
+ }
495
+ };
496
+ function createError(message, ctx, code = "VALIDATION_ERROR") {
497
+ const parts = [message];
498
+ if (isNonEmptyArray(ctx.path)) {
499
+ parts.push(`Path: ${ctx.path.join(".")}`);
500
+ }
501
+ if (isNotNullish(ctx.modelName)) {
502
+ parts.push(`Model: ${ctx.modelName}`);
503
+ }
504
+ if (isNonEmptyArray(ctx.availableFields)) {
505
+ parts.push(`Available fields: ${ctx.availableFields.join(", ")}`);
506
+ }
507
+ return new SqlBuilderError(parts.join("\n"), code, ctx);
551
508
  }
552
509
 
553
510
  // src/builder/shared/validators/sql-validators.ts
@@ -693,23 +650,104 @@ function needsQuoting(identifier) {
693
650
  return false;
694
651
  }
695
652
 
696
- // src/builder/shared/sql-utils.ts
697
- function containsControlChars(s) {
698
- return /[\u0000-\u001F\u007F]/.test(s);
699
- }
700
- function assertNoControlChars(label, s) {
701
- if (containsControlChars(s)) {
702
- throw new Error(
703
- `${label} contains invalid characters: ${JSON.stringify(s)}`
704
- );
653
+ // src/builder/shared/model-field-cache.ts
654
+ var MODEL_CACHE = /* @__PURE__ */ new WeakMap();
655
+ function quoteIdent(id) {
656
+ if (typeof id !== "string" || id.trim().length === 0) {
657
+ throw new Error("quoteIdent: identifier is required and cannot be empty");
705
658
  }
706
- }
707
- function quoteRawIdent(id) {
708
- return `"${id.replace(/"/g, '""')}"`;
709
- }
710
- function isIdentCharCode(c) {
711
- return c >= 48 && c <= 57 || c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 95;
712
- }
659
+ for (let i = 0; i < id.length; i++) {
660
+ const code = id.charCodeAt(i);
661
+ if (code >= 0 && code <= 31 || code === 127) {
662
+ throw new Error(
663
+ `quoteIdent: identifier contains invalid characters: ${JSON.stringify(id)}`
664
+ );
665
+ }
666
+ }
667
+ if (needsQuoting(id)) {
668
+ return `"${id.replace(/"/g, '""')}"`;
669
+ }
670
+ return id;
671
+ }
672
+ function ensureFullCache(model) {
673
+ let cache = MODEL_CACHE.get(model);
674
+ if (!cache) {
675
+ const fieldInfo = /* @__PURE__ */ new Map();
676
+ const scalarFields = /* @__PURE__ */ new Set();
677
+ const relationFields = /* @__PURE__ */ new Set();
678
+ const columnMap = /* @__PURE__ */ new Map();
679
+ const fieldByName = /* @__PURE__ */ new Map();
680
+ const quotedColumns = /* @__PURE__ */ new Map();
681
+ for (const f of model.fields) {
682
+ const info = {
683
+ name: f.name,
684
+ dbName: f.dbName || f.name,
685
+ type: f.type,
686
+ isRelation: !!f.isRelation,
687
+ isRequired: !!f.isRequired
688
+ };
689
+ fieldInfo.set(f.name, info);
690
+ fieldByName.set(f.name, f);
691
+ if (info.isRelation) {
692
+ relationFields.add(f.name);
693
+ } else {
694
+ scalarFields.add(f.name);
695
+ const dbName = info.dbName;
696
+ columnMap.set(f.name, dbName);
697
+ quotedColumns.set(f.name, quoteIdent(dbName));
698
+ }
699
+ }
700
+ cache = {
701
+ fieldInfo,
702
+ scalarFields,
703
+ relationFields,
704
+ columnMap,
705
+ fieldByName,
706
+ quotedColumns
707
+ };
708
+ MODEL_CACHE.set(model, cache);
709
+ }
710
+ return cache;
711
+ }
712
+ function getFieldInfo(model, fieldName) {
713
+ return ensureFullCache(model).fieldInfo.get(fieldName);
714
+ }
715
+ function getScalarFieldSet(model) {
716
+ return ensureFullCache(model).scalarFields;
717
+ }
718
+ function getRelationFieldSet(model) {
719
+ return ensureFullCache(model).relationFields;
720
+ }
721
+ function getColumnMap(model) {
722
+ return ensureFullCache(model).columnMap;
723
+ }
724
+ function getQuotedColumn(model, fieldName) {
725
+ return ensureFullCache(model).quotedColumns.get(fieldName);
726
+ }
727
+
728
+ // src/builder/shared/sql-utils.ts
729
+ function containsControlChars(s) {
730
+ for (let i = 0; i < s.length; i++) {
731
+ const code = s.charCodeAt(i);
732
+ if (code >= 0 && code <= 31 || code === 127) {
733
+ return true;
734
+ }
735
+ }
736
+ return false;
737
+ }
738
+ function assertNoControlChars(label, s) {
739
+ if (containsControlChars(s)) {
740
+ throw new Error(
741
+ `${label} contains invalid characters: ${JSON.stringify(s)}`
742
+ );
743
+ }
744
+ }
745
+ function quoteRawIdent(id) {
746
+ return `"${id.replace(/"/g, '""')}"`;
747
+ }
748
+ function isIdentCharCode(c) {
749
+ return c >= 48 && c <= 57 || c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 95;
750
+ }
713
751
  function isIdentStartCharCode(c) {
714
752
  return c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 95;
715
753
  }
@@ -770,20 +808,9 @@ function parseUnquotedPart(input, start) {
770
808
  }
771
809
  return i;
772
810
  }
773
- function assertSafeQualifiedName(input) {
774
- const raw = String(input);
775
- const trimmed = raw.trim();
776
- if (trimmed.length === 0) {
777
- throw new Error("tableName/tableRef is required and cannot be empty");
778
- }
779
- if (raw !== trimmed) {
780
- throw new Error(
781
- `tableName/tableRef must not contain leading/trailing whitespace: ${JSON.stringify(raw)}`
782
- );
783
- }
784
- assertNoControlChars("tableName/tableRef", trimmed);
785
- for (let i2 = 0; i2 < trimmed.length; i2++) {
786
- const c = trimmed.charCodeAt(i2);
811
+ function validateQualifiedNameFormat(trimmed) {
812
+ for (let i = 0; i < trimmed.length; i++) {
813
+ const c = trimmed.charCodeAt(i);
787
814
  if (c === 9 || c === 11 || c === 12 || c === 32) {
788
815
  throw new Error(
789
816
  `tableName/tableRef must not contain whitespace: ${JSON.stringify(trimmed)}`
@@ -800,6 +827,8 @@ function assertSafeQualifiedName(input) {
800
827
  );
801
828
  }
802
829
  }
830
+ }
831
+ function parseQualifiedNameParts(trimmed) {
803
832
  let i = 0;
804
833
  const n = trimmed.length;
805
834
  let parts = 0;
@@ -835,7 +864,22 @@ function assertSafeQualifiedName(input) {
835
864
  }
836
865
  }
837
866
  }
838
- function quote2(id) {
867
+ function assertSafeQualifiedName(input) {
868
+ const raw = String(input);
869
+ const trimmed = raw.trim();
870
+ if (trimmed.length === 0) {
871
+ throw new Error("tableName/tableRef is required and cannot be empty");
872
+ }
873
+ if (raw !== trimmed) {
874
+ throw new Error(
875
+ `tableName/tableRef must not contain leading/trailing whitespace: ${JSON.stringify(raw)}`
876
+ );
877
+ }
878
+ assertNoControlChars("tableName/tableRef", trimmed);
879
+ validateQualifiedNameFormat(trimmed);
880
+ parseQualifiedNameParts(trimmed);
881
+ }
882
+ function quote(id) {
839
883
  if (isEmptyString(id)) {
840
884
  throw new Error("quote: identifier is required and cannot be empty");
841
885
  }
@@ -855,13 +899,13 @@ function resolveColumnName(model, fieldName) {
855
899
  return columnMap.get(fieldName) || fieldName;
856
900
  }
857
901
  function quoteColumn(model, fieldName) {
858
- if (!model) return quote2(fieldName);
902
+ if (!model) return quote(fieldName);
859
903
  const cached = getQuotedColumn(model, fieldName);
860
- return cached || quote2(fieldName);
904
+ return cached || quote(fieldName);
861
905
  }
862
906
  function col(alias, field, model) {
863
907
  const columnName = model ? getColumnMap(model).get(field) || field : field;
864
- const quoted = model ? getQuotedColumn(model, field) || quote2(columnName) : quote2(columnName);
908
+ const quoted = model ? getQuotedColumn(model, field) || quote(columnName) : quote(columnName);
865
909
  return alias + "." + quoted;
866
910
  }
867
911
  function colWithAlias(alias, field, model) {
@@ -872,9 +916,9 @@ function colWithAlias(alias, field, model) {
872
916
  throw new Error("colWithAlias: field is required and cannot be empty");
873
917
  }
874
918
  const columnName = resolveColumnName(model, field);
875
- const columnRef = alias + "." + (model ? getQuotedColumn(model, field) || quote2(columnName) : quote2(columnName));
919
+ const columnRef = alias + "." + (model ? getQuotedColumn(model, field) || quote(columnName) : quote(columnName));
876
920
  if (columnName !== field) {
877
- return columnRef + " AS " + quote2(field);
921
+ return columnRef + " AS " + quote(field);
878
922
  }
879
923
  return columnRef;
880
924
  }
@@ -897,7 +941,7 @@ function buildTableReference(schemaName, tableName, dialect) {
897
941
  }
898
942
  const d = dialect != null ? dialect : "postgres";
899
943
  if (d === "sqlite") {
900
- return quote2(tableName);
944
+ return quote(tableName);
901
945
  }
902
946
  if (isEmptyString(schemaName)) {
903
947
  throw new Error(
@@ -924,7 +968,7 @@ function assertSafeAlias(alias) {
924
968
  if (a !== alias) {
925
969
  throw new Error("Invalid alias: leading/trailing whitespace");
926
970
  }
927
- if (/[\u0000-\u001F\u007F]/.test(a)) {
971
+ if (containsControlChars(a)) {
928
972
  throw new Error(
929
973
  "Invalid alias: contains unsafe characters (control characters)"
930
974
  );
@@ -945,7 +989,7 @@ function assertSafeAlias(alias) {
945
989
  "Invalid alias: must be a simple identifier without whitespace"
946
990
  );
947
991
  }
948
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(a)) {
992
+ if (!/^[A-Za-z_]\w*$/.test(a)) {
949
993
  throw new Error(
950
994
  `Invalid alias: must be a simple identifier (alphanumeric with underscores): "${alias}"`
951
995
  );
@@ -993,8 +1037,7 @@ function isValidRelationField(field) {
993
1037
  return false;
994
1038
  const fk = normalizeKeyList(field.foreignKey);
995
1039
  if (fk.length === 0) return false;
996
- const refsRaw = field.references;
997
- const refs = normalizeKeyList(refsRaw);
1040
+ const refs = normalizeKeyList(field.references);
998
1041
  if (refs.length === 0) {
999
1042
  return fk.length === 1;
1000
1043
  }
@@ -1002,8 +1045,7 @@ function isValidRelationField(field) {
1002
1045
  return true;
1003
1046
  }
1004
1047
  function getReferenceFieldNames(field, foreignKeyCount) {
1005
- const refsRaw = field.references;
1006
- const refs = normalizeKeyList(refsRaw);
1048
+ const refs = normalizeKeyList(field.references);
1007
1049
  if (refs.length === 0) {
1008
1050
  if (foreignKeyCount === 1) return [SPECIAL_FIELDS.ID];
1009
1051
  return [];
@@ -1164,29 +1206,35 @@ function normalizeOrderByInput(orderBy, parseValue) {
1164
1206
  }
1165
1207
 
1166
1208
  // src/builder/shared/order-by-determinism.ts
1167
- function modelHasScalarId(model) {
1168
- if (!model) return false;
1169
- return getScalarFieldSet(model).has("id");
1209
+ function findTiebreakerField(model) {
1210
+ if (!model) return null;
1211
+ const scalarSet = getScalarFieldSet(model);
1212
+ for (const f of model.fields) {
1213
+ if (f.isId && !f.isRelation && scalarSet.has(f.name)) return f.name;
1214
+ }
1215
+ if (scalarSet.has("id")) return "id";
1216
+ return null;
1170
1217
  }
1171
- function hasIdTiebreaker(orderBy, parse) {
1218
+ function hasTiebreaker(orderBy, parse, field) {
1172
1219
  if (!isNotNullish(orderBy)) return false;
1173
1220
  const normalized = normalizeOrderByInput(orderBy, parse);
1174
1221
  return normalized.some(
1175
- (obj) => Object.prototype.hasOwnProperty.call(obj, "id")
1222
+ (obj) => Object.prototype.hasOwnProperty.call(obj, field)
1176
1223
  );
1177
1224
  }
1178
- function addIdTiebreaker(orderBy) {
1179
- if (Array.isArray(orderBy)) return [...orderBy, { id: "asc" }];
1180
- return [orderBy, { id: "asc" }];
1225
+ function addTiebreaker(orderBy, field) {
1226
+ if (Array.isArray(orderBy)) return [...orderBy, { [field]: "asc" }];
1227
+ return [orderBy, { [field]: "asc" }];
1181
1228
  }
1182
1229
  function ensureDeterministicOrderByInput(args) {
1183
1230
  const { orderBy, model, parseValue } = args;
1184
- if (!modelHasScalarId(model)) return orderBy;
1231
+ const tiebreaker = findTiebreakerField(model);
1232
+ if (!tiebreaker) return orderBy;
1185
1233
  if (!isNotNullish(orderBy)) {
1186
- return { id: "asc" };
1234
+ return { [tiebreaker]: "asc" };
1187
1235
  }
1188
- if (hasIdTiebreaker(orderBy, parseValue)) return orderBy;
1189
- return addIdTiebreaker(orderBy);
1236
+ if (hasTiebreaker(orderBy, parseValue, tiebreaker)) return orderBy;
1237
+ return addTiebreaker(orderBy, tiebreaker);
1190
1238
  }
1191
1239
 
1192
1240
  // src/builder/shared/validators/field-assertions.ts
@@ -1333,11 +1381,9 @@ function defaultNullsFor(dialect, direction) {
1333
1381
  function ensureCursorFieldsInOrder(orderEntries, cursorEntries) {
1334
1382
  if (cursorEntries.length === 0) return orderEntries;
1335
1383
  const existing = /* @__PURE__ */ new Set();
1336
- for (let i = 0; i < orderEntries.length; i++)
1337
- existing.add(orderEntries[i].field);
1384
+ for (const entry of orderEntries) existing.add(entry.field);
1338
1385
  let out = null;
1339
- for (let i = 0; i < cursorEntries.length; i++) {
1340
- const field = cursorEntries[i][0];
1386
+ for (const [field] of cursorEntries) {
1341
1387
  if (!existing.has(field)) {
1342
1388
  if (!out) out = orderEntries.slice();
1343
1389
  out.push({ field, direction: "asc" });
@@ -1562,6 +1608,7 @@ function buildOrderEntries(orderBy) {
1562
1608
  }
1563
1609
  return entries;
1564
1610
  }
1611
+ var MAX_NOT_DEPTH = 50;
1565
1612
  function buildNotComposite(expr, val, params, dialect, buildOp, separator) {
1566
1613
  const entries = Object.entries(val).filter(
1567
1614
  ([k, v]) => k !== "mode" && v !== void 0
@@ -1576,45 +1623,71 @@ function buildNotComposite(expr, val, params, dialect, buildOp, separator) {
1576
1623
  if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
1577
1624
  return `${SQL_TEMPLATES.NOT} (${clauses.join(separator)})`;
1578
1625
  }
1579
- function buildScalarOperator(expr, op, val, params, mode, fieldType, dialect) {
1580
- if (val === void 0) return "";
1581
- if (val === null) {
1582
- return handleNullValue(expr, op);
1583
- }
1584
- if (op === Ops.NOT && isPlainObject(val)) {
1585
- return handleNotOperator(expr, val, params, mode, fieldType, dialect);
1626
+ function validateOperatorRequirements(op, mode, dialect) {
1627
+ const STRING_LIKE_OPS = /* @__PURE__ */ new Set([
1628
+ Ops.CONTAINS,
1629
+ Ops.STARTS_WITH,
1630
+ Ops.ENDS_WITH
1631
+ ]);
1632
+ if (STRING_LIKE_OPS.has(op) && !isNotNullish(dialect)) {
1633
+ throw createError(`Like operators require a SQL dialect`, { operator: op });
1586
1634
  }
1587
- if (op === Ops.NOT) {
1588
- const placeholder = params.addAuto(val);
1589
- return `${expr} <> ${placeholder}`;
1635
+ if ((op === Ops.IN || op === Ops.NOT_IN) && !isNotNullish(dialect)) {
1636
+ throw createError(`IN operators require a SQL dialect`, { operator: op });
1590
1637
  }
1591
- if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && isNotNullish(dialect)) {
1592
- const placeholder = params.addAuto(val);
1593
- return caseInsensitiveEquals(expr, placeholder);
1638
+ if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && !isNotNullish(dialect)) {
1639
+ throw createError(`Insensitive equals requires a SQL dialect`, {
1640
+ operator: op
1641
+ });
1594
1642
  }
1643
+ }
1644
+ function routeOperatorHandler(expr, op, val, params, mode, dialect) {
1595
1645
  const STRING_LIKE_OPS = /* @__PURE__ */ new Set([
1596
1646
  Ops.CONTAINS,
1597
1647
  Ops.STARTS_WITH,
1598
1648
  Ops.ENDS_WITH
1599
1649
  ]);
1600
1650
  if (STRING_LIKE_OPS.has(op)) {
1601
- if (!isNotNullish(dialect)) {
1602
- throw createError(`Like operators require a SQL dialect`, {
1603
- operator: op
1604
- });
1605
- }
1606
1651
  return handleLikeOperator(expr, op, val, params, mode, dialect);
1607
1652
  }
1608
1653
  if (op === Ops.IN || op === Ops.NOT_IN) {
1609
- if (!isNotNullish(dialect)) {
1610
- throw createError(`IN operators require a SQL dialect`, { operator: op });
1611
- }
1612
1654
  return handleInOperator(expr, op, val, params, dialect);
1613
1655
  }
1614
- if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && !isNotNullish(dialect)) {
1615
- throw createError(`Insensitive equals requires a SQL dialect`, {
1616
- operator: op
1617
- });
1656
+ if (op === Ops.EQUALS && mode === Modes.INSENSITIVE && isNotNullish(dialect)) {
1657
+ const placeholder = params.addAuto(val);
1658
+ return caseInsensitiveEquals(expr, placeholder);
1659
+ }
1660
+ return null;
1661
+ }
1662
+ function buildScalarOperator(expr, op, val, params, options = {}) {
1663
+ const { mode, fieldType, dialect, depth = 0 } = options;
1664
+ if (val === void 0) return "";
1665
+ if (depth > MAX_NOT_DEPTH) {
1666
+ throw new Error(
1667
+ `NOT operator nesting too deep (max ${MAX_NOT_DEPTH} levels). This usually indicates a circular reference or adversarial input.`
1668
+ );
1669
+ }
1670
+ if (val === null) {
1671
+ return handleNullValue(expr, op);
1672
+ }
1673
+ if (op === Ops.NOT && isPlainObject(val)) {
1674
+ return handleNotOperator(expr, val, params, mode, fieldType, dialect, depth);
1675
+ }
1676
+ if (op === Ops.NOT) {
1677
+ const placeholder = params.addAuto(val);
1678
+ return `${expr} <> ${placeholder}`;
1679
+ }
1680
+ validateOperatorRequirements(op, mode, dialect);
1681
+ const specialHandler = routeOperatorHandler(
1682
+ expr,
1683
+ op,
1684
+ val,
1685
+ params,
1686
+ mode,
1687
+ dialect
1688
+ );
1689
+ if (specialHandler !== null) {
1690
+ return specialHandler;
1618
1691
  }
1619
1692
  return handleComparisonOperator(expr, op, val, params);
1620
1693
  }
@@ -1628,7 +1701,7 @@ function normalizeMode(v) {
1628
1701
  if (v === Modes.DEFAULT) return Modes.DEFAULT;
1629
1702
  return void 0;
1630
1703
  }
1631
- function handleNotOperator(expr, val, params, outerMode, fieldType, dialect) {
1704
+ function handleNotOperator(expr, val, params, outerMode, fieldType, dialect, depth = 0) {
1632
1705
  const innerMode = normalizeMode(val.mode);
1633
1706
  const effectiveMode = innerMode != null ? innerMode : outerMode;
1634
1707
  const entries = Object.entries(val).filter(
@@ -1638,43 +1711,35 @@ function handleNotOperator(expr, val, params, outerMode, fieldType, dialect) {
1638
1711
  if (!isNotNullish(dialect)) {
1639
1712
  const clauses = [];
1640
1713
  for (const [subOp, subVal] of entries) {
1641
- const sub = buildScalarOperator(
1642
- expr,
1643
- subOp,
1644
- subVal,
1645
- params,
1646
- effectiveMode,
1714
+ const sub = buildScalarOperator(expr, subOp, subVal, params, {
1715
+ mode: effectiveMode,
1647
1716
  fieldType,
1648
- void 0
1649
- );
1717
+ dialect: void 0,
1718
+ depth: depth + 1
1719
+ });
1650
1720
  if (sub && sub.trim().length > 0) clauses.push(`(${sub})`);
1651
1721
  }
1652
1722
  if (clauses.length === 0) return "";
1653
1723
  if (clauses.length === 1) return `${SQL_TEMPLATES.NOT} ${clauses[0]}`;
1654
- return `${SQL_TEMPLATES.NOT} (${clauses.join(` ${SQL_TEMPLATES.AND} `)})`;
1724
+ const separator2 = ` ${SQL_TEMPLATES.AND} `;
1725
+ return `${SQL_TEMPLATES.NOT} (${clauses.join(separator2)})`;
1655
1726
  }
1727
+ const separator = ` ${SQL_TEMPLATES.AND} `;
1656
1728
  return buildNotComposite(
1657
1729
  expr,
1658
1730
  val,
1659
1731
  params,
1660
1732
  dialect,
1661
- (e, subOp, subVal, p, d) => buildScalarOperator(e, subOp, subVal, p, effectiveMode, fieldType, d),
1662
- ` ${SQL_TEMPLATES.AND} `
1733
+ (e, subOp, subVal, p, d) => buildScalarOperator(e, subOp, subVal, p, {
1734
+ mode: effectiveMode,
1735
+ fieldType,
1736
+ dialect: d,
1737
+ depth: depth + 1
1738
+ }),
1739
+ separator
1663
1740
  );
1664
1741
  }
1665
- function buildDynamicLikePattern(op, placeholder, dialect) {
1666
- if (dialect === "postgres") {
1667
- switch (op) {
1668
- case Ops.CONTAINS:
1669
- return `('%' || ${placeholder} || '%')`;
1670
- case Ops.STARTS_WITH:
1671
- return `(${placeholder} || '%')`;
1672
- case Ops.ENDS_WITH:
1673
- return `('%' || ${placeholder})`;
1674
- default:
1675
- return placeholder;
1676
- }
1677
- }
1742
+ function buildDynamicLikePattern(op, placeholder) {
1678
1743
  switch (op) {
1679
1744
  case Ops.CONTAINS:
1680
1745
  return `('%' || ${placeholder} || '%')`;
@@ -1690,7 +1755,7 @@ function handleLikeOperator(expr, op, val, params, mode, dialect) {
1690
1755
  if (val === void 0) return "";
1691
1756
  if (isDynamicParameter(val)) {
1692
1757
  const placeholder2 = params.addAuto(val);
1693
- const patternExpr = buildDynamicLikePattern(op, placeholder2, dialect);
1758
+ const patternExpr = buildDynamicLikePattern(op, placeholder2);
1694
1759
  if (mode === Modes.INSENSITIVE) {
1695
1760
  return caseInsensitiveLike(expr, patternExpr, dialect);
1696
1761
  }
@@ -1868,24 +1933,42 @@ function handleArrayIsEmpty(expr, val, dialect) {
1868
1933
  // src/builder/where/operators-json.ts
1869
1934
  var SAFE_JSON_PATH_SEGMENT = /^[a-zA-Z_]\w*$/;
1870
1935
  var MAX_PATH_SEGMENT_LENGTH = 255;
1936
+ var MAX_PATH_SEGMENTS = 100;
1937
+ function sanitizeForError(s) {
1938
+ let result = "";
1939
+ for (let i = 0; i < s.length; i++) {
1940
+ const code = s.charCodeAt(i);
1941
+ if (code >= 0 && code <= 31 || code === 127) {
1942
+ result += `\\x${code.toString(16).padStart(2, "0")}`;
1943
+ } else {
1944
+ result += s[i];
1945
+ }
1946
+ }
1947
+ return result;
1948
+ }
1871
1949
  function validateJsonPathSegments(segments) {
1872
- for (const segment of segments) {
1950
+ if (segments.length > MAX_PATH_SEGMENTS) {
1951
+ throw createError(`JSON path too long: max ${MAX_PATH_SEGMENTS} segments`, {
1952
+ operator: Ops.PATH
1953
+ });
1954
+ }
1955
+ for (let i = 0; i < segments.length; i++) {
1956
+ const segment = segments[i];
1873
1957
  if (typeof segment !== "string") {
1874
- throw createError("JSON path segments must be strings", {
1875
- operator: Ops.PATH,
1876
- value: segment
1958
+ throw createError(`JSON path segment at index ${i} must be string`, {
1959
+ operator: Ops.PATH
1877
1960
  });
1878
1961
  }
1879
1962
  if (segment.length > MAX_PATH_SEGMENT_LENGTH) {
1880
1963
  throw createError(
1881
- `JSON path segment too long: max ${MAX_PATH_SEGMENT_LENGTH} characters`,
1882
- { operator: Ops.PATH, value: `[${segment.length} chars]` }
1964
+ `JSON path segment at index ${i} too long: max ${MAX_PATH_SEGMENT_LENGTH} characters`,
1965
+ { operator: Ops.PATH }
1883
1966
  );
1884
1967
  }
1885
1968
  if (!SAFE_JSON_PATH_SEGMENT.test(segment)) {
1886
1969
  throw createError(
1887
- `Invalid JSON path segment: '${segment}'. Must be alphanumeric with underscores, starting with letter or underscore.`,
1888
- { operator: Ops.PATH, value: segment }
1970
+ `Invalid JSON path segment at index ${i}: '${sanitizeForError(segment)}'`,
1971
+ { operator: Ops.PATH }
1889
1972
  );
1890
1973
  }
1891
1974
  }
@@ -1976,7 +2059,7 @@ function freezeJoins(items) {
1976
2059
  function isListRelation(fieldType) {
1977
2060
  return typeof fieldType === "string" && fieldType.endsWith("[]");
1978
2061
  }
1979
- function buildToOneNullCheck(field, parentAlias, relTable, relAlias, join, wantNull) {
2062
+ function buildToOneNullCheck(field, parentModel, parentAlias, relTable, relAlias, join, wantNull) {
1980
2063
  const isLocal = field.isForeignKeyLocal === true;
1981
2064
  const fkFields = normalizeKeyList(field.foreignKey);
1982
2065
  if (isLocal) {
@@ -1986,8 +2069,7 @@ function buildToOneNullCheck(field, parentAlias, relTable, relAlias, join, wantN
1986
2069
  });
1987
2070
  }
1988
2071
  const parts = fkFields.map((fk) => {
1989
- const safe = fk.replace(/"/g, '""');
1990
- const expr = `${parentAlias}."${safe}"`;
2072
+ const expr = `${parentAlias}.${quoteColumn(parentModel, fk)}`;
1991
2073
  return wantNull ? `${expr} ${SQL_TEMPLATES.IS_NULL}` : `${expr} ${SQL_TEMPLATES.IS_NOT_NULL}`;
1992
2074
  });
1993
2075
  if (parts.length === 1) return parts[0];
@@ -2004,6 +2086,35 @@ function buildToOneNotExistsMatch(relTable, relAlias, join, sub) {
2004
2086
  const joins = sub.joins.length > 0 ? ` ${sub.joins.join(" ")}` : "";
2005
2087
  return `${SQL_TEMPLATES.NOT} EXISTS (${SQL_TEMPLATES.SELECT} 1 ${SQL_TEMPLATES.FROM} ${relTable} ${relAlias}${joins} ${SQL_TEMPLATES.WHERE} ${join} ${SQL_TEMPLATES.AND} ${sub.clause})`;
2006
2088
  }
2089
+ function tryOptimizeNoneFilter(noneValue, ctx, relModel, relTable, relAlias, join, sub) {
2090
+ const isEmptyFilter = isPlainObject(noneValue) && Object.keys(noneValue).length === 0;
2091
+ const canOptimize = !ctx.isSubquery && isEmptyFilter && sub.clause === DEFAULT_WHERE_CLAUSE && sub.joins.length === 0;
2092
+ if (!canOptimize) return null;
2093
+ const checkField = relModel.fields.find(
2094
+ (f) => !f.isRelation && f.isRequired && f.name !== "id"
2095
+ ) || relModel.fields.find((f) => !f.isRelation && f.name === "id");
2096
+ if (!checkField) return null;
2097
+ const leftJoinSql = `LEFT JOIN ${relTable} ${relAlias} ON ${join}`;
2098
+ const whereClause = `${relAlias}.${quoteColumn(relModel, checkField.name)} IS NULL`;
2099
+ return Object.freeze({
2100
+ clause: whereClause,
2101
+ joins: freezeJoins([leftJoinSql])
2102
+ });
2103
+ }
2104
+ function processRelationFilter(key, wrap, args) {
2105
+ const { value, fieldName, ctx, relAlias, relModel, whereBuilder } = args;
2106
+ const raw = value[key];
2107
+ if (raw === void 0 || raw === null) return null;
2108
+ const sub = whereBuilder.build(raw, __spreadProps(__spreadValues({}, ctx), {
2109
+ alias: relAlias,
2110
+ model: relModel,
2111
+ path: [...ctx.path, fieldName, key],
2112
+ isSubquery: true,
2113
+ depth: ctx.depth + 1
2114
+ }));
2115
+ const j = sub.joins.length > 0 ? ` ${sub.joins.join(" ")}` : "";
2116
+ return wrap(sub.clause, j);
2117
+ }
2007
2118
  function buildListRelationFilters(args) {
2008
2119
  const {
2009
2120
  fieldName,
@@ -2024,21 +2135,16 @@ function buildListRelationFilters(args) {
2024
2135
  isSubquery: true,
2025
2136
  depth: ctx.depth + 1
2026
2137
  }));
2027
- const isEmptyFilter = isPlainObject(noneValue) && Object.keys(noneValue).length === 0;
2028
- const canOptimize = !ctx.isSubquery && isEmptyFilter && sub.clause === DEFAULT_WHERE_CLAUSE && sub.joins.length === 0;
2029
- if (canOptimize) {
2030
- const checkField = relModel.fields.find(
2031
- (f) => !f.isRelation && f.isRequired && f.name !== "id"
2032
- ) || relModel.fields.find((f) => !f.isRelation && f.name === "id");
2033
- if (checkField) {
2034
- const leftJoinSql = `LEFT JOIN ${relTable} ${relAlias} ON ${join}`;
2035
- const whereClause = `${relAlias}.${quote2(checkField.name)} IS NULL`;
2036
- return Object.freeze({
2037
- clause: whereClause,
2038
- joins: freezeJoins([leftJoinSql])
2039
- });
2040
- }
2041
- }
2138
+ const optimized = tryOptimizeNoneFilter(
2139
+ noneValue,
2140
+ ctx,
2141
+ relModel,
2142
+ relTable,
2143
+ relAlias,
2144
+ join,
2145
+ sub
2146
+ );
2147
+ if (optimized) return optimized;
2042
2148
  }
2043
2149
  const filters = [
2044
2150
  {
@@ -2059,17 +2165,8 @@ function buildListRelationFilters(args) {
2059
2165
  ];
2060
2166
  const clauses = [];
2061
2167
  for (const { key, wrap } of filters) {
2062
- const raw = value[key];
2063
- if (raw === void 0 || raw === null) continue;
2064
- const sub = whereBuilder.build(raw, __spreadProps(__spreadValues({}, ctx), {
2065
- alias: relAlias,
2066
- model: relModel,
2067
- path: [...ctx.path, fieldName, key],
2068
- isSubquery: true,
2069
- depth: ctx.depth + 1
2070
- }));
2071
- const j = sub.joins.length > 0 ? ` ${sub.joins.join(" ")}` : "";
2072
- clauses.push(wrap(sub.clause, j));
2168
+ const clause = processRelationFilter(key, wrap, args);
2169
+ if (clause) clauses.push(clause);
2073
2170
  }
2074
2171
  if (clauses.length === 0) {
2075
2172
  throw createError(
@@ -2125,6 +2222,7 @@ function buildToOneRelationFilters(args) {
2125
2222
  const wantNull = filterKey === "is";
2126
2223
  const clause2 = buildToOneNullCheck(
2127
2224
  field,
2225
+ ctx.model,
2128
2226
  ctx.alias,
2129
2227
  relTable,
2130
2228
  relAlias,
@@ -2354,7 +2452,8 @@ function buildWhereInternal(where, ctx, builder) {
2354
2452
  }
2355
2453
  const allJoins = [];
2356
2454
  const clauses = [];
2357
- for (const [key, value] of Object.entries(where)) {
2455
+ for (const key in where) {
2456
+ const value = where[key];
2358
2457
  if (value === void 0) continue;
2359
2458
  const result = buildWhereEntry(key, value, ctx, builder);
2360
2459
  appendResult(result, clauses, allJoins);
@@ -2469,15 +2568,11 @@ function buildOperator(expr, op, val, ctx, mode, fieldType) {
2469
2568
  if (fieldType && isJsonType(fieldType) && JSON_OPS.has(op)) {
2470
2569
  return buildJsonOperator(expr, op, val, ctx.params, ctx.dialect);
2471
2570
  }
2472
- return buildScalarOperator(
2473
- expr,
2474
- op,
2475
- val,
2476
- ctx.params,
2571
+ return buildScalarOperator(expr, op, val, ctx.params, {
2477
2572
  mode,
2478
2573
  fieldType,
2479
- ctx.dialect
2480
- );
2574
+ dialect: ctx.dialect
2575
+ });
2481
2576
  }
2482
2577
 
2483
2578
  // src/builder/shared/alias-generator.ts
@@ -2591,49 +2686,50 @@ function validateState(params, mappings, index) {
2591
2686
  validateMappings(mappings);
2592
2687
  assertNextIndexMatches(mappings.length, index);
2593
2688
  }
2594
- function createStoreInternal(startIndex, initialParams = [], initialMappings = []) {
2595
- let index = startIndex;
2596
- const params = initialParams.length > 0 ? initialParams.slice() : [];
2597
- const mappings = initialMappings.length > 0 ? initialMappings.slice() : [];
2689
+ function buildDynamicNameIndex(mappings) {
2598
2690
  const dynamicNameToIndex = /* @__PURE__ */ new Map();
2599
- for (let i = 0; i < mappings.length; i++) {
2600
- const m = mappings[i];
2691
+ for (const m of mappings) {
2601
2692
  if (typeof m.dynamicName === "string") {
2602
2693
  dynamicNameToIndex.set(m.dynamicName.trim(), m.index);
2603
2694
  }
2604
2695
  }
2605
- let dirty = true;
2606
- let cachedSnapshot = null;
2607
- let frozenParams = null;
2608
- let frozenMappings = null;
2609
- function assertCanAdd() {
2610
- if (index > MAX_PARAM_INDEX) {
2611
- throw new Error(
2612
- `CRITICAL: Cannot add param - would overflow MAX_SAFE_INTEGER. Current index: ${index}`
2613
- );
2614
- }
2615
- }
2616
- function format(position) {
2617
- return `$${position}`;
2696
+ return dynamicNameToIndex;
2697
+ }
2698
+ function assertCanAddParam(currentIndex) {
2699
+ if (currentIndex > MAX_PARAM_INDEX) {
2700
+ throw new Error(
2701
+ `CRITICAL: Cannot add param - would overflow MAX_SAFE_INTEGER. Current index: ${currentIndex}`
2702
+ );
2618
2703
  }
2619
- function normalizeDynamicName(dynamicName) {
2620
- const dn = dynamicName.trim();
2621
- if (dn.length === 0) {
2622
- throw new Error("CRITICAL: dynamicName cannot be empty");
2623
- }
2624
- return dn;
2704
+ }
2705
+ function formatPosition(position) {
2706
+ return `$${position}`;
2707
+ }
2708
+ function validateDynamicName(dynamicName) {
2709
+ const dn = dynamicName.trim();
2710
+ if (dn.length === 0) {
2711
+ throw new Error("CRITICAL: dynamicName cannot be empty");
2625
2712
  }
2713
+ return dn;
2714
+ }
2715
+ function createStoreInternal(startIndex, initialParams = [], initialMappings = []) {
2716
+ let index = startIndex;
2717
+ const params = initialParams.length > 0 ? initialParams.slice() : [];
2718
+ const mappings = initialMappings.length > 0 ? initialMappings.slice() : [];
2719
+ const dynamicNameToIndex = buildDynamicNameIndex(mappings);
2720
+ let dirty = true;
2721
+ let cachedSnapshot = null;
2626
2722
  function addDynamic(dynamicName) {
2627
- const dn = normalizeDynamicName(dynamicName);
2723
+ const dn = validateDynamicName(dynamicName);
2628
2724
  const existing = dynamicNameToIndex.get(dn);
2629
- if (existing !== void 0) return format(existing);
2725
+ if (existing !== void 0) return formatPosition(existing);
2630
2726
  const position = index;
2631
2727
  dynamicNameToIndex.set(dn, position);
2632
2728
  params.push(void 0);
2633
2729
  mappings.push({ index: position, dynamicName: dn });
2634
2730
  index++;
2635
2731
  dirty = true;
2636
- return format(position);
2732
+ return formatPosition(position);
2637
2733
  }
2638
2734
  function addStatic(value) {
2639
2735
  const position = index;
@@ -2642,10 +2738,10 @@ function createStoreInternal(startIndex, initialParams = [], initialMappings = [
2642
2738
  mappings.push({ index: position, value: normalizedValue });
2643
2739
  index++;
2644
2740
  dirty = true;
2645
- return format(position);
2741
+ return formatPosition(position);
2646
2742
  }
2647
2743
  function add(value, dynamicName) {
2648
- assertCanAdd();
2744
+ assertCanAddParam(index);
2649
2745
  return dynamicName === void 0 ? addStatic(value) : addDynamic(dynamicName);
2650
2746
  }
2651
2747
  function addAuto(value) {
@@ -2657,17 +2753,13 @@ function createStoreInternal(startIndex, initialParams = [], initialMappings = [
2657
2753
  }
2658
2754
  function snapshot() {
2659
2755
  if (!dirty && cachedSnapshot) return cachedSnapshot;
2660
- if (!frozenParams) frozenParams = Object.freeze(params.slice());
2661
- if (!frozenMappings) frozenMappings = Object.freeze(mappings.slice());
2662
2756
  const snap = {
2663
2757
  index,
2664
- params: frozenParams,
2665
- mappings: frozenMappings
2758
+ params: params.slice(),
2759
+ mappings: mappings.slice()
2666
2760
  };
2667
2761
  cachedSnapshot = snap;
2668
2762
  dirty = false;
2669
- frozenParams = null;
2670
- frozenMappings = null;
2671
2763
  return snap;
2672
2764
  }
2673
2765
  return {
@@ -2870,6 +2962,17 @@ function buildRelationSelect(relArgs, relModel, relAlias) {
2870
2962
  var MAX_INCLUDE_DEPTH = 10;
2871
2963
  var MAX_TOTAL_SUBQUERIES = 100;
2872
2964
  var MAX_TOTAL_INCLUDES = 50;
2965
+ function buildIncludeScope(includePath) {
2966
+ if (includePath.length === 0) return "include";
2967
+ let scope = "include";
2968
+ for (let i = 0; i < includePath.length; i++) {
2969
+ scope += `.${includePath[i]}`;
2970
+ if (i < includePath.length - 1) {
2971
+ scope += ".include";
2972
+ }
2973
+ }
2974
+ return scope;
2975
+ }
2873
2976
  function getRelationTableReference(relModel, dialect) {
2874
2977
  return buildTableReference(
2875
2978
  SQL_TEMPLATES.PUBLIC_SCHEMA,
@@ -3013,19 +3116,11 @@ function finalizeOrderByForInclude(args) {
3013
3116
  }
3014
3117
  function buildSelectWithNestedIncludes(relArgs, relModel, relAlias, ctx) {
3015
3118
  let relSelect = buildRelationSelect(relArgs, relModel, relAlias);
3016
- const nestedIncludes = isPlainObject(relArgs) ? buildIncludeSqlInternal(
3017
- relArgs,
3018
- relModel,
3019
- ctx.schemas,
3020
- ctx.schemaByName,
3021
- relAlias,
3022
- ctx.aliasGen,
3023
- ctx.params,
3024
- ctx.dialect,
3025
- ctx.visitPath || [],
3026
- (ctx.depth || 0) + 1,
3027
- ctx.stats
3028
- ) : [];
3119
+ const nestedIncludes = isPlainObject(relArgs) ? buildIncludeSqlInternal(relArgs, __spreadProps(__spreadValues({}, ctx), {
3120
+ model: relModel,
3121
+ parentAlias: relAlias,
3122
+ depth: (ctx.depth || 0) + 1
3123
+ })) : [];
3029
3124
  if (isNonEmptyArray(nestedIncludes)) {
3030
3125
  const emptyJson = ctx.dialect === "postgres" ? `'[]'::json` : `json('[]')`;
3031
3126
  const nestedSelects = nestedIncludes.map(
@@ -3080,6 +3175,7 @@ function buildOneToOneIncludeSql(args) {
3080
3175
  whereClause: args.whereClause
3081
3176
  });
3082
3177
  if (args.orderBySql) sql += ` ${SQL_TEMPLATES.ORDER_BY} ${args.orderBySql}`;
3178
+ const scopeBase = buildIncludeScope(args.ctx.includePath);
3083
3179
  if (isNotNullish(args.takeVal)) {
3084
3180
  return appendLimitOffset(
3085
3181
  sql,
@@ -3087,15 +3183,10 @@ function buildOneToOneIncludeSql(args) {
3087
3183
  args.ctx.params,
3088
3184
  args.takeVal,
3089
3185
  args.skipVal,
3090
- `include.${args.relName}`
3186
+ scopeBase
3091
3187
  );
3092
3188
  }
3093
- return limitOneSql(
3094
- sql,
3095
- args.ctx.params,
3096
- args.skipVal,
3097
- `include.${args.relName}`
3098
- );
3189
+ return limitOneSql(sql, args.ctx.params, args.skipVal, scopeBase);
3099
3190
  }
3100
3191
  function buildListIncludeSpec(args) {
3101
3192
  const rowExpr = jsonBuildObject(args.relSelect, args.ctx.dialect);
@@ -3123,13 +3214,14 @@ function buildListIncludeSpec(args) {
3123
3214
  whereClause: args.whereClause
3124
3215
  });
3125
3216
  if (args.orderBySql) base += ` ${SQL_TEMPLATES.ORDER_BY} ${args.orderBySql}`;
3217
+ const scopeBase = buildIncludeScope(args.ctx.includePath);
3126
3218
  base = appendLimitOffset(
3127
3219
  base,
3128
3220
  args.ctx.dialect,
3129
3221
  args.ctx.params,
3130
3222
  args.takeVal,
3131
3223
  args.skipVal,
3132
- `include.${args.relName}`
3224
+ scopeBase
3133
3225
  );
3134
3226
  const selectExpr = jsonAgg("row", args.ctx.dialect);
3135
3227
  const sql = `${SQL_TEMPLATES.SELECT} ${selectExpr} ${SQL_TEMPLATES.FROM} (${base}) ${SQL_TEMPLATES.AS} ${rowAlias}`;
@@ -3178,7 +3270,6 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3178
3270
  );
3179
3271
  if (!isList) {
3180
3272
  const sql = buildOneToOneIncludeSql({
3181
- relName,
3182
3273
  relTable,
3183
3274
  relAlias,
3184
3275
  joins: whereParts.joins,
@@ -3206,16 +3297,22 @@ function buildSingleInclude(relName, relArgs, field, relModel, ctx) {
3206
3297
  ctx
3207
3298
  });
3208
3299
  }
3209
- function buildIncludeSqlInternal(args, model, schemas, schemaByName, parentAlias, aliasGen, params, dialect, visitPath = [], depth = 0, stats) {
3210
- if (!stats) stats = { totalIncludes: 0, totalSubqueries: 0, maxDepth: 0 };
3211
- if (depth > MAX_INCLUDE_DEPTH) {
3300
+ function buildIncludeSqlInternal(args, ctx) {
3301
+ const stats = ctx.stats || {
3302
+ totalIncludes: 0,
3303
+ totalSubqueries: 0,
3304
+ maxDepth: 0
3305
+ };
3306
+ const depth = ctx.depth || 0;
3307
+ const visitPath = ctx.visitPath || [];
3308
+ if (depth > MAX_INCLUDE_DEPTH) {
3212
3309
  throw new Error(
3213
3310
  `Maximum include depth of ${MAX_INCLUDE_DEPTH} exceeded. Path: ${visitPath.join(" -> ")}. Deep includes cause exponential SQL complexity and performance issues.`
3214
3311
  );
3215
3312
  }
3216
3313
  stats.maxDepth = Math.max(stats.maxDepth, depth);
3217
3314
  const includes = [];
3218
- const entries = relationEntriesFromArgs(args, model);
3315
+ const entries = relationEntriesFromArgs(args, ctx.model);
3219
3316
  for (const [relName, relArgs] of entries) {
3220
3317
  if (relArgs === false) continue;
3221
3318
  stats.totalIncludes++;
@@ -3230,8 +3327,12 @@ function buildIncludeSqlInternal(args, model, schemas, schemaByName, parentAlias
3230
3327
  `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.`
3231
3328
  );
3232
3329
  }
3233
- const resolved = resolveRelationOrThrow(model, schemaByName, relName);
3234
- const relationPath = `${model.name}.${relName}`;
3330
+ const resolved = resolveRelationOrThrow(
3331
+ ctx.model,
3332
+ ctx.schemaByName,
3333
+ relName
3334
+ );
3335
+ const relationPath = `${ctx.model.name}.${relName}`;
3235
3336
  const currentPath = [...visitPath, relationPath];
3236
3337
  if (visitPath.includes(relationPath)) {
3237
3338
  throw new Error(
@@ -3246,19 +3347,14 @@ function buildIncludeSqlInternal(args, model, schemas, schemaByName, parentAlias
3246
3347
  `Include too deeply nested: model '${resolved.relModel.name}' appears ${modelOccurrences} times in path: ${currentPath.join(" -> ")}`
3247
3348
  );
3248
3349
  }
3350
+ const nextIncludePath = [...ctx.includePath, relName];
3249
3351
  includes.push(
3250
- buildSingleInclude(relName, relArgs, resolved.field, resolved.relModel, {
3251
- model,
3252
- schemas,
3253
- schemaByName,
3254
- parentAlias,
3255
- aliasGen,
3256
- dialect,
3257
- params,
3352
+ buildSingleInclude(relName, relArgs, resolved.field, resolved.relModel, __spreadProps(__spreadValues({}, ctx), {
3353
+ includePath: nextIncludePath,
3258
3354
  visitPath: currentPath,
3259
3355
  depth: depth + 1,
3260
3356
  stats
3261
- })
3357
+ }))
3262
3358
  );
3263
3359
  }
3264
3360
  return includes;
@@ -3272,8 +3368,7 @@ function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
3272
3368
  };
3273
3369
  const schemaByName = /* @__PURE__ */ new Map();
3274
3370
  for (const m of schemas) schemaByName.set(m.name, m);
3275
- return buildIncludeSqlInternal(
3276
- args,
3371
+ return buildIncludeSqlInternal(args, {
3277
3372
  model,
3278
3373
  schemas,
3279
3374
  schemaByName,
@@ -3281,10 +3376,11 @@ function buildIncludeSql(args, model, schemas, parentAlias, params, dialect) {
3281
3376
  aliasGen,
3282
3377
  params,
3283
3378
  dialect,
3284
- [],
3285
- 0,
3379
+ includePath: [],
3380
+ visitPath: [],
3381
+ depth: 0,
3286
3382
  stats
3287
- );
3383
+ });
3288
3384
  }
3289
3385
  function resolveCountRelationOrThrow(relName, model, schemaByName) {
3290
3386
  const relationSet = getRelationFieldSet(model);
@@ -3329,8 +3425,7 @@ function resolveCountKeyPairs(field) {
3329
3425
  if (fkFields.length === 0) {
3330
3426
  throw new Error("Relation count requires foreignKey");
3331
3427
  }
3332
- const refsRaw = field.references;
3333
- const refs = normalizeKeyList(refsRaw);
3428
+ const refs = normalizeKeyList(field.references);
3334
3429
  const refFields = refs.length > 0 ? refs : defaultReferencesForCount(fkFields.length);
3335
3430
  if (refFields.length !== fkFields.length) {
3336
3431
  throw new Error(
@@ -3465,139 +3560,146 @@ function finalizeSql(sql, params, dialect) {
3465
3560
  paramMappings: snapshot.mappings
3466
3561
  };
3467
3562
  }
3468
- function parseSimpleScalarSelect(select, fromAlias) {
3469
- const raw = select.trim();
3470
- if (raw.length === 0) return [];
3471
- const fromLower = fromAlias.toLowerCase();
3472
- const parts = raw.split(SQL_SEPARATORS.FIELD_LIST);
3473
- const names = [];
3474
- const isIdent = (s) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(s);
3475
- const readIdentOrQuoted = (s, start) => {
3476
- const n = s.length;
3477
- if (start >= n) return { text: "", next: start, quoted: false };
3478
- if (s.charCodeAt(start) === 34) {
3479
- let i2 = start + 1;
3480
- let out = "";
3481
- let saw = false;
3482
- while (i2 < n) {
3483
- const c = s.charCodeAt(i2);
3484
- if (c === 34) {
3485
- const next = i2 + 1;
3486
- if (next < n && s.charCodeAt(next) === 34) {
3487
- out += '"';
3488
- saw = true;
3489
- i2 += 2;
3490
- continue;
3491
- }
3492
- if (!saw)
3493
- throw new Error(
3494
- `sqlite distinct emulation: empty quoted identifier in: ${s}`
3495
- );
3496
- return { text: out, next: i2 + 1, quoted: true };
3497
- }
3498
- out += s[i2];
3499
- saw = true;
3500
- i2++;
3501
- }
3502
- throw new Error(
3503
- `sqlite distinct emulation: unterminated quoted identifier in: ${s}`
3504
- );
3505
- }
3506
- let i = start;
3507
- while (i < n) {
3508
- const c = s.charCodeAt(i);
3509
- if (c === 32 || c === 9) break;
3510
- if (c === 46) break;
3511
- i++;
3512
- }
3513
- return { text: s.slice(start, i), next: i, quoted: false };
3514
- };
3515
- const skipSpaces = (s, i) => {
3516
- while (i < s.length) {
3517
- const c = s.charCodeAt(i);
3518
- if (c !== 32 && c !== 9) break;
3563
+ function isIdent(s) {
3564
+ return /^[A-Za-z_]\w*$/.test(s);
3565
+ }
3566
+ function skipSpaces(s, i) {
3567
+ while (i < s.length) {
3568
+ const c = s.charCodeAt(i);
3569
+ if (c !== 32 && c !== 9) break;
3570
+ i++;
3571
+ }
3572
+ return i;
3573
+ }
3574
+ function parseQuotedIdentifier(s, start) {
3575
+ const n = s.length;
3576
+ let i = start + 1;
3577
+ let out = "";
3578
+ let saw = false;
3579
+ while (i < n) {
3580
+ const c = s.charCodeAt(i);
3581
+ if (c !== 34) {
3582
+ out += s[i];
3583
+ saw = true;
3519
3584
  i++;
3585
+ continue;
3520
3586
  }
3521
- return i;
3522
- };
3523
- for (let idx = 0; idx < parts.length; idx++) {
3524
- const p = parts[idx].trim();
3525
- if (p.length === 0) continue;
3526
- let i = 0;
3527
- i = skipSpaces(p, i);
3528
- const a = readIdentOrQuoted(p, i);
3529
- const actualAlias = a.text.toLowerCase();
3530
- if (!isIdent(a.text)) {
3531
- throw new Error(
3532
- `sqlite distinct emulation requires scalar select fields to be simple columns (alias.column). Got: ${p}`
3533
- );
3534
- }
3535
- if (actualAlias !== fromLower) {
3536
- throw new Error(`Expected alias '${fromAlias}', got '${a.text}' in: ${p}`);
3587
+ const next = i + 1;
3588
+ if (next < n && s.charCodeAt(next) === 34) {
3589
+ out += '"';
3590
+ saw = true;
3591
+ i += 2;
3592
+ continue;
3537
3593
  }
3538
- i = a.next;
3539
- if (i >= p.length || p.charCodeAt(i) !== 46) {
3594
+ if (!saw) {
3540
3595
  throw new Error(
3541
- `sqlite distinct emulation requires scalar select fields to be simple columns (alias.column). Got: ${p}`
3596
+ `sqlite distinct emulation: empty quoted identifier in: ${s}`
3542
3597
  );
3543
3598
  }
3599
+ return { text: out, next: i + 1, quoted: true };
3600
+ }
3601
+ throw new Error(
3602
+ `sqlite distinct emulation: unterminated quoted identifier in: ${s}`
3603
+ );
3604
+ }
3605
+ function parseUnquotedIdentifier(s, start) {
3606
+ const n = s.length;
3607
+ let i = start;
3608
+ while (i < n) {
3609
+ const c = s.charCodeAt(i);
3610
+ if (c === 32 || c === 9 || c === 46) break;
3544
3611
  i++;
3545
- i = skipSpaces(p, i);
3546
- const colPart = readIdentOrQuoted(p, i);
3547
- const columnName = colPart.text.trim();
3548
- if (columnName.length === 0) {
3549
- throw new Error(`Failed to parse selected column name from: ${p}`);
3550
- }
3551
- i = colPart.next;
3552
- i = skipSpaces(p, i);
3553
- let outAlias = "";
3554
- if (i < p.length) {
3555
- const rest = p.slice(i).trim();
3556
- if (rest.length > 0) {
3557
- const m = rest.match(/^AS\s+/i);
3558
- if (!m) {
3559
- throw new Error(
3560
- `sqlite distinct emulation requires scalar select fields to be simple columns (optionally with AS). Got: ${p}`
3561
- );
3562
- }
3563
- let j = i;
3564
- j = skipSpaces(p, j);
3565
- if (!/^AS\b/i.test(p.slice(j))) {
3566
- throw new Error(`Failed to parse AS in: ${p}`);
3567
- }
3568
- j += 2;
3569
- j = skipSpaces(p, j);
3570
- const out = readIdentOrQuoted(p, j);
3571
- outAlias = out.text.trim();
3572
- if (outAlias.length === 0) {
3573
- throw new Error(`Failed to parse output alias from: ${p}`);
3574
- }
3575
- j = skipSpaces(p, out.next);
3576
- if (j !== p.length) {
3577
- throw new Error(
3578
- `sqlite distinct emulation requires scalar select fields to be simple columns (optionally with AS). Got: ${p}`
3579
- );
3580
- }
3581
- }
3582
- }
3583
- const name = outAlias.length > 0 ? outAlias : columnName;
3584
- names.push(name);
3585
3612
  }
3586
- return names;
3613
+ return { text: s.slice(start, i), next: i, quoted: false };
3614
+ }
3615
+ function readIdentOrQuoted(s, start) {
3616
+ const n = s.length;
3617
+ if (start >= n) return { text: "", next: start, quoted: false };
3618
+ if (s.charCodeAt(start) === 34) {
3619
+ return parseQuotedIdentifier(s, start);
3620
+ }
3621
+ return parseUnquotedIdentifier(s, start);
3622
+ }
3623
+ function parseOutputAlias(p, i) {
3624
+ if (i >= p.length) return "";
3625
+ const rest = p.slice(i).trim();
3626
+ if (rest.length === 0) return "";
3627
+ const m = rest.match(/^AS\s+/i);
3628
+ if (!m) {
3629
+ throw new Error(
3630
+ `sqlite distinct emulation requires scalar select fields to be simple columns (optionally with AS). Got: ${p}`
3631
+ );
3632
+ }
3633
+ let j = i;
3634
+ j = skipSpaces(p, j);
3635
+ if (!/^AS\b/i.test(p.slice(j))) {
3636
+ throw new Error(`Failed to parse AS in: ${p}`);
3637
+ }
3638
+ j += 2;
3639
+ j = skipSpaces(p, j);
3640
+ const out = readIdentOrQuoted(p, j);
3641
+ const outAlias = out.text.trim();
3642
+ if (outAlias.length === 0) {
3643
+ throw new Error(`Failed to parse output alias from: ${p}`);
3644
+ }
3645
+ j = skipSpaces(p, out.next);
3646
+ if (j !== p.length) {
3647
+ throw new Error(
3648
+ `sqlite distinct emulation requires scalar select fields to be simple columns (optionally with AS). Got: ${p}`
3649
+ );
3650
+ }
3651
+ return outAlias;
3652
+ }
3653
+ function parseSelectField(p, fromAlias) {
3654
+ const fromLower = fromAlias.toLowerCase();
3655
+ let i = 0;
3656
+ i = skipSpaces(p, i);
3657
+ const a = readIdentOrQuoted(p, i);
3658
+ const actualAlias = a.text.toLowerCase();
3659
+ if (!isIdent(a.text)) {
3660
+ throw new Error(
3661
+ `sqlite distinct emulation requires scalar select fields to be simple columns (alias.column). Got: ${p}`
3662
+ );
3663
+ }
3664
+ if (actualAlias !== fromLower) {
3665
+ throw new Error(`Expected alias '${fromAlias}', got '${a.text}' in: ${p}`);
3666
+ }
3667
+ i = a.next;
3668
+ if (i >= p.length || p.charCodeAt(i) !== 46) {
3669
+ throw new Error(
3670
+ `sqlite distinct emulation requires scalar select fields to be simple columns (alias.column). Got: ${p}`
3671
+ );
3672
+ }
3673
+ i++;
3674
+ i = skipSpaces(p, i);
3675
+ const colPart = readIdentOrQuoted(p, i);
3676
+ const columnName = colPart.text.trim();
3677
+ if (columnName.length === 0) {
3678
+ throw new Error(`Failed to parse selected column name from: ${p}`);
3679
+ }
3680
+ i = colPart.next;
3681
+ i = skipSpaces(p, i);
3682
+ const outAlias = parseOutputAlias(p, i);
3683
+ return outAlias.length > 0 ? outAlias : columnName;
3587
3684
  }
3588
- function replaceOrderByAlias(orderBy, fromAlias, outerAlias) {
3589
- const src = String(fromAlias);
3590
- if (src.length === 0) return orderBy;
3591
- const escaped = src.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3592
- const re = new RegExp(`\\b${escaped}\\.`, "gi");
3593
- return orderBy.replace(re, outerAlias + ".");
3685
+ function parseSimpleScalarSelect(select, fromAlias) {
3686
+ const raw = select.trim();
3687
+ if (raw.length === 0) return [];
3688
+ const parts = raw.split(SQL_SEPARATORS.FIELD_LIST);
3689
+ const names = [];
3690
+ for (const p of parts) {
3691
+ const trimmed = p.trim();
3692
+ if (trimmed.length === 0) continue;
3693
+ names.push(parseSelectField(trimmed, fromAlias));
3694
+ }
3695
+ return names;
3594
3696
  }
3595
3697
  function buildDistinctColumns(distinct, fromAlias, model) {
3596
3698
  return distinct.map((f) => col(fromAlias, f, model)).join(SQL_SEPARATORS.FIELD_LIST);
3597
3699
  }
3598
3700
  function buildOutputColumns(scalarNames, includeNames, hasCount) {
3599
3701
  const outputCols = hasCount ? [...scalarNames, ...includeNames, "_count"] : [...scalarNames, ...includeNames];
3600
- const formatted = outputCols.map((n) => quote2(n)).join(SQL_SEPARATORS.FIELD_LIST);
3702
+ const formatted = outputCols.map((n) => quote(n)).join(SQL_SEPARATORS.FIELD_LIST);
3601
3703
  if (!isNonEmptyString(formatted)) {
3602
3704
  throw new Error("distinct emulation requires at least one output column");
3603
3705
  }
@@ -3614,6 +3716,52 @@ function buildWindowOrder(args) {
3614
3716
  const idTiebreaker = idField ? ", " + col(fromAlias, "id", model) + " ASC" : "";
3615
3717
  return baseOrder + idTiebreaker;
3616
3718
  }
3719
+ function extractDistinctOrderEntries(spec) {
3720
+ if (isNotNullish(spec.args.orderBy)) {
3721
+ const normalized = normalizeOrderByInput(
3722
+ spec.args.orderBy,
3723
+ parseOrderByValue
3724
+ );
3725
+ const entries = [];
3726
+ for (const item of normalized) {
3727
+ for (const field in item) {
3728
+ if (!Object.prototype.hasOwnProperty.call(item, field)) continue;
3729
+ const value = item[field];
3730
+ if (typeof value === "string") {
3731
+ entries.push({ field, direction: value });
3732
+ } else {
3733
+ const obj = value;
3734
+ entries.push({ field, direction: obj.sort, nulls: obj.nulls });
3735
+ }
3736
+ }
3737
+ }
3738
+ if (entries.length > 0) return entries;
3739
+ }
3740
+ if (isNotNullish(spec.distinct) && isNonEmptyArray(spec.distinct)) {
3741
+ return [...spec.distinct].map((f) => ({
3742
+ field: f,
3743
+ direction: "asc"
3744
+ }));
3745
+ }
3746
+ return [];
3747
+ }
3748
+ function buildFieldNameOrderBy(entries, alias) {
3749
+ if (entries.length === 0) return "";
3750
+ const out = [];
3751
+ for (const e of entries) {
3752
+ const dir = e.direction.toUpperCase();
3753
+ const c = `${alias}.${quote(e.field)}`;
3754
+ if (isNotNullish(e.nulls)) {
3755
+ const isNullExpr = `(${c} IS NULL)`;
3756
+ const nullRankDir = e.nulls === "first" ? "DESC" : "ASC";
3757
+ out.push(isNullExpr + " " + nullRankDir);
3758
+ out.push(c + " " + dir);
3759
+ continue;
3760
+ }
3761
+ out.push(c + " " + dir);
3762
+ }
3763
+ return out.join(SQL_SEPARATORS.ORDER_BY);
3764
+ }
3617
3765
  function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
3618
3766
  var _a, _b;
3619
3767
  const { includes, from, whereClause, whereJoins, orderBy, distinct, model } = spec;
@@ -3640,7 +3788,8 @@ function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
3640
3788
  fromAlias: from.alias,
3641
3789
  model
3642
3790
  });
3643
- const outerOrder = isNonEmptyString(orderBy) ? replaceOrderByAlias(orderBy, from.alias, '"__tp_distinct"') : replaceOrderByAlias(fallbackOrder, from.alias, '"__tp_distinct"');
3791
+ const outerEntries = extractDistinctOrderEntries(spec);
3792
+ const outerOrder = buildFieldNameOrderBy(outerEntries, '"__tp_distinct"');
3644
3793
  const joins = buildJoinsSql(whereJoins, countJoins);
3645
3794
  const conditions = [];
3646
3795
  if (whereClause && whereClause !== "1=1") conditions.push(whereClause);
@@ -3675,17 +3824,33 @@ function buildSqliteDistinctQuery(spec, selectWithIncludes, countJoins) {
3675
3824
  }
3676
3825
  return outerParts.join(" ");
3677
3826
  }
3827
+ function resolveCountSelect(countSelectRaw, model) {
3828
+ if (countSelectRaw === true) {
3829
+ const relationSet = getRelationFieldSet(model);
3830
+ if (relationSet.size === 0) return null;
3831
+ const allRelations = {};
3832
+ for (const name of relationSet) {
3833
+ allRelations[name] = true;
3834
+ }
3835
+ return allRelations;
3836
+ }
3837
+ if (isPlainObject(countSelectRaw) && "select" in countSelectRaw) {
3838
+ return countSelectRaw.select;
3839
+ }
3840
+ return null;
3841
+ }
3678
3842
  function buildIncludeColumns(spec) {
3679
3843
  var _a, _b;
3680
3844
  const { select, includes, dialect, model, schemas, from, params } = spec;
3681
3845
  const baseSelect = (select != null ? select : "").trim();
3682
3846
  let countCols = "";
3683
3847
  let countJoins = [];
3684
- const countSelect = (_b = (_a = spec.args) == null ? void 0 : _a.select) == null ? void 0 : _b._count;
3685
- if (countSelect) {
3686
- if (isPlainObject(countSelect) && "select" in countSelect) {
3848
+ const countSelectRaw = (_b = (_a = spec.args) == null ? void 0 : _a.select) == null ? void 0 : _b._count;
3849
+ if (countSelectRaw) {
3850
+ const resolvedCountSelect = resolveCountSelect(countSelectRaw, model);
3851
+ if (resolvedCountSelect && Object.keys(resolvedCountSelect).length > 0) {
3687
3852
  const countBuild = buildRelationCountSql(
3688
- countSelect.select,
3853
+ resolvedCountSelect,
3689
3854
  model,
3690
3855
  schemas,
3691
3856
  from.alias,
@@ -3693,7 +3858,7 @@ function buildIncludeColumns(spec) {
3693
3858
  dialect
3694
3859
  );
3695
3860
  if (countBuild.jsonPairs) {
3696
- countCols = jsonBuildObject(countBuild.jsonPairs, dialect) + " " + SQL_TEMPLATES.AS + " " + quote2("_count");
3861
+ countCols = jsonBuildObject(countBuild.jsonPairs, dialect) + " " + SQL_TEMPLATES.AS + " " + quote("_count");
3697
3862
  }
3698
3863
  countJoins = countBuild.joins;
3699
3864
  }
@@ -3706,7 +3871,7 @@ function buildIncludeColumns(spec) {
3706
3871
  const emptyJson = dialect === "postgres" ? `'[]'::json` : `json('[]')`;
3707
3872
  const includeCols = hasIncludes ? includes.map((inc) => {
3708
3873
  const expr = inc.isOneToOne ? "(" + inc.sql + ")" : "COALESCE((" + inc.sql + "), " + emptyJson + ")";
3709
- return expr + " " + SQL_TEMPLATES.AS + " " + quote2(inc.name);
3874
+ return expr + " " + SQL_TEMPLATES.AS + " " + quote(inc.name);
3710
3875
  }).join(SQL_SEPARATORS.FIELD_LIST) : "";
3711
3876
  const allCols = joinNonEmpty(
3712
3877
  [includeCols, countCols],
@@ -4071,6 +4236,7 @@ function buildComparisons(expr, filter, params, dialect, builder, excludeKeys =
4071
4236
  }
4072
4237
 
4073
4238
  // src/builder/aggregates.ts
4239
+ var MAX_NOT_DEPTH2 = 50;
4074
4240
  var AGGREGATES = [
4075
4241
  ["_sum", "SUM"],
4076
4242
  ["_avg", "AVG"],
@@ -4185,8 +4351,13 @@ function buildBinaryComparison(expr, op, val, params) {
4185
4351
  const placeholder = addHavingParam(params, op, val);
4186
4352
  return expr + " " + sqlOp + " " + placeholder;
4187
4353
  }
4188
- function buildSimpleComparison(expr, op, val, params, dialect) {
4354
+ function buildSimpleComparison(expr, op, val, params, dialect, depth = 0) {
4189
4355
  assertHavingOp(op);
4356
+ if (depth > MAX_NOT_DEPTH2) {
4357
+ throw new Error(
4358
+ `NOT operator nesting too deep in HAVING (max ${MAX_NOT_DEPTH2} levels).`
4359
+ );
4360
+ }
4190
4361
  if (val === null) return buildNullComparison(expr, op);
4191
4362
  if (op === Ops.NOT && isPlainObject(val)) {
4192
4363
  return buildNotComposite(
@@ -4194,7 +4365,7 @@ function buildSimpleComparison(expr, op, val, params, dialect) {
4194
4365
  val,
4195
4366
  params,
4196
4367
  dialect,
4197
- buildSimpleComparison,
4368
+ (e, subOp, subVal, p, d) => buildSimpleComparison(e, subOp, subVal, p, d, depth + 1),
4198
4369
  SQL_SEPARATORS.CONDITION_AND
4199
4370
  );
4200
4371
  }
@@ -4211,23 +4382,36 @@ function combineLogical(key, subClauses) {
4211
4382
  if (key === LogicalOps.NOT) return negateClauses(subClauses);
4212
4383
  return subClauses.join(" " + key + " ");
4213
4384
  }
4214
- function buildHavingNode(node, alias, params, dialect, model) {
4385
+ function buildHavingNode(node, alias, params, dialect, model, depth = 0) {
4386
+ if (depth > LIMITS.MAX_HAVING_DEPTH) {
4387
+ throw new Error(
4388
+ `HAVING clause nesting too deep (max ${LIMITS.MAX_HAVING_DEPTH} levels). This usually indicates a circular reference.`
4389
+ );
4390
+ }
4215
4391
  const clauses = [];
4216
4392
  for (const key in node) {
4217
4393
  if (!Object.prototype.hasOwnProperty.call(node, key)) continue;
4218
4394
  const value = node[key];
4219
- const built = buildHavingEntry(key, value, alias, params, dialect, model);
4395
+ const built = buildHavingEntry(
4396
+ key,
4397
+ value,
4398
+ alias,
4399
+ params,
4400
+ dialect,
4401
+ model,
4402
+ depth
4403
+ );
4220
4404
  for (const c of built) {
4221
4405
  if (c && c.length > 0) clauses.push(c);
4222
4406
  }
4223
4407
  }
4224
4408
  return clauses.join(SQL_SEPARATORS.CONDITION_AND);
4225
4409
  }
4226
- function buildLogicalClause2(key, value, alias, params, dialect, model) {
4410
+ function buildLogicalClause2(key, value, alias, params, dialect, model, depth = 0) {
4227
4411
  const items = normalizeLogicalValue2(key, value);
4228
4412
  const subClauses = [];
4229
4413
  for (const it of items) {
4230
- const c = buildHavingNode(it, alias, params, dialect, model);
4414
+ const c = buildHavingNode(it, alias, params, dialect, model, depth + 1);
4231
4415
  if (c && c.length > 0) subClauses.push("(" + c + ")");
4232
4416
  }
4233
4417
  if (subClauses.length === 0) return "";
@@ -4286,7 +4470,7 @@ function buildHavingForFieldFirstShape(fieldName, target, alias, params, dialect
4286
4470
  }
4287
4471
  return out;
4288
4472
  }
4289
- function buildHavingEntry(key, value, alias, params, dialect, model) {
4473
+ function buildHavingEntry(key, value, alias, params, dialect, model, depth = 0) {
4290
4474
  if (isLogicalKey(key)) {
4291
4475
  const logical = buildLogicalClause2(
4292
4476
  key,
@@ -4294,7 +4478,8 @@ function buildHavingEntry(key, value, alias, params, dialect, model) {
4294
4478
  alias,
4295
4479
  params,
4296
4480
  dialect,
4297
- model
4481
+ model,
4482
+ depth
4298
4483
  );
4299
4484
  return logical ? [logical] : [];
4300
4485
  }
@@ -4321,7 +4506,7 @@ function buildHavingClause(having, alias, params, model, dialect) {
4321
4506
  if (!isNotNullish(having)) return "";
4322
4507
  const d = dialect != null ? dialect : getGlobalDialect();
4323
4508
  if (!isPlainObject(having)) throw new Error("having must be an object");
4324
- return buildHavingNode(having, alias, params, d, model);
4509
+ return buildHavingNode(having, alias, params, d, model, 0);
4325
4510
  }
4326
4511
  function normalizeCountArg(v) {
4327
4512
  if (!isNotNullish(v)) return void 0;
@@ -4331,13 +4516,13 @@ function normalizeCountArg(v) {
4331
4516
  }
4332
4517
  function pushCountAllField(fields) {
4333
4518
  fields.push(
4334
- SQL_TEMPLATES.COUNT_ALL + " " + SQL_TEMPLATES.AS + " " + quote2("_count._all")
4519
+ SQL_TEMPLATES.COUNT_ALL + " " + SQL_TEMPLATES.AS + " " + quote("_count._all")
4335
4520
  );
4336
4521
  }
4337
4522
  function pushCountField(fields, alias, fieldName, model) {
4338
4523
  const outAlias = "_count." + fieldName;
4339
4524
  fields.push(
4340
- "COUNT(" + col(alias, fieldName, model) + ") " + SQL_TEMPLATES.AS + " " + quote2(outAlias)
4525
+ "COUNT(" + col(alias, fieldName, model) + ") " + SQL_TEMPLATES.AS + " " + quote(outAlias)
4341
4526
  );
4342
4527
  }
4343
4528
  function addCountFields(fields, countArg, alias, model) {
@@ -4374,7 +4559,7 @@ function assertAggregatableScalarField(model, agg, fieldName) {
4374
4559
  function pushAggregateFieldSql(fields, aggFn, alias, agg, fieldName, model) {
4375
4560
  const outAlias = agg + "." + fieldName;
4376
4561
  fields.push(
4377
- aggFn + "(" + col(alias, fieldName, model) + ") " + SQL_TEMPLATES.AS + " " + quote2(outAlias)
4562
+ aggFn + "(" + col(alias, fieldName, model) + ") " + SQL_TEMPLATES.AS + " " + quote(outAlias)
4378
4563
  );
4379
4564
  }
4380
4565
  function addAggregateFields(fields, args, alias, model) {
@@ -4490,38 +4675,38 @@ function buildGroupBySql(args, whereResult, tableName, alias, model, dialect) {
4490
4675
  paramMappings: allMappings
4491
4676
  };
4492
4677
  }
4678
+ function isPositiveInteger(value) {
4679
+ return Number.isFinite(value) && Number.isInteger(value) && value > 0;
4680
+ }
4681
+ function parseSkipValue(skip) {
4682
+ return typeof skip === "string" ? Number(skip.trim()) : skip;
4683
+ }
4684
+ function validateSkipParameter(skip) {
4685
+ if (skip === void 0 || skip === null) {
4686
+ return;
4687
+ }
4688
+ if (isDynamicParameter(skip)) {
4689
+ throw new Error(
4690
+ "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."
4691
+ );
4692
+ }
4693
+ const skipValue = parseSkipValue(skip);
4694
+ if (isPositiveInteger(skipValue)) {
4695
+ throw new Error(
4696
+ "count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
4697
+ );
4698
+ }
4699
+ }
4493
4700
  function buildCountSql(whereResult, tableName, alias, skip, _dialect) {
4494
4701
  assertSafeAlias(alias);
4495
4702
  assertSafeTableRef(tableName);
4496
- if (skip !== void 0 && skip !== null) {
4497
- if (isDynamicParameter(skip)) {
4498
- throw new Error(
4499
- "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."
4500
- );
4501
- }
4502
- if (typeof skip === "string") {
4503
- const s = skip.trim();
4504
- if (s.length > 0) {
4505
- const n = Number(s);
4506
- if (Number.isFinite(n) && Number.isInteger(n) && n > 0) {
4507
- throw new Error(
4508
- "count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
4509
- );
4510
- }
4511
- }
4512
- }
4513
- if (typeof skip === "number" && Number.isFinite(skip) && Number.isInteger(skip) && skip > 0) {
4514
- throw new Error(
4515
- "count() with skip is not supported because it produces nondeterministic results. Use findMany().length or add explicit orderBy to ensure deterministic behavior."
4516
- );
4517
- }
4518
- }
4703
+ validateSkipParameter(skip);
4519
4704
  const whereClause = isValidWhereClause(whereResult.clause) ? SQL_TEMPLATES.WHERE + " " + whereResult.clause : "";
4520
4705
  const parts = [
4521
4706
  SQL_TEMPLATES.SELECT,
4522
4707
  SQL_TEMPLATES.COUNT_ALL,
4523
4708
  SQL_TEMPLATES.AS,
4524
- quote2("_count._all"),
4709
+ quote("_count._all"),
4525
4710
  SQL_TEMPLATES.FROM,
4526
4711
  tableName,
4527
4712
  alias
@@ -4562,15 +4747,21 @@ function buildSqlResult(args) {
4562
4747
  return buildAggregateSql(processed, whereResult, tableName, alias, modelDef);
4563
4748
  }
4564
4749
  if (method === "groupBy") {
4565
- return buildGroupBySql(processed, whereResult, tableName, alias, modelDef);
4750
+ return buildGroupBySql(
4751
+ processed,
4752
+ whereResult,
4753
+ tableName,
4754
+ alias,
4755
+ modelDef,
4756
+ dialect
4757
+ );
4566
4758
  }
4567
4759
  if (method === "count") {
4568
4760
  return buildCountSql(
4569
4761
  whereResult,
4570
4762
  tableName,
4571
4763
  alias,
4572
- processed.skip
4573
- );
4764
+ processed.skip);
4574
4765
  }
4575
4766
  return buildSelectSql({
4576
4767
  method,
@@ -4610,22 +4801,23 @@ function normalizeSqlAndMappingsForDialect(sql, paramMappings, dialect) {
4610
4801
  }
4611
4802
  function buildParamsFromMappings(mappings) {
4612
4803
  const sorted = [...mappings].sort((a, b) => a.index - b.index);
4613
- return sorted.reduce(
4614
- (acc, m) => {
4615
- if (m.dynamicName !== void 0) {
4616
- acc.dynamicKeys.push(m.dynamicName);
4617
- return acc;
4618
- }
4619
- if (m.value !== void 0) {
4620
- acc.staticParams.push(m.value);
4621
- return acc;
4622
- }
4804
+ const staticParams = [];
4805
+ const dynamicKeys = [];
4806
+ let paramOrder = "";
4807
+ for (const m of sorted) {
4808
+ if (m.dynamicName !== void 0) {
4809
+ dynamicKeys.push(m.dynamicName);
4810
+ paramOrder += "d";
4811
+ } else if (m.value !== void 0) {
4812
+ staticParams.push(m.value);
4813
+ paramOrder += "s";
4814
+ } else {
4623
4815
  throw new Error(
4624
4816
  `CRITICAL: ParamMap ${m.index} has neither dynamicName nor value`
4625
4817
  );
4626
- },
4627
- { staticParams: [], dynamicKeys: [] }
4628
- );
4818
+ }
4819
+ }
4820
+ return { staticParams, dynamicKeys, paramOrder };
4629
4821
  }
4630
4822
  function resolveModelContext(directive) {
4631
4823
  const { model, datamodel } = directive.context;
@@ -4691,12 +4883,13 @@ function finalizeDirective(args) {
4691
4883
  return (_a = m.value) != null ? _a : void 0;
4692
4884
  });
4693
4885
  validateParamConsistencyByDialect(normalizedSql, params, dialect);
4694
- const { staticParams, dynamicKeys } = buildParamsFromMappings(normalizedMappings);
4886
+ const { staticParams, dynamicKeys, paramOrder } = buildParamsFromMappings(normalizedMappings);
4695
4887
  return {
4696
4888
  method: directive.method,
4697
4889
  sql: normalizedSql,
4698
4890
  staticParams,
4699
4891
  dynamicKeys,
4892
+ paramOrder,
4700
4893
  paramMappings: normalizedMappings,
4701
4894
  originalDirective: directive
4702
4895
  };
@@ -4732,62 +4925,6 @@ function generateSQL(directive) {
4732
4925
  });
4733
4926
  }
4734
4927
 
4735
- // src/result-transformers.ts
4736
- function parseAggregateValue(value) {
4737
- if (typeof value === "string" && /^-?\d+(\.\d+)?$/.test(value)) {
4738
- return parseFloat(value);
4739
- }
4740
- return value;
4741
- }
4742
- function transformGroupByResults(results) {
4743
- return results.map((row) => {
4744
- const raw = row;
4745
- const parsed = {};
4746
- for (const [key, value] of Object.entries(raw)) {
4747
- const parts = key.split(".");
4748
- if (parts.length === 2) {
4749
- const [group, field] = parts;
4750
- if (!parsed[group]) parsed[group] = {};
4751
- parsed[group][field] = parseAggregateValue(value);
4752
- } else {
4753
- parsed[key] = value;
4754
- }
4755
- }
4756
- return parsed;
4757
- });
4758
- }
4759
- function transformCountResults(results) {
4760
- const result = results[0];
4761
- const count = (result == null ? void 0 : result["_count._all"]) || (result == null ? void 0 : result.count) || 0;
4762
- return typeof count === "string" ? parseInt(count, 10) : count;
4763
- }
4764
- function transformAggregateResults(results) {
4765
- const raw = results[0] || {};
4766
- const parsed = {};
4767
- for (const [key, value] of Object.entries(raw)) {
4768
- const parts = key.split(".");
4769
- if (parts.length === 2) {
4770
- const [group, field] = parts;
4771
- if (!parsed[group]) parsed[group] = {};
4772
- parsed[group][field] = parseAggregateValue(value);
4773
- } else {
4774
- parsed[key] = value;
4775
- }
4776
- }
4777
- return parsed;
4778
- }
4779
- var RESULT_TRANSFORMERS = {
4780
- findFirst: (results) => results[0] || null,
4781
- findUnique: (results) => results[0] || null,
4782
- count: transformCountResults,
4783
- aggregate: transformAggregateResults,
4784
- groupBy: transformGroupByResults
4785
- };
4786
- function transformQueryResults(method, results) {
4787
- const transformer = RESULT_TRANSFORMERS[method];
4788
- return transformer ? transformer(results) : results;
4789
- }
4790
-
4791
4928
  // src/utils/s3-fifo.ts
4792
4929
  function withDispose(it) {
4793
4930
  const anyIt = it;
@@ -5007,107 +5144,374 @@ function createBoundedCache(maxSize) {
5007
5144
  return new BoundedCache(maxSize);
5008
5145
  }
5009
5146
 
5010
- // src/query-cache.ts
5011
- var queryCache = createBoundedCache(1e3);
5012
- function makeAlias(name) {
5013
- const base = name.toLowerCase().replace(/[^a-z0-9_]/g, "_").slice(0, 50);
5014
- const safe = /^[a-z_]/.test(base) ? base : `_${base}`;
5015
- return SQL_RESERVED_WORDS.has(safe) ? `${safe}_t` : safe;
5016
- }
5017
- function toSqliteParams(sql, params) {
5018
- const reorderedParams = [];
5019
- let lastIndex = 0;
5020
- const parts = [];
5021
- for (let i = 0; i < sql.length; i++) {
5022
- if (sql[i] === "$" && i + 1 < sql.length) {
5023
- let num = 0;
5024
- let j = i + 1;
5025
- while (j < sql.length && sql[j] >= "0" && sql[j] <= "9") {
5026
- num = num * 10 + (sql.charCodeAt(j) - 48);
5027
- j++;
5028
- }
5029
- if (j > i + 1) {
5030
- parts.push(sql.substring(lastIndex, i));
5031
- parts.push("?");
5032
- reorderedParams.push(params[num - 1]);
5033
- i = j - 1;
5034
- lastIndex = j;
5035
- }
5036
- }
5037
- }
5038
- parts.push(sql.substring(lastIndex));
5039
- return { sql: parts.join(""), params: reorderedParams };
5147
+ // src/fast-path.ts
5148
+ function getIdField(model) {
5149
+ const idField = model.fields.find((f) => f.name === "id" && !f.isRelation);
5150
+ if (!idField) return null;
5151
+ return {
5152
+ name: "id",
5153
+ dbName: idField.dbName || "id"
5154
+ };
5040
5155
  }
5041
- function canonicalizeQuery(modelName, method, args, dialect) {
5042
- function normalize(obj) {
5043
- if (obj === null || obj === void 0) return obj;
5044
- if (obj instanceof Date) {
5045
- return "__DATE_PARAM__";
5046
- }
5047
- if (Array.isArray(obj)) {
5048
- return obj.map(normalize);
5049
- }
5050
- if (typeof obj === "object") {
5051
- const sorted = {};
5052
- for (const key of Object.keys(obj).sort()) {
5053
- const value = obj[key];
5054
- sorted[key] = isDynamicParameter(value) ? "__DYN__" : normalize(value);
5055
- }
5056
- return sorted;
5156
+ function buildColumnList(model, alias) {
5157
+ const cols = [];
5158
+ for (const f of model.fields) {
5159
+ if (f.isRelation) continue;
5160
+ if (f.name.startsWith("@") || f.name.startsWith("//")) continue;
5161
+ const dbName = f.dbName || f.name;
5162
+ if (dbName !== f.name) {
5163
+ cols.push(`${alias}.${quote(dbName)} AS ${quote(f.name)}`);
5164
+ } else {
5165
+ cols.push(`${alias}.${quote(dbName)}`);
5057
5166
  }
5058
- return obj;
5059
5167
  }
5060
- const canonical = normalize(args);
5061
- return `${dialect}:${modelName}:${method}:${JSON.stringify(canonical)}`;
5168
+ return cols.join(", ");
5062
5169
  }
5063
- function buildSQLFull(model, models, method, args, dialect) {
5170
+ function getTableAndAlias(model, dialect) {
5064
5171
  const tableName = buildTableReference(
5065
5172
  SQL_TEMPLATES.PUBLIC_SCHEMA,
5066
5173
  model.tableName,
5067
5174
  dialect
5068
5175
  );
5069
5176
  const alias = makeAlias(model.tableName);
5070
- const whereResult = buildWhereClause(
5071
- args.where || {},
5072
- {
5073
- alias,
5074
- schemaModels: models,
5075
- model,
5076
- path: ["where"],
5077
- isSubquery: false,
5078
- dialect
5079
- }
5177
+ return { tableName, alias };
5178
+ }
5179
+ function buildSimpleQuery(model, dialect, where, params, suffix = "") {
5180
+ const { tableName, alias } = getTableAndAlias(model, dialect);
5181
+ const columns = buildColumnList(model, alias);
5182
+ const sql = `SELECT ${columns} FROM ${tableName} ${alias} ${where}${suffix}`;
5183
+ return { sql, params };
5184
+ }
5185
+ function norm(value) {
5186
+ return normalizeValue(value);
5187
+ }
5188
+ function isScalar(value) {
5189
+ return value !== null && typeof value !== "object";
5190
+ }
5191
+ function tryFindUniqueById(model, args, dialect) {
5192
+ const where = args.where;
5193
+ if (!isPlainObject(where) || Object.keys(where).length !== 1 || !("id" in where) || !isScalar(where.id) || args.select || args.include) {
5194
+ return null;
5195
+ }
5196
+ const idField = getIdField(model);
5197
+ if (!idField) return null;
5198
+ const { alias } = getTableAndAlias(model, dialect);
5199
+ const whereClause = dialect === "sqlite" ? `WHERE ${alias}.${quote(idField.dbName)} = ?` : `WHERE ${alias}.${quote(idField.dbName)} = $1`;
5200
+ return buildSimpleQuery(
5201
+ model,
5202
+ dialect,
5203
+ whereClause,
5204
+ [norm(where.id)],
5205
+ " LIMIT 1"
5080
5206
  );
5081
- const withMethod = __spreadProps(__spreadValues({}, args), { method });
5082
- let result;
5083
- switch (method) {
5084
- case "aggregate":
5085
- result = buildAggregateSql(
5086
- withMethod,
5087
- whereResult,
5088
- tableName,
5089
- alias,
5090
- model
5091
- );
5092
- break;
5093
- case "groupBy":
5094
- result = buildGroupBySql(
5095
- withMethod,
5096
- whereResult,
5097
- tableName,
5098
- alias,
5099
- model,
5100
- dialect
5101
- );
5102
- break;
5103
- case "count":
5104
- result = buildCountSql(
5105
- whereResult,
5106
- tableName,
5107
- alias,
5108
- args.skip);
5109
- break;
5110
- default:
5207
+ }
5208
+ function tryFindManyById(model, args, dialect) {
5209
+ const where = args.where;
5210
+ if (!isPlainObject(where) || Object.keys(where).length !== 1 || !("id" in where) || !isScalar(where.id) || args.select || args.include || args.orderBy || args.take || args.skip || args.distinct || args.cursor) {
5211
+ return null;
5212
+ }
5213
+ const idField = getIdField(model);
5214
+ if (!idField) return null;
5215
+ const { alias } = getTableAndAlias(model, dialect);
5216
+ const whereClause = dialect === "sqlite" ? `WHERE ${alias}.${quote(idField.dbName)} = ?` : `WHERE ${alias}.${quote(idField.dbName)} = $1`;
5217
+ return buildSimpleQuery(model, dialect, whereClause, [norm(where.id)]);
5218
+ }
5219
+ function tryCountAll(model, args, dialect) {
5220
+ const where = args.where;
5221
+ if (where && Object.keys(where).length > 0 || args.select || args.skip) {
5222
+ return null;
5223
+ }
5224
+ const { tableName, alias } = getTableAndAlias(model, dialect);
5225
+ const sql = `SELECT COUNT(*) AS ${quote("_count._all")} FROM ${tableName} ${alias}`;
5226
+ return { sql, params: [] };
5227
+ }
5228
+ function tryFindManyWithLimit(model, args, dialect) {
5229
+ const where = args.where;
5230
+ if (where && Object.keys(where).length > 0 || args.select || args.include || args.orderBy || args.skip || args.distinct || args.cursor || typeof args.take !== "number" || !Number.isInteger(args.take) || args.take <= 0) {
5231
+ return null;
5232
+ }
5233
+ const { tableName, alias } = getTableAndAlias(model, dialect);
5234
+ const columns = buildColumnList(model, alias);
5235
+ const sql = dialect === "sqlite" ? `SELECT ${columns} FROM ${tableName} ${alias} LIMIT ?` : `SELECT ${columns} FROM ${tableName} ${alias} LIMIT $1`;
5236
+ return { sql, params: [norm(args.take)] };
5237
+ }
5238
+ function tryFindFirstBySingleField(model, args, dialect) {
5239
+ const where = args.where;
5240
+ if (!isPlainObject(where) || Object.keys(where).length !== 1 || args.select || args.include || args.orderBy || args.skip) {
5241
+ return null;
5242
+ }
5243
+ const field = Object.keys(where)[0];
5244
+ const value = where[field];
5245
+ if (value === null || typeof value === "object") {
5246
+ return null;
5247
+ }
5248
+ const fieldDef = model.fields.find((f) => f.name === field);
5249
+ if (!fieldDef || fieldDef.isRelation) {
5250
+ return null;
5251
+ }
5252
+ const { alias } = getTableAndAlias(model, dialect);
5253
+ const columnName = fieldDef.dbName || field;
5254
+ const whereClause = dialect === "sqlite" ? `WHERE ${alias}.${quote(columnName)} = ?` : `WHERE ${alias}.${quote(columnName)} = $1`;
5255
+ return buildSimpleQuery(
5256
+ model,
5257
+ dialect,
5258
+ whereClause,
5259
+ [norm(value)],
5260
+ " LIMIT 1"
5261
+ );
5262
+ }
5263
+ function tryFindManyBySingleField(model, args, dialect) {
5264
+ const where = args.where;
5265
+ if (!isPlainObject(where) || Object.keys(where).length !== 1 || args.select || args.include || args.orderBy || args.take || args.skip || args.distinct || args.cursor) {
5266
+ return null;
5267
+ }
5268
+ const field = Object.keys(where)[0];
5269
+ const value = where[field];
5270
+ if (value === null || typeof value === "object") {
5271
+ return null;
5272
+ }
5273
+ const fieldDef = model.fields.find((f) => f.name === field);
5274
+ if (!fieldDef || fieldDef.isRelation) {
5275
+ return null;
5276
+ }
5277
+ const { alias } = getTableAndAlias(model, dialect);
5278
+ const columnName = fieldDef.dbName || field;
5279
+ const whereClause = dialect === "sqlite" ? `WHERE ${alias}.${quote(columnName)} = ?` : `WHERE ${alias}.${quote(columnName)} = $1`;
5280
+ return buildSimpleQuery(model, dialect, whereClause, [norm(value)]);
5281
+ }
5282
+ function tryFindManyAll(model, args, dialect) {
5283
+ const where = args.where;
5284
+ if (where && Object.keys(where).length > 0 || args.select || args.include || args.orderBy || args.take !== void 0 || args.skip !== void 0 || args.distinct || args.cursor) {
5285
+ return null;
5286
+ }
5287
+ const { tableName, alias } = getTableAndAlias(model, dialect);
5288
+ const columns = buildColumnList(model, alias);
5289
+ const sql = `SELECT ${columns} FROM ${tableName} ${alias}`;
5290
+ return { sql, params: [] };
5291
+ }
5292
+ function tryFastPath(model, method, args, dialect) {
5293
+ if (method === "findUnique") {
5294
+ return tryFindUniqueById(model, args, dialect);
5295
+ }
5296
+ if (method === "count") {
5297
+ return tryCountAll(model, args, dialect);
5298
+ }
5299
+ if (method === "findFirst") {
5300
+ return tryFindFirstBySingleField(model, args, dialect);
5301
+ }
5302
+ if (method === "findMany") {
5303
+ return tryFindManyById(model, args, dialect) || tryFindManyWithLimit(model, args, dialect) || tryFindManyBySingleField(model, args, dialect) || tryFindManyAll(model, args, dialect);
5304
+ }
5305
+ return null;
5306
+ }
5307
+
5308
+ // src/query-cache.ts
5309
+ var _hits, _misses;
5310
+ var QueryCacheStats = class {
5311
+ constructor() {
5312
+ __privateAdd(this, _hits, 0);
5313
+ __privateAdd(this, _misses, 0);
5314
+ }
5315
+ hit() {
5316
+ __privateWrapper(this, _hits)._++;
5317
+ }
5318
+ miss() {
5319
+ __privateWrapper(this, _misses)._++;
5320
+ }
5321
+ reset() {
5322
+ __privateSet(this, _hits, 0);
5323
+ __privateSet(this, _misses, 0);
5324
+ }
5325
+ get snapshot() {
5326
+ return Object.freeze({
5327
+ hits: __privateGet(this, _hits),
5328
+ misses: __privateGet(this, _misses),
5329
+ size: queryCache.size
5330
+ });
5331
+ }
5332
+ };
5333
+ _hits = new WeakMap();
5334
+ _misses = new WeakMap();
5335
+ var queryCache = createBoundedCache(1e3);
5336
+ var queryCacheStats = new QueryCacheStats();
5337
+ function makeAlias(name) {
5338
+ const base = name.toLowerCase().replace(/[^a-z0-9_]/g, "_").slice(0, 50);
5339
+ const safe = /^[a-z_]/.test(base) ? base : `_${base}`;
5340
+ return SQL_RESERVED_WORDS.has(safe) ? `${safe}_t` : safe;
5341
+ }
5342
+ function handleSingleQuote(sql, state) {
5343
+ const n = sql.length;
5344
+ let i = state.position;
5345
+ let out = state.output + sql[i];
5346
+ i++;
5347
+ while (i < n) {
5348
+ out += sql[i];
5349
+ if (sql.charCodeAt(i) === 39) {
5350
+ if (i + 1 < n && sql.charCodeAt(i + 1) === 39) {
5351
+ out += sql[i + 1];
5352
+ i += 2;
5353
+ continue;
5354
+ }
5355
+ return __spreadProps(__spreadValues({}, state), { mode: "normal", position: i + 1, output: out });
5356
+ }
5357
+ i++;
5358
+ }
5359
+ return __spreadProps(__spreadValues({}, state), { position: i, output: out });
5360
+ }
5361
+ function handleDoubleQuote(sql, state) {
5362
+ const n = sql.length;
5363
+ let i = state.position;
5364
+ let out = state.output + sql[i];
5365
+ i++;
5366
+ while (i < n) {
5367
+ out += sql[i];
5368
+ if (sql.charCodeAt(i) === 34) {
5369
+ if (i + 1 < n && sql.charCodeAt(i + 1) === 34) {
5370
+ out += sql[i + 1];
5371
+ i += 2;
5372
+ continue;
5373
+ }
5374
+ return __spreadProps(__spreadValues({}, state), { mode: "normal", position: i + 1, output: out });
5375
+ }
5376
+ i++;
5377
+ }
5378
+ return __spreadProps(__spreadValues({}, state), { position: i, output: out });
5379
+ }
5380
+ function extractParameterNumber(sql, startPos) {
5381
+ const n = sql.length;
5382
+ let j = startPos + 1;
5383
+ let num = 0;
5384
+ let hasDigit = false;
5385
+ while (j < n) {
5386
+ const d = sql.charCodeAt(j);
5387
+ if (d >= 48 && d <= 57) {
5388
+ num = num * 10 + (d - 48);
5389
+ hasDigit = true;
5390
+ j++;
5391
+ } else {
5392
+ break;
5393
+ }
5394
+ }
5395
+ if (hasDigit && num >= 1) {
5396
+ return { num, nextPos: j };
5397
+ }
5398
+ return null;
5399
+ }
5400
+ function handleParameterSubstitution(sql, params, state) {
5401
+ const result = extractParameterNumber(sql, state.position);
5402
+ if (result) {
5403
+ return __spreadProps(__spreadValues({}, state), {
5404
+ position: result.nextPos,
5405
+ output: state.output + "?",
5406
+ reorderedParams: [...state.reorderedParams, params[result.num - 1]]
5407
+ });
5408
+ }
5409
+ return __spreadProps(__spreadValues({}, state), {
5410
+ position: state.position + 1,
5411
+ output: state.output + sql[state.position]
5412
+ });
5413
+ }
5414
+ function toSqliteParams(sql, params) {
5415
+ const n = sql.length;
5416
+ let state = {
5417
+ mode: "normal",
5418
+ position: 0,
5419
+ output: "",
5420
+ reorderedParams: []
5421
+ };
5422
+ while (state.position < n) {
5423
+ const ch = sql.charCodeAt(state.position);
5424
+ if (state.mode === "single") {
5425
+ state = handleSingleQuote(sql, state);
5426
+ continue;
5427
+ }
5428
+ if (state.mode === "double") {
5429
+ state = handleDoubleQuote(sql, state);
5430
+ continue;
5431
+ }
5432
+ if (ch === 39) {
5433
+ state = __spreadProps(__spreadValues({}, state), { mode: "single" });
5434
+ continue;
5435
+ }
5436
+ if (ch === 34) {
5437
+ state = __spreadProps(__spreadValues({}, state), { mode: "double" });
5438
+ continue;
5439
+ }
5440
+ if (ch === 36) {
5441
+ state = handleParameterSubstitution(sql, params, state);
5442
+ continue;
5443
+ }
5444
+ state = __spreadProps(__spreadValues({}, state), {
5445
+ position: state.position + 1,
5446
+ output: state.output + sql[state.position]
5447
+ });
5448
+ }
5449
+ return { sql: state.output, params: state.reorderedParams };
5450
+ }
5451
+ function canonicalizeReplacer(key, value) {
5452
+ if (typeof value === "bigint") return `__bigint__${value.toString()}`;
5453
+ if (value && typeof value === "object" && !Array.isArray(value)) {
5454
+ const obj = value;
5455
+ const sorted = {};
5456
+ for (const k of Object.keys(obj).sort()) {
5457
+ sorted[k] = obj[k];
5458
+ }
5459
+ return sorted;
5460
+ }
5461
+ return value;
5462
+ }
5463
+ function canonicalizeQuery(modelName, method, args, dialect) {
5464
+ if (!args) return `${dialect}:${modelName}:${method}:{}`;
5465
+ return `${dialect}:${modelName}:${method}:${JSON.stringify(args, canonicalizeReplacer)}`;
5466
+ }
5467
+ function buildSQLFull(model, models, method, args, dialect) {
5468
+ const tableName = buildTableReference(
5469
+ SQL_TEMPLATES.PUBLIC_SCHEMA,
5470
+ model.tableName,
5471
+ dialect
5472
+ );
5473
+ const alias = makeAlias(model.tableName);
5474
+ const whereResult = buildWhereClause(
5475
+ args.where || {},
5476
+ {
5477
+ alias,
5478
+ schemaModels: models,
5479
+ model,
5480
+ path: ["where"],
5481
+ isSubquery: false,
5482
+ dialect
5483
+ }
5484
+ );
5485
+ const withMethod = __spreadProps(__spreadValues({}, args), { method });
5486
+ let result;
5487
+ switch (method) {
5488
+ case "aggregate":
5489
+ result = buildAggregateSql(
5490
+ withMethod,
5491
+ whereResult,
5492
+ tableName,
5493
+ alias,
5494
+ model
5495
+ );
5496
+ break;
5497
+ case "groupBy":
5498
+ result = buildGroupBySql(
5499
+ withMethod,
5500
+ whereResult,
5501
+ tableName,
5502
+ alias,
5503
+ model,
5504
+ dialect
5505
+ );
5506
+ break;
5507
+ case "count":
5508
+ result = buildCountSql(
5509
+ whereResult,
5510
+ tableName,
5511
+ alias,
5512
+ args.skip);
5513
+ break;
5514
+ default:
5111
5515
  result = buildSelectSql({
5112
5516
  method,
5113
5517
  args: withMethod,
@@ -5122,207 +5526,211 @@ function buildSQLFull(model, models, method, args, dialect) {
5122
5526
  }
5123
5527
  function buildSQLWithCache(model, models, method, args, dialect) {
5124
5528
  const cacheKey = canonicalizeQuery(model.name, method, args, dialect);
5125
- const cachedSql = queryCache.get(cacheKey);
5126
- if (cachedSql) {
5127
- const result2 = buildSQLFull(model, models, method, args, dialect);
5128
- return { sql: cachedSql, params: result2.params };
5529
+ const cached = queryCache.get(cacheKey);
5530
+ if (cached) {
5531
+ queryCacheStats.hit();
5532
+ return { sql: cached.sql, params: [...cached.params] };
5533
+ }
5534
+ queryCacheStats.miss();
5535
+ const fastResult = tryFastPath(model, method, args, dialect);
5536
+ if (fastResult) {
5537
+ queryCache.set(cacheKey, {
5538
+ sql: fastResult.sql,
5539
+ params: [...fastResult.params]
5540
+ });
5541
+ return fastResult;
5129
5542
  }
5130
5543
  const result = buildSQLFull(model, models, method, args, dialect);
5131
- queryCache.set(cacheKey, result.sql);
5132
- queryCache.size;
5544
+ queryCache.set(cacheKey, { sql: result.sql, params: [...result.params] });
5133
5545
  return result;
5134
5546
  }
5135
5547
 
5136
5548
  // src/batch.ts
5137
- function assertNoControlChars2(label, s) {
5138
- for (let i = 0; i < s.length; i++) {
5139
- const c = s.charCodeAt(i);
5140
- if (c <= 31 || c === 127) {
5141
- throw new Error(`${label} contains control characters`);
5142
- }
5549
+ function quoteBatchIdent(id) {
5550
+ const raw = String(id);
5551
+ assertSafeAlias(raw);
5552
+ return `"${raw.replace(/"/g, '""')}"`;
5553
+ }
5554
+ function makeBatchAlias(i) {
5555
+ return `k${i}`;
5556
+ }
5557
+ function isDigit(charCode) {
5558
+ return charCode >= 48 && charCode <= 57;
5559
+ }
5560
+ function isAlphaNumericOrUnderscore(charCode) {
5561
+ return isDigit(charCode) || charCode >= 65 && charCode <= 90 || charCode >= 97 && charCode <= 122 || charCode === 95;
5562
+ }
5563
+ function readDollarTag(s, pos, n) {
5564
+ if (s.charCodeAt(pos) !== 36) return null;
5565
+ let j = pos + 1;
5566
+ while (j < n && isAlphaNumericOrUnderscore(s.charCodeAt(j))) {
5567
+ j++;
5568
+ }
5569
+ if (j < n && s.charCodeAt(j) === 36 && j > pos) {
5570
+ return s.slice(pos, j + 1);
5571
+ }
5572
+ if (pos + 1 < n && s.charCodeAt(pos + 1) === 36) {
5573
+ return "$$";
5143
5574
  }
5575
+ return null;
5144
5576
  }
5145
- function assertSafeIdentifier(label, s) {
5146
- const raw = String(s);
5147
- if (raw.trim() !== raw) {
5148
- throw new Error(`${label} must not contain leading/trailing whitespace`);
5577
+ function parseParamPlaceholder(s, i, n, replace) {
5578
+ let j = i + 1;
5579
+ if (j >= n) {
5580
+ return { consumed: 1, output: s[i] };
5149
5581
  }
5150
- if (raw.length === 0) throw new Error(`${label} cannot be empty`);
5151
- assertNoControlChars2(label, raw);
5152
- if (/[ \t\r\n]/.test(raw)) {
5153
- throw new Error(`${label} must not contain whitespace`);
5582
+ const c1 = s.charCodeAt(j);
5583
+ if (!isDigit(c1)) {
5584
+ return { consumed: 1, output: s[i] };
5154
5585
  }
5155
- if (raw.includes(";")) {
5156
- throw new Error(`${label} must not contain semicolons`);
5586
+ while (j < n && isDigit(s.charCodeAt(j))) {
5587
+ j++;
5157
5588
  }
5158
- if (raw.includes("--") || raw.includes("/*") || raw.includes("*/")) {
5159
- throw new Error(`${label} must not contain SQL comment tokens`);
5589
+ const numStr = s.slice(i + 1, j);
5590
+ const oldIndex = Number(numStr);
5591
+ if (!Number.isInteger(oldIndex) || oldIndex < 1) {
5592
+ throw new Error(`Invalid param placeholder: $${numStr}`);
5160
5593
  }
5594
+ return { consumed: j - i, output: replace(oldIndex) };
5161
5595
  }
5162
- function quoteIdent(id) {
5163
- const raw = String(id);
5164
- assertSafeIdentifier("Identifier", raw);
5165
- return `"${raw.replace(/"/g, '""')}"`;
5596
+ function handleDollarInNormalMode(s, i, n, state, replace) {
5597
+ const tag = readDollarTag(s, i, n);
5598
+ if (tag) {
5599
+ return {
5600
+ consumed: tag.length,
5601
+ output: tag,
5602
+ newState: { mode: "dollar", dollarTag: tag }
5603
+ };
5604
+ }
5605
+ const placeholder = parseParamPlaceholder(s, i, n, replace);
5606
+ return __spreadProps(__spreadValues({}, placeholder), { newState: state });
5166
5607
  }
5167
- function makeBatchAlias(i) {
5168
- return `k${i}`;
5608
+ function handleCommentStart(s, i, n, ch) {
5609
+ if (ch === 45 && i + 1 < n && s.charCodeAt(i + 1) === 45) {
5610
+ return {
5611
+ consumed: 2,
5612
+ output: "--",
5613
+ newState: { mode: "lineComment", dollarTag: null }
5614
+ };
5615
+ }
5616
+ if (ch === 47 && i + 1 < n && s.charCodeAt(i + 1) === 42) {
5617
+ return {
5618
+ consumed: 2,
5619
+ output: "/*",
5620
+ newState: { mode: "blockComment", dollarTag: null }
5621
+ };
5622
+ }
5623
+ return null;
5624
+ }
5625
+ function processNormalMode(s, i, n, state, replace) {
5626
+ const ch = s.charCodeAt(i);
5627
+ if (ch === 39) {
5628
+ return {
5629
+ consumed: 1,
5630
+ output: s[i],
5631
+ newState: { mode: "single", dollarTag: null }
5632
+ };
5633
+ }
5634
+ if (ch === 34) {
5635
+ return {
5636
+ consumed: 1,
5637
+ output: s[i],
5638
+ newState: { mode: "double", dollarTag: null }
5639
+ };
5640
+ }
5641
+ const commentResult = handleCommentStart(s, i, n, ch);
5642
+ if (commentResult) return commentResult;
5643
+ if (ch === 36) {
5644
+ return handleDollarInNormalMode(s, i, n, state, replace);
5645
+ }
5646
+ return { consumed: 1, output: s[i], newState: state };
5647
+ }
5648
+ function processQuoteMode(s, i, n, quoteChar) {
5649
+ const ch = s.charCodeAt(i);
5650
+ if (ch === quoteChar) {
5651
+ if (i + 1 < n && s.charCodeAt(i + 1) === quoteChar) {
5652
+ return { consumed: 2, output: s[i] + s[i + 1], shouldExitMode: false };
5653
+ }
5654
+ return { consumed: 1, output: s[i], shouldExitMode: true };
5655
+ }
5656
+ return { consumed: 1, output: s[i], shouldExitMode: false };
5657
+ }
5658
+ function processBlockCommentMode(s, i, n) {
5659
+ const ch = s.charCodeAt(i);
5660
+ if (ch === 42 && i + 1 < n && s.charCodeAt(i + 1) === 47) {
5661
+ return { consumed: 2, output: "*/", shouldExitMode: true };
5662
+ }
5663
+ return { consumed: 1, output: s[i], shouldExitMode: false };
5664
+ }
5665
+ function processDollarMode(s, i, dollarTag) {
5666
+ if (s.slice(i, i + dollarTag.length) === dollarTag) {
5667
+ return {
5668
+ consumed: dollarTag.length,
5669
+ output: dollarTag,
5670
+ shouldExitMode: true
5671
+ };
5672
+ }
5673
+ return { consumed: 1, output: s[i], shouldExitMode: false };
5674
+ }
5675
+ function processLineCommentMode(ch) {
5676
+ return { shouldExitMode: ch === 10 };
5677
+ }
5678
+ function processCharacter(s, i, n, state, replace) {
5679
+ const ch = s.charCodeAt(i);
5680
+ switch (state.mode) {
5681
+ case "normal":
5682
+ return processNormalMode(s, i, n, state, replace);
5683
+ case "single":
5684
+ return processQuoteMode(s, i, n, 39);
5685
+ case "double":
5686
+ return processQuoteMode(s, i, n, 34);
5687
+ case "lineComment": {
5688
+ const result = processLineCommentMode(ch);
5689
+ return {
5690
+ consumed: 1,
5691
+ output: s[i],
5692
+ shouldExitMode: result.shouldExitMode
5693
+ };
5694
+ }
5695
+ case "blockComment":
5696
+ return processBlockCommentMode(s, i, n);
5697
+ case "dollar":
5698
+ return state.dollarTag ? processDollarMode(s, i, state.dollarTag) : { consumed: 1, output: s[i], shouldExitMode: false };
5699
+ default:
5700
+ return { consumed: 1, output: s[i], shouldExitMode: false };
5701
+ }
5702
+ }
5703
+ function updateStateAfterProcessing(currentState, result) {
5704
+ if (result.newState) {
5705
+ return result.newState;
5706
+ }
5707
+ if (result.shouldExitMode) {
5708
+ return { mode: "normal", dollarTag: null };
5709
+ }
5710
+ return currentState;
5169
5711
  }
5170
5712
  function replacePgPlaceholders(sql, replace) {
5171
5713
  const s = String(sql);
5172
5714
  const n = s.length;
5173
5715
  let i = 0;
5174
- let mode = "normal";
5175
- let dollarTag = null;
5716
+ let state = { mode: "normal", dollarTag: null };
5176
5717
  let out = "";
5177
- const startsWith = (pos, lit) => s.slice(pos, pos + lit.length) === lit;
5178
- const readDollarTag = (pos) => {
5179
- if (s.charCodeAt(pos) !== 36) return null;
5180
- let j = pos + 1;
5181
- while (j < n) {
5182
- const c = s.charCodeAt(j);
5183
- if (c >= 48 && c <= 57 || c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 95) {
5184
- j++;
5185
- continue;
5186
- }
5187
- break;
5188
- }
5189
- if (j < n && s.charCodeAt(j) === 36 && j > pos) {
5190
- return s.slice(pos, j + 1);
5191
- }
5192
- if (pos + 1 < n && s.charCodeAt(pos + 1) === 36) {
5193
- return "$$";
5194
- }
5195
- return null;
5196
- };
5197
- while (i < n) {
5198
- const ch = s.charCodeAt(i);
5199
- if (mode === "normal") {
5200
- if (ch === 39) {
5201
- out += s[i];
5202
- mode = "single";
5203
- i++;
5204
- continue;
5205
- }
5206
- if (ch === 34) {
5207
- out += s[i];
5208
- mode = "double";
5209
- i++;
5210
- continue;
5211
- }
5212
- if (ch === 45 && i + 1 < n && s.charCodeAt(i + 1) === 45) {
5213
- out += "--";
5214
- mode = "lineComment";
5215
- i += 2;
5216
- continue;
5217
- }
5218
- if (ch === 47 && i + 1 < n && s.charCodeAt(i + 1) === 42) {
5219
- out += "/*";
5220
- mode = "blockComment";
5221
- i += 2;
5222
- continue;
5223
- }
5224
- if (ch === 36) {
5225
- const tag = readDollarTag(i);
5226
- if (tag) {
5227
- out += tag;
5228
- mode = "dollar";
5229
- dollarTag = tag;
5230
- i += tag.length;
5231
- continue;
5232
- }
5233
- let j = i + 1;
5234
- if (j < n) {
5235
- const c1 = s.charCodeAt(j);
5236
- if (c1 >= 48 && c1 <= 57) {
5237
- while (j < n) {
5238
- const cj = s.charCodeAt(j);
5239
- if (cj >= 48 && cj <= 57) {
5240
- j++;
5241
- continue;
5242
- }
5243
- break;
5244
- }
5245
- const numStr = s.slice(i + 1, j);
5246
- const oldIndex = Number(numStr);
5247
- if (!Number.isInteger(oldIndex) || oldIndex < 1) {
5248
- throw new Error(`Invalid param placeholder: $${numStr}`);
5249
- }
5250
- out += replace(oldIndex);
5251
- i = j;
5252
- continue;
5253
- }
5254
- }
5255
- }
5256
- out += s[i];
5257
- i++;
5258
- continue;
5259
- }
5260
- if (mode === "single") {
5261
- out += s[i];
5262
- if (ch === 39) {
5263
- if (i + 1 < n && s.charCodeAt(i + 1) === 39) {
5264
- out += s[i + 1];
5265
- i += 2;
5266
- continue;
5267
- }
5268
- mode = "normal";
5269
- i++;
5270
- continue;
5271
- }
5272
- i++;
5273
- continue;
5274
- }
5275
- if (mode === "double") {
5276
- out += s[i];
5277
- if (ch === 34) {
5278
- if (i + 1 < n && s.charCodeAt(i + 1) === 34) {
5279
- out += s[i + 1];
5280
- i += 2;
5281
- continue;
5282
- }
5283
- mode = "normal";
5284
- i++;
5285
- continue;
5286
- }
5287
- i++;
5288
- continue;
5289
- }
5290
- if (mode === "lineComment") {
5291
- out += s[i];
5292
- if (ch === 10) {
5293
- mode = "normal";
5294
- }
5295
- i++;
5296
- continue;
5297
- }
5298
- if (mode === "blockComment") {
5299
- if (ch === 42 && i + 1 < n && s.charCodeAt(i + 1) === 47) {
5300
- out += "*/";
5301
- i += 2;
5302
- mode = "normal";
5303
- continue;
5304
- }
5305
- out += s[i];
5306
- i++;
5307
- continue;
5308
- }
5309
- if (mode === "dollar") {
5310
- if (dollarTag && startsWith(i, dollarTag)) {
5311
- out += dollarTag;
5312
- i += dollarTag.length;
5313
- mode = "normal";
5314
- dollarTag = null;
5315
- continue;
5316
- }
5317
- out += s[i];
5318
- i++;
5319
- continue;
5320
- }
5321
- out += s[i];
5322
- i++;
5718
+ while (i < n) {
5719
+ const result = processCharacter(s, i, n, state, replace);
5720
+ out += result.output;
5721
+ i += result.consumed;
5722
+ state = updateStateAfterProcessing(state, result);
5323
5723
  }
5324
5724
  return out;
5325
5725
  }
5726
+ function containsPgPlaceholder(sql) {
5727
+ let found = false;
5728
+ replacePgPlaceholders(sql, (oldIndex) => {
5729
+ found = true;
5730
+ return `$${oldIndex}`;
5731
+ });
5732
+ return found;
5733
+ }
5326
5734
  function reindexParams(sql, params, offset) {
5327
5735
  if (!Number.isInteger(offset) || offset < 0) {
5328
5736
  throw new Error(`Invalid param offset: ${offset}`);
@@ -5346,7 +5754,7 @@ function reindexParams(sql, params, offset) {
5346
5754
  return { sql: reindexed, params: newParams };
5347
5755
  }
5348
5756
  function wrapQueryForMethod(method, cteName, resultAlias) {
5349
- const outKey = quoteIdent(resultAlias);
5757
+ const outKey = quoteBatchIdent(resultAlias);
5350
5758
  switch (method) {
5351
5759
  case "findMany":
5352
5760
  case "groupBy":
@@ -5364,24 +5772,70 @@ function wrapQueryForMethod(method, cteName, resultAlias) {
5364
5772
  }
5365
5773
  function isAllCountQueries(queries, keys) {
5366
5774
  var _a;
5367
- for (let i = 0; i < keys.length; i++) {
5368
- if (((_a = queries[keys[i]]) == null ? void 0 : _a.method) !== "count") return false;
5775
+ for (const key of keys) {
5776
+ if (((_a = queries[key]) == null ? void 0 : _a.method) !== "count") return false;
5369
5777
  }
5370
5778
  return true;
5371
5779
  }
5372
5780
  function looksTooComplexForFilter(sql) {
5373
5781
  const s = sql.toLowerCase();
5374
- if (s.includes(" group by ")) return true;
5375
- if (s.includes(" having ")) return true;
5376
- if (s.includes(" union ")) return true;
5377
- if (s.includes(" intersect ")) return true;
5378
- if (s.includes(" except ")) return true;
5379
- if (s.includes(" window ")) return true;
5380
- if (s.includes(" distinct ")) return true;
5381
- return false;
5782
+ const complexKeywords = [
5783
+ " group by ",
5784
+ " having ",
5785
+ " union ",
5786
+ " intersect ",
5787
+ " except ",
5788
+ " window ",
5789
+ " distinct "
5790
+ ];
5791
+ return complexKeywords.some((keyword) => s.includes(keyword));
5382
5792
  }
5383
- function containsPgPlaceholder(sql) {
5384
- return /\$[1-9][0-9]*/.test(sql);
5793
+ function skipWhitespace(s, i) {
5794
+ while (i < s.length && /\s/.test(s[i])) {
5795
+ i++;
5796
+ }
5797
+ return i;
5798
+ }
5799
+ function matchKeyword(s, i, keyword) {
5800
+ const lower = s.slice(i).toLowerCase();
5801
+ if (!lower.startsWith(keyword)) return -1;
5802
+ const endPos = i + keyword.length;
5803
+ if (endPos < s.length && /[a-z0-9_]/i.test(s[endPos])) return -1;
5804
+ return endPos;
5805
+ }
5806
+ function parseQuotedIdentifier2(s, i) {
5807
+ let j = i + 1;
5808
+ while (j < s.length) {
5809
+ if (s[j] === '"') {
5810
+ if (j + 1 < s.length && s[j + 1] === '"') {
5811
+ j += 2;
5812
+ continue;
5813
+ }
5814
+ return { value: s.slice(i, j + 1), endPos: j + 1 };
5815
+ }
5816
+ j++;
5817
+ }
5818
+ return null;
5819
+ }
5820
+ function parseUnquotedIdentifier2(s, i) {
5821
+ if (!/[a-z_]/i.test(s[i])) return null;
5822
+ let j = i;
5823
+ while (j < s.length && /[a-z0-9_.]/i.test(s[j])) {
5824
+ j++;
5825
+ }
5826
+ return { value: s.slice(i, j), endPos: j };
5827
+ }
5828
+ function parseIdentifier(s, i) {
5829
+ if (i >= s.length) return null;
5830
+ if (s[i] === '"') {
5831
+ return parseQuotedIdentifier2(s, i);
5832
+ }
5833
+ return parseUnquotedIdentifier2(s, i);
5834
+ }
5835
+ function findFromClauseEnd(s, fromStart) {
5836
+ const lower = s.slice(fromStart).toLowerCase();
5837
+ const whereIdx = lower.indexOf(" where ");
5838
+ return whereIdx === -1 ? s.length : fromStart + whereIdx;
5385
5839
  }
5386
5840
  function parseSimpleCountSql(sql) {
5387
5841
  const trimmed = sql.trim().replace(/;$/, "").trim();
@@ -5389,111 +5843,181 @@ function parseSimpleCountSql(sql) {
5389
5843
  if (!lower.startsWith("select")) return null;
5390
5844
  if (!lower.includes("count(*)")) return null;
5391
5845
  if (looksTooComplexForFilter(trimmed)) return null;
5392
- const match = trimmed.match(
5393
- /^select\s+count\(\*\)(?:\s*::\s*[a-zA-Z0-9_\."]+)?\s+as\s+("[^"]+"|[a-zA-Z_][a-zA-Z0-9_\.]*)\s+from\s+([\s\S]+?)(?:\s+where\s+([\s\S]+))?$/i
5394
- );
5395
- if (!match) return null;
5396
- const fromSql = match[2].trim();
5397
- const whereSql = match[3] ? match[3].trim() : null;
5846
+ let pos = matchKeyword(trimmed, 0, "select");
5847
+ if (pos === -1) return null;
5848
+ pos = skipWhitespace(trimmed, pos);
5849
+ const countMatch = trimmed.slice(pos).match(/^count\(\*\)/i);
5850
+ if (!countMatch) return null;
5851
+ pos += countMatch[0].length;
5852
+ pos = skipWhitespace(trimmed, pos);
5853
+ const castMatch = trimmed.slice(pos).match(/^::\s*\w+/);
5854
+ if (castMatch) {
5855
+ pos += castMatch[0].length;
5856
+ pos = skipWhitespace(trimmed, pos);
5857
+ }
5858
+ const asPos = matchKeyword(trimmed, pos, "as");
5859
+ if (asPos === -1) return null;
5860
+ pos = skipWhitespace(trimmed, asPos);
5861
+ const ident = parseIdentifier(trimmed, pos);
5862
+ if (!ident) return null;
5863
+ pos = skipWhitespace(trimmed, ident.endPos);
5864
+ const fromPos = matchKeyword(trimmed, pos, "from");
5865
+ if (fromPos === -1) return null;
5866
+ pos = skipWhitespace(trimmed, fromPos);
5867
+ const fromEnd = findFromClauseEnd(trimmed, pos);
5868
+ const fromSql = trimmed.slice(pos, fromEnd).trim();
5398
5869
  if (!fromSql) return null;
5870
+ let whereSql = null;
5871
+ if (fromEnd < trimmed.length) {
5872
+ const adjustedPos = skipWhitespace(trimmed, fromEnd);
5873
+ const wherePos = matchKeyword(trimmed, adjustedPos, "where");
5874
+ if (wherePos !== -1) {
5875
+ whereSql = trimmed.slice(skipWhitespace(trimmed, wherePos)).trim();
5876
+ }
5877
+ }
5399
5878
  return { fromSql, whereSql };
5400
5879
  }
5401
- function buildMergedCountBatchSql(queries, keys, aliasesByKey, modelMap, models, dialect) {
5880
+ function processCountQuery(item, model, models, dialect, sharedFrom, localParams) {
5881
+ const built = buildSQLWithCache(model, models, "count", item.args, dialect);
5882
+ const parsed = parseSimpleCountSql(built.sql);
5883
+ if (!parsed) return null;
5884
+ if (containsPgPlaceholder(parsed.fromSql)) return null;
5885
+ const currentFrom = parsed.fromSql;
5886
+ if (sharedFrom !== null && sharedFrom !== currentFrom) return null;
5887
+ if (!parsed.whereSql) {
5888
+ if (built.params.length > 0) return null;
5889
+ return {
5890
+ expression: `count(*) AS ${quoteBatchIdent(item.alias)}`,
5891
+ reindexedParams: [],
5892
+ sharedFrom: currentFrom
5893
+ };
5894
+ }
5895
+ const re = reindexParams(parsed.whereSql, built.params, localParams.length);
5896
+ return {
5897
+ expression: `count(*) FILTER (WHERE ${re.sql}) AS ${quoteBatchIdent(item.alias)}`,
5898
+ reindexedParams: re.params,
5899
+ sharedFrom: currentFrom
5900
+ };
5901
+ }
5902
+ function buildCountSubqueriesForModel(items, model, models, dialect, aliasIndex) {
5903
+ let sharedFrom = null;
5904
+ const expressions = [];
5905
+ const localParams = [];
5906
+ const localKeys = [];
5907
+ const localAliases = [];
5908
+ for (const item of items) {
5909
+ const result = processCountQuery(
5910
+ item,
5911
+ model,
5912
+ models,
5913
+ dialect,
5914
+ sharedFrom,
5915
+ localParams
5916
+ );
5917
+ if (!result) return null;
5918
+ sharedFrom = result.sharedFrom;
5919
+ expressions.push(result.expression);
5920
+ for (const param of result.reindexedParams) {
5921
+ localParams.push(param);
5922
+ }
5923
+ localKeys.push(item.key);
5924
+ localAliases.push(item.alias);
5925
+ }
5926
+ if (!sharedFrom) return null;
5927
+ const alias = `m_${aliasIndex}`;
5928
+ const subSql = `(SELECT ${expressions.join(", ")} FROM ${sharedFrom}) ${alias}`;
5929
+ return {
5930
+ alias,
5931
+ sql: subSql,
5932
+ params: localParams,
5933
+ keys: localKeys,
5934
+ aliases: localAliases
5935
+ };
5936
+ }
5937
+ function groupQueriesByModel(queries, keys, aliasesByKey) {
5402
5938
  const modelGroups = /* @__PURE__ */ new Map();
5403
- for (let i = 0; i < keys.length; i++) {
5404
- const key = keys[i];
5939
+ for (const key of keys) {
5405
5940
  const q = queries[key];
5406
5941
  const alias = aliasesByKey.get(key);
5407
5942
  if (!alias) return null;
5408
- if (!modelGroups.has(q.model)) modelGroups.set(q.model, []);
5409
- modelGroups.get(q.model).push({ key, alias, args: q.args || {} });
5943
+ if (!modelGroups.has(q.model)) {
5944
+ modelGroups.set(q.model, []);
5945
+ }
5946
+ const items = modelGroups.get(q.model);
5947
+ if (items) {
5948
+ items.push({
5949
+ key,
5950
+ alias,
5951
+ args: q.args || {}
5952
+ });
5953
+ }
5410
5954
  }
5955
+ return modelGroups;
5956
+ }
5957
+ function buildSubqueriesFromGroups(modelGroups, modelMap, models, dialect) {
5411
5958
  const subqueries = [];
5412
5959
  let aliasIndex = 0;
5413
5960
  for (const [modelName, items] of modelGroups) {
5414
5961
  const model = modelMap.get(modelName);
5415
5962
  if (!model) return null;
5416
- let sharedFrom = null;
5417
- const expressions = [];
5418
- const localParams = [];
5419
- const localKeys = [];
5420
- const localAliases = [];
5421
- for (let i = 0; i < items.length; i++) {
5422
- const { key, alias: alias2, args } = items[i];
5423
- const built = buildSQLWithCache(model, models, "count", args, dialect);
5424
- const parsed = parseSimpleCountSql(built.sql);
5425
- if (!parsed) return null;
5426
- if (containsPgPlaceholder(parsed.fromSql)) {
5427
- return null;
5428
- }
5429
- if (!parsed.whereSql) {
5430
- if (built.params.length > 0) return null;
5431
- if (sharedFrom === null) sharedFrom = parsed.fromSql;
5432
- if (sharedFrom !== parsed.fromSql) return null;
5433
- expressions.push(`count(*) AS ${quoteIdent(alias2)}`);
5434
- localKeys.push(key);
5435
- localAliases.push(alias2);
5436
- continue;
5437
- }
5438
- if (sharedFrom === null) sharedFrom = parsed.fromSql;
5439
- if (sharedFrom !== parsed.fromSql) return null;
5440
- const re = reindexParams(
5441
- parsed.whereSql,
5442
- built.params,
5443
- localParams.length
5444
- );
5445
- for (let p = 0; p < re.params.length; p++) localParams.push(re.params[p]);
5446
- expressions.push(
5447
- `count(*) FILTER (WHERE ${re.sql}) AS ${quoteIdent(alias2)}`
5448
- );
5449
- localKeys.push(key);
5450
- localAliases.push(alias2);
5451
- }
5452
- if (!sharedFrom) return null;
5453
- const alias = `m_${aliasIndex++}`;
5454
- const subSql = `(SELECT ${expressions.join(", ")} FROM ${sharedFrom}) ${alias}`;
5455
- subqueries.push({
5456
- alias,
5457
- sql: subSql,
5458
- params: localParams,
5459
- keys: localKeys,
5460
- aliases: localAliases
5461
- });
5963
+ const subquery = buildCountSubqueriesForModel(
5964
+ items,
5965
+ model,
5966
+ models,
5967
+ dialect,
5968
+ aliasIndex++
5969
+ );
5970
+ if (!subquery) return null;
5971
+ subqueries.push(subquery);
5462
5972
  }
5463
- if (subqueries.length === 0) return null;
5973
+ return subqueries.length > 0 ? subqueries : null;
5974
+ }
5975
+ function reindexSubqueries(subqueries) {
5464
5976
  let offset = 0;
5465
5977
  const rewrittenSubs = [];
5466
5978
  const finalParams = [];
5467
- for (let i = 0; i < subqueries.length; i++) {
5468
- const sq = subqueries[i];
5979
+ for (const sq of subqueries) {
5469
5980
  const re = reindexParams(sq.sql, sq.params, offset);
5470
5981
  offset += re.params.length;
5471
5982
  rewrittenSubs.push(re.sql);
5472
- for (let p = 0; p < re.params.length; p++) finalParams.push(re.params[p]);
5983
+ for (const p of re.params) {
5984
+ finalParams.push(p);
5985
+ }
5473
5986
  }
5987
+ return { sql: rewrittenSubs, params: finalParams };
5988
+ }
5989
+ function buildSelectParts(subqueries) {
5474
5990
  const selectParts = [];
5475
- for (let i = 0; i < subqueries.length; i++) {
5476
- const sq = subqueries[i];
5477
- for (let k = 0; k < sq.aliases.length; k++) {
5478
- const outAlias = sq.aliases[k];
5991
+ for (const sq of subqueries) {
5992
+ for (const outAlias of sq.aliases) {
5479
5993
  selectParts.push(
5480
- `${sq.alias}.${quoteIdent(outAlias)} AS ${quoteIdent(outAlias)}`
5994
+ `${sq.alias}.${quoteBatchIdent(outAlias)} AS ${quoteBatchIdent(outAlias)}`
5481
5995
  );
5482
5996
  }
5483
5997
  }
5998
+ return selectParts;
5999
+ }
6000
+ function buildMergedCountBatchSql(queries, keys, aliasesByKey, modelMap, models, dialect) {
6001
+ const modelGroups = groupQueriesByModel(queries, keys, aliasesByKey);
6002
+ if (!modelGroups) return null;
6003
+ const subqueries = buildSubqueriesFromGroups(
6004
+ modelGroups,
6005
+ modelMap,
6006
+ models,
6007
+ dialect
6008
+ );
6009
+ if (!subqueries) return null;
6010
+ const { sql: rewrittenSubs, params: finalParams } = reindexSubqueries(subqueries);
6011
+ const selectParts = buildSelectParts(subqueries);
5484
6012
  const fromSql = rewrittenSubs.join(" CROSS JOIN ");
5485
6013
  const sql = `SELECT ${selectParts.join(", ")} FROM ${fromSql}`;
5486
- const aliases = keys.map((k) => aliasesByKey.get(k));
6014
+ const aliases = keys.map((k) => {
6015
+ var _a;
6016
+ return (_a = aliasesByKey.get(k)) != null ? _a : "";
6017
+ });
5487
6018
  return { sql, params: finalParams, keys, aliases };
5488
6019
  }
5489
- function buildBatchSql(queries, modelMap, models, dialect) {
5490
- const keys = Object.keys(queries);
5491
- if (keys.length === 0) {
5492
- throw new Error("buildBatchSql requires at least one query");
5493
- }
5494
- if (dialect !== "postgres") {
5495
- throw new Error("Batch queries are only supported for postgres dialect");
5496
- }
6020
+ function buildAliasesForKeys(keys) {
5497
6021
  const aliases = new Array(keys.length);
5498
6022
  const aliasesByKey = /* @__PURE__ */ new Map();
5499
6023
  for (let i = 0; i < keys.length; i++) {
@@ -5501,17 +6025,9 @@ function buildBatchSql(queries, modelMap, models, dialect) {
5501
6025
  aliases[i] = a;
5502
6026
  aliasesByKey.set(keys[i], a);
5503
6027
  }
5504
- if (isAllCountQueries(queries, keys)) {
5505
- const merged = buildMergedCountBatchSql(
5506
- queries,
5507
- keys,
5508
- aliasesByKey,
5509
- modelMap,
5510
- models,
5511
- dialect
5512
- );
5513
- if (merged) return merged;
5514
- }
6028
+ return { aliases, aliasesByKey };
6029
+ }
6030
+ function buildRegularBatchQueries(queries, keys, aliases, modelMap, models, dialect) {
5515
6031
  const ctes = new Array(keys.length);
5516
6032
  const selects = new Array(keys.length);
5517
6033
  const allParams = [];
@@ -5536,541 +6052,51 @@ function buildBatchSql(queries, modelMap, models, dialect) {
5536
6052
  queryParams,
5537
6053
  allParams.length
5538
6054
  );
5539
- for (let p = 0; p < reindexedParams.length; p++) {
5540
- allParams.push(reindexedParams[p]);
6055
+ for (const p of reindexedParams) {
6056
+ allParams.push(p);
5541
6057
  }
5542
6058
  const cteName = `batch_${i}`;
5543
6059
  ctes[i] = `${cteName} AS (${reindexedSql})`;
5544
6060
  selects[i] = wrapQueryForMethod(query.method, cteName, aliases[i]);
5545
6061
  }
5546
6062
  const sql = `WITH ${ctes.join(", ")} SELECT ${selects.join(", ")}`;
5547
- return { sql, params: allParams, keys, aliases };
6063
+ return { sql, params: allParams };
5548
6064
  }
5549
- function buildBatchCountSql(queries, modelMap, models, dialect) {
5550
- if (queries.length === 0) {
5551
- throw new Error("buildBatchCountSql requires at least one query");
6065
+ function buildBatchSql(queries, modelMap, models, dialect) {
6066
+ const keys = Object.keys(queries);
6067
+ if (keys.length === 0) {
6068
+ throw new Error("buildBatchSql requires at least one query");
5552
6069
  }
5553
6070
  if (dialect !== "postgres") {
5554
- throw new Error(
5555
- "Batch count queries are only supported for postgres dialect"
5556
- );
6071
+ throw new Error("Batch queries are only supported for postgres dialect");
5557
6072
  }
5558
- const ctes = new Array(queries.length);
5559
- const selects = new Array(queries.length);
5560
- const allParams = [];
5561
- for (let i = 0; i < queries.length; i++) {
5562
- const query = queries[i];
5563
- const model = modelMap.get(query.model);
5564
- if (!model) {
5565
- throw new Error(
5566
- `Model '${query.model}' not found. Available: ${[...modelMap.keys()].join(", ")}`
5567
- );
5568
- }
5569
- const { sql: querySql, params: queryParams } = buildSQLWithCache(
5570
- model,
6073
+ const { aliases, aliasesByKey } = buildAliasesForKeys(keys);
6074
+ if (isAllCountQueries(queries, keys)) {
6075
+ const merged = buildMergedCountBatchSql(
6076
+ queries,
6077
+ keys,
6078
+ aliasesByKey,
6079
+ modelMap,
5571
6080
  models,
5572
- "count",
5573
- query.args || {},
5574
6081
  dialect
5575
6082
  );
5576
- const { sql: reindexedSql, params: reindexedParams } = reindexParams(
5577
- querySql,
5578
- queryParams,
5579
- allParams.length
5580
- );
5581
- for (let p = 0; p < reindexedParams.length; p++) {
5582
- allParams.push(reindexedParams[p]);
5583
- }
5584
- const cteName = `count_${i}`;
5585
- const resultKey = `count_${i}`;
5586
- ctes[i] = `${cteName} AS (${reindexedSql})`;
5587
- selects[i] = `(SELECT * FROM ${cteName}) AS ${quoteIdent(resultKey)}`;
5588
- }
5589
- const sql = `WITH ${ctes.join(", ")} SELECT ${selects.join(", ")}`;
5590
- return { sql, params: allParams };
5591
- }
5592
- function looksLikeJsonString(s) {
5593
- const t = s.trim();
5594
- if (t.length === 0) return false;
5595
- const c0 = t.charCodeAt(0);
5596
- const cN = t.charCodeAt(t.length - 1);
5597
- if (c0 === 123 && cN === 125) return true;
5598
- if (c0 === 91 && cN === 93) return true;
5599
- if (t === "null" || t === "true" || t === "false") return true;
5600
- return false;
5601
- }
5602
- function parseJsonValue(value) {
5603
- if (typeof value !== "string") return value;
5604
- if (!looksLikeJsonString(value)) return value;
5605
- try {
5606
- return JSON.parse(value);
5607
- } catch (e) {
5608
- return value;
5609
- }
5610
- }
5611
- function parseCountValue(value) {
5612
- if (value === null || value === void 0) return 0;
5613
- if (typeof value === "number") return value;
5614
- if (typeof value === "bigint") {
5615
- const n = Number(value);
5616
- return Number.isSafeInteger(n) ? n : 0;
5617
- }
5618
- if (typeof value === "string") {
5619
- const n = Number.parseInt(value, 10);
5620
- return Number.isFinite(n) ? n : 0;
5621
- }
5622
- if (typeof value === "object") {
5623
- const obj = value;
5624
- const countKey = Object.prototype.hasOwnProperty.call(obj, "count") ? "count" : Object.prototype.hasOwnProperty.call(obj, "_count") ? "_count" : Object.keys(obj).find((k) => k.endsWith("_count"));
5625
- if (countKey !== void 0) {
5626
- const v = obj[countKey];
5627
- if (typeof v === "number") return v;
5628
- if (typeof v === "bigint") {
5629
- const n = Number(v);
5630
- return Number.isSafeInteger(n) ? n : 0;
5631
- }
5632
- if (typeof v === "string") {
5633
- const n = Number.parseInt(v, 10);
5634
- return Number.isFinite(n) ? n : 0;
5635
- }
5636
- }
5637
- }
5638
- return 0;
5639
- }
5640
- function parseBatchCountResults(row, count) {
5641
- const results = [];
5642
- for (let i = 0; i < count; i++) {
5643
- const key = `count_${i}`;
5644
- const value = row[key];
5645
- results.push(parseCountValue(value));
5646
- }
5647
- return results;
5648
- }
5649
- function parseBatchResults(row, keys, queries, aliases) {
5650
- const results = {};
5651
- for (let i = 0; i < keys.length; i++) {
5652
- const key = keys[i];
5653
- const columnKey = aliases && aliases[i] ? aliases[i] : key;
5654
- const rawValue = row[columnKey];
5655
- const query = queries[key];
5656
- switch (query.method) {
5657
- case "findMany": {
5658
- const parsed = parseJsonValue(rawValue);
5659
- results[key] = Array.isArray(parsed) ? parsed : [];
5660
- break;
5661
- }
5662
- case "findFirst":
5663
- case "findUnique": {
5664
- const parsed = parseJsonValue(rawValue);
5665
- results[key] = parsed != null ? parsed : null;
5666
- break;
5667
- }
5668
- case "count": {
5669
- results[key] = parseCountValue(rawValue);
5670
- break;
5671
- }
5672
- case "aggregate": {
5673
- const parsed = parseJsonValue(rawValue);
5674
- const obj = parsed != null ? parsed : {};
5675
- results[key] = transformQueryResults("aggregate", [obj]);
5676
- break;
5677
- }
5678
- case "groupBy": {
5679
- const parsed = parseJsonValue(rawValue);
5680
- const arr = Array.isArray(parsed) ? parsed : [];
5681
- results[key] = transformQueryResults("groupBy", arr);
5682
- break;
5683
- }
5684
- default:
5685
- results[key] = rawValue;
5686
- }
6083
+ if (merged) return merged;
5687
6084
  }
5688
- return results;
6085
+ const result = buildRegularBatchQueries(
6086
+ queries,
6087
+ keys,
6088
+ aliases,
6089
+ modelMap,
6090
+ models,
6091
+ dialect
6092
+ );
6093
+ return __spreadProps(__spreadValues({}, result), { keys, aliases });
5689
6094
  }
5690
6095
 
5691
- // src/transaction.ts
5692
- function isolationLevelToPostgresKeyword(level) {
5693
- switch (level) {
5694
- case "ReadCommitted":
5695
- return "read committed";
5696
- case "RepeatableRead":
5697
- return "repeatable read";
5698
- case "Serializable":
5699
- return "serializable";
5700
- default:
5701
- return void 0;
5702
- }
5703
- }
5704
- function createTransactionExecutor(deps) {
5705
- const { modelMap, allModels, dialect, executeRaw, postgresClient } = deps;
5706
- return {
5707
- execute(queries, options) {
5708
- return __async(this, null, function* () {
5709
- if (queries.length === 0) return [];
5710
- if (dialect !== "postgres") {
5711
- throw new Error("$transaction is only supported for postgres dialect");
5712
- }
5713
- if (!postgresClient) {
5714
- throw new Error("postgresClient is required for transactions");
5715
- }
5716
- const transactionCallback = (sql) => __async(null, null, function* () {
5717
- const results = [];
5718
- const isolationLevel = isolationLevelToPostgresKeyword(
5719
- options == null ? void 0 : options.isolationLevel
5720
- );
5721
- if (isolationLevel) {
5722
- yield sql.unsafe(
5723
- `SET TRANSACTION ISOLATION LEVEL ${isolationLevel.toUpperCase()}`
5724
- );
5725
- }
5726
- if (options == null ? void 0 : options.timeout) {
5727
- yield sql.unsafe(
5728
- `SET LOCAL statement_timeout = ${Math.floor(options.timeout)}`
5729
- );
5730
- }
5731
- for (const q of queries) {
5732
- const model = modelMap.get(q.model);
5733
- if (!model) {
5734
- throw new Error(
5735
- `Model '${q.model}' not found. Available: ${[...modelMap.keys()].join(", ")}`
5736
- );
5737
- }
5738
- const { sql: sqlStr, params } = buildSQLWithCache(
5739
- model,
5740
- allModels,
5741
- q.method,
5742
- q.args || {},
5743
- dialect
5744
- );
5745
- const rawResults = yield sql.unsafe(sqlStr, params);
5746
- results.push(transformQueryResults(q.method, rawResults));
5747
- }
5748
- return results;
5749
- });
5750
- return yield postgresClient.begin(transactionCallback);
5751
- });
5752
- }
5753
- };
5754
- }
5755
- var ACCELERATED_METHODS = /* @__PURE__ */ new Set([
5756
- "findMany",
5757
- "findFirst",
5758
- "findUnique",
5759
- "count",
5760
- "aggregate",
5761
- "groupBy"
5762
- ]);
6096
+ // src/index.ts
5763
6097
  function buildSQL(model, models, method, args, dialect) {
5764
6098
  return buildSQLWithCache(model, models, method, args, dialect);
5765
6099
  }
5766
- function executePostgres(client, sql, params) {
5767
- return __async(this, null, function* () {
5768
- return yield client.unsafe(sql, params);
5769
- });
5770
- }
5771
- function shouldSqliteUseGet(method) {
5772
- return method === "count" || method === "findFirst" || method === "findUnique" || method === "aggregate";
5773
- }
5774
- function executeSqlite(db, method, sql, params) {
5775
- const stmt = db.prepare(sql);
5776
- if (shouldSqliteUseGet(method)) {
5777
- const row = stmt.get(...params);
5778
- if (row === void 0) return [];
5779
- return [row];
5780
- }
5781
- return stmt.all(...params);
5782
- }
5783
- function executeWithTiming(input) {
5784
- return __async(this, null, function* () {
5785
- var _a;
5786
- const startTime = Date.now();
5787
- const { sql, params } = buildSQL(
5788
- input.model,
5789
- input.allModels,
5790
- input.method,
5791
- input.args,
5792
- input.dialect
5793
- );
5794
- if (input.debug) {
5795
- console.log(`[${input.dialect}] ${input.modelName}.${input.method}`);
5796
- console.log("SQL:", sql);
5797
- console.log("Params:", params);
5798
- }
5799
- const results = yield input.executeQuery(input.method, sql, params);
5800
- const duration = Date.now() - startTime;
5801
- (_a = input.onQuery) == null ? void 0 : _a.call(input, {
5802
- model: input.modelName,
5803
- method: input.method,
5804
- sql,
5805
- params,
5806
- duration
5807
- });
5808
- return results;
5809
- });
5810
- }
5811
- function resolveModelName(ctx) {
5812
- return (ctx == null ? void 0 : ctx.name) || (ctx == null ? void 0 : ctx.$name);
5813
- }
5814
- function isAllowedModel(allowedModels, modelName) {
5815
- if (!allowedModels) return true;
5816
- return allowedModels.includes(modelName);
5817
- }
5818
- function getModelOrNull(modelMap, modelName) {
5819
- var _a;
5820
- return (_a = modelMap.get(modelName)) != null ? _a : null;
5821
- }
5822
- function fallbackToPrisma(ctx, modelName, method, args) {
5823
- return ctx.$parent[modelName][method](args);
5824
- }
5825
- function createExecuteQuery(client, dialect) {
5826
- return (method, sql, params) => __async(null, null, function* () {
5827
- if (dialect === "postgres") {
5828
- return yield executePostgres(client, sql, params);
5829
- }
5830
- return executeSqlite(client, method, sql, params);
5831
- });
5832
- }
5833
- function logAcceleratedError(debug, dialect, modelName, method, error) {
5834
- if (!debug) return;
5835
- console.error(`[${dialect}] ${modelName}.${method} failed:`, error);
5836
- }
5837
- function canAccelerate(deps, modelName, method) {
5838
- if (!ACCELERATED_METHODS.has(method)) return false;
5839
- if (!isAllowedModel(deps.allowedModels, modelName)) return false;
5840
- return deps.modelMap.has(modelName);
5841
- }
5842
- function runAccelerated(deps, modelName, method, model, args) {
5843
- return __async(this, null, function* () {
5844
- const results = yield executeWithTiming({
5845
- modelName,
5846
- method,
5847
- model,
5848
- allModels: deps.allModels,
5849
- args: args || {},
5850
- dialect: deps.dialect,
5851
- debug: deps.debug,
5852
- executeQuery: deps.executeQuery,
5853
- onQuery: deps.onQuery
5854
- });
5855
- return transformQueryResults(method, results);
5856
- });
5857
- }
5858
- function handleMethodCall(ctx, method, args, deps) {
5859
- return __async(this, null, function* () {
5860
- const modelName = resolveModelName(ctx);
5861
- if (!canAccelerate(deps, modelName, method)) {
5862
- return fallbackToPrisma(ctx, modelName, method, args);
5863
- }
5864
- const model = getModelOrNull(deps.modelMap, modelName);
5865
- if (!model) {
5866
- return fallbackToPrisma(ctx, modelName, method, args);
5867
- }
5868
- try {
5869
- return yield runAccelerated(deps, modelName, method, model, args);
5870
- } catch (error) {
5871
- logAcceleratedError(deps.debug, deps.dialect, modelName, method, error);
5872
- return fallbackToPrisma(ctx, modelName, method, args);
5873
- }
5874
- });
5875
- }
5876
- var DeferredQuery = class {
5877
- constructor(model, method, args) {
5878
- this.model = model;
5879
- this.method = method;
5880
- this.args = args;
5881
- }
5882
- then(onfulfilled, onrejected) {
5883
- throw new Error(
5884
- "Cannot await a batch query. Batch queries must not be awaited inside the $batch callback."
5885
- );
5886
- }
5887
- };
5888
- function createBatchProxy(modelMap, allowedModels) {
5889
- return new Proxy(
5890
- {},
5891
- {
5892
- get(_target, modelName) {
5893
- if (typeof modelName === "symbol") return void 0;
5894
- const model = modelMap.get(modelName);
5895
- if (!model) {
5896
- throw new Error(
5897
- `Model '${modelName}' not found. Available: ${[...modelMap.keys()].join(", ")}`
5898
- );
5899
- }
5900
- if (allowedModels && !allowedModels.includes(modelName)) {
5901
- throw new Error(
5902
- `Model '${modelName}' not allowed. Allowed: ${allowedModels.join(", ")}`
5903
- );
5904
- }
5905
- return new Proxy(
5906
- {},
5907
- {
5908
- get(_target2, method) {
5909
- if (!ACCELERATED_METHODS.has(method)) {
5910
- throw new Error(
5911
- `Method '${method}' not supported in batch. Supported: ${[...ACCELERATED_METHODS].join(", ")}`
5912
- );
5913
- }
5914
- return (args) => {
5915
- return new DeferredQuery(
5916
- modelName,
5917
- method,
5918
- args
5919
- );
5920
- };
5921
- }
5922
- }
5923
- );
5924
- }
5925
- }
5926
- );
5927
- }
5928
- function speedExtension(config) {
5929
- const {
5930
- postgres,
5931
- sqlite,
5932
- models: providedModels,
5933
- dmmf,
5934
- debug = false,
5935
- allowedModels,
5936
- onQuery
5937
- } = config;
5938
- if (!postgres && !sqlite) {
5939
- throw new Error("speedExtension requires either postgres or sqlite client");
5940
- }
5941
- if (postgres && sqlite) {
5942
- throw new Error(
5943
- "speedExtension cannot use both postgres and sqlite clients"
5944
- );
5945
- }
5946
- let models;
5947
- if (providedModels) {
5948
- models = providedModels;
5949
- } else if (dmmf) {
5950
- models = convertDMMFToModels(dmmf.datamodel);
5951
- } else {
5952
- throw new Error("speedExtension requires either models or dmmf parameter.");
5953
- }
5954
- if (!Array.isArray(models) || models.length === 0) {
5955
- throw new Error("speedExtension: models array is empty or invalid");
5956
- }
5957
- const dialect = postgres ? "postgres" : "sqlite";
5958
- const client = postgres || sqlite;
5959
- setGlobalDialect(dialect);
5960
- return (prisma) => {
5961
- const modelMap = new Map(models.map((m) => [m.name, m]));
5962
- const executeQuery = createExecuteQuery(client, dialect);
5963
- const deps = {
5964
- dialect,
5965
- debug,
5966
- onQuery,
5967
- allowedModels,
5968
- allModels: models,
5969
- modelMap,
5970
- executeQuery
5971
- };
5972
- const createMethodHandler = (method) => {
5973
- return function(args) {
5974
- return __async(this, null, function* () {
5975
- return handleMethodCall(this, method, args, deps);
5976
- });
5977
- };
5978
- };
5979
- const methodHandlers = {};
5980
- for (const method of ACCELERATED_METHODS) {
5981
- methodHandlers[method] = createMethodHandler(method);
5982
- }
5983
- const executeRaw = (sql, params) => __async(null, null, function* () {
5984
- if (dialect === "postgres") {
5985
- return yield client.unsafe(sql, params);
5986
- }
5987
- throw new Error("Raw execution for sqlite not supported in transactions");
5988
- });
5989
- const txExecutor = createTransactionExecutor({
5990
- modelMap,
5991
- allModels: models,
5992
- dialect,
5993
- executeRaw,
5994
- postgresClient: postgres
5995
- });
5996
- function batch(callback) {
5997
- return __async(this, null, function* () {
5998
- const batchProxy = createBatchProxy(modelMap, allowedModels);
5999
- const queries = yield callback(batchProxy);
6000
- const batchQueries = {};
6001
- for (const [key, deferred] of Object.entries(queries)) {
6002
- if (!(deferred instanceof DeferredQuery)) {
6003
- throw new Error(
6004
- `Batch query '${key}' must be a deferred query. Did you await it?`
6005
- );
6006
- }
6007
- batchQueries[key] = {
6008
- model: deferred.model,
6009
- method: deferred.method,
6010
- args: deferred.args || {}
6011
- };
6012
- }
6013
- const startTime = Date.now();
6014
- const { sql, params, keys, aliases } = buildBatchSql(
6015
- batchQueries,
6016
- modelMap,
6017
- models,
6018
- dialect
6019
- );
6020
- if (debug) {
6021
- console.log(`[${dialect}] $batch (${keys.length} queries)`);
6022
- console.log("SQL:", sql);
6023
- console.log("Params:", params);
6024
- }
6025
- const rows = yield executeQuery("findMany", sql, params);
6026
- const row = rows[0];
6027
- const results = parseBatchResults(row, keys, batchQueries, aliases);
6028
- const duration = Date.now() - startTime;
6029
- onQuery == null ? void 0 : onQuery({
6030
- model: "_batch",
6031
- method: "batch",
6032
- sql,
6033
- params,
6034
- duration
6035
- });
6036
- return results;
6037
- });
6038
- }
6039
- function transaction(queries, options) {
6040
- return __async(this, null, function* () {
6041
- const startTime = Date.now();
6042
- if (debug) {
6043
- console.log(`[${dialect}] $transaction (${queries.length} queries)`);
6044
- }
6045
- const results = yield txExecutor.execute(queries, options);
6046
- const duration = Date.now() - startTime;
6047
- onQuery == null ? void 0 : onQuery({
6048
- model: "_transaction",
6049
- method: "transaction",
6050
- sql: `TRANSACTION(${queries.length})`,
6051
- params: [],
6052
- duration
6053
- });
6054
- return results;
6055
- });
6056
- }
6057
- return prisma.$extends({
6058
- name: "prisma-sql-speed",
6059
- client: {
6060
- $original: prisma,
6061
- $batch: batch,
6062
- $transaction: transaction
6063
- },
6064
- model: {
6065
- $allModels: methodHandlers
6066
- }
6067
- });
6068
- };
6069
- }
6070
- function extendPrisma(prisma, config) {
6071
- const extension = speedExtension(config);
6072
- return extension(prisma);
6073
- }
6074
6100
  function createToSQLFunction(models, dialect) {
6075
6101
  if (!models || !Array.isArray(models) || models.length === 0) {
6076
6102
  throw new Error("createToSQL requires non-empty models array");
@@ -6158,6 +6184,6 @@ function generateSQLByModel(directives) {
6158
6184
  return byModel;
6159
6185
  }
6160
6186
 
6161
- export { buildBatchCountSql, buildBatchSql, buildSQL, createPrismaSQL, createToSQL, createTransactionExecutor, extendPrisma, generateAllSQL, generateSQL2 as generateSQL, generateSQLByModel, getGlobalDialect, parseBatchCountResults, parseBatchResults, setGlobalDialect, speedExtension, transformQueryResults };
6187
+ export { buildSQL, createPrismaSQL, createToSQL, generateAllSQL, generateSQL2 as generateSQL, generateSQLByModel };
6162
6188
  //# sourceMappingURL=index.js.map
6163
6189
  //# sourceMappingURL=index.js.map