@an5/orm 1.0.8 → 1.1.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.
Files changed (73) hide show
  1. package/README.md +174 -175
  2. package/dist/an5Metadata.js +64 -0
  3. package/dist/an5Orm.d.ts +92 -3
  4. package/dist/an5Orm.js +697 -206
  5. package/dist/config.d.ts +9 -1
  6. package/dist/config.js +13 -18
  7. package/dist/errors.d.ts +1 -1
  8. package/dist/errors.js +1 -1
  9. package/dist/generator/src/api.d.ts +35 -0
  10. package/dist/generator/src/api.js +58 -0
  11. package/dist/generator/src/code-generator.d.ts +14 -13
  12. package/dist/generator/src/code-generator.js +249 -225
  13. package/dist/generator/src/config.d.ts +162 -0
  14. package/dist/generator/src/config.js +354 -0
  15. package/dist/generator/src/dialect.d.ts +88 -0
  16. package/dist/generator/src/dialect.js +318 -0
  17. package/dist/generator/src/dotnet-generator.d.ts +21 -11
  18. package/dist/generator/src/dotnet-generator.js +1053 -546
  19. package/dist/generator/src/field-types.d.ts +96 -0
  20. package/dist/generator/src/field-types.js +433 -0
  21. package/dist/generator/src/golang-generator.d.ts +36 -0
  22. package/dist/generator/src/golang-generator.js +1229 -0
  23. package/dist/generator/src/index.d.ts +1 -1
  24. package/dist/generator/src/index.js +137 -88
  25. package/dist/generator/src/metadata-generator.d.ts +11 -8
  26. package/dist/generator/src/metadata-generator.js +105 -90
  27. package/dist/generator/src/parser.d.ts +37 -11
  28. package/dist/generator/src/parser.js +208 -206
  29. package/dist/generator/src/push-apply.d.ts +37 -0
  30. package/dist/generator/src/push-apply.js +109 -0
  31. package/dist/generator/src/push-schema.d.ts +93 -0
  32. package/dist/generator/src/push-schema.js +189 -0
  33. package/dist/generator/src/python-generator.d.ts +37 -9
  34. package/dist/generator/src/python-generator.js +428 -105
  35. package/dist/generator/src/rust-generator.d.ts +38 -0
  36. package/dist/generator/src/rust-generator.js +971 -0
  37. package/dist/generator/src/suggest.d.ts +17 -0
  38. package/dist/generator/src/suggest.js +45 -0
  39. package/dist/generator/src/type-kinds.d.ts +27 -0
  40. package/dist/generator/src/type-kinds.js +72 -0
  41. package/dist/generator/src/types.d.ts +60 -27
  42. package/dist/generator/src/types.js +41 -2
  43. package/dist/index.d.ts +3 -2
  44. package/dist/index.js +19 -8
  45. package/dist/logger.js +1 -1
  46. package/dist/migration-core.d.ts +102 -0
  47. package/dist/migration-core.js +731 -0
  48. package/dist/orm.d.ts +147 -0
  49. package/dist/orm.js +1654 -0
  50. package/dist/provider-support.d.ts +27 -0
  51. package/dist/provider-support.js +50 -0
  52. package/dist/scripts/release-cli.d.ts +2 -2
  53. package/dist/scripts/release-cli.js +115 -115
  54. package/dist/sql-utils.d.ts +3 -1
  55. package/dist/sql-utils.js +94 -24
  56. package/generator/src/api.ts +45 -0
  57. package/generator/src/code-generator.ts +259 -230
  58. package/generator/src/config.ts +496 -0
  59. package/generator/src/dialect.ts +415 -0
  60. package/generator/src/dotnet-generator.ts +1071 -544
  61. package/generator/src/field-types.ts +473 -0
  62. package/generator/src/golang-generator.ts +1267 -0
  63. package/generator/src/index.ts +146 -116
  64. package/generator/src/metadata-generator.ts +116 -101
  65. package/generator/src/parser.ts +234 -202
  66. package/generator/src/push-apply.ts +148 -0
  67. package/generator/src/push-schema.ts +250 -0
  68. package/generator/src/python-generator.ts +480 -114
  69. package/generator/src/rust-generator.ts +974 -0
  70. package/generator/src/suggest.ts +41 -0
  71. package/generator/src/type-kinds.ts +78 -0
  72. package/generator/src/types.ts +77 -29
  73. package/package.json +32 -6
package/dist/an5Orm.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.an5Orm = exports.An5ORM = void 0;
3
+ exports.an5Orm = exports.An5ORM = exports.ViewClient = exports.TableClient = void 0;
4
4
  const adapters_1 = require("@an5/adapters");
5
5
  const crypto_1 = require("crypto");
6
6
  const logger_1 = require("./logger");
@@ -8,7 +8,7 @@ const errors_1 = require("./errors");
8
8
  const metadata_1 = require("./metadata");
9
9
  const sql_utils_1 = require("./sql-utils");
10
10
  function aggSelect(fn, key) {
11
- return `${fn}(${(0, sql_utils_1.quoteIdentifier)(key)}) as ${aggAlias(fn, key)}`;
11
+ return `${fn}(${(0, sql_utils_1.quoteIdentifier)(key)}) as ${aggAlias(fn.toLowerCase(), key)}`;
12
12
  }
13
13
  function aggAlias(fn, key) {
14
14
  return (0, sql_utils_1.sanitizeParamName)(`${fn}_${key}`);
@@ -21,9 +21,204 @@ async function getAdapter() {
21
21
  }
22
22
  return adapter;
23
23
  }
24
- async function execQuery(queryText, params) {
24
+ const execQuery = Object.assign(async (queryText, params) => {
25
25
  const a = await getAdapter();
26
26
  return a.exec(queryText, params);
27
+ }, {
28
+ executeRaw: async (queryText, params) => {
29
+ const a = await getAdapter();
30
+ return a._executeRaw(queryText, params);
31
+ },
32
+ transaction: async (fn, options) => {
33
+ const a = await getAdapter();
34
+ return a.$transaction(async (tx) => fn(executorFromAdapterLike(tx)), options);
35
+ },
36
+ beginTransaction: async () => {
37
+ const a = await getAdapter();
38
+ if (typeof a.$begin !== "function") {
39
+ throw new Error("Interactive transactions require an adapter with $begin support");
40
+ }
41
+ const tx = await a.$begin();
42
+ return {
43
+ executor: executorFromAdapterLike(tx),
44
+ commit: () => tx.$commit(),
45
+ rollback: () => tx.$rollback(),
46
+ };
47
+ },
48
+ });
49
+ function normalizeAffectedCount(result) {
50
+ if (typeof result === "number")
51
+ return result;
52
+ if (!result)
53
+ return 0;
54
+ if (Array.isArray(result.rowsAffected))
55
+ return Number(result.rowsAffected[0] ?? 0);
56
+ if (typeof result.rowsAffected === "number")
57
+ return result.rowsAffected;
58
+ if (typeof result.count === "number")
59
+ return result.count;
60
+ if (Array.isArray(result) && result.length === 1)
61
+ return normalizeAffectedCount(result[0]);
62
+ return 0;
63
+ }
64
+ const FILTER_OPERATOR_KEYS = new Set(["in", "notIn", "contains", "startsWith", "endsWith", "not", "gte", "lte", "gt", "lt"]);
65
+ function executorFromAdapterLike(adapterLike) {
66
+ return Object.assign(async (queryText, params) => {
67
+ return adapterLike.exec(queryText, params);
68
+ }, {
69
+ executeRaw: async (queryText, params) => {
70
+ if (typeof adapterLike._executeRaw === "function") {
71
+ return adapterLike._executeRaw(queryText, params);
72
+ }
73
+ if (typeof adapterLike.executeRaw === "function") {
74
+ return adapterLike.executeRaw(queryText, params);
75
+ }
76
+ return normalizeAffectedCount(await adapterLike.exec(queryText, params));
77
+ },
78
+ });
79
+ }
80
+ function flattenSimpleEqualityWhere(where) {
81
+ if (!where || typeof where !== "object" || where instanceof Date || Array.isArray(where)) {
82
+ return null;
83
+ }
84
+ const flat = {};
85
+ for (const [key, value] of Object.entries(where)) {
86
+ if (value === null || value === undefined || Array.isArray(value))
87
+ return null;
88
+ if (value instanceof Date || typeof value !== "object") {
89
+ flat[key] = value;
90
+ continue;
91
+ }
92
+ if (!key.includes("_"))
93
+ return null;
94
+ for (const [innerKey, innerValue] of Object.entries(value)) {
95
+ if (FILTER_OPERATOR_KEYS.has(innerKey))
96
+ return null;
97
+ if (innerValue === null || innerValue === undefined || (typeof innerValue === "object" && !(innerValue instanceof Date))) {
98
+ return null;
99
+ }
100
+ flat[innerKey] = innerValue;
101
+ }
102
+ }
103
+ return Object.keys(flat).length > 0 ? flat : null;
104
+ }
105
+ function asArray(value) {
106
+ if (value === undefined)
107
+ return [];
108
+ return Array.isArray(value) ? value : [value];
109
+ }
110
+ function hasOwn(obj, key) {
111
+ return !!obj && Object.prototype.hasOwnProperty.call(obj, key);
112
+ }
113
+ function appendUpdateSet(sets, params, key, val) {
114
+ const col = (0, sql_utils_1.quoteIdentifier)(key);
115
+ const safeKey = (0, sql_utils_1.sanitizeParamName)(key);
116
+ if (val && typeof val === "object" && !(val instanceof Date)) {
117
+ const ops = [
118
+ ['increment', '+'],
119
+ ['decrement', '-'],
120
+ ['multiply', '*'],
121
+ ['divide', '/'],
122
+ ];
123
+ for (const [opKey, symbol] of ops) {
124
+ if (val[opKey] !== undefined) {
125
+ const paramName = `${safeKey}_${opKey.slice(0, 3)}`;
126
+ sets.push(`${col} = ${col} ${symbol} @${paramName}`);
127
+ params[paramName] = val[opKey];
128
+ return;
129
+ }
130
+ }
131
+ if (val.set !== undefined) {
132
+ const paramName = `${safeKey}_set`;
133
+ sets.push(`${col} = @${paramName}`);
134
+ params[paramName] = val.set;
135
+ return;
136
+ }
137
+ }
138
+ sets.push(`${col} = @${safeKey}`);
139
+ params[safeKey] = val;
140
+ }
141
+ function normalizeByFields(by) {
142
+ if (typeof by === "string")
143
+ return [by];
144
+ return Array.isArray(by) ? by.filter((field) => typeof field === "string" && field.length > 0) : [];
145
+ }
146
+ function selectedAggregateFields(fields) {
147
+ if (!fields || typeof fields !== "object")
148
+ return [];
149
+ return Object.keys(fields).filter((key) => fields[key]);
150
+ }
151
+ function aggregateExpression(kind, field) {
152
+ const upper = kind.toUpperCase();
153
+ if (upper === "COUNT" && field === "_all")
154
+ return "COUNT(*)";
155
+ return `${upper}(${(0, sql_utils_1.quoteIdentifier)(field)})`;
156
+ }
157
+ function addHavingPredicate(conditions, params, expr, paramBase, filter) {
158
+ if (filter && typeof filter === "object" && !(filter instanceof Date)) {
159
+ for (const [op, value] of Object.entries(filter)) {
160
+ const p = (0, sql_utils_1.sanitizeParamName)(`having_${paramBase}_${op}`);
161
+ if (op === "equals") {
162
+ conditions.push(`${expr} = @${p}`);
163
+ params[p] = value;
164
+ }
165
+ else if (op === "gt" || op === "gte" || op === "lt" || op === "lte") {
166
+ const symbol = op === "gt" ? ">" : op === "gte" ? ">=" : op === "lt" ? "<" : "<=";
167
+ conditions.push(`${expr} ${symbol} @${p}`);
168
+ params[p] = value;
169
+ }
170
+ else if (op === "not") {
171
+ if (value && typeof value === "object" && !(value instanceof Date)) {
172
+ const nested = [];
173
+ addHavingPredicate(nested, params, expr, `${paramBase}_not`, value);
174
+ if (nested.length > 0)
175
+ conditions.push(`NOT (${nested.join(" AND ")})`);
176
+ }
177
+ else {
178
+ conditions.push(`${expr} <> @${p}`);
179
+ params[p] = value;
180
+ }
181
+ }
182
+ else if ((op === "in" || op === "notIn") && Array.isArray(value)) {
183
+ if (value.length === 0) {
184
+ conditions.push(op === "in" ? "1 = 0" : "1 = 1");
185
+ }
186
+ else {
187
+ const placeholders = value.map((item, idx) => {
188
+ const itemParam = (0, sql_utils_1.sanitizeParamName)(`${p}_${idx}`);
189
+ params[itemParam] = item;
190
+ return `@${itemParam}`;
191
+ });
192
+ conditions.push(`${expr} ${op === "in" ? "IN" : "NOT IN"} (${placeholders.join(", ")})`);
193
+ }
194
+ }
195
+ }
196
+ return;
197
+ }
198
+ const p = (0, sql_utils_1.sanitizeParamName)(`having_${paramBase}`);
199
+ conditions.push(`${expr} = @${p}`);
200
+ params[p] = filter;
201
+ }
202
+ function buildHavingSql(having, params) {
203
+ if (!having || typeof having !== "object")
204
+ return "";
205
+ const conditions = [];
206
+ const aggregateKeys = {
207
+ _count: "COUNT",
208
+ _sum: "SUM",
209
+ _avg: "AVG",
210
+ _min: "MIN",
211
+ _max: "MAX",
212
+ };
213
+ for (const [key, sqlFn] of Object.entries(aggregateKeys)) {
214
+ const fields = having[key];
215
+ if (!fields || typeof fields !== "object")
216
+ continue;
217
+ for (const [field, filter] of Object.entries(fields)) {
218
+ addHavingPredicate(conditions, params, aggregateExpression(sqlFn, field), `${key}_${field}`, filter);
219
+ }
220
+ }
221
+ return conditions.length > 0 ? conditions.join(" AND ") : "";
27
222
  }
28
223
  function projectFields(row, select) {
29
224
  if (!row || !select)
@@ -39,6 +234,52 @@ function projectFields(row, select) {
39
234
  }
40
235
  return projected;
41
236
  }
237
+ function collectRelationSelections(modelName, select, metadata) {
238
+ if (!select || typeof select !== "object")
239
+ return undefined;
240
+ const modelRelations = metadata.relationMap[modelName] || {};
241
+ const relationSelections = {};
242
+ for (const [key, value] of Object.entries(select)) {
243
+ if (!value)
244
+ continue;
245
+ if (key === "_count" || modelRelations[key]) {
246
+ relationSelections[key] = value;
247
+ }
248
+ }
249
+ return Object.keys(relationSelections).length > 0 ? relationSelections : undefined;
250
+ }
251
+ function mergeIncludes(primary, secondary) {
252
+ if (!primary)
253
+ return secondary;
254
+ if (!secondary)
255
+ return primary;
256
+ return { ...secondary, ...primary };
257
+ }
258
+ function relationOrderClause(orderBy, fallbackKey) {
259
+ const sql = (0, sql_utils_1.buildOrderBy)(orderBy);
260
+ return sql ? sql.replace(/^\s*ORDER\s+BY\s+/i, "") : `${(0, sql_utils_1.quoteIdentifier)(fallbackKey)} ASC`;
261
+ }
262
+ function requiredRelationKeys(modelName, include, metadata) {
263
+ if (!include || typeof include !== "object")
264
+ return [];
265
+ const modelRelations = metadata.relationMap[modelName] || {};
266
+ const keys = new Set();
267
+ for (const [key, value] of Object.entries(include)) {
268
+ if (!value)
269
+ continue;
270
+ const relation = modelRelations[key];
271
+ if (!relation) {
272
+ if (key === "_count") {
273
+ for (const rel of Object.values(modelRelations)) {
274
+ keys.add(rel.localKey);
275
+ }
276
+ }
277
+ continue;
278
+ }
279
+ keys.add(relation.relationType === "one" ? relation.foreignKey : relation.localKey);
280
+ }
281
+ return Array.from(keys);
282
+ }
42
283
  // Helper to batch-query and resolve relationships
43
284
  async function resolveIncludes(modelName, rows, include, executor, metadata) {
44
285
  if (!rows || rows.length === 0 || !include)
@@ -51,22 +292,24 @@ async function resolveIncludes(modelName, rows, include, executor, metadata) {
51
292
  continue;
52
293
  const relation = modelRelations[key];
53
294
  if (!relation) {
54
- if (key === "_count" && value && typeof value === "object") {
55
- const countFields = Object.keys(value.select || {});
295
+ if (key === "_count" && value) {
296
+ const countFields = value === true
297
+ ? Object.keys(modelRelations)
298
+ : Object.keys(value.select || {}).filter((field) => value.select[field]);
56
299
  for (const countField of countFields) {
57
300
  const rel = modelRelations[countField];
58
301
  if (rel) {
59
- const relTable = metadata.modelToTable[rel.modelName];
302
+ const relTable = (0, sql_utils_1.quoteTableIdentifier)(metadata.modelToTable[rel.modelName] || rel.modelName);
60
303
  const localKeys = rows.map(r => r[rel.localKey]).filter(Boolean);
61
304
  if (localKeys.length === 0) {
62
305
  rows.forEach(r => { r._count = { ...r._count, [countField]: 0 }; });
63
306
  continue;
64
307
  }
65
- const sqlText = `
66
- SELECT ${(0, sql_utils_1.quoteIdentifier)(rel.foreignKey)} as parentId, COUNT(*) as count
67
- FROM ${relTable} WITH (NOLOCK)
68
- WHERE ${(0, sql_utils_1.quoteIdentifier)(rel.foreignKey)} IN (${localKeys.map((_, i) => `@lk_${i}`).join(", ")})
69
- GROUP BY ${(0, sql_utils_1.quoteIdentifier)(rel.foreignKey)}
308
+ const sqlText = `
309
+ SELECT ${(0, sql_utils_1.quoteIdentifier)(rel.foreignKey)} as parentId, COUNT(*) as count
310
+ FROM ${relTable} WITH (NOLOCK)
311
+ WHERE ${(0, sql_utils_1.quoteIdentifier)(rel.foreignKey)} IN (${localKeys.map((_, i) => `@lk_${i}`).join(", ")})
312
+ GROUP BY ${(0, sql_utils_1.quoteIdentifier)(rel.foreignKey)}
70
313
  `;
71
314
  const countParams = {};
72
315
  localKeys.forEach((lk, i) => { countParams[`lk_${i}`] = lk; });
@@ -82,7 +325,7 @@ async function resolveIncludes(modelName, rows, include, executor, metadata) {
82
325
  }
83
326
  continue;
84
327
  }
85
- const relTable = metadata.modelToTable[relation.modelName];
328
+ const relTable = (0, sql_utils_1.quoteTableIdentifier)(metadata.modelToTable[relation.modelName] || relation.modelName);
86
329
  const isMany = relation.relationType === "many";
87
330
  const matchKey = relation.relationType === "one" ? relation.foreignKey : relation.localKey;
88
331
  const searchKey = relation.relationType === "one" ? relation.localKey : relation.foreignKey;
@@ -93,6 +336,9 @@ async function resolveIncludes(modelName, rows, include, executor, metadata) {
93
336
  }
94
337
  const uniqueKeys = Array.from(new Set(keys));
95
338
  let relCols = "*";
339
+ const nestedInclude = value && typeof value === "object"
340
+ ? mergeIncludes(value.include, collectRelationSelections(relation.modelName, value.select, metadata))
341
+ : undefined;
96
342
  if (value && typeof value === "object" && value.select) {
97
343
  const subSelect = value.select;
98
344
  const subRelations = metadata.relationMap[relation.modelName] || {};
@@ -100,37 +346,67 @@ async function resolveIncludes(modelName, rows, include, executor, metadata) {
100
346
  .filter(k => subSelect[k] && !subRelations[k])
101
347
  .map(k => (0, sql_utils_1.quoteIdentifier)(k));
102
348
  if (selectedSubCols.length > 0) {
103
- const quotedSearchKey = (0, sql_utils_1.quoteIdentifier)(searchKey);
104
- if (!selectedSubCols.includes(quotedSearchKey)) {
105
- selectedSubCols.push(quotedSearchKey);
349
+ for (const requiredKey of [searchKey, ...requiredRelationKeys(relation.modelName, nestedInclude, metadata)]) {
350
+ const quotedRequiredKey = (0, sql_utils_1.quoteIdentifier)(requiredKey);
351
+ if (!selectedSubCols.includes(quotedRequiredKey)) {
352
+ selectedSubCols.push(quotedRequiredKey);
353
+ }
106
354
  }
107
355
  relCols = selectedSubCols.join(", ");
108
356
  }
109
357
  }
110
- let sqlText = `SELECT ${relCols} FROM ${relTable} WITH (NOLOCK) WHERE ${(0, sql_utils_1.quoteIdentifier)(searchKey)} IN (${uniqueKeys.map((_, i) => `@k_${i}`).join(", ")})`;
358
+ const filters = [`${(0, sql_utils_1.quoteIdentifier)(searchKey)} IN (${uniqueKeys.map((_, i) => `@k_${i}`).join(", ")})`];
359
+ let sqlText = "";
111
360
  const subParams = {};
112
361
  uniqueKeys.forEach((k, i) => { subParams[`k_${i}`] = k; });
362
+ let subArgs = undefined;
113
363
  if (value && typeof value === "object") {
114
- const subArgs = value;
115
- if (subArgs.orderBy) {
364
+ subArgs = value;
365
+ const subWhereSql = (0, sql_utils_1.parseWhere)(relation.modelName, subArgs.where, subParams, `rel_${key}_`, {
366
+ relationMap: metadata.relationMap,
367
+ modelToTable: metadata.modelToTable,
368
+ });
369
+ if (subWhereSql) {
370
+ filters.push(subWhereSql);
371
+ }
372
+ }
373
+ const hasRelationPagination = isMany && subArgs && (subArgs.take !== undefined || subArgs.skip !== undefined);
374
+ if (hasRelationPagination) {
375
+ const rn = (0, sql_utils_1.quoteIdentifier)("__an5_rn");
376
+ const skip = (0, sql_utils_1.toNonNegativeInt)(subArgs.skip);
377
+ const take = subArgs.take !== undefined ? (0, sql_utils_1.toNonNegativeInt)(subArgs.take, 1) : undefined;
378
+ const whereSql = filters.join(" AND ");
379
+ sqlText = `SELECT * FROM (
380
+ SELECT ${relCols}, ROW_NUMBER() OVER (PARTITION BY ${(0, sql_utils_1.quoteIdentifier)(searchKey)} ORDER BY ${relationOrderClause(subArgs.orderBy, searchKey)}) AS ${rn}
381
+ FROM ${relTable} WITH (NOLOCK)
382
+ WHERE ${whereSql}
383
+ ) AS [an5_rel]
384
+ WHERE ${rn} > ${skip}`;
385
+ if (take !== undefined) {
386
+ sqlText += ` AND ${rn} <= ${skip + take}`;
387
+ }
388
+ sqlText += ` ORDER BY ${(0, sql_utils_1.quoteIdentifier)(searchKey)}, ${rn}`;
389
+ }
390
+ else {
391
+ sqlText = `SELECT ${relCols} FROM ${relTable} WITH (NOLOCK) WHERE ${filters.join(" AND ")}`;
392
+ if (subArgs?.orderBy) {
116
393
  sqlText += (0, sql_utils_1.buildOrderBy)(subArgs.orderBy);
117
394
  }
118
395
  }
119
396
  const relatedRows = await executor(sqlText, subParams);
120
- if (value && typeof value === "object" && value.select) {
121
- relatedRows.forEach((r, idx) => {
122
- relatedRows[idx] = projectFields(r, value.select);
123
- });
124
- }
125
- if (value && typeof value === "object" && value.include) {
126
- await resolveIncludes(relation.modelName, relatedRows, value.include, executor, metadata);
397
+ relatedRows.forEach((r) => { delete r.__an5_rn; });
398
+ if (nestedInclude) {
399
+ await resolveIncludes(relation.modelName, relatedRows, nestedInclude, executor, metadata);
127
400
  }
401
+ const outputRows = value && typeof value === "object" && value.select
402
+ ? relatedRows.map((r) => projectFields(r, value.select))
403
+ : relatedRows;
128
404
  const groupMap = new Map();
129
- relatedRows.forEach((r) => {
405
+ relatedRows.forEach((r, idx) => {
130
406
  const k = r[searchKey];
131
407
  if (!groupMap.has(k))
132
408
  groupMap.set(k, []);
133
- groupMap.get(k).push(r);
409
+ groupMap.get(k).push(outputRows[idx]);
134
410
  });
135
411
  rows.forEach(r => {
136
412
  const k = r[matchKey];
@@ -148,20 +424,33 @@ async function resolveIncludes(modelName, rows, include, executor, metadata) {
148
424
  class TableClient {
149
425
  constructor(modelName, tableName, executor, orm) {
150
426
  this.modelName = modelName;
151
- this.tableName = tableName;
152
427
  this.executor = executor;
153
428
  this.orm = orm;
429
+ this.tableName = (0, sql_utils_1.quoteTableIdentifier)(tableName);
430
+ }
431
+ async executeRaw(queryText, params) {
432
+ if (this.executor.executeRaw) {
433
+ return this.executor.executeRaw(queryText, params);
434
+ }
435
+ return normalizeAffectedCount(await this.executor(queryText, params));
154
436
  }
155
437
  async findMany(args) {
156
438
  return this.orm._executeMiddleware({ model: this.modelName, action: 'findMany', args }, async (params) => {
157
439
  const { args: finalArgs } = params;
158
440
  const hasSkip = finalArgs?.skip !== undefined && finalArgs?.skip !== null;
441
+ const selectedRelations = collectRelationSelections(this.modelName, finalArgs?.select, this.orm.metadata);
442
+ const include = mergeIncludes(finalArgs?.include, selectedRelations);
159
443
  let cols = "*";
160
444
  if (finalArgs?.select) {
161
445
  const modelRelations = this.orm.metadata.relationMap[this.modelName] || {};
162
- const selectedCols = Object.keys(finalArgs.select)
163
- .filter(k => finalArgs.select[k] && !modelRelations[k])
164
- .map(k => (0, sql_utils_1.quoteIdentifier)(k));
446
+ const selectedColNames = Object.keys(finalArgs.select)
447
+ .filter(k => finalArgs.select[k] && !modelRelations[k] && k !== "_count");
448
+ for (const key of requiredRelationKeys(this.modelName, include, this.orm.metadata)) {
449
+ if (!selectedColNames.includes(key)) {
450
+ selectedColNames.push(key);
451
+ }
452
+ }
453
+ const selectedCols = selectedColNames.map(k => (0, sql_utils_1.quoteIdentifier)(k));
165
454
  if (selectedCols.length > 0) {
166
455
  cols = selectedCols.join(", ");
167
456
  }
@@ -190,14 +479,14 @@ class TableClient {
190
479
  }
191
480
  }
192
481
  const rows = await this.executor(sqlText, p);
482
+ if (include) {
483
+ await resolveIncludes(this.modelName, rows, include, this.executor, this.orm.metadata);
484
+ }
193
485
  if (finalArgs?.select) {
194
486
  rows.forEach((r, idx) => {
195
487
  rows[idx] = projectFields(r, finalArgs.select);
196
488
  });
197
489
  }
198
- if (finalArgs?.include) {
199
- await resolveIncludes(this.modelName, rows, finalArgs.include, this.executor, this.orm.metadata);
200
- }
201
490
  return rows;
202
491
  });
203
492
  }
@@ -225,6 +514,12 @@ class TableClient {
225
514
  return result[0]?.count || 0;
226
515
  });
227
516
  }
517
+ scopedRelationWhere(relation, parentId, where) {
518
+ const parentWhere = { [relation.foreignKey]: parentId };
519
+ if (!where || Object.keys(where).length === 0)
520
+ return parentWhere;
521
+ return { AND: [where, parentWhere] };
522
+ }
228
523
  async handleNestedWrites(data, parentId) {
229
524
  const modelRelations = this.orm.metadata.relationMap[this.modelName] || {};
230
525
  for (const [key, value] of Object.entries(data)) {
@@ -235,32 +530,72 @@ class TableClient {
235
530
  if (!relTableClient)
236
531
  continue;
237
532
  const nestedOps = value;
533
+ if (relation.relationType !== "many")
534
+ continue;
535
+ if (hasOwn(nestedOps, "set")) {
536
+ await relTableClient.updateMany({
537
+ where: { [relation.foreignKey]: parentId },
538
+ data: { [relation.foreignKey]: null },
539
+ });
540
+ for (const item of asArray(nestedOps.set)) {
541
+ await relTableClient.update({
542
+ where: item,
543
+ data: { [relation.foreignKey]: parentId }
544
+ });
545
+ }
546
+ }
238
547
  // Handle deleteMany (an5Orm-style)
239
- if (nestedOps.deleteMany) {
548
+ if (hasOwn(nestedOps, "deleteMany")) {
240
549
  const deleteWhere = Array.isArray(nestedOps.deleteMany) ? { OR: nestedOps.deleteMany } : nestedOps.deleteMany;
241
550
  // Scope deletion to parent
242
- const scopedWhere = { ...deleteWhere, [relation.foreignKey]: parentId };
551
+ const scopedWhere = this.scopedRelationWhere(relation, parentId, deleteWhere);
243
552
  await relTableClient.deleteMany({ where: scopedWhere });
244
553
  }
554
+ if (hasOwn(nestedOps, "delete")) {
555
+ const deleteWhere = Array.isArray(nestedOps.delete) ? { OR: nestedOps.delete } : nestedOps.delete;
556
+ await relTableClient.deleteMany({
557
+ where: this.scopedRelationWhere(relation, parentId, deleteWhere),
558
+ });
559
+ }
560
+ if (hasOwn(nestedOps, "disconnect")) {
561
+ const disconnectWhere = Array.isArray(nestedOps.disconnect) ? { OR: nestedOps.disconnect } : nestedOps.disconnect;
562
+ await relTableClient.updateMany({
563
+ where: this.scopedRelationWhere(relation, parentId, disconnectWhere),
564
+ data: { [relation.foreignKey]: null },
565
+ });
566
+ }
567
+ if (hasOwn(nestedOps, "update")) {
568
+ for (const item of asArray(nestedOps.update)) {
569
+ await relTableClient.update({
570
+ where: this.scopedRelationWhere(relation, parentId, item.where),
571
+ data: item.data,
572
+ });
573
+ }
574
+ }
575
+ if (hasOwn(nestedOps, "upsert")) {
576
+ for (const item of asArray(nestedOps.upsert)) {
577
+ await relTableClient.upsert({
578
+ where: this.scopedRelationWhere(relation, parentId, item.where),
579
+ create: { ...item.create, [relation.foreignKey]: parentId },
580
+ update: item.update,
581
+ });
582
+ }
583
+ }
245
584
  // Handle create
246
- if (nestedOps.create) {
247
- const createItems = Array.isArray(nestedOps.create) ? nestedOps.create : [nestedOps.create];
248
- for (const item of createItems) {
585
+ if (hasOwn(nestedOps, "create")) {
586
+ for (const item of asArray(nestedOps.create)) {
249
587
  // Inject parent ID
250
588
  const itemData = { ...item, [relation.foreignKey]: parentId };
251
589
  await relTableClient.create({ data: itemData });
252
590
  }
253
591
  }
254
592
  // Handle connect
255
- if (nestedOps.connect) {
256
- const connectItems = Array.isArray(nestedOps.connect) ? nestedOps.connect : [nestedOps.connect];
257
- for (const item of connectItems) {
258
- if (relation.relationType === "many") {
259
- await relTableClient.update({
260
- where: item,
261
- data: { [relation.foreignKey]: parentId }
262
- });
263
- }
593
+ if (hasOwn(nestedOps, "connect")) {
594
+ for (const item of asArray(nestedOps.connect)) {
595
+ await relTableClient.update({
596
+ where: item,
597
+ data: { [relation.foreignKey]: parentId }
598
+ });
264
599
  }
265
600
  }
266
601
  }
@@ -290,9 +625,10 @@ class TableClient {
290
625
  // Handle one-relation connect where we hold the FK
291
626
  for (const [key, value] of Object.entries(nestedData)) {
292
627
  const rel = modelRelations[key];
293
- if (rel && rel.relationType === "one" && value.connect) {
294
- const connectObj = value.connect;
295
- const targetId = connectObj.id || Object.values(connectObj)[0];
628
+ const nestedValue = value && typeof value === "object" ? value : {};
629
+ if (rel && rel.relationType === "one" && (nestedValue.connect || nestedValue.set)) {
630
+ const connectObj = nestedValue.connect || nestedValue.set;
631
+ const targetId = connectObj[rel.localKey] || connectObj.id || Object.values(connectObj)[0];
296
632
  data[rel.foreignKey] = targetId;
297
633
  }
298
634
  }
@@ -369,48 +705,35 @@ class TableClient {
369
705
  // Handle one-relation connect where we hold the FK
370
706
  for (const [key, value] of Object.entries(nestedData)) {
371
707
  const rel = modelRelations[key];
372
- if (rel && rel.relationType === "one" && value.connect) {
373
- const connectObj = value.connect;
374
- const targetId = connectObj.id || Object.values(connectObj)[0];
375
- data[rel.foreignKey] = targetId;
708
+ const nestedValue = value && typeof value === "object" ? value : {};
709
+ if (rel && rel.relationType === "one") {
710
+ if (nestedValue.connect || nestedValue.set) {
711
+ const connectObj = nestedValue.connect || nestedValue.set;
712
+ const targetId = connectObj[rel.localKey] || connectObj.id || Object.values(connectObj)[0];
713
+ data[rel.foreignKey] = targetId;
714
+ }
715
+ else if (hasOwn(nestedValue, "disconnect") || value === null) {
716
+ data[rel.foreignKey] = null;
717
+ }
376
718
  }
377
719
  }
378
720
  const sets = [];
379
721
  const p = {};
380
722
  for (const key of Object.keys(data)) {
381
723
  const val = data[key];
382
- const col = (0, sql_utils_1.quoteIdentifier)(key);
383
- const safeKey = (0, sql_utils_1.sanitizeParamName)(key);
384
- if (val && typeof val === "object" && !(val instanceof Date)) {
385
- if (val.increment !== undefined) {
386
- sets.push(`${col} = ${col} + @${safeKey}_inc`);
387
- p[`${safeKey}_inc`] = val.increment;
388
- continue;
389
- }
390
- else if (val.decrement !== undefined) {
391
- sets.push(`${col} = ${col} - @${safeKey}_dec`);
392
- p[`${safeKey}_dec`] = val.decrement;
393
- continue;
394
- }
395
- else if (val.set !== undefined) {
396
- sets.push(`${col} = @${safeKey}_set`);
397
- p[`${safeKey}_set`] = val.set;
398
- continue;
399
- }
400
- }
401
- sets.push(`${col} = @${safeKey}`);
402
- p[safeKey] = val;
724
+ appendUpdateSet(sets, p, key, val);
403
725
  }
404
726
  const whereParams = {};
405
727
  const whereSql = this.orm.parseWhere(this.modelName, finalArgs.where, whereParams, "w_");
406
728
  Object.assign(p, whereParams);
407
- const sqlText = `UPDATE ${this.tableName} SET ${sets.join(", ")} WHERE ${whereSql}`;
408
- await this.executor(sqlText, p);
409
- // Get parent ID for nested writes
410
729
  const existing = await this.findUnique({ where: finalArgs.where });
411
730
  if (!existing)
412
731
  throw new Error("Record not found to update");
413
732
  const parentId = existing.id;
733
+ if (sets.length > 0) {
734
+ const sqlText = `UPDATE ${this.tableName} SET ${sets.join(", ")} WHERE ${whereSql}`;
735
+ await this.executor(sqlText, p);
736
+ }
414
737
  // Process nested writes
415
738
  await this.handleNestedWrites(nestedData, parentId);
416
739
  const updated = await this.findUnique({ where: finalArgs.where, include: finalArgs.include });
@@ -434,33 +757,16 @@ class TableClient {
434
757
  const val = data[key];
435
758
  if (this.orm.metadata.relationMap[this.modelName]?.[key])
436
759
  continue;
437
- const col = (0, sql_utils_1.quoteIdentifier)(key);
438
- const safeKey = (0, sql_utils_1.sanitizeParamName)(key);
439
- if (val && typeof val === "object" && !(val instanceof Date)) {
440
- if (val.increment !== undefined) {
441
- sets.push(`${col} = ${col} + @${safeKey}_inc`);
442
- p[`${safeKey}_inc`] = val.increment;
443
- continue;
444
- }
445
- else if (val.decrement !== undefined) {
446
- sets.push(`${col} = ${col} - @${safeKey}_dec`);
447
- p[`${safeKey}_dec`] = val.decrement;
448
- continue;
449
- }
450
- else if (val.set !== undefined) {
451
- sets.push(`${col} = @${safeKey}_set`);
452
- p[`${safeKey}_set`] = val.set;
453
- continue;
454
- }
455
- }
456
- sets.push(`${col} = @${safeKey}`);
457
- p[safeKey] = val;
760
+ appendUpdateSet(sets, p, key, val);
761
+ }
762
+ if (sets.length === 0) {
763
+ return { count: 0 };
458
764
  }
459
765
  const whereParams = {};
460
766
  const whereSql = this.orm.parseWhere(this.modelName, finalArgs.where, whereParams, "w_");
461
767
  Object.assign(p, whereParams);
462
768
  const sqlText = `UPDATE ${this.tableName} SET ${sets.join(", ")} ${whereSql ? `WHERE ${whereSql}` : ""}`;
463
- const count = await (await getAdapter())._executeRaw(sqlText, p);
769
+ const count = await this.executeRaw(sqlText, p);
464
770
  return { count };
465
771
  });
466
772
  }
@@ -483,7 +789,7 @@ class TableClient {
483
789
  const p = {};
484
790
  const whereSql = this.orm.parseWhere(this.modelName, finalArgs?.where, p);
485
791
  const sqlText = `DELETE FROM ${this.tableName} ${whereSql ? `WHERE ${whereSql}` : ""}`;
486
- const count = await (await getAdapter())._executeRaw(sqlText, p);
792
+ const count = await this.executeRaw(sqlText, p);
487
793
  return { count };
488
794
  });
489
795
  }
@@ -524,7 +830,7 @@ class TableClient {
524
830
  return rows;
525
831
  }
526
832
  catch (err) {
527
- const msg = String(err.message || "").toLowerCase();
833
+ const msg = (String(err?.message || "") + " " + String(err?.originalError?.message || "")).toLowerCase();
528
834
  // Handle specific float16 to float32 conversion error by retrying with float16
529
835
  if (msg.includes("float16") && msg.includes("float32") && msg.includes("conversion") && !finalArgs.vectorElementType) {
530
836
  logger_1.logger.info(`Detected float16 vector storage. Retrying vectorSearch with float16 element type.`);
@@ -532,8 +838,17 @@ class TableClient {
532
838
  }
533
839
  const isUnsupported = msg.includes("vector_distance") ||
534
840
  msg.includes("type vector") ||
841
+ msg.includes("type \"vector\"") ||
842
+ msg.includes("data type vector") ||
843
+ msg.includes("incorrect syntax") ||
844
+ msg.includes("syntax near") ||
845
+ msg.includes("not a recognized built-in function") ||
846
+ msg.includes("not a defined system type") ||
535
847
  msg.includes("limit of 1998") ||
536
- err?.number === 195;
848
+ err?.number === 195 ||
849
+ err?.number === 102 ||
850
+ err?.number === 319 ||
851
+ err?.originalError?.number === 319;
537
852
  if (!isUnsupported) {
538
853
  throw err;
539
854
  }
@@ -618,7 +933,7 @@ class TableClient {
618
933
  rowPlaceholders.push(`(${vals.join(", ")})`);
619
934
  });
620
935
  const sqlText = `INSERT INTO ${this.tableName} (${cols.map(sql_utils_1.quoteIdentifier).join(", ")}) VALUES ${rowPlaceholders.join(", ")}`;
621
- await (await getAdapter())._executeRaw(sqlText, params);
936
+ await this.executeRaw(sqlText, params);
622
937
  return { count: rows.length };
623
938
  }
624
939
  catch (err) {
@@ -646,25 +961,25 @@ class TableClient {
646
961
  const resultObj = {};
647
962
  if (finalArgs._sum) {
648
963
  resultObj._sum = {};
649
- for (const key of Object.keys(finalArgs._sum)) {
964
+ for (const key of selectedAggregateFields(finalArgs._sum)) {
650
965
  selects.push(aggSelect("SUM", key));
651
966
  }
652
967
  }
653
968
  if (finalArgs._avg) {
654
969
  resultObj._avg = {};
655
- for (const key of Object.keys(finalArgs._avg)) {
970
+ for (const key of selectedAggregateFields(finalArgs._avg)) {
656
971
  selects.push(aggSelect("AVG", key));
657
972
  }
658
973
  }
659
974
  if (finalArgs._min) {
660
975
  resultObj._min = {};
661
- for (const key of Object.keys(finalArgs._min)) {
976
+ for (const key of selectedAggregateFields(finalArgs._min)) {
662
977
  selects.push(aggSelect("MIN", key));
663
978
  }
664
979
  }
665
980
  if (finalArgs._max) {
666
981
  resultObj._max = {};
667
- for (const key of Object.keys(finalArgs._max)) {
982
+ for (const key of selectedAggregateFields(finalArgs._max)) {
668
983
  selects.push(aggSelect("MAX", key));
669
984
  }
670
985
  }
@@ -674,7 +989,7 @@ class TableClient {
674
989
  selects.push(`COUNT(*) as count_all`);
675
990
  }
676
991
  else {
677
- for (const key of Object.keys(finalArgs._count)) {
992
+ for (const key of selectedAggregateFields(finalArgs._count)) {
678
993
  selects.push(aggSelect("COUNT", key));
679
994
  }
680
995
  }
@@ -691,22 +1006,22 @@ class TableClient {
691
1006
  const rows = await this.executor(sqlText, p);
692
1007
  const row = rows[0] || {};
693
1008
  if (finalArgs._sum) {
694
- for (const key of Object.keys(finalArgs._sum)) {
1009
+ for (const key of selectedAggregateFields(finalArgs._sum)) {
695
1010
  resultObj._sum[key] = row[aggAlias("sum", key)] !== undefined ? row[aggAlias("sum", key)] : null;
696
1011
  }
697
1012
  }
698
1013
  if (finalArgs._avg) {
699
- for (const key of Object.keys(finalArgs._avg)) {
1014
+ for (const key of selectedAggregateFields(finalArgs._avg)) {
700
1015
  resultObj._avg[key] = row[aggAlias("avg", key)] !== undefined ? row[aggAlias("avg", key)] : null;
701
1016
  }
702
1017
  }
703
1018
  if (finalArgs._min) {
704
- for (const key of Object.keys(finalArgs._min)) {
1019
+ for (const key of selectedAggregateFields(finalArgs._min)) {
705
1020
  resultObj._min[key] = row[aggAlias("min", key)] !== undefined ? row[aggAlias("min", key)] : null;
706
1021
  }
707
1022
  }
708
1023
  if (finalArgs._max) {
709
- for (const key of Object.keys(finalArgs._max)) {
1024
+ for (const key of selectedAggregateFields(finalArgs._max)) {
710
1025
  resultObj._max[key] = row[aggAlias("max", key)] !== undefined ? row[aggAlias("max", key)] : null;
711
1026
  }
712
1027
  }
@@ -715,7 +1030,7 @@ class TableClient {
715
1030
  resultObj._count._all = row[`count_all`] || 0;
716
1031
  }
717
1032
  else {
718
- for (const key of Object.keys(finalArgs._count)) {
1033
+ for (const key of selectedAggregateFields(finalArgs._count)) {
719
1034
  resultObj._count[key] = row[aggAlias("count", key)] || 0;
720
1035
  }
721
1036
  }
@@ -726,7 +1041,7 @@ class TableClient {
726
1041
  async groupBy(args) {
727
1042
  return this.orm._executeMiddleware({ model: this.modelName, action: 'groupBy', args }, async (params) => {
728
1043
  const { args: finalArgs } = params;
729
- const byFields = finalArgs.by || [];
1044
+ const byFields = normalizeByFields(finalArgs.by);
730
1045
  if (byFields.length === 0) {
731
1046
  throw new Error("groupBy requires 'by' fields");
732
1047
  }
@@ -736,28 +1051,28 @@ class TableClient {
736
1051
  selects.push(`COUNT(*) as count_all`);
737
1052
  }
738
1053
  else {
739
- for (const key of Object.keys(finalArgs._count)) {
1054
+ for (const key of selectedAggregateFields(finalArgs._count)) {
740
1055
  selects.push(aggSelect("COUNT", key));
741
1056
  }
742
1057
  }
743
1058
  }
744
1059
  if (finalArgs._sum) {
745
- for (const key of Object.keys(finalArgs._sum)) {
1060
+ for (const key of selectedAggregateFields(finalArgs._sum)) {
746
1061
  selects.push(aggSelect("SUM", key));
747
1062
  }
748
1063
  }
749
1064
  if (finalArgs._avg) {
750
- for (const key of Object.keys(finalArgs._avg)) {
1065
+ for (const key of selectedAggregateFields(finalArgs._avg)) {
751
1066
  selects.push(aggSelect("AVG", key));
752
1067
  }
753
1068
  }
754
1069
  if (finalArgs._min) {
755
- for (const key of Object.keys(finalArgs._min)) {
1070
+ for (const key of selectedAggregateFields(finalArgs._min)) {
756
1071
  selects.push(aggSelect("MIN", key));
757
1072
  }
758
1073
  }
759
1074
  if (finalArgs._max) {
760
- for (const key of Object.keys(finalArgs._max)) {
1075
+ for (const key of selectedAggregateFields(finalArgs._max)) {
761
1076
  selects.push(aggSelect("MAX", key));
762
1077
  }
763
1078
  }
@@ -768,6 +1083,24 @@ class TableClient {
768
1083
  sqlText += ` WHERE ${whereSql}`;
769
1084
  }
770
1085
  sqlText += ` GROUP BY ${byFields.map((f) => (0, sql_utils_1.quoteIdentifier)(f)).join(", ")}`;
1086
+ const havingSql = buildHavingSql(finalArgs?.having, p);
1087
+ if (havingSql) {
1088
+ sqlText += ` HAVING ${havingSql}`;
1089
+ }
1090
+ const hasSkip = finalArgs?.skip !== undefined && finalArgs?.skip !== null;
1091
+ const hasTake = finalArgs?.take !== undefined && finalArgs?.take !== null;
1092
+ if (finalArgs?.orderBy) {
1093
+ sqlText += (0, sql_utils_1.buildOrderBy)(finalArgs.orderBy);
1094
+ }
1095
+ else if (hasSkip || hasTake) {
1096
+ sqlText += ` ORDER BY ${byFields.map((f) => (0, sql_utils_1.quoteIdentifier)(f)).join(", ")}`;
1097
+ }
1098
+ if (hasSkip || hasTake) {
1099
+ sqlText += ` OFFSET ${(0, sql_utils_1.toNonNegativeInt)(finalArgs.skip)} ROWS`;
1100
+ if (hasTake) {
1101
+ sqlText += ` FETCH NEXT ${(0, sql_utils_1.toNonNegativeInt)(finalArgs.take, 1)} ROWS ONLY`;
1102
+ }
1103
+ }
771
1104
  const rows = await this.executor(sqlText, p);
772
1105
  return rows.map((row) => {
773
1106
  const item = {};
@@ -780,32 +1113,32 @@ class TableClient {
780
1113
  item._count._all = row[`count_all`] || 0;
781
1114
  }
782
1115
  else {
783
- for (const key of Object.keys(finalArgs._count)) {
1116
+ for (const key of selectedAggregateFields(finalArgs._count)) {
784
1117
  item._count[key] = row[aggAlias("count", key)] || 0;
785
1118
  }
786
1119
  }
787
1120
  }
788
1121
  if (finalArgs._sum) {
789
1122
  item._sum = {};
790
- for (const key of Object.keys(finalArgs._sum)) {
1123
+ for (const key of selectedAggregateFields(finalArgs._sum)) {
791
1124
  item._sum[key] = row[aggAlias("sum", key)] !== undefined ? row[aggAlias("sum", key)] : null;
792
1125
  }
793
1126
  }
794
1127
  if (finalArgs._avg) {
795
1128
  item._avg = {};
796
- for (const key of Object.keys(finalArgs._avg)) {
1129
+ for (const key of selectedAggregateFields(finalArgs._avg)) {
797
1130
  item._avg[key] = row[aggAlias("avg", key)] !== undefined ? row[aggAlias("avg", key)] : null;
798
1131
  }
799
1132
  }
800
1133
  if (finalArgs._min) {
801
1134
  item._min = {};
802
- for (const key of Object.keys(finalArgs._min)) {
1135
+ for (const key of selectedAggregateFields(finalArgs._min)) {
803
1136
  item._min[key] = row[aggAlias("min", key)] !== undefined ? row[aggAlias("min", key)] : null;
804
1137
  }
805
1138
  }
806
1139
  if (finalArgs._max) {
807
1140
  item._max = {};
808
- for (const key of Object.keys(finalArgs._max)) {
1141
+ for (const key of selectedAggregateFields(finalArgs._max)) {
809
1142
  item._max[key] = row[aggAlias("max", key)] !== undefined ? row[aggAlias("max", key)] : null;
810
1143
  }
811
1144
  }
@@ -813,6 +1146,13 @@ class TableClient {
813
1146
  });
814
1147
  });
815
1148
  }
1149
+ async sequentialUpsert(finalArgs) {
1150
+ const existing = await this.findUnique({ where: finalArgs.where });
1151
+ if (existing) {
1152
+ return this.update({ where: finalArgs.where, data: finalArgs.update, include: finalArgs.include });
1153
+ }
1154
+ return this.create({ data: finalArgs.create, include: finalArgs.include });
1155
+ }
816
1156
  async upsert(args) {
817
1157
  return this.orm._executeMiddleware({ model: this.modelName, action: 'upsert', args }, async (params) => {
818
1158
  const { args: finalArgs } = params;
@@ -838,8 +1178,11 @@ class TableClient {
838
1178
  cleanCreate.updatedAt = now;
839
1179
  if (!cleanUpdate.updatedAt && this.orm.metadata.modelFields[this.modelName]?.updatedAt)
840
1180
  cleanUpdate.updatedAt = now;
1181
+ const atomicWhere = flattenSimpleEqualityWhere(where);
1182
+ if (!atomicWhere || Object.keys(cleanUpdate).length === 0) {
1183
+ return this.sequentialUpsert(finalArgs);
1184
+ }
841
1185
  const p = {};
842
- const whereSql = this.orm.parseWhere(this.modelName, where, p, "upw_");
843
1186
  const allKeys = Array.from(new Set([...Object.keys(cleanCreate), ...Object.keys(cleanUpdate)]));
844
1187
  const cParam = (k) => `c_${(0, sql_utils_1.sanitizeParamName)(k)}`;
845
1188
  const uParam = (k) => `u_${(0, sql_utils_1.sanitizeParamName)(k)}`;
@@ -849,6 +1192,9 @@ class TableClient {
849
1192
  if (cleanUpdate[k] !== undefined)
850
1193
  p[uParam(k)] = cleanUpdate[k];
851
1194
  }
1195
+ for (const [k, v] of Object.entries(atomicWhere)) {
1196
+ p[`upw_${(0, sql_utils_1.sanitizeParamName)(k)}`] = v;
1197
+ }
852
1198
  const sourceSelect = allKeys.map(k => {
853
1199
  const val = cleanCreate[k] !== undefined ? `@${cParam(k)}` : (cleanUpdate[k] !== undefined ? `@${uParam(k)}` : "NULL");
854
1200
  return `${val} as ${(0, sql_utils_1.quoteIdentifier)(k)}`;
@@ -856,26 +1202,18 @@ class TableClient {
856
1202
  const updateSets = Object.keys(cleanUpdate).map(k => `target.${(0, sql_utils_1.quoteIdentifier)(k)} = source.${(0, sql_utils_1.quoteIdentifier)(k)}`).join(", ");
857
1203
  const insertCols = Object.keys(cleanCreate).map(k => (0, sql_utils_1.quoteIdentifier)(k)).join(", ");
858
1204
  const insertVals = Object.keys(cleanCreate).map(k => `source.${(0, sql_utils_1.quoteIdentifier)(k)}`).join(", ");
859
- // Note: This MERGE assumes the 'where' translates to a simple ON clause.
860
- // For complex 'where', parseWhere might return something not easily usable in ON.
861
- // We'll try to extract simple equality for ON if possible, or fallback to sequential.
862
- const onClause = Object.keys(where).map(k => {
863
- if (typeof where[k] === 'object' && where[k] !== null && !(where[k] instanceof Date)) {
864
- // Flatten unique object if needed
865
- const inner = where[k];
866
- return Object.keys(inner).map(ik => `target.${(0, sql_utils_1.quoteIdentifier)(ik)} = @${(0, sql_utils_1.sanitizeParamName)(`upw_${k}_${ik}`)}`).join(" AND ");
867
- }
868
- return `target.${(0, sql_utils_1.quoteIdentifier)(k)} = @${(0, sql_utils_1.sanitizeParamName)(`upw_${k}`)}`;
869
- }).join(" AND ");
870
- const sqlText = `
871
- MERGE INTO ${this.tableName} WITH (HOLDLOCK) AS target
872
- USING (SELECT ${sourceSelect}) AS source
873
- ON (${onClause})
874
- WHEN MATCHED THEN
875
- UPDATE SET ${updateSets}
876
- WHEN NOT MATCHED THEN
877
- INSERT (${insertCols}) VALUES (${insertVals})
878
- OUTPUT inserted.*;
1205
+ const onClause = Object.keys(atomicWhere)
1206
+ .map(k => `target.${(0, sql_utils_1.quoteIdentifier)(k)} = @upw_${(0, sql_utils_1.sanitizeParamName)(k)}`)
1207
+ .join(" AND ");
1208
+ const sqlText = `
1209
+ MERGE INTO ${this.tableName} WITH (HOLDLOCK) AS target
1210
+ USING (SELECT ${sourceSelect}) AS source
1211
+ ON (${onClause})
1212
+ WHEN MATCHED THEN
1213
+ UPDATE SET ${updateSets}
1214
+ WHEN NOT MATCHED THEN
1215
+ INSERT (${insertCols}) VALUES (${insertVals})
1216
+ OUTPUT inserted.*;
879
1217
  `;
880
1218
  try {
881
1219
  const rows = await this.executor(sqlText, p);
@@ -887,17 +1225,12 @@ class TableClient {
887
1225
  }
888
1226
  catch (err) {
889
1227
  logger_1.logger.warn(`Atomic upsert failed, falling back to sequential: ${err.message}`);
890
- const existing = await this.findUnique({ where: finalArgs.where });
891
- if (existing) {
892
- return this.update({ where: finalArgs.where, data: finalArgs.update, include: finalArgs.include });
893
- }
894
- else {
895
- return this.create({ data: finalArgs.create, include: finalArgs.include });
896
- }
1228
+ return this.sequentialUpsert(finalArgs);
897
1229
  }
898
1230
  });
899
1231
  }
900
1232
  }
1233
+ exports.TableClient = TableClient;
901
1234
  function addNoLockToQuery(sql, metadata) {
902
1235
  // If it's not a SELECT query, don't modify it
903
1236
  if (!/^\s*SELECT/i.test(sql)) {
@@ -935,28 +1268,124 @@ function addNoLockToQuery(sql, metadata) {
935
1268
  // Lazy-load generated schema metadata from the ORM's own generated copy so
936
1269
  // `new An5ORM()` resolves schema models out of the box without the core ever
937
1270
  // importing from the generated client package (client is generated FROM the ORM).
1271
+ class ViewClient {
1272
+ constructor(viewName, rawTableName, executor, orm) {
1273
+ this.viewName = viewName;
1274
+ this.rawTableName = rawTableName;
1275
+ this.executor = executor;
1276
+ this.orm = orm;
1277
+ this.tableClient = new TableClient(viewName, rawTableName, executor, orm);
1278
+ }
1279
+ async findMany(args) { return this.tableClient.findMany(args); }
1280
+ async findFirst(args) { return this.tableClient.findFirst(args); }
1281
+ async findUnique(args) { return this.tableClient.findUnique(args); }
1282
+ async count(args) { return this.tableClient.count(args); }
1283
+ async aggregate(args) { return this.tableClient.aggregate(args); }
1284
+ async groupBy(args) { return this.tableClient.groupBy(args); }
1285
+ async vectorSearch(args) { return this.tableClient.vectorSearch(args); }
1286
+ async create() {
1287
+ throw new errors_1.An5ClientKnownRequestError("Views are read-only and do not support create operations.", { code: "P2000", clientVersion: "1.0.8" });
1288
+ }
1289
+ async createMany() {
1290
+ throw new errors_1.An5ClientKnownRequestError("Views are read-only and do not support createMany operations.", { code: "P2000", clientVersion: "1.0.8" });
1291
+ }
1292
+ async update() {
1293
+ throw new errors_1.An5ClientKnownRequestError("Views are read-only and do not support update operations.", { code: "P2000", clientVersion: "1.0.8" });
1294
+ }
1295
+ async updateMany() {
1296
+ throw new errors_1.An5ClientKnownRequestError("Views are read-only and do not support updateMany operations.", { code: "P2000", clientVersion: "1.0.8" });
1297
+ }
1298
+ async delete() {
1299
+ throw new errors_1.An5ClientKnownRequestError("Views are read-only and do not support delete operations.", { code: "P2000", clientVersion: "1.0.8" });
1300
+ }
1301
+ async deleteMany() {
1302
+ throw new errors_1.An5ClientKnownRequestError("Views are read-only and do not support deleteMany operations.", { code: "P2000", clientVersion: "1.0.8" });
1303
+ }
1304
+ async upsert() {
1305
+ throw new errors_1.An5ClientKnownRequestError("Views are read-only and do not support upsert operations.", { code: "P2000", clientVersion: "1.0.8" });
1306
+ }
1307
+ }
1308
+ exports.ViewClient = ViewClient;
1309
+ function buildProcCallSql(procName, params) {
1310
+ const p = {};
1311
+ const targetProc = (0, sql_utils_1.quoteTableIdentifier)(procName);
1312
+ if (!params) {
1313
+ return { sqlText: `EXEC ${targetProc};`, p };
1314
+ }
1315
+ if (Array.isArray(params)) {
1316
+ const argList = params.map((val, idx) => {
1317
+ const key = `p_${idx}`;
1318
+ p[key] = val;
1319
+ return `@${key}`;
1320
+ }).join(", ");
1321
+ return { sqlText: `EXEC ${targetProc} ${argList};`, p };
1322
+ }
1323
+ const argList = Object.entries(params).map(([key, val], idx) => {
1324
+ const paramKey = (0, sql_utils_1.sanitizeParamName)(`p_${key}_${idx}`);
1325
+ p[paramKey] = val;
1326
+ return `@${(0, sql_utils_1.sanitizeParamName)(key)} = @${paramKey}`;
1327
+ }).join(", ");
1328
+ return { sqlText: `EXEC ${targetProc} ${argList};`, p };
1329
+ }
1330
+ function buildFunctionCallSql(fnName, params) {
1331
+ const p = {};
1332
+ const targetFn = (0, sql_utils_1.quoteTableIdentifier)(fnName);
1333
+ if (!params) {
1334
+ return { sqlText: `SELECT * FROM ${targetFn}();`, p };
1335
+ }
1336
+ if (Array.isArray(params)) {
1337
+ const argList = params.map((val, idx) => {
1338
+ const key = `p_${idx}`;
1339
+ p[key] = val;
1340
+ return `@${key}`;
1341
+ }).join(", ");
1342
+ return { sqlText: `SELECT * FROM ${targetFn}(${argList});`, p };
1343
+ }
1344
+ const argList = Object.entries(params).map(([key, val], idx) => {
1345
+ const paramKey = (0, sql_utils_1.sanitizeParamName)(`p_${key}_${idx}`);
1346
+ p[paramKey] = val;
1347
+ return `@${paramKey}`;
1348
+ }).join(", ");
1349
+ return { sqlText: `SELECT * FROM ${targetFn}(${argList});`, p };
1350
+ }
938
1351
  let autoMetadata = null;
939
1352
  function loadAutoMetadata() {
940
1353
  if (autoMetadata)
941
1354
  return autoMetadata;
942
- try {
943
- const m = require("./an5Metadata");
944
- autoMetadata = {
945
- modelToTable: m.modelToTable || {},
946
- relationMap: m.relationMap || {},
947
- modelFields: m.modelFields || {},
948
- };
949
- }
950
- catch {
951
- autoMetadata = metadata_1.DEFAULT_METADATA;
1355
+ const candidates = [
1356
+ "an5-client/typescript/an5Metadata",
1357
+ "../an5Client/typescript/an5Metadata",
1358
+ "./an5Metadata"
1359
+ ];
1360
+ for (const cand of candidates) {
1361
+ try {
1362
+ const m = require(cand);
1363
+ if (m && m.modelToTable && Object.keys(m.modelToTable).length > 0) {
1364
+ autoMetadata = {
1365
+ modelToTable: m.modelToTable || {},
1366
+ relationMap: m.relationMap || {},
1367
+ modelFields: m.modelFields || {},
1368
+ };
1369
+ return autoMetadata;
1370
+ }
1371
+ }
1372
+ catch { }
952
1373
  }
1374
+ autoMetadata = metadata_1.DEFAULT_METADATA;
953
1375
  return autoMetadata;
954
1376
  }
955
1377
  // Proxied AN5 ORM client class
956
1378
  class An5ORM {
957
- constructor(customExecutor, metadata) {
958
- this.customExecutor = customExecutor;
1379
+ constructor(customExecutor, metadata, inTransaction = false, transactionControl) {
1380
+ this.inTransaction = inTransaction;
1381
+ this.transactionControl = transactionControl;
959
1382
  this.middlewares = [];
1383
+ if (customExecutor && typeof customExecutor === "object" && typeof customExecutor.exec === "function") {
1384
+ this.customExecutor = executorFromAdapterLike(customExecutor);
1385
+ }
1386
+ else if (typeof customExecutor === "function") {
1387
+ this.customExecutor = customExecutor;
1388
+ }
960
1389
  this.metadata = metadata ?? loadAutoMetadata();
961
1390
  // Add default logging middleware
962
1391
  this.$use(async (params, next) => {
@@ -976,43 +1405,68 @@ class An5ORM {
976
1405
  }
977
1406
  });
978
1407
  return new Proxy(this, {
979
- get(target, prop) {
980
- if (prop === "$use") {
981
- return target.$use.bind(target);
982
- }
983
- if (prop === "$transaction") {
984
- return target.$transaction.bind(target);
985
- }
986
- if (prop === "$connect") {
987
- return target.$connect.bind(target);
988
- }
989
- if (prop === "$disconnect") {
990
- return target.$disconnect.bind(target);
991
- }
992
- if (prop === "$queryRaw") {
993
- return target.$queryRaw.bind(target);
994
- }
995
- if (prop === "$queryRawUnsafe") {
996
- return target.$queryRawUnsafe.bind(target);
997
- }
998
- if (prop === "$executeRaw") {
999
- return target.$executeRaw.bind(target);
1000
- }
1001
- if (prop === "$executeRawUnsafe") {
1002
- return target.$executeRawUnsafe.bind(target);
1408
+ get(target, prop, receiver) {
1409
+ if (typeof prop === "string" && prop in target && typeof target[prop] === "function") {
1410
+ return target[prop].bind(target);
1003
1411
  }
1004
1412
  if (!(prop in target) && typeof prop === "string" && !prop.startsWith("_")) {
1005
- // Resolve modelName in camelCase and map to table name
1006
- const tableName = target.metadata.modelToTable[prop];
1007
- if (tableName) {
1008
- target[prop] = new TableClient(prop, tableName, target.customExecutor || execQuery, target // Pass ORM instance for middleware access
1009
- );
1413
+ let modelName = prop;
1414
+ let tableName = target.metadata.modelToTable[prop];
1415
+ if (!tableName) {
1416
+ const lowerProp = prop.toLowerCase();
1417
+ for (const [mName, tName] of Object.entries(target.metadata.modelToTable)) {
1418
+ const lowerModel = mName.toLowerCase();
1419
+ const lowerTable = tName.toLowerCase();
1420
+ if (lowerModel === lowerProp ||
1421
+ lowerTable === lowerProp ||
1422
+ lowerModel + "s" === lowerProp ||
1423
+ lowerModel + "es" === lowerProp ||
1424
+ lowerTable + "s" === lowerProp ||
1425
+ lowerTable + "es" === lowerProp) {
1426
+ modelName = mName;
1427
+ tableName = tName;
1428
+ break;
1429
+ }
1430
+ }
1431
+ }
1432
+ if (!tableName) {
1433
+ tableName = prop;
1010
1434
  }
1435
+ target[prop] = new TableClient(modelName, tableName, target.customExecutor || execQuery, receiver);
1011
1436
  }
1012
1437
  return target[prop];
1013
1438
  }
1014
1439
  });
1015
1440
  }
1441
+ table(name) {
1442
+ const rawTable = this.metadata.modelToTable[name] || name;
1443
+ return new TableClient(name, rawTable, this.customExecutor || execQuery, this);
1444
+ }
1445
+ view(name) {
1446
+ const rawTable = this.metadata.modelToTable[name] || name;
1447
+ return new ViewClient(name, rawTable, this.customExecutor || execQuery, this);
1448
+ }
1449
+ $view(name) {
1450
+ return this.view(name);
1451
+ }
1452
+ async $queryProc(procName, params) {
1453
+ const executor = this.customExecutor || execQuery;
1454
+ const { sqlText, p } = buildProcCallSql(procName, params);
1455
+ return executor(sqlText, p);
1456
+ }
1457
+ async $executeProc(procName, params) {
1458
+ const executor = this.customExecutor || execQuery;
1459
+ const { sqlText, p } = buildProcCallSql(procName, params);
1460
+ if (executor.executeRaw) {
1461
+ return executor.executeRaw(sqlText, p);
1462
+ }
1463
+ return normalizeAffectedCount(await executor(sqlText, p));
1464
+ }
1465
+ async $queryFunction(fnName, params) {
1466
+ const executor = this.customExecutor || execQuery;
1467
+ const { sqlText, p } = buildFunctionCallSql(fnName, params);
1468
+ return executor(sqlText, p);
1469
+ }
1016
1470
  $use(middleware) {
1017
1471
  this.middlewares.push(middleware);
1018
1472
  }
@@ -1116,8 +1570,10 @@ class An5ORM {
1116
1570
  throw new Error("Invalid query format for $executeRaw");
1117
1571
  }
1118
1572
  queryText = addNoLockToQuery(queryText, this.metadata);
1119
- const result = (await executor(queryText, params));
1120
- return result.rowsAffected?.[0] || 0;
1573
+ if (executor.executeRaw) {
1574
+ return executor.executeRaw(queryText, params);
1575
+ }
1576
+ return normalizeAffectedCount(await executor(queryText, params));
1121
1577
  }
1122
1578
  async $executeRawUnsafe(queryText, ...values) {
1123
1579
  const executor = this.customExecutor || execQuery;
@@ -1129,23 +1585,58 @@ class An5ORM {
1129
1585
  });
1130
1586
  }
1131
1587
  const modifiedQueryText = addNoLockToQuery(queryText, this.metadata);
1132
- const result = (await executor(modifiedQueryText, params));
1133
- return result.rowsAffected?.[0] || 0;
1588
+ if (executor.executeRaw) {
1589
+ return executor.executeRaw(modifiedQueryText, params);
1590
+ }
1591
+ return normalizeAffectedCount(await executor(modifiedQueryText, params));
1134
1592
  }
1135
1593
  async $transaction(fn, options) {
1136
1594
  if (Array.isArray(fn)) {
1137
1595
  return Promise.all(fn);
1138
1596
  }
1139
- const a = await getAdapter();
1140
- return a.$transaction(async (tx) => {
1141
- const txExecutor = async (queryText, params) => {
1142
- const rows = await tx.exec(queryText, params);
1143
- return rows;
1144
- };
1145
- const txClient = new An5ORM(txExecutor, this.metadata);
1597
+ const executor = this.customExecutor || execQuery;
1598
+ if (!executor.transaction) {
1599
+ if (this.inTransaction) {
1600
+ const txClient = new An5ORM(executor, this.metadata, true);
1601
+ return fn(txClient);
1602
+ }
1603
+ throw new Error("Transactions require an executor with transaction support");
1604
+ }
1605
+ return executor.transaction(async (txExecutor) => {
1606
+ const txClient = new An5ORM(txExecutor, this.metadata, true);
1146
1607
  return fn(txClient);
1147
1608
  }, options);
1148
1609
  }
1610
+ async $begin() {
1611
+ if (this.inTransaction) {
1612
+ throw new Error("Interactive transactions cannot be nested");
1613
+ }
1614
+ const executor = this.customExecutor || execQuery;
1615
+ if (!executor.beginTransaction) {
1616
+ throw new Error("Interactive transactions require an executor with beginTransaction support");
1617
+ }
1618
+ const tx = await executor.beginTransaction();
1619
+ return new An5ORM(tx.executor, this.metadata, true, {
1620
+ commit: tx.commit,
1621
+ rollback: tx.rollback,
1622
+ });
1623
+ }
1624
+ async $commit() {
1625
+ if (!this.transactionControl) {
1626
+ throw new Error("$commit can only be called on an interactive transaction client");
1627
+ }
1628
+ const control = this.transactionControl;
1629
+ this.transactionControl = undefined;
1630
+ await control.commit();
1631
+ }
1632
+ async $rollback() {
1633
+ if (!this.transactionControl) {
1634
+ throw new Error("$rollback can only be called on an interactive transaction client");
1635
+ }
1636
+ const control = this.transactionControl;
1637
+ this.transactionControl = undefined;
1638
+ await control.rollback();
1639
+ }
1149
1640
  }
1150
1641
  exports.An5ORM = An5ORM;
1151
1642
  exports.an5Orm = new An5ORM();