@an5/orm 1.0.7 → 1.0.9

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 (46) hide show
  1. package/README.md +97 -83
  2. package/dist/an5Metadata.d.ts +10 -0
  3. package/dist/an5Metadata.js +93 -0
  4. package/dist/an5Orm.d.ts +95 -3
  5. package/dist/an5Orm.js +808 -512
  6. package/dist/config.d.ts +2 -0
  7. package/dist/config.js +24 -0
  8. package/dist/errors.d.ts +14 -0
  9. package/dist/errors.js +16 -0
  10. package/dist/generator/src/code-generator.d.ts +1 -0
  11. package/dist/generator/src/code-generator.js +44 -20
  12. package/dist/generator/src/dotnet-generator.d.ts +2 -0
  13. package/dist/generator/src/dotnet-generator.js +332 -6
  14. package/dist/generator/src/index.js +18 -2
  15. package/dist/generator/src/metadata-generator.d.ts +4 -1
  16. package/dist/generator/src/metadata-generator.js +22 -8
  17. package/dist/generator/src/python-generator.d.ts +10 -0
  18. package/dist/generator/src/python-generator.js +242 -2
  19. package/dist/index.d.ts +6 -2
  20. package/dist/index.js +23 -8
  21. package/dist/logger.d.ts +53 -0
  22. package/dist/logger.js +117 -0
  23. package/dist/metadata.d.ts +24 -0
  24. package/dist/metadata.js +15 -0
  25. package/dist/migration-core.d.ts +87 -0
  26. package/dist/migration-core.js +692 -0
  27. package/dist/sql-utils.d.ts +31 -0
  28. package/dist/sql-utils.js +290 -0
  29. package/generator/src/code-generator.ts +45 -20
  30. package/generator/src/dotnet-generator.ts +344 -6
  31. package/generator/src/golang-generator.ts +1242 -0
  32. package/generator/src/index.ts +8 -1
  33. package/generator/src/metadata-generator.ts +29 -12
  34. package/generator/src/python-generator.ts +314 -4
  35. package/package.json +12 -10
  36. package/dist/cleanup.d.ts +0 -2
  37. package/dist/cleanup.js +0 -120
  38. package/dist/cli.js +0 -64
  39. package/dist/migrate.d.ts +0 -11
  40. package/dist/migrate.js +0 -301
  41. package/dist/pull.d.ts +0 -2
  42. package/dist/pull.js +0 -339
  43. package/dist/push.d.ts +0 -2
  44. package/dist/push.js +0 -281
  45. package/dist/seed.d.ts +0 -2
  46. package/dist/seed.js +0 -401
package/dist/an5Orm.js CHANGED
@@ -1,205 +1,224 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.an5Orm = exports.An5ORM = void 0;
7
- const mssql_1 = __importDefault(require("mssql"));
8
- const an5_adapters_1 = require("@an5/adapters");
3
+ exports.an5Orm = exports.An5ORM = exports.ViewClient = exports.TableClient = void 0;
4
+ const adapters_1 = require("@an5/adapters");
9
5
  const crypto_1 = require("crypto");
10
- const logger_1 = require("@an5/lib/logger");
11
- const typescript_1 = require("an5-client/typescript");
12
- const an5Metadata_1 = require("an5-client/typescript/an5Metadata");
6
+ const logger_1 = require("./logger");
7
+ const errors_1 = require("./errors");
8
+ const metadata_1 = require("./metadata");
9
+ const sql_utils_1 = require("./sql-utils");
10
+ function aggSelect(fn, key) {
11
+ return `${fn}(${(0, sql_utils_1.quoteIdentifier)(key)}) as ${aggAlias(fn.toLowerCase(), key)}`;
12
+ }
13
+ function aggAlias(fn, key) {
14
+ return (0, sql_utils_1.sanitizeParamName)(`${fn}_${key}`);
15
+ }
13
16
  let adapter = null;
14
17
  async function getAdapter() {
15
18
  if (!adapter) {
16
- adapter = new an5_adapters_1.An5Adapter({ connectionString: process.env.DATABASE_URL });
19
+ adapter = new adapters_1.An5Adapter({ connectionString: process.env.DATABASE_URL });
17
20
  await adapter.$connect();
18
21
  }
19
22
  return adapter;
20
23
  }
21
- async function execQuery(queryText, params) {
24
+ const execQuery = Object.assign(async (queryText, params) => {
22
25
  const a = await getAdapter();
23
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;
24
63
  }
25
- function parseWhere(modelName, where, params, prefix = "") {
26
- if (!where)
27
- return "";
28
- const conditions = [];
29
- const cleanWhere = {};
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 = {};
30
85
  for (const [key, value] of Object.entries(where)) {
31
- if (key.includes("_") &&
32
- value &&
33
- typeof value === "object" &&
34
- !(value instanceof Date) &&
35
- !value.in &&
36
- !value.contains &&
37
- !value.not &&
38
- !value.gte &&
39
- !value.lte &&
40
- !value.gt &&
41
- !value.lt) {
42
- Object.assign(cleanWhere, value);
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;
43
91
  }
44
- else {
45
- cleanWhere[key] = value;
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;
46
101
  }
47
102
  }
48
- for (const [key, value] of Object.entries(cleanWhere)) {
49
- if (key === "OR" && Array.isArray(value)) {
50
- const orConditions = value.map((subWhere, idx) => parseWhere(modelName, subWhere, params, `${prefix}or_${idx}_`));
51
- const filtered = orConditions.filter(Boolean);
52
- if (filtered.length > 0) {
53
- conditions.push(`(${filtered.join(" OR ")})`);
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;
54
129
  }
55
130
  }
56
- else if (key === "AND" && Array.isArray(value)) {
57
- const andConditions = value.map((subWhere, idx) => parseWhere(modelName, subWhere, params, `${prefix}and_${idx}_`));
58
- const filtered = andConditions.filter(Boolean);
59
- if (filtered.length > 0) {
60
- conditions.push(`(${filtered.join(" AND ")})`);
61
- }
131
+ if (val.set !== undefined) {
132
+ const paramName = `${safeKey}_set`;
133
+ sets.push(`${col} = @${paramName}`);
134
+ params[paramName] = val.set;
135
+ return;
62
136
  }
63
- else {
64
- const modelRelations = an5Metadata_1.relationMap[modelName];
65
- const relation = modelRelations?.[key];
66
- if (relation) {
67
- // Relation subquery
68
- const relationTable = an5Metadata_1.modelToTable[relation.modelName];
69
- const subParams = {};
70
- let subWhere = value;
71
- let op = "some"; // default
72
- if (value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date)) {
73
- if (value.some) {
74
- subWhere = value.some;
75
- op = "some";
76
- }
77
- else if (value.none) {
78
- subWhere = value.none;
79
- op = "none";
80
- }
81
- else if (value.every) {
82
- subWhere = value.every;
83
- op = "every";
84
- }
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 ")})`);
85
176
  }
86
- const subWhereSql = parseWhere(relation.modelName, subWhere, subParams, `${prefix}${key}_`);
87
- Object.assign(params, subParams);
88
- if (subWhereSql) {
89
- if (relation.relationType === "one") {
90
- if (op === "none") {
91
- conditions.push(`${relation.foreignKey} NOT IN (SELECT ${relation.localKey} FROM ${relationTable} WITH (NOLOCK) WHERE ${subWhereSql})`);
92
- }
93
- else {
94
- conditions.push(`${relation.foreignKey} IN (SELECT ${relation.localKey} FROM ${relationTable} WITH (NOLOCK) WHERE ${subWhereSql})`);
95
- }
96
- }
97
- else {
98
- if (op === "none") {
99
- conditions.push(`${relation.localKey} NOT IN (SELECT ${relation.foreignKey} FROM ${relationTable} WITH (NOLOCK) WHERE ${subWhereSql})`);
100
- }
101
- else {
102
- conditions.push(`${relation.localKey} IN (SELECT ${relation.foreignKey} FROM ${relationTable} WITH (NOLOCK) WHERE ${subWhereSql})`);
103
- }
104
- }
177
+ else {
178
+ conditions.push(`${expr} <> @${p}`);
179
+ params[p] = value;
105
180
  }
106
181
  }
107
- else {
108
- const paramName = `${prefix}${key}`;
109
- if (value && typeof value === "object" && !(value instanceof Date)) {
110
- const ops = Object.entries(value);
111
- for (const [op, opVal] of ops) {
112
- if (op === "in" && Array.isArray(opVal)) {
113
- if (opVal.length === 0) {
114
- conditions.push("1 = 0");
115
- }
116
- else {
117
- const inParams = [];
118
- opVal.forEach((item, idx) => {
119
- const inParamName = `${paramName}_in_${idx}`;
120
- inParams.push(`@${inParamName}`);
121
- params[inParamName] = item;
122
- });
123
- conditions.push(`${key} IN (${inParams.join(", ")})`);
124
- }
125
- }
126
- else if (op === "notIn" && Array.isArray(opVal)) {
127
- if (opVal.length === 0) {
128
- // NOT IN empty list is always true, but let's be safe
129
- conditions.push("1 = 1");
130
- }
131
- else {
132
- const inParams = [];
133
- opVal.forEach((item, idx) => {
134
- const inParamName = `${paramName}_notin_${idx}`;
135
- inParams.push(`@${inParamName}`);
136
- params[inParamName] = item;
137
- });
138
- conditions.push(`${key} NOT IN (${inParams.join(", ")})`);
139
- }
140
- }
141
- else if (op === "contains") {
142
- conditions.push(`${key} LIKE @${paramName}_contains`);
143
- params[`${paramName}_contains`] = `%${opVal}%`;
144
- }
145
- else if (op === "startsWith") {
146
- conditions.push(`${key} LIKE @${paramName}_startsWith`);
147
- params[`${paramName}_startsWith`] = `${opVal}%`;
148
- }
149
- else if (op === "endsWith") {
150
- conditions.push(`${key} LIKE @${paramName}_endsWith`);
151
- params[`${paramName}_endsWith`] = `%${opVal}`;
152
- }
153
- else if (op === "not") {
154
- conditions.push(`${key} <> @${paramName}_not`);
155
- params[`${paramName}_not`] = opVal;
156
- }
157
- else if (op === "gte") {
158
- conditions.push(`${key} >= @${paramName}_gte`);
159
- params[`${paramName}_gte`] = opVal;
160
- }
161
- else if (op === "lte") {
162
- conditions.push(`${key} <= @${paramName}_lte`);
163
- params[`${paramName}_lte`] = opVal;
164
- }
165
- else if (op === "gt") {
166
- conditions.push(`${key} > @${paramName}_gt`);
167
- params[`${paramName}_gt`] = opVal;
168
- }
169
- else if (op === "lt") {
170
- conditions.push(`${key} < @${paramName}_lt`);
171
- params[`${paramName}_lt`] = opVal;
172
- }
173
- }
182
+ else if ((op === "in" || op === "notIn") && Array.isArray(value)) {
183
+ if (value.length === 0) {
184
+ conditions.push(op === "in" ? "1 = 0" : "1 = 1");
174
185
  }
175
186
  else {
176
- if (value === null) {
177
- conditions.push(`${key} IS NULL`);
178
- }
179
- else {
180
- conditions.push(`${key} = @${paramName}`);
181
- params[paramName] = value;
182
- }
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(", ")})`);
183
193
  }
184
194
  }
185
195
  }
196
+ return;
186
197
  }
187
- return conditions.join(" AND ");
198
+ const p = (0, sql_utils_1.sanitizeParamName)(`having_${paramBase}`);
199
+ conditions.push(`${expr} = @${p}`);
200
+ params[p] = filter;
188
201
  }
189
- function buildOrderBy(orderBy) {
190
- if (!orderBy)
202
+ function buildHavingSql(having, params) {
203
+ if (!having || typeof having !== "object")
191
204
  return "";
192
- const orderClauses = [];
193
- const orderByArr = Array.isArray(orderBy) ? orderBy : [orderBy];
194
- for (const orderObj of orderByArr) {
195
- if (orderObj && typeof orderObj === "object") {
196
- for (const [k, dir] of Object.entries(orderObj)) {
197
- const dirStr = typeof dir === "string" ? dir.toUpperCase() : "ASC";
198
- orderClauses.push(`${k} ${dirStr}`);
199
- }
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);
200
219
  }
201
220
  }
202
- return orderClauses.length > 0 ? ` ORDER BY ` + orderClauses.join(", ") : "";
221
+ return conditions.length > 0 ? conditions.join(" AND ") : "";
203
222
  }
204
223
  function projectFields(row, select) {
205
224
  if (!row || !select)
@@ -215,11 +234,57 @@ function projectFields(row, select) {
215
234
  }
216
235
  return projected;
217
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
+ }
218
283
  // Helper to batch-query and resolve relationships
219
- async function resolveIncludes(modelName, rows, include, executor) {
284
+ async function resolveIncludes(modelName, rows, include, executor, metadata) {
220
285
  if (!rows || rows.length === 0 || !include)
221
286
  return;
222
- const modelRelations = an5Metadata_1.relationMap[modelName];
287
+ const modelRelations = metadata.relationMap[modelName];
223
288
  if (!modelRelations)
224
289
  return;
225
290
  for (const [key, value] of Object.entries(include)) {
@@ -227,22 +292,24 @@ async function resolveIncludes(modelName, rows, include, executor) {
227
292
  continue;
228
293
  const relation = modelRelations[key];
229
294
  if (!relation) {
230
- if (key === "_count" && value && typeof value === "object") {
231
- 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]);
232
299
  for (const countField of countFields) {
233
300
  const rel = modelRelations[countField];
234
301
  if (rel) {
235
- const relTable = an5Metadata_1.modelToTable[rel.modelName];
302
+ const relTable = (0, sql_utils_1.quoteTableIdentifier)(metadata.modelToTable[rel.modelName] || rel.modelName);
236
303
  const localKeys = rows.map(r => r[rel.localKey]).filter(Boolean);
237
304
  if (localKeys.length === 0) {
238
305
  rows.forEach(r => { r._count = { ...r._count, [countField]: 0 }; });
239
306
  continue;
240
307
  }
241
308
  const sqlText = `
242
- SELECT ${rel.foreignKey} as parentId, COUNT(*) as count
309
+ SELECT ${(0, sql_utils_1.quoteIdentifier)(rel.foreignKey)} as parentId, COUNT(*) as count
243
310
  FROM ${relTable} WITH (NOLOCK)
244
- WHERE ${rel.foreignKey} IN (${localKeys.map((_, i) => `@lk_${i}`).join(", ")})
245
- GROUP BY ${rel.foreignKey}
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)}
246
313
  `;
247
314
  const countParams = {};
248
315
  localKeys.forEach((lk, i) => { countParams[`lk_${i}`] = lk; });
@@ -258,7 +325,7 @@ async function resolveIncludes(modelName, rows, include, executor) {
258
325
  }
259
326
  continue;
260
327
  }
261
- const relTable = an5Metadata_1.modelToTable[relation.modelName];
328
+ const relTable = (0, sql_utils_1.quoteTableIdentifier)(metadata.modelToTable[relation.modelName] || relation.modelName);
262
329
  const isMany = relation.relationType === "many";
263
330
  const matchKey = relation.relationType === "one" ? relation.foreignKey : relation.localKey;
264
331
  const searchKey = relation.relationType === "one" ? relation.localKey : relation.foreignKey;
@@ -269,43 +336,77 @@ async function resolveIncludes(modelName, rows, include, executor) {
269
336
  }
270
337
  const uniqueKeys = Array.from(new Set(keys));
271
338
  let relCols = "*";
339
+ const nestedInclude = value && typeof value === "object"
340
+ ? mergeIncludes(value.include, collectRelationSelections(relation.modelName, value.select, metadata))
341
+ : undefined;
272
342
  if (value && typeof value === "object" && value.select) {
273
343
  const subSelect = value.select;
274
- const subRelations = an5Metadata_1.relationMap[relation.modelName] || {};
344
+ const subRelations = metadata.relationMap[relation.modelName] || {};
275
345
  const selectedSubCols = Object.keys(subSelect)
276
346
  .filter(k => subSelect[k] && !subRelations[k])
277
- .map(k => `[${k}]`);
347
+ .map(k => (0, sql_utils_1.quoteIdentifier)(k));
278
348
  if (selectedSubCols.length > 0) {
279
- if (!selectedSubCols.includes(`[${searchKey}]`)) {
280
- selectedSubCols.push(`[${searchKey}]`);
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
+ }
281
354
  }
282
355
  relCols = selectedSubCols.join(", ");
283
356
  }
284
357
  }
285
- let sqlText = `SELECT ${relCols} FROM ${relTable} WITH (NOLOCK) WHERE ${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 = "";
286
360
  const subParams = {};
287
361
  uniqueKeys.forEach((k, i) => { subParams[`k_${i}`] = k; });
362
+ let subArgs = undefined;
288
363
  if (value && typeof value === "object") {
289
- const subArgs = value;
290
- if (subArgs.orderBy) {
291
- sqlText += buildOrderBy(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);
292
371
  }
293
372
  }
294
- const relatedRows = await executor(sqlText, subParams);
295
- if (value && typeof value === "object" && value.select) {
296
- relatedRows.forEach((r, idx) => {
297
- relatedRows[idx] = projectFields(r, value.select);
298
- });
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}`;
299
389
  }
300
- if (value && typeof value === "object" && value.include) {
301
- await resolveIncludes(relation.modelName, relatedRows, value.include, executor);
390
+ else {
391
+ sqlText = `SELECT ${relCols} FROM ${relTable} WITH (NOLOCK) WHERE ${filters.join(" AND ")}`;
392
+ if (subArgs?.orderBy) {
393
+ sqlText += (0, sql_utils_1.buildOrderBy)(subArgs.orderBy);
394
+ }
395
+ }
396
+ const relatedRows = await executor(sqlText, subParams);
397
+ relatedRows.forEach((r) => { delete r.__an5_rn; });
398
+ if (nestedInclude) {
399
+ await resolveIncludes(relation.modelName, relatedRows, nestedInclude, executor, metadata);
302
400
  }
401
+ const outputRows = value && typeof value === "object" && value.select
402
+ ? relatedRows.map((r) => projectFields(r, value.select))
403
+ : relatedRows;
303
404
  const groupMap = new Map();
304
- relatedRows.forEach((r) => {
405
+ relatedRows.forEach((r, idx) => {
305
406
  const k = r[searchKey];
306
407
  if (!groupMap.has(k))
307
408
  groupMap.set(k, []);
308
- groupMap.get(k).push(r);
409
+ groupMap.get(k).push(outputRows[idx]);
309
410
  });
310
411
  rows.forEach(r => {
311
412
  const k = r[matchKey];
@@ -323,56 +424,69 @@ async function resolveIncludes(modelName, rows, include, executor) {
323
424
  class TableClient {
324
425
  constructor(modelName, tableName, executor, orm) {
325
426
  this.modelName = modelName;
326
- this.tableName = tableName;
327
427
  this.executor = executor;
328
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));
329
436
  }
330
437
  async findMany(args) {
331
438
  return this.orm._executeMiddleware({ model: this.modelName, action: 'findMany', args }, async (params) => {
332
439
  const { args: finalArgs } = params;
333
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);
334
443
  let cols = "*";
335
444
  if (finalArgs?.select) {
336
- const modelRelations = an5Metadata_1.relationMap[this.modelName] || {};
337
- const selectedCols = Object.keys(finalArgs.select)
338
- .filter(k => finalArgs.select[k] && !modelRelations[k])
339
- .map(k => `[${k}]`);
445
+ const modelRelations = this.orm.metadata.relationMap[this.modelName] || {};
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));
340
454
  if (selectedCols.length > 0) {
341
455
  cols = selectedCols.join(", ");
342
456
  }
343
457
  }
344
458
  let sqlText = "SELECT";
345
459
  if (finalArgs?.take && !hasSkip) {
346
- sqlText += ` TOP (${finalArgs.take})`;
460
+ sqlText += ` TOP (${(0, sql_utils_1.toNonNegativeInt)(finalArgs.take, 1)})`;
347
461
  }
348
462
  sqlText += ` ${cols} FROM ${this.tableName} WITH (NOLOCK)`;
349
463
  const p = {};
350
- const whereSql = parseWhere(this.modelName, finalArgs?.where, p);
464
+ const whereSql = this.orm.parseWhere(this.modelName, finalArgs?.where, p);
351
465
  if (whereSql) {
352
466
  sqlText += ` WHERE ${whereSql}`;
353
467
  }
354
468
  if (finalArgs?.orderBy) {
355
- sqlText += buildOrderBy(finalArgs.orderBy);
469
+ sqlText += (0, sql_utils_1.buildOrderBy)(finalArgs.orderBy);
356
470
  }
357
471
  else if (hasSkip) {
358
472
  // OFFSET requires an ORDER BY clause in SQL Server
359
473
  sqlText += " ORDER BY (SELECT NULL)";
360
474
  }
361
475
  if (hasSkip) {
362
- sqlText += ` OFFSET ${finalArgs.skip} ROWS`;
476
+ sqlText += ` OFFSET ${(0, sql_utils_1.toNonNegativeInt)(finalArgs.skip)} ROWS`;
363
477
  if (finalArgs?.take) {
364
- sqlText += ` FETCH NEXT ${finalArgs.take} ROWS ONLY`;
478
+ sqlText += ` FETCH NEXT ${(0, sql_utils_1.toNonNegativeInt)(finalArgs.take, 1)} ROWS ONLY`;
365
479
  }
366
480
  }
367
481
  const rows = await this.executor(sqlText, p);
482
+ if (include) {
483
+ await resolveIncludes(this.modelName, rows, include, this.executor, this.orm.metadata);
484
+ }
368
485
  if (finalArgs?.select) {
369
486
  rows.forEach((r, idx) => {
370
487
  rows[idx] = projectFields(r, finalArgs.select);
371
488
  });
372
489
  }
373
- if (finalArgs?.include) {
374
- await resolveIncludes(this.modelName, rows, finalArgs.include, this.executor);
375
- }
376
490
  return rows;
377
491
  });
378
492
  }
@@ -392,7 +506,7 @@ class TableClient {
392
506
  const { args: finalArgs } = params;
393
507
  let sqlText = `SELECT COUNT(*) as count FROM ${this.tableName} WITH (NOLOCK)`;
394
508
  const p = {};
395
- const whereSql = parseWhere(this.modelName, finalArgs?.where, p);
509
+ const whereSql = this.orm.parseWhere(this.modelName, finalArgs?.where, p);
396
510
  if (whereSql) {
397
511
  sqlText += ` WHERE ${whereSql}`;
398
512
  }
@@ -400,8 +514,14 @@ class TableClient {
400
514
  return result[0]?.count || 0;
401
515
  });
402
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
+ }
403
523
  async handleNestedWrites(data, parentId) {
404
- const modelRelations = an5Metadata_1.relationMap[this.modelName] || {};
524
+ const modelRelations = this.orm.metadata.relationMap[this.modelName] || {};
405
525
  for (const [key, value] of Object.entries(data)) {
406
526
  const relation = modelRelations[key];
407
527
  if (!relation || !value || typeof value !== "object" || (value instanceof Date))
@@ -410,32 +530,72 @@ class TableClient {
410
530
  if (!relTableClient)
411
531
  continue;
412
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
+ }
413
547
  // Handle deleteMany (an5Orm-style)
414
- if (nestedOps.deleteMany) {
548
+ if (hasOwn(nestedOps, "deleteMany")) {
415
549
  const deleteWhere = Array.isArray(nestedOps.deleteMany) ? { OR: nestedOps.deleteMany } : nestedOps.deleteMany;
416
550
  // Scope deletion to parent
417
- const scopedWhere = { ...deleteWhere, [relation.foreignKey]: parentId };
551
+ const scopedWhere = this.scopedRelationWhere(relation, parentId, deleteWhere);
418
552
  await relTableClient.deleteMany({ where: scopedWhere });
419
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
+ }
420
584
  // Handle create
421
- if (nestedOps.create) {
422
- const createItems = Array.isArray(nestedOps.create) ? nestedOps.create : [nestedOps.create];
423
- for (const item of createItems) {
585
+ if (hasOwn(nestedOps, "create")) {
586
+ for (const item of asArray(nestedOps.create)) {
424
587
  // Inject parent ID
425
588
  const itemData = { ...item, [relation.foreignKey]: parentId };
426
589
  await relTableClient.create({ data: itemData });
427
590
  }
428
591
  }
429
592
  // Handle connect
430
- if (nestedOps.connect) {
431
- const connectItems = Array.isArray(nestedOps.connect) ? nestedOps.connect : [nestedOps.connect];
432
- for (const item of connectItems) {
433
- if (relation.relationType === "many") {
434
- await relTableClient.update({
435
- where: item,
436
- data: { [relation.foreignKey]: parentId }
437
- });
438
- }
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
+ });
439
599
  }
440
600
  }
441
601
  }
@@ -447,35 +607,39 @@ class TableClient {
447
607
  const data = { ...finalArgs.data };
448
608
  // Extract nested writes
449
609
  const nestedData = {};
450
- const modelRelations = an5Metadata_1.relationMap[this.modelName] || {};
610
+ const modelRelations = this.orm.metadata.relationMap[this.modelName] || {};
451
611
  for (const key of Object.keys(data)) {
452
612
  if (modelRelations[key]) {
453
613
  nestedData[key] = data[key];
454
614
  delete data[key];
455
615
  }
456
616
  }
457
- if (!data.id && an5Metadata_1.modelFields[this.modelName]?.id?.ts === "string") {
617
+ if (!data.id && this.orm.metadata.modelFields[this.modelName]?.id?.ts === "string") {
458
618
  data.id = (0, crypto_1.randomUUID)();
459
619
  }
460
620
  const now = new Date();
461
- if (!data.createdAt && an5Metadata_1.modelFields[this.modelName]?.createdAt)
621
+ if (!data.createdAt && this.orm.metadata.modelFields[this.modelName]?.createdAt)
462
622
  data.createdAt = now;
463
- if (!data.updatedAt && an5Metadata_1.modelFields[this.modelName]?.updatedAt)
623
+ if (!data.updatedAt && this.orm.metadata.modelFields[this.modelName]?.updatedAt)
464
624
  data.updatedAt = now;
465
625
  // Handle one-relation connect where we hold the FK
466
626
  for (const [key, value] of Object.entries(nestedData)) {
467
627
  const rel = modelRelations[key];
468
- if (rel && rel.relationType === "one" && value.connect) {
469
- const connectObj = value.connect;
470
- 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];
471
632
  data[rel.foreignKey] = targetId;
472
633
  }
473
634
  }
474
635
  const keys = Object.keys(data);
475
- const columns = keys.map(k => `[${k}]`).join(", ");
476
- const placeholders = keys.map(k => `@${k}`).join(", ");
636
+ const columns = keys.map(k => (0, sql_utils_1.quoteIdentifier)(k)).join(", ");
637
+ const safeKeys = keys.map(k => (0, sql_utils_1.sanitizeParamName)(k));
638
+ const placeholders = safeKeys.map(k => `@${k}`).join(", ");
639
+ const safeData = {};
640
+ keys.forEach((k, i) => { safeData[safeKeys[i]] = data[k]; });
477
641
  const sqlText = `INSERT INTO ${this.tableName} (${columns}) OUTPUT inserted.* VALUES (${placeholders})`;
478
- const rows = await this.executor(sqlText, data);
642
+ const rows = await this.executor(sqlText, safeData);
479
643
  const createdRow = rows[0];
480
644
  if (!createdRow) {
481
645
  throw new Error("Failed to insert record and retrieve output");
@@ -500,19 +664,19 @@ class TableClient {
500
664
  msg.includes('unique') ||
501
665
  errNumber === 2627 ||
502
666
  errNumber === 2601) {
503
- throw new typescript_1.An5.An5ClientKnownRequestError("Unique constraint failed", {
667
+ throw new errors_1.An5ClientKnownRequestError("Unique constraint failed", {
504
668
  code: "P2002",
505
669
  clientVersion: "mock",
506
670
  });
507
671
  }
508
672
  if (msg.includes('foreign key') || errNumber === 547) {
509
- throw new typescript_1.An5.An5ClientKnownRequestError("Foreign key constraint failed", {
673
+ throw new errors_1.An5ClientKnownRequestError("Foreign key constraint failed", {
510
674
  code: "P2003",
511
675
  clientVersion: "mock",
512
676
  });
513
677
  }
514
678
  if (msg.includes('not found') || errNumber === 404) {
515
- throw new typescript_1.An5.An5ClientKnownRequestError("Record not found", {
679
+ throw new errors_1.An5ClientKnownRequestError("Record not found", {
516
680
  code: "P2025",
517
681
  clientVersion: "mock",
518
682
  });
@@ -525,12 +689,12 @@ class TableClient {
525
689
  return this.orm._executeMiddleware({ model: this.modelName, action: 'update', args }, async (params) => {
526
690
  const { args: finalArgs } = params;
527
691
  const data = { ...finalArgs.data };
528
- if (an5Metadata_1.modelFields[this.modelName]?.updatedAt) {
692
+ if (this.orm.metadata.modelFields[this.modelName]?.updatedAt) {
529
693
  data.updatedAt = new Date();
530
694
  }
531
695
  // Extract nested writes
532
696
  const nestedData = {};
533
- const modelRelations = an5Metadata_1.relationMap[this.modelName] || {};
697
+ const modelRelations = this.orm.metadata.relationMap[this.modelName] || {};
534
698
  for (const key of Object.keys(data)) {
535
699
  if (modelRelations[key]) {
536
700
  nestedData[key] = data[key];
@@ -541,46 +705,35 @@ class TableClient {
541
705
  // Handle one-relation connect where we hold the FK
542
706
  for (const [key, value] of Object.entries(nestedData)) {
543
707
  const rel = modelRelations[key];
544
- if (rel && rel.relationType === "one" && value.connect) {
545
- const connectObj = value.connect;
546
- const targetId = connectObj.id || Object.values(connectObj)[0];
547
- 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
+ }
548
718
  }
549
719
  }
550
720
  const sets = [];
551
721
  const p = {};
552
722
  for (const key of Object.keys(data)) {
553
723
  const val = data[key];
554
- if (val && typeof val === "object" && !(val instanceof Date)) {
555
- if (val.increment !== undefined) {
556
- sets.push(`[${key}] = [${key}] + @${key}_inc`);
557
- p[`${key}_inc`] = val.increment;
558
- continue;
559
- }
560
- else if (val.decrement !== undefined) {
561
- sets.push(`[${key}] = [${key}] - @${key}_dec`);
562
- p[`${key}_dec`] = val.decrement;
563
- continue;
564
- }
565
- else if (val.set !== undefined) {
566
- sets.push(`[${key}] = @${key}_set`);
567
- p[`${key}_set`] = val.set;
568
- continue;
569
- }
570
- }
571
- sets.push(`[${key}] = @${key}`);
572
- p[key] = val;
724
+ appendUpdateSet(sets, p, key, val);
573
725
  }
574
726
  const whereParams = {};
575
- const whereSql = parseWhere(this.modelName, finalArgs.where, whereParams, "w_");
727
+ const whereSql = this.orm.parseWhere(this.modelName, finalArgs.where, whereParams, "w_");
576
728
  Object.assign(p, whereParams);
577
- const sqlText = `UPDATE ${this.tableName} SET ${sets.join(", ")} WHERE ${whereSql}`;
578
- await this.executor(sqlText, p);
579
- // Get parent ID for nested writes
580
729
  const existing = await this.findUnique({ where: finalArgs.where });
581
730
  if (!existing)
582
731
  throw new Error("Record not found to update");
583
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
+ }
584
737
  // Process nested writes
585
738
  await this.handleNestedWrites(nestedData, parentId);
586
739
  const updated = await this.findUnique({ where: finalArgs.where, include: finalArgs.include });
@@ -594,7 +747,7 @@ class TableClient {
594
747
  return this.orm._executeMiddleware({ model: this.modelName, action: 'updateMany', args }, async (params) => {
595
748
  const { args: finalArgs } = params;
596
749
  const data = { ...finalArgs.data };
597
- if (an5Metadata_1.modelFields[this.modelName]?.updatedAt) {
750
+ if (this.orm.metadata.modelFields[this.modelName]?.updatedAt) {
598
751
  data.updatedAt = new Date();
599
752
  }
600
753
  delete data.id;
@@ -602,39 +755,19 @@ class TableClient {
602
755
  const p = {};
603
756
  for (const key of Object.keys(data)) {
604
757
  const val = data[key];
605
- if (an5Metadata_1.relationMap[this.modelName]?.[key])
758
+ if (this.orm.metadata.relationMap[this.modelName]?.[key])
606
759
  continue;
607
- if (val && typeof val === "object" && !(val instanceof Date)) {
608
- if (val.increment !== undefined) {
609
- sets.push(`[${key}] = [${key}] + @${key}_inc`);
610
- p[`${key}_inc`] = val.increment;
611
- continue;
612
- }
613
- else if (val.decrement !== undefined) {
614
- sets.push(`[${key}] = [${key}] - @${key}_dec`);
615
- p[`${key}_dec`] = val.decrement;
616
- continue;
617
- }
618
- else if (val.set !== undefined) {
619
- sets.push(`[${key}] = @${key}_set`);
620
- p[`${key}_set`] = val.set;
621
- continue;
622
- }
623
- }
624
- sets.push(`[${key}] = @${key}`);
625
- p[key] = val;
760
+ appendUpdateSet(sets, p, key, val);
761
+ }
762
+ if (sets.length === 0) {
763
+ return { count: 0 };
626
764
  }
627
765
  const whereParams = {};
628
- const whereSql = parseWhere(this.modelName, finalArgs.where, whereParams, "w_");
766
+ const whereSql = this.orm.parseWhere(this.modelName, finalArgs.where, whereParams, "w_");
629
767
  Object.assign(p, whereParams);
630
768
  const sqlText = `UPDATE ${this.tableName} SET ${sets.join(", ")} ${whereSql ? `WHERE ${whereSql}` : ""}`;
631
- const pool = await (await getAdapter()).getPool();
632
- const request = new mssql_1.default.Request(pool);
633
- for (const [key, value] of Object.entries(p)) {
634
- request.input(key, value);
635
- }
636
- const result = await request.query(sqlText);
637
- return { count: result.rowsAffected?.[0] || 0 };
769
+ const count = await this.executeRaw(sqlText, p);
770
+ return { count };
638
771
  });
639
772
  }
640
773
  async delete(args) {
@@ -644,7 +777,7 @@ class TableClient {
644
777
  if (!existing)
645
778
  throw new Error("Record not found to delete");
646
779
  const p = {};
647
- const whereSql = parseWhere(this.modelName, finalArgs.where, p);
780
+ const whereSql = this.orm.parseWhere(this.modelName, finalArgs.where, p);
648
781
  const sqlText = `DELETE FROM ${this.tableName} WHERE ${whereSql}`;
649
782
  await this.executor(sqlText, p);
650
783
  return existing;
@@ -654,39 +787,37 @@ class TableClient {
654
787
  return this.orm._executeMiddleware({ model: this.modelName, action: 'deleteMany', args }, async (params) => {
655
788
  const { args: finalArgs } = params;
656
789
  const p = {};
657
- const whereSql = parseWhere(this.modelName, finalArgs?.where, p);
790
+ const whereSql = this.orm.parseWhere(this.modelName, finalArgs?.where, p);
658
791
  const sqlText = `DELETE FROM ${this.tableName} ${whereSql ? `WHERE ${whereSql}` : ""}`;
659
- const pool = await (await getAdapter()).getPool();
660
- const request = new mssql_1.default.Request(pool);
661
- for (const [key, value] of Object.entries(p)) {
662
- request.input(key, value);
663
- }
664
- const result = await request.query(sqlText);
665
- return { count: result.rowsAffected?.[0] || 0 };
792
+ const count = await this.executeRaw(sqlText, p);
793
+ return { count };
666
794
  });
667
795
  }
668
796
  async vectorSearch(args) {
669
797
  return this.orm._executeMiddleware({ model: this.modelName, action: 'vectorSearch', args }, async (params) => {
670
798
  const { args: finalArgs } = params;
671
799
  const field = finalArgs.vectorField || "embedding";
672
- const metric = finalArgs.distanceMetric || "cosine";
673
- const elementType = finalArgs.vectorElementType || "float32";
674
- const take = finalArgs.take || 10;
675
- const dim = finalArgs.vector.length;
800
+ const METRICS = ["cosine", "euclidean", "dot"];
801
+ const ELEMENT_TYPES = ["float32", "float16", "uint8"];
802
+ const metric = METRICS.includes(finalArgs.distanceMetric) ? finalArgs.distanceMetric : "cosine";
803
+ const elementType = ELEMENT_TYPES.includes(finalArgs.vectorElementType) ? finalArgs.vectorElementType : "float32";
804
+ const take = (0, sql_utils_1.toNonNegativeInt)(finalArgs.take, 10);
805
+ const dim = Number(finalArgs.vector?.length) || 0;
676
806
  const vectorJson = JSON.stringify(finalArgs.vector);
807
+ const col = (0, sql_utils_1.quoteIdentifier)(field);
677
808
  try {
678
809
  if (dim > 1998) {
679
810
  throw new Error("Vector dimension exceeds SQL Server limit of 1998");
680
811
  }
681
812
  let sqlText = `SELECT TOP (${take}) *, `;
682
- sqlText += `VECTOR_DISTANCE('${metric}', CAST([${field}] AS VECTOR(${dim}, ${elementType})), CAST(@query_vector AS VECTOR(${dim}, ${elementType}))) AS distance `;
813
+ sqlText += `VECTOR_DISTANCE('${metric}', CAST(${col} AS VECTOR(${dim}, ${elementType})), CAST(@query_vector AS VECTOR(${dim}, ${elementType}))) AS distance `;
683
814
  sqlText += `FROM ${this.tableName} WITH (NOLOCK) `;
684
815
  const p = {
685
816
  query_vector: vectorJson
686
817
  };
687
818
  const whereClauses = [];
688
- whereClauses.push(`[${field}] IS NOT NULL`);
689
- const whereSql = parseWhere(this.modelName, finalArgs.where, p, "v_");
819
+ whereClauses.push(`${col} IS NOT NULL`);
820
+ const whereSql = this.orm.parseWhere(this.modelName, finalArgs.where, p, "v_");
690
821
  if (whereSql) {
691
822
  whereClauses.push(whereSql);
692
823
  }
@@ -694,12 +825,12 @@ class TableClient {
694
825
  sqlText += `ORDER BY distance ASC`;
695
826
  const rows = await this.executor(sqlText, p);
696
827
  if (finalArgs.include) {
697
- await resolveIncludes(this.modelName, rows, finalArgs.include, this.executor);
828
+ await resolveIncludes(this.modelName, rows, finalArgs.include, this.executor, this.orm.metadata);
698
829
  }
699
830
  return rows;
700
831
  }
701
832
  catch (err) {
702
- const msg = String(err.message || "").toLowerCase();
833
+ const msg = (String(err?.message || "") + " " + String(err?.originalError?.message || "")).toLowerCase();
703
834
  // Handle specific float16 to float32 conversion error by retrying with float16
704
835
  if (msg.includes("float16") && msg.includes("float32") && msg.includes("conversion") && !finalArgs.vectorElementType) {
705
836
  logger_1.logger.info(`Detected float16 vector storage. Retrying vectorSearch with float16 element type.`);
@@ -707,8 +838,17 @@ class TableClient {
707
838
  }
708
839
  const isUnsupported = msg.includes("vector_distance") ||
709
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") ||
710
847
  msg.includes("limit of 1998") ||
711
- err?.number === 195;
848
+ err?.number === 195 ||
849
+ err?.number === 102 ||
850
+ err?.number === 319 ||
851
+ err?.originalError?.number === 319;
712
852
  if (!isUnsupported) {
713
853
  throw err;
714
854
  }
@@ -716,8 +856,8 @@ class TableClient {
716
856
  let fallbackSql = `SELECT * FROM ${this.tableName} WITH (NOLOCK) `;
717
857
  const fallbackParams = {};
718
858
  const fallbackWhereClauses = [];
719
- fallbackWhereClauses.push(`[${field}] IS NOT NULL`);
720
- const fallbackWhereSql = parseWhere(this.modelName, finalArgs.where, fallbackParams, "vf_");
859
+ fallbackWhereClauses.push(`${col} IS NOT NULL`);
860
+ const fallbackWhereSql = this.orm.parseWhere(this.modelName, finalArgs.where, fallbackParams, "vf_");
721
861
  if (fallbackWhereSql) {
722
862
  fallbackWhereClauses.push(fallbackWhereSql);
723
863
  }
@@ -754,7 +894,7 @@ class TableClient {
754
894
  .sort((a, b) => a.distance - b.distance)
755
895
  .slice(0, take);
756
896
  if (finalArgs.include && results.length > 0) {
757
- await resolveIncludes(this.modelName, results, finalArgs.include, this.executor);
897
+ await resolveIncludes(this.modelName, results, finalArgs.include, this.executor, this.orm.metadata);
758
898
  }
759
899
  return results;
760
900
  }
@@ -766,42 +906,10 @@ class TableClient {
766
906
  if (!finalArgs.data || finalArgs.data.length === 0)
767
907
  return { count: 0 };
768
908
  try {
769
- const pool = await (await getAdapter()).getPool();
770
- const table = new mssql_1.default.Table(this.tableName);
771
- const fields = an5Metadata_1.modelFields[this.modelName] || {};
909
+ const fields = this.orm.metadata.modelFields[this.modelName] || {};
772
910
  const fieldNames = Object.keys(fields);
773
- for (const col of fieldNames) {
774
- const fieldDef = fields[col];
775
- const rawType = fieldDef?.ts || 'string';
776
- const sqlType = fieldDef?.sql || 'NVARCHAR(MAX)';
777
- const isNullable = rawType.endsWith('?');
778
- const tsType = isNullable ? rawType.slice(0, -1) : rawType;
779
- // Map SQL Server type to mssql package type
780
- const sqlBase = sqlType.toUpperCase().split('(')[0];
781
- if (sqlBase === 'INT' || sqlBase === 'SMALLINT' || sqlBase === 'TINYINT') {
782
- table.columns.add(col, mssql_1.default.Int, { nullable: isNullable });
783
- }
784
- else if (sqlBase === 'BIGINT') {
785
- table.columns.add(col, mssql_1.default.BigInt, { nullable: isNullable });
786
- }
787
- else if (sqlBase === 'FLOAT' || sqlBase === 'REAL' || sqlBase === 'DECIMAL' || sqlBase === 'NUMERIC' || sqlBase === 'MONEY' || sqlBase === 'SMALLMONEY') {
788
- table.columns.add(col, mssql_1.default.Float, { nullable: isNullable });
789
- }
790
- else if (sqlBase === 'BIT') {
791
- table.columns.add(col, mssql_1.default.Bit, { nullable: isNullable });
792
- }
793
- else if (sqlBase === 'DATETIME' || sqlBase === 'DATETIME2' || sqlBase === 'SMALLDATETIME' || sqlBase === 'DATE' || sqlBase === 'TIME') {
794
- table.columns.add(col, mssql_1.default.DateTime, { nullable: isNullable });
795
- }
796
- else if (sqlBase === 'UNIQUEIDENTIFIER') {
797
- table.columns.add(col, mssql_1.default.UniqueIdentifier, { nullable: isNullable });
798
- }
799
- else {
800
- table.columns.add(col, mssql_1.default.NVarChar(mssql_1.default.MAX), { nullable: isNullable });
801
- }
802
- }
803
911
  const now = new Date();
804
- for (const item of finalArgs.data) {
912
+ const rows = finalArgs.data.map((item) => {
805
913
  const rowData = { ...item };
806
914
  if (!rowData.id && fields.id?.ts === "string")
807
915
  rowData.id = (0, crypto_1.randomUUID)();
@@ -809,17 +917,24 @@ class TableClient {
809
917
  rowData.createdAt = now;
810
918
  if (!rowData.updatedAt && fields.updatedAt)
811
919
  rowData.updatedAt = now;
812
- const rowValues = fieldNames.map(col => {
813
- const v = rowData[col];
814
- if (v === undefined)
815
- return null;
816
- return v;
920
+ return rowData;
921
+ });
922
+ const cols = fieldNames.filter(col => rows.some((r) => r[col] !== undefined));
923
+ if (cols.length === 0)
924
+ throw new Error("No insertable columns");
925
+ const params = {};
926
+ const rowPlaceholders = [];
927
+ rows.forEach((row, rowIdx) => {
928
+ const vals = cols.map(col => {
929
+ const p = (0, sql_utils_1.sanitizeParamName)(`r${rowIdx}_${col}`);
930
+ params[p] = row[col] ?? null;
931
+ return `@${p}`;
817
932
  });
818
- table.rows.add(...rowValues);
819
- }
820
- const request = new mssql_1.default.Request(pool);
821
- await request.bulk(table);
822
- return { count: finalArgs.data.length };
933
+ rowPlaceholders.push(`(${vals.join(", ")})`);
934
+ });
935
+ const sqlText = `INSERT INTO ${this.tableName} (${cols.map(sql_utils_1.quoteIdentifier).join(", ")}) VALUES ${rowPlaceholders.join(", ")}`;
936
+ await this.executeRaw(sqlText, params);
937
+ return { count: rows.length };
823
938
  }
824
939
  catch (err) {
825
940
  logger_1.logger.warn(`Bulk insert failed, falling back to sequential inserts: ${err.message}`);
@@ -846,26 +961,26 @@ class TableClient {
846
961
  const resultObj = {};
847
962
  if (finalArgs._sum) {
848
963
  resultObj._sum = {};
849
- for (const key of Object.keys(finalArgs._sum)) {
850
- selects.push(`SUM(${key}) as sum_${key}`);
964
+ for (const key of selectedAggregateFields(finalArgs._sum)) {
965
+ selects.push(aggSelect("SUM", key));
851
966
  }
852
967
  }
853
968
  if (finalArgs._avg) {
854
969
  resultObj._avg = {};
855
- for (const key of Object.keys(finalArgs._avg)) {
856
- selects.push(`AVG(${key}) as avg_${key}`);
970
+ for (const key of selectedAggregateFields(finalArgs._avg)) {
971
+ selects.push(aggSelect("AVG", key));
857
972
  }
858
973
  }
859
974
  if (finalArgs._min) {
860
975
  resultObj._min = {};
861
- for (const key of Object.keys(finalArgs._min)) {
862
- selects.push(`MIN(${key}) as min_${key}`);
976
+ for (const key of selectedAggregateFields(finalArgs._min)) {
977
+ selects.push(aggSelect("MIN", key));
863
978
  }
864
979
  }
865
980
  if (finalArgs._max) {
866
981
  resultObj._max = {};
867
- for (const key of Object.keys(finalArgs._max)) {
868
- selects.push(`MAX(${key}) as max_${key}`);
982
+ for (const key of selectedAggregateFields(finalArgs._max)) {
983
+ selects.push(aggSelect("MAX", key));
869
984
  }
870
985
  }
871
986
  if (finalArgs._count) {
@@ -874,8 +989,8 @@ class TableClient {
874
989
  selects.push(`COUNT(*) as count_all`);
875
990
  }
876
991
  else {
877
- for (const key of Object.keys(finalArgs._count)) {
878
- selects.push(`COUNT(${key}) as count_${key}`);
992
+ for (const key of selectedAggregateFields(finalArgs._count)) {
993
+ selects.push(aggSelect("COUNT", key));
879
994
  }
880
995
  }
881
996
  }
@@ -884,30 +999,30 @@ class TableClient {
884
999
  }
885
1000
  let sqlText = `SELECT ${selects.join(", ")} FROM ${this.tableName} WITH (NOLOCK)`;
886
1001
  const p = {};
887
- const whereSql = parseWhere(this.modelName, finalArgs?.where, p);
1002
+ const whereSql = this.orm.parseWhere(this.modelName, finalArgs?.where, p);
888
1003
  if (whereSql) {
889
1004
  sqlText += ` WHERE ${whereSql}`;
890
1005
  }
891
1006
  const rows = await this.executor(sqlText, p);
892
1007
  const row = rows[0] || {};
893
1008
  if (finalArgs._sum) {
894
- for (const key of Object.keys(finalArgs._sum)) {
895
- resultObj._sum[key] = row[`sum_${key}`] !== undefined ? row[`sum_${key}`] : null;
1009
+ for (const key of selectedAggregateFields(finalArgs._sum)) {
1010
+ resultObj._sum[key] = row[aggAlias("sum", key)] !== undefined ? row[aggAlias("sum", key)] : null;
896
1011
  }
897
1012
  }
898
1013
  if (finalArgs._avg) {
899
- for (const key of Object.keys(finalArgs._avg)) {
900
- resultObj._avg[key] = row[`avg_${key}`] !== undefined ? row[`avg_${key}`] : null;
1014
+ for (const key of selectedAggregateFields(finalArgs._avg)) {
1015
+ resultObj._avg[key] = row[aggAlias("avg", key)] !== undefined ? row[aggAlias("avg", key)] : null;
901
1016
  }
902
1017
  }
903
1018
  if (finalArgs._min) {
904
- for (const key of Object.keys(finalArgs._min)) {
905
- resultObj._min[key] = row[`min_${key}`] !== undefined ? row[`min_${key}`] : null;
1019
+ for (const key of selectedAggregateFields(finalArgs._min)) {
1020
+ resultObj._min[key] = row[aggAlias("min", key)] !== undefined ? row[aggAlias("min", key)] : null;
906
1021
  }
907
1022
  }
908
1023
  if (finalArgs._max) {
909
- for (const key of Object.keys(finalArgs._max)) {
910
- resultObj._max[key] = row[`max_${key}`] !== undefined ? row[`max_${key}`] : null;
1024
+ for (const key of selectedAggregateFields(finalArgs._max)) {
1025
+ resultObj._max[key] = row[aggAlias("max", key)] !== undefined ? row[aggAlias("max", key)] : null;
911
1026
  }
912
1027
  }
913
1028
  if (finalArgs._count) {
@@ -915,8 +1030,8 @@ class TableClient {
915
1030
  resultObj._count._all = row[`count_all`] || 0;
916
1031
  }
917
1032
  else {
918
- for (const key of Object.keys(finalArgs._count)) {
919
- resultObj._count[key] = row[`count_${key}`] || 0;
1033
+ for (const key of selectedAggregateFields(finalArgs._count)) {
1034
+ resultObj._count[key] = row[aggAlias("count", key)] || 0;
920
1035
  }
921
1036
  }
922
1037
  }
@@ -926,48 +1041,66 @@ class TableClient {
926
1041
  async groupBy(args) {
927
1042
  return this.orm._executeMiddleware({ model: this.modelName, action: 'groupBy', args }, async (params) => {
928
1043
  const { args: finalArgs } = params;
929
- const byFields = finalArgs.by || [];
1044
+ const byFields = normalizeByFields(finalArgs.by);
930
1045
  if (byFields.length === 0) {
931
1046
  throw new Error("groupBy requires 'by' fields");
932
1047
  }
933
- const selects = [...byFields];
1048
+ const selects = byFields.map((f) => (0, sql_utils_1.quoteIdentifier)(f));
934
1049
  if (finalArgs._count) {
935
1050
  if (finalArgs._count === true || finalArgs._count._all) {
936
1051
  selects.push(`COUNT(*) as count_all`);
937
1052
  }
938
1053
  else {
939
- for (const key of Object.keys(finalArgs._count)) {
940
- selects.push(`COUNT(${key}) as count_${key}`);
1054
+ for (const key of selectedAggregateFields(finalArgs._count)) {
1055
+ selects.push(aggSelect("COUNT", key));
941
1056
  }
942
1057
  }
943
1058
  }
944
1059
  if (finalArgs._sum) {
945
- for (const key of Object.keys(finalArgs._sum)) {
946
- selects.push(`SUM(${key}) as sum_${key}`);
1060
+ for (const key of selectedAggregateFields(finalArgs._sum)) {
1061
+ selects.push(aggSelect("SUM", key));
947
1062
  }
948
1063
  }
949
1064
  if (finalArgs._avg) {
950
- for (const key of Object.keys(finalArgs._avg)) {
951
- selects.push(`AVG(${key}) as avg_${key}`);
1065
+ for (const key of selectedAggregateFields(finalArgs._avg)) {
1066
+ selects.push(aggSelect("AVG", key));
952
1067
  }
953
1068
  }
954
1069
  if (finalArgs._min) {
955
- for (const key of Object.keys(finalArgs._min)) {
956
- selects.push(`MIN(${key}) as min_${key}`);
1070
+ for (const key of selectedAggregateFields(finalArgs._min)) {
1071
+ selects.push(aggSelect("MIN", key));
957
1072
  }
958
1073
  }
959
1074
  if (finalArgs._max) {
960
- for (const key of Object.keys(finalArgs._max)) {
961
- selects.push(`MAX(${key}) as max_${key}`);
1075
+ for (const key of selectedAggregateFields(finalArgs._max)) {
1076
+ selects.push(aggSelect("MAX", key));
962
1077
  }
963
1078
  }
964
1079
  let sqlText = `SELECT ${selects.join(", ")} FROM ${this.tableName} WITH (NOLOCK)`;
965
1080
  const p = {};
966
- const whereSql = parseWhere(this.modelName, finalArgs?.where, p);
1081
+ const whereSql = this.orm.parseWhere(this.modelName, finalArgs?.where, p);
967
1082
  if (whereSql) {
968
1083
  sqlText += ` WHERE ${whereSql}`;
969
1084
  }
970
- sqlText += ` GROUP BY ${byFields.join(", ")}`;
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
+ }
971
1104
  const rows = await this.executor(sqlText, p);
972
1105
  return rows.map((row) => {
973
1106
  const item = {};
@@ -980,39 +1113,46 @@ class TableClient {
980
1113
  item._count._all = row[`count_all`] || 0;
981
1114
  }
982
1115
  else {
983
- for (const key of Object.keys(finalArgs._count)) {
984
- item._count[key] = row[`count_${key}`] || 0;
1116
+ for (const key of selectedAggregateFields(finalArgs._count)) {
1117
+ item._count[key] = row[aggAlias("count", key)] || 0;
985
1118
  }
986
1119
  }
987
1120
  }
988
1121
  if (finalArgs._sum) {
989
1122
  item._sum = {};
990
- for (const key of Object.keys(finalArgs._sum)) {
991
- item._sum[key] = row[`sum_${key}`] !== undefined ? row[`sum_${key}`] : null;
1123
+ for (const key of selectedAggregateFields(finalArgs._sum)) {
1124
+ item._sum[key] = row[aggAlias("sum", key)] !== undefined ? row[aggAlias("sum", key)] : null;
992
1125
  }
993
1126
  }
994
1127
  if (finalArgs._avg) {
995
1128
  item._avg = {};
996
- for (const key of Object.keys(finalArgs._avg)) {
997
- item._avg[key] = row[`avg_${key}`] !== undefined ? row[`avg_${key}`] : null;
1129
+ for (const key of selectedAggregateFields(finalArgs._avg)) {
1130
+ item._avg[key] = row[aggAlias("avg", key)] !== undefined ? row[aggAlias("avg", key)] : null;
998
1131
  }
999
1132
  }
1000
1133
  if (finalArgs._min) {
1001
1134
  item._min = {};
1002
- for (const key of Object.keys(finalArgs._min)) {
1003
- item._min[key] = row[`min_${key}`] !== undefined ? row[`min_${key}`] : null;
1135
+ for (const key of selectedAggregateFields(finalArgs._min)) {
1136
+ item._min[key] = row[aggAlias("min", key)] !== undefined ? row[aggAlias("min", key)] : null;
1004
1137
  }
1005
1138
  }
1006
1139
  if (finalArgs._max) {
1007
1140
  item._max = {};
1008
- for (const key of Object.keys(finalArgs._max)) {
1009
- item._max[key] = row[`max_${key}`] !== undefined ? row[`max_${key}`] : null;
1141
+ for (const key of selectedAggregateFields(finalArgs._max)) {
1142
+ item._max[key] = row[aggAlias("max", key)] !== undefined ? row[aggAlias("max", key)] : null;
1010
1143
  }
1011
1144
  }
1012
1145
  return item;
1013
1146
  });
1014
1147
  });
1015
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
+ }
1016
1156
  async upsert(args) {
1017
1157
  return this.orm._executeMiddleware({ model: this.modelName, action: 'upsert', args }, async (params) => {
1018
1158
  const { args: finalArgs } = params;
@@ -1021,50 +1161,50 @@ class TableClient {
1021
1161
  const cleanCreate = { ...createData };
1022
1162
  const cleanUpdate = { ...updateData };
1023
1163
  for (const key of Object.keys(cleanCreate)) {
1024
- if (an5Metadata_1.relationMap[this.modelName]?.[key])
1164
+ if (this.orm.metadata.relationMap[this.modelName]?.[key])
1025
1165
  delete cleanCreate[key];
1026
1166
  }
1027
1167
  for (const key of Object.keys(cleanUpdate)) {
1028
- if (an5Metadata_1.relationMap[this.modelName]?.[key])
1168
+ if (this.orm.metadata.relationMap[this.modelName]?.[key])
1029
1169
  delete cleanUpdate[key];
1030
1170
  }
1031
- if (!cleanCreate.id && an5Metadata_1.modelFields[this.modelName]?.id?.ts === "string") {
1171
+ if (!cleanCreate.id && this.orm.metadata.modelFields[this.modelName]?.id?.ts === "string") {
1032
1172
  cleanCreate.id = (0, crypto_1.randomUUID)();
1033
1173
  }
1034
1174
  const now = new Date();
1035
- if (!cleanCreate.createdAt && an5Metadata_1.modelFields[this.modelName]?.createdAt)
1175
+ if (!cleanCreate.createdAt && this.orm.metadata.modelFields[this.modelName]?.createdAt)
1036
1176
  cleanCreate.createdAt = now;
1037
- if (!cleanCreate.updatedAt && an5Metadata_1.modelFields[this.modelName]?.updatedAt)
1177
+ if (!cleanCreate.updatedAt && this.orm.metadata.modelFields[this.modelName]?.updatedAt)
1038
1178
  cleanCreate.updatedAt = now;
1039
- if (!cleanUpdate.updatedAt && an5Metadata_1.modelFields[this.modelName]?.updatedAt)
1179
+ if (!cleanUpdate.updatedAt && this.orm.metadata.modelFields[this.modelName]?.updatedAt)
1040
1180
  cleanUpdate.updatedAt = now;
1181
+ const atomicWhere = flattenSimpleEqualityWhere(where);
1182
+ if (!atomicWhere || Object.keys(cleanUpdate).length === 0) {
1183
+ return this.sequentialUpsert(finalArgs);
1184
+ }
1041
1185
  const p = {};
1042
- const whereSql = parseWhere(this.modelName, where, p, "upw_");
1043
1186
  const allKeys = Array.from(new Set([...Object.keys(cleanCreate), ...Object.keys(cleanUpdate)]));
1187
+ const cParam = (k) => `c_${(0, sql_utils_1.sanitizeParamName)(k)}`;
1188
+ const uParam = (k) => `u_${(0, sql_utils_1.sanitizeParamName)(k)}`;
1044
1189
  for (const k of allKeys) {
1045
1190
  if (cleanCreate[k] !== undefined)
1046
- p[`c_${k}`] = cleanCreate[k];
1191
+ p[cParam(k)] = cleanCreate[k];
1047
1192
  if (cleanUpdate[k] !== undefined)
1048
- p[`u_${k}`] = cleanUpdate[k];
1193
+ p[uParam(k)] = cleanUpdate[k];
1194
+ }
1195
+ for (const [k, v] of Object.entries(atomicWhere)) {
1196
+ p[`upw_${(0, sql_utils_1.sanitizeParamName)(k)}`] = v;
1049
1197
  }
1050
1198
  const sourceSelect = allKeys.map(k => {
1051
- const val = cleanCreate[k] !== undefined ? `@c_${k}` : (cleanUpdate[k] !== undefined ? `@u_${k}` : "NULL");
1052
- return `${val} as [${k}]`;
1199
+ const val = cleanCreate[k] !== undefined ? `@${cParam(k)}` : (cleanUpdate[k] !== undefined ? `@${uParam(k)}` : "NULL");
1200
+ return `${val} as ${(0, sql_utils_1.quoteIdentifier)(k)}`;
1053
1201
  }).join(", ");
1054
- const updateSets = Object.keys(cleanUpdate).map(k => `target.[${k}] = source.[${k}]`).join(", ");
1055
- const insertCols = Object.keys(cleanCreate).map(k => `[${k}]`).join(", ");
1056
- const insertVals = Object.keys(cleanCreate).map(k => `source.[${k}]`).join(", ");
1057
- // Note: This MERGE assumes the 'where' translates to a simple ON clause.
1058
- // For complex 'where', parseWhere might return something not easily usable in ON.
1059
- // We'll try to extract simple equality for ON if possible, or fallback to sequential.
1060
- const onClause = Object.keys(where).map(k => {
1061
- if (typeof where[k] === 'object' && where[k] !== null && !(where[k] instanceof Date)) {
1062
- // Flatten unique object if needed
1063
- const inner = where[k];
1064
- return Object.keys(inner).map(ik => `target.[${ik}] = @upw_${k}_${ik}`).join(" AND ");
1065
- }
1066
- return `target.[${k}] = @upw_${k}`;
1067
- }).join(" AND ");
1202
+ const updateSets = Object.keys(cleanUpdate).map(k => `target.${(0, sql_utils_1.quoteIdentifier)(k)} = source.${(0, sql_utils_1.quoteIdentifier)(k)}`).join(", ");
1203
+ const insertCols = Object.keys(cleanCreate).map(k => (0, sql_utils_1.quoteIdentifier)(k)).join(", ");
1204
+ const insertVals = Object.keys(cleanCreate).map(k => `source.${(0, sql_utils_1.quoteIdentifier)(k)}`).join(", ");
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 ");
1068
1208
  const sqlText = `
1069
1209
  MERGE INTO ${this.tableName} WITH (HOLDLOCK) AS target
1070
1210
  USING (SELECT ${sourceSelect}) AS source
@@ -1079,29 +1219,24 @@ class TableClient {
1079
1219
  const rows = await this.executor(sqlText, p);
1080
1220
  const result = rows[0];
1081
1221
  if (include && result) {
1082
- await resolveIncludes(this.modelName, [result], include, this.executor);
1222
+ await resolveIncludes(this.modelName, [result], include, this.executor, this.orm.metadata);
1083
1223
  }
1084
1224
  return result;
1085
1225
  }
1086
1226
  catch (err) {
1087
1227
  logger_1.logger.warn(`Atomic upsert failed, falling back to sequential: ${err.message}`);
1088
- const existing = await this.findUnique({ where: finalArgs.where });
1089
- if (existing) {
1090
- return this.update({ where: finalArgs.where, data: finalArgs.update, include: finalArgs.include });
1091
- }
1092
- else {
1093
- return this.create({ data: finalArgs.create, include: finalArgs.include });
1094
- }
1228
+ return this.sequentialUpsert(finalArgs);
1095
1229
  }
1096
1230
  });
1097
1231
  }
1098
1232
  }
1099
- function addNoLockToQuery(sql) {
1233
+ exports.TableClient = TableClient;
1234
+ function addNoLockToQuery(sql, metadata) {
1100
1235
  // If it's not a SELECT query, don't modify it
1101
1236
  if (!/^\s*SELECT/i.test(sql)) {
1102
1237
  return sql;
1103
1238
  }
1104
- const tableNames = Object.values(an5Metadata_1.modelToTable);
1239
+ const tableNames = Object.values(metadata.modelToTable);
1105
1240
  let modifiedSql = sql;
1106
1241
  for (const table of tableNames) {
1107
1242
  const escapedTable = table.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
@@ -1130,11 +1265,128 @@ function addNoLockToQuery(sql) {
1130
1265
  }
1131
1266
  return modifiedSql;
1132
1267
  }
1268
+ // Lazy-load generated schema metadata from the ORM's own generated copy so
1269
+ // `new An5ORM()` resolves schema models out of the box without the core ever
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
+ }
1351
+ let autoMetadata = null;
1352
+ function loadAutoMetadata() {
1353
+ if (autoMetadata)
1354
+ return autoMetadata;
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 { }
1373
+ }
1374
+ autoMetadata = metadata_1.DEFAULT_METADATA;
1375
+ return autoMetadata;
1376
+ }
1133
1377
  // Proxied AN5 ORM client class
1134
1378
  class An5ORM {
1135
- constructor(customExecutor) {
1136
- this.customExecutor = customExecutor;
1379
+ constructor(customExecutor, metadata, inTransaction = false, transactionControl) {
1380
+ this.inTransaction = inTransaction;
1381
+ this.transactionControl = transactionControl;
1137
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
+ }
1389
+ this.metadata = metadata ?? loadAutoMetadata();
1138
1390
  // Add default logging middleware
1139
1391
  this.$use(async (params, next) => {
1140
1392
  const start = Date.now();
@@ -1153,46 +1405,77 @@ class An5ORM {
1153
1405
  }
1154
1406
  });
1155
1407
  return new Proxy(this, {
1156
- get(target, prop) {
1157
- if (prop === "$use") {
1158
- return target.$use.bind(target);
1159
- }
1160
- if (prop === "$transaction") {
1161
- return target.$transaction.bind(target);
1162
- }
1163
- if (prop === "$connect") {
1164
- return target.$connect.bind(target);
1165
- }
1166
- if (prop === "$disconnect") {
1167
- return target.$disconnect.bind(target);
1168
- }
1169
- if (prop === "$queryRaw") {
1170
- return target.$queryRaw.bind(target);
1171
- }
1172
- if (prop === "$queryRawUnsafe") {
1173
- return target.$queryRawUnsafe.bind(target);
1174
- }
1175
- if (prop === "$executeRaw") {
1176
- return target.$executeRaw.bind(target);
1177
- }
1178
- if (prop === "$executeRawUnsafe") {
1179
- 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);
1180
1411
  }
1181
1412
  if (!(prop in target) && typeof prop === "string" && !prop.startsWith("_")) {
1182
- // Resolve modelName in camelCase and map to table name
1183
- const tableName = an5Metadata_1.modelToTable[prop];
1184
- if (tableName) {
1185
- target[prop] = new TableClient(prop, tableName, target.customExecutor || execQuery, target // Pass ORM instance for middleware access
1186
- );
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
+ }
1187
1431
  }
1432
+ if (!tableName) {
1433
+ tableName = prop;
1434
+ }
1435
+ target[prop] = new TableClient(modelName, tableName, target.customExecutor || execQuery, receiver);
1188
1436
  }
1189
1437
  return target[prop];
1190
1438
  }
1191
1439
  });
1192
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
+ }
1193
1470
  $use(middleware) {
1194
1471
  this.middlewares.push(middleware);
1195
1472
  }
1473
+ parseWhere(modelName, where, params, prefix = "") {
1474
+ return (0, sql_utils_1.parseWhere)(modelName, where, params, prefix, {
1475
+ relationMap: this.metadata.relationMap,
1476
+ modelToTable: this.metadata.modelToTable,
1477
+ });
1478
+ }
1196
1479
  async _executeMiddleware(params, finalAction) {
1197
1480
  let index = -1;
1198
1481
  const dispatch = async (i, currentParams) => {
@@ -1243,7 +1526,7 @@ class An5ORM {
1243
1526
  else {
1244
1527
  throw new Error("Invalid query format for $queryRaw");
1245
1528
  }
1246
- queryText = addNoLockToQuery(queryText);
1529
+ queryText = addNoLockToQuery(queryText, this.metadata);
1247
1530
  return executor(queryText, params);
1248
1531
  }
1249
1532
  async $queryRawUnsafe(queryText, ...values) {
@@ -1255,7 +1538,7 @@ class An5ORM {
1255
1538
  params[paramName] = val;
1256
1539
  });
1257
1540
  }
1258
- const modifiedQueryText = addNoLockToQuery(queryText);
1541
+ const modifiedQueryText = addNoLockToQuery(queryText, this.metadata);
1259
1542
  const result = await executor(modifiedQueryText, params);
1260
1543
  return result;
1261
1544
  }
@@ -1286,9 +1569,11 @@ class An5ORM {
1286
1569
  else {
1287
1570
  throw new Error("Invalid query format for $executeRaw");
1288
1571
  }
1289
- queryText = addNoLockToQuery(queryText);
1290
- const result = (await executor(queryText, params));
1291
- return result.rowsAffected?.[0] || 0;
1572
+ queryText = addNoLockToQuery(queryText, this.metadata);
1573
+ if (executor.executeRaw) {
1574
+ return executor.executeRaw(queryText, params);
1575
+ }
1576
+ return normalizeAffectedCount(await executor(queryText, params));
1292
1577
  }
1293
1578
  async $executeRawUnsafe(queryText, ...values) {
1294
1579
  const executor = this.customExecutor || execQuery;
@@ -1299,50 +1584,61 @@ class An5ORM {
1299
1584
  params[paramName] = val;
1300
1585
  });
1301
1586
  }
1302
- const modifiedQueryText = addNoLockToQuery(queryText);
1303
- const result = (await executor(modifiedQueryText, params));
1304
- return result.rowsAffected?.[0] || 0;
1587
+ const modifiedQueryText = addNoLockToQuery(queryText, this.metadata);
1588
+ if (executor.executeRaw) {
1589
+ return executor.executeRaw(modifiedQueryText, params);
1590
+ }
1591
+ return normalizeAffectedCount(await executor(modifiedQueryText, params));
1305
1592
  }
1306
1593
  async $transaction(fn, options) {
1307
1594
  if (Array.isArray(fn)) {
1308
1595
  return Promise.all(fn);
1309
1596
  }
1310
- const pool = await getDbPool();
1311
- const transaction = new mssql_1.default.Transaction(pool);
1312
- await transaction.begin();
1313
- logger_1.logger.info("Database Transaction Initiated");
1314
- const txExecutor = async (queryText, params) => {
1315
- const request = new mssql_1.default.Request(transaction);
1316
- if (params) {
1317
- for (const [key, value] of Object.entries(params)) {
1318
- request.input(key, value);
1319
- }
1320
- }
1321
- const result = await request.query(queryText);
1322
- const recordset = (result.recordset || []);
1323
- recordset.rowsAffected = result.rowsAffected;
1324
- return recordset;
1325
- };
1326
- const txClient = new An5ORM(txExecutor);
1327
- try {
1328
- const result = await fn(txClient);
1329
- await transaction.commit();
1330
- logger_1.logger.info("Database Transaction Committed Successfully");
1331
- return result;
1332
- }
1333
- catch (err) {
1334
- try {
1335
- await transaction.rollback();
1336
- logger_1.logger.warn("Database Transaction Rolled Back");
1337
- }
1338
- catch (rollbackErr) {
1339
- logger_1.logger.error("Failed to rollback database transaction", rollbackErr);
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);
1340
1602
  }
1341
- throw err;
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);
1607
+ return fn(txClient);
1608
+ }, options);
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");
1342
1635
  }
1636
+ const control = this.transactionControl;
1637
+ this.transactionControl = undefined;
1638
+ await control.rollback();
1343
1639
  }
1344
1640
  }
1345
1641
  exports.An5ORM = An5ORM;
1346
1642
  exports.an5Orm = new An5ORM();
1347
1643
  exports.default = exports.an5Orm;
1348
- //# sourceMappingURL=an5Orm.js.map
1644
+ //# sourceMappingURL=an5Orm.js.map