@an5/orm 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/an5Orm.js ADDED
@@ -0,0 +1,1348 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ 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");
9
+ 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");
13
+ let adapter = null;
14
+ async function getAdapter() {
15
+ if (!adapter) {
16
+ adapter = new an5_adapters_1.An5Adapter({ connectionString: process.env.DATABASE_URL });
17
+ await adapter.$connect();
18
+ }
19
+ return adapter;
20
+ }
21
+ async function execQuery(queryText, params) {
22
+ const a = await getAdapter();
23
+ return a.exec(queryText, params);
24
+ }
25
+ function parseWhere(modelName, where, params, prefix = "") {
26
+ if (!where)
27
+ return "";
28
+ const conditions = [];
29
+ const cleanWhere = {};
30
+ 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);
43
+ }
44
+ else {
45
+ cleanWhere[key] = value;
46
+ }
47
+ }
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 ")})`);
54
+ }
55
+ }
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
+ }
62
+ }
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
+ }
85
+ }
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
+ }
105
+ }
106
+ }
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
+ }
174
+ }
175
+ else {
176
+ if (value === null) {
177
+ conditions.push(`${key} IS NULL`);
178
+ }
179
+ else {
180
+ conditions.push(`${key} = @${paramName}`);
181
+ params[paramName] = value;
182
+ }
183
+ }
184
+ }
185
+ }
186
+ }
187
+ return conditions.join(" AND ");
188
+ }
189
+ function buildOrderBy(orderBy) {
190
+ if (!orderBy)
191
+ 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
+ }
200
+ }
201
+ }
202
+ return orderClauses.length > 0 ? ` ORDER BY ` + orderClauses.join(", ") : "";
203
+ }
204
+ function projectFields(row, select) {
205
+ if (!row || !select)
206
+ return row;
207
+ const projected = {};
208
+ for (const [key, val] of Object.entries(select)) {
209
+ if (val) {
210
+ projected[key] = row[key];
211
+ }
212
+ }
213
+ if (row._count) {
214
+ projected._count = row._count;
215
+ }
216
+ return projected;
217
+ }
218
+ // Helper to batch-query and resolve relationships
219
+ async function resolveIncludes(modelName, rows, include, executor) {
220
+ if (!rows || rows.length === 0 || !include)
221
+ return;
222
+ const modelRelations = an5Metadata_1.relationMap[modelName];
223
+ if (!modelRelations)
224
+ return;
225
+ for (const [key, value] of Object.entries(include)) {
226
+ if (!value)
227
+ continue;
228
+ const relation = modelRelations[key];
229
+ if (!relation) {
230
+ if (key === "_count" && value && typeof value === "object") {
231
+ const countFields = Object.keys(value.select || {});
232
+ for (const countField of countFields) {
233
+ const rel = modelRelations[countField];
234
+ if (rel) {
235
+ const relTable = an5Metadata_1.modelToTable[rel.modelName];
236
+ const localKeys = rows.map(r => r[rel.localKey]).filter(Boolean);
237
+ if (localKeys.length === 0) {
238
+ rows.forEach(r => { r._count = { ...r._count, [countField]: 0 }; });
239
+ continue;
240
+ }
241
+ const sqlText = `
242
+ SELECT ${rel.foreignKey} as parentId, COUNT(*) as count
243
+ FROM ${relTable} WITH (NOLOCK)
244
+ WHERE ${rel.foreignKey} IN (${localKeys.map((_, i) => `@lk_${i}`).join(", ")})
245
+ GROUP BY ${rel.foreignKey}
246
+ `;
247
+ const countParams = {};
248
+ localKeys.forEach((lk, i) => { countParams[`lk_${i}`] = lk; });
249
+ const counts = await executor(sqlText, countParams);
250
+ const countMap = new Map(counts.map((c) => [c.parentId, c.count]));
251
+ rows.forEach(r => {
252
+ if (!r._count)
253
+ r._count = {};
254
+ r._count[countField] = countMap.get(r[rel.localKey]) || 0;
255
+ });
256
+ }
257
+ }
258
+ }
259
+ continue;
260
+ }
261
+ const relTable = an5Metadata_1.modelToTable[relation.modelName];
262
+ const isMany = relation.relationType === "many";
263
+ const matchKey = relation.relationType === "one" ? relation.foreignKey : relation.localKey;
264
+ const searchKey = relation.relationType === "one" ? relation.localKey : relation.foreignKey;
265
+ const keys = rows.map(r => r[matchKey]).filter(Boolean);
266
+ if (keys.length === 0) {
267
+ rows.forEach(r => { r[key] = isMany ? [] : null; });
268
+ continue;
269
+ }
270
+ const uniqueKeys = Array.from(new Set(keys));
271
+ let relCols = "*";
272
+ if (value && typeof value === "object" && value.select) {
273
+ const subSelect = value.select;
274
+ const subRelations = an5Metadata_1.relationMap[relation.modelName] || {};
275
+ const selectedSubCols = Object.keys(subSelect)
276
+ .filter(k => subSelect[k] && !subRelations[k])
277
+ .map(k => `[${k}]`);
278
+ if (selectedSubCols.length > 0) {
279
+ if (!selectedSubCols.includes(`[${searchKey}]`)) {
280
+ selectedSubCols.push(`[${searchKey}]`);
281
+ }
282
+ relCols = selectedSubCols.join(", ");
283
+ }
284
+ }
285
+ let sqlText = `SELECT ${relCols} FROM ${relTable} WITH (NOLOCK) WHERE ${searchKey} IN (${uniqueKeys.map((_, i) => `@k_${i}`).join(", ")})`;
286
+ const subParams = {};
287
+ uniqueKeys.forEach((k, i) => { subParams[`k_${i}`] = k; });
288
+ if (value && typeof value === "object") {
289
+ const subArgs = value;
290
+ if (subArgs.orderBy) {
291
+ sqlText += buildOrderBy(subArgs.orderBy);
292
+ }
293
+ }
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
+ });
299
+ }
300
+ if (value && typeof value === "object" && value.include) {
301
+ await resolveIncludes(relation.modelName, relatedRows, value.include, executor);
302
+ }
303
+ const groupMap = new Map();
304
+ relatedRows.forEach((r) => {
305
+ const k = r[searchKey];
306
+ if (!groupMap.has(k))
307
+ groupMap.set(k, []);
308
+ groupMap.get(k).push(r);
309
+ });
310
+ rows.forEach(r => {
311
+ const k = r[matchKey];
312
+ const matches = groupMap.get(k) || [];
313
+ if (isMany) {
314
+ r[key] = matches;
315
+ }
316
+ else {
317
+ r[key] = matches[0] || null;
318
+ }
319
+ });
320
+ }
321
+ }
322
+ // Table query executor client class
323
+ class TableClient {
324
+ constructor(modelName, tableName, executor, orm) {
325
+ this.modelName = modelName;
326
+ this.tableName = tableName;
327
+ this.executor = executor;
328
+ this.orm = orm;
329
+ }
330
+ async findMany(args) {
331
+ return this.orm._executeMiddleware({ model: this.modelName, action: 'findMany', args }, async (params) => {
332
+ const { args: finalArgs } = params;
333
+ const hasSkip = finalArgs?.skip !== undefined && finalArgs?.skip !== null;
334
+ let cols = "*";
335
+ 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}]`);
340
+ if (selectedCols.length > 0) {
341
+ cols = selectedCols.join(", ");
342
+ }
343
+ }
344
+ let sqlText = "SELECT";
345
+ if (finalArgs?.take && !hasSkip) {
346
+ sqlText += ` TOP (${finalArgs.take})`;
347
+ }
348
+ sqlText += ` ${cols} FROM ${this.tableName} WITH (NOLOCK)`;
349
+ const p = {};
350
+ const whereSql = parseWhere(this.modelName, finalArgs?.where, p);
351
+ if (whereSql) {
352
+ sqlText += ` WHERE ${whereSql}`;
353
+ }
354
+ if (finalArgs?.orderBy) {
355
+ sqlText += buildOrderBy(finalArgs.orderBy);
356
+ }
357
+ else if (hasSkip) {
358
+ // OFFSET requires an ORDER BY clause in SQL Server
359
+ sqlText += " ORDER BY (SELECT NULL)";
360
+ }
361
+ if (hasSkip) {
362
+ sqlText += ` OFFSET ${finalArgs.skip} ROWS`;
363
+ if (finalArgs?.take) {
364
+ sqlText += ` FETCH NEXT ${finalArgs.take} ROWS ONLY`;
365
+ }
366
+ }
367
+ const rows = await this.executor(sqlText, p);
368
+ if (finalArgs?.select) {
369
+ rows.forEach((r, idx) => {
370
+ rows[idx] = projectFields(r, finalArgs.select);
371
+ });
372
+ }
373
+ if (finalArgs?.include) {
374
+ await resolveIncludes(this.modelName, rows, finalArgs.include, this.executor);
375
+ }
376
+ return rows;
377
+ });
378
+ }
379
+ async findFirst(args) {
380
+ return this.orm._executeMiddleware({ model: this.modelName, action: 'findFirst', args }, async (params) => {
381
+ const rows = await this.findMany({ ...params.args, take: 1 });
382
+ return rows[0] || null;
383
+ });
384
+ }
385
+ async findUnique(args) {
386
+ return this.orm._executeMiddleware({ model: this.modelName, action: 'findUnique', args }, async (params) => {
387
+ return this.findFirst(params.args);
388
+ });
389
+ }
390
+ async count(args) {
391
+ return this.orm._executeMiddleware({ model: this.modelName, action: 'count', args }, async (params) => {
392
+ const { args: finalArgs } = params;
393
+ let sqlText = `SELECT COUNT(*) as count FROM ${this.tableName} WITH (NOLOCK)`;
394
+ const p = {};
395
+ const whereSql = parseWhere(this.modelName, finalArgs?.where, p);
396
+ if (whereSql) {
397
+ sqlText += ` WHERE ${whereSql}`;
398
+ }
399
+ const result = await this.executor(sqlText, p);
400
+ return result[0]?.count || 0;
401
+ });
402
+ }
403
+ async handleNestedWrites(data, parentId) {
404
+ const modelRelations = an5Metadata_1.relationMap[this.modelName] || {};
405
+ for (const [key, value] of Object.entries(data)) {
406
+ const relation = modelRelations[key];
407
+ if (!relation || !value || typeof value !== "object" || (value instanceof Date))
408
+ continue;
409
+ const relTableClient = this.orm[relation.modelName];
410
+ if (!relTableClient)
411
+ continue;
412
+ const nestedOps = value;
413
+ // Handle deleteMany (an5Orm-style)
414
+ if (nestedOps.deleteMany) {
415
+ const deleteWhere = Array.isArray(nestedOps.deleteMany) ? { OR: nestedOps.deleteMany } : nestedOps.deleteMany;
416
+ // Scope deletion to parent
417
+ const scopedWhere = { ...deleteWhere, [relation.foreignKey]: parentId };
418
+ await relTableClient.deleteMany({ where: scopedWhere });
419
+ }
420
+ // Handle create
421
+ if (nestedOps.create) {
422
+ const createItems = Array.isArray(nestedOps.create) ? nestedOps.create : [nestedOps.create];
423
+ for (const item of createItems) {
424
+ // Inject parent ID
425
+ const itemData = { ...item, [relation.foreignKey]: parentId };
426
+ await relTableClient.create({ data: itemData });
427
+ }
428
+ }
429
+ // 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
+ }
439
+ }
440
+ }
441
+ }
442
+ }
443
+ async create(args) {
444
+ return this.orm._executeMiddleware({ model: this.modelName, action: 'create', args }, async (params) => {
445
+ const { args: finalArgs } = params;
446
+ try {
447
+ const data = { ...finalArgs.data };
448
+ // Extract nested writes
449
+ const nestedData = {};
450
+ const modelRelations = an5Metadata_1.relationMap[this.modelName] || {};
451
+ for (const key of Object.keys(data)) {
452
+ if (modelRelations[key]) {
453
+ nestedData[key] = data[key];
454
+ delete data[key];
455
+ }
456
+ }
457
+ if (!data.id && an5Metadata_1.modelFields[this.modelName]?.id?.ts === "string") {
458
+ data.id = (0, crypto_1.randomUUID)();
459
+ }
460
+ const now = new Date();
461
+ if (!data.createdAt && an5Metadata_1.modelFields[this.modelName]?.createdAt)
462
+ data.createdAt = now;
463
+ if (!data.updatedAt && an5Metadata_1.modelFields[this.modelName]?.updatedAt)
464
+ data.updatedAt = now;
465
+ // Handle one-relation connect where we hold the FK
466
+ for (const [key, value] of Object.entries(nestedData)) {
467
+ 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];
471
+ data[rel.foreignKey] = targetId;
472
+ }
473
+ }
474
+ const keys = Object.keys(data);
475
+ const columns = keys.map(k => `[${k}]`).join(", ");
476
+ const placeholders = keys.map(k => `@${k}`).join(", ");
477
+ const sqlText = `INSERT INTO ${this.tableName} (${columns}) OUTPUT inserted.* VALUES (${placeholders})`;
478
+ const rows = await this.executor(sqlText, data);
479
+ const createdRow = rows[0];
480
+ if (!createdRow) {
481
+ throw new Error("Failed to insert record and retrieve output");
482
+ }
483
+ const pkField = ("id" in createdRow) ? "id" : Object.keys(createdRow).find(k => k.endsWith("_id"));
484
+ const insertedId = pkField ? createdRow[pkField] : undefined;
485
+ // Process other nested writes
486
+ await this.handleNestedWrites(nestedData, insertedId);
487
+ const created = await this.findUnique({
488
+ where: pkField ? { [pkField]: insertedId } : {},
489
+ include: finalArgs.include
490
+ });
491
+ if (!created) {
492
+ throw new Error(`Failed to retrieve newly created record with ID ${insertedId}`);
493
+ }
494
+ return created;
495
+ }
496
+ catch (error) {
497
+ const msg = String(error?.message || '').toLowerCase();
498
+ const errNumber = error?.number;
499
+ if (msg.includes('duplicate') ||
500
+ msg.includes('unique') ||
501
+ errNumber === 2627 ||
502
+ errNumber === 2601) {
503
+ throw new typescript_1.An5.An5ClientKnownRequestError("Unique constraint failed", {
504
+ code: "P2002",
505
+ clientVersion: "mock",
506
+ });
507
+ }
508
+ if (msg.includes('foreign key') || errNumber === 547) {
509
+ throw new typescript_1.An5.An5ClientKnownRequestError("Foreign key constraint failed", {
510
+ code: "P2003",
511
+ clientVersion: "mock",
512
+ });
513
+ }
514
+ if (msg.includes('not found') || errNumber === 404) {
515
+ throw new typescript_1.An5.An5ClientKnownRequestError("Record not found", {
516
+ code: "P2025",
517
+ clientVersion: "mock",
518
+ });
519
+ }
520
+ throw error;
521
+ }
522
+ });
523
+ }
524
+ async update(args) {
525
+ return this.orm._executeMiddleware({ model: this.modelName, action: 'update', args }, async (params) => {
526
+ const { args: finalArgs } = params;
527
+ const data = { ...finalArgs.data };
528
+ if (an5Metadata_1.modelFields[this.modelName]?.updatedAt) {
529
+ data.updatedAt = new Date();
530
+ }
531
+ // Extract nested writes
532
+ const nestedData = {};
533
+ const modelRelations = an5Metadata_1.relationMap[this.modelName] || {};
534
+ for (const key of Object.keys(data)) {
535
+ if (modelRelations[key]) {
536
+ nestedData[key] = data[key];
537
+ delete data[key];
538
+ }
539
+ }
540
+ delete data.id;
541
+ // Handle one-relation connect where we hold the FK
542
+ for (const [key, value] of Object.entries(nestedData)) {
543
+ 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;
548
+ }
549
+ }
550
+ const sets = [];
551
+ const p = {};
552
+ for (const key of Object.keys(data)) {
553
+ 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;
573
+ }
574
+ const whereParams = {};
575
+ const whereSql = parseWhere(this.modelName, finalArgs.where, whereParams, "w_");
576
+ 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
+ const existing = await this.findUnique({ where: finalArgs.where });
581
+ if (!existing)
582
+ throw new Error("Record not found to update");
583
+ const parentId = existing.id;
584
+ // Process nested writes
585
+ await this.handleNestedWrites(nestedData, parentId);
586
+ const updated = await this.findUnique({ where: finalArgs.where, include: finalArgs.include });
587
+ if (!updated) {
588
+ throw new Error(`Failed to retrieve updated record`);
589
+ }
590
+ return updated;
591
+ });
592
+ }
593
+ async updateMany(args) {
594
+ return this.orm._executeMiddleware({ model: this.modelName, action: 'updateMany', args }, async (params) => {
595
+ const { args: finalArgs } = params;
596
+ const data = { ...finalArgs.data };
597
+ if (an5Metadata_1.modelFields[this.modelName]?.updatedAt) {
598
+ data.updatedAt = new Date();
599
+ }
600
+ delete data.id;
601
+ const sets = [];
602
+ const p = {};
603
+ for (const key of Object.keys(data)) {
604
+ const val = data[key];
605
+ if (an5Metadata_1.relationMap[this.modelName]?.[key])
606
+ 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;
626
+ }
627
+ const whereParams = {};
628
+ const whereSql = parseWhere(this.modelName, finalArgs.where, whereParams, "w_");
629
+ Object.assign(p, whereParams);
630
+ 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 };
638
+ });
639
+ }
640
+ async delete(args) {
641
+ return this.orm._executeMiddleware({ model: this.modelName, action: 'delete', args }, async (params) => {
642
+ const { args: finalArgs } = params;
643
+ const existing = await this.findUnique({ where: finalArgs.where });
644
+ if (!existing)
645
+ throw new Error("Record not found to delete");
646
+ const p = {};
647
+ const whereSql = parseWhere(this.modelName, finalArgs.where, p);
648
+ const sqlText = `DELETE FROM ${this.tableName} WHERE ${whereSql}`;
649
+ await this.executor(sqlText, p);
650
+ return existing;
651
+ });
652
+ }
653
+ async deleteMany(args) {
654
+ return this.orm._executeMiddleware({ model: this.modelName, action: 'deleteMany', args }, async (params) => {
655
+ const { args: finalArgs } = params;
656
+ const p = {};
657
+ const whereSql = parseWhere(this.modelName, finalArgs?.where, p);
658
+ 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 };
666
+ });
667
+ }
668
+ async vectorSearch(args) {
669
+ return this.orm._executeMiddleware({ model: this.modelName, action: 'vectorSearch', args }, async (params) => {
670
+ const { args: finalArgs } = params;
671
+ 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;
676
+ const vectorJson = JSON.stringify(finalArgs.vector);
677
+ try {
678
+ if (dim > 1998) {
679
+ throw new Error("Vector dimension exceeds SQL Server limit of 1998");
680
+ }
681
+ 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 `;
683
+ sqlText += `FROM ${this.tableName} WITH (NOLOCK) `;
684
+ const p = {
685
+ query_vector: vectorJson
686
+ };
687
+ const whereClauses = [];
688
+ whereClauses.push(`[${field}] IS NOT NULL`);
689
+ const whereSql = parseWhere(this.modelName, finalArgs.where, p, "v_");
690
+ if (whereSql) {
691
+ whereClauses.push(whereSql);
692
+ }
693
+ sqlText += `WHERE ${whereClauses.join(" AND ")} `;
694
+ sqlText += `ORDER BY distance ASC`;
695
+ const rows = await this.executor(sqlText, p);
696
+ if (finalArgs.include) {
697
+ await resolveIncludes(this.modelName, rows, finalArgs.include, this.executor);
698
+ }
699
+ return rows;
700
+ }
701
+ catch (err) {
702
+ const msg = String(err.message || "").toLowerCase();
703
+ // Handle specific float16 to float32 conversion error by retrying with float16
704
+ if (msg.includes("float16") && msg.includes("float32") && msg.includes("conversion") && !finalArgs.vectorElementType) {
705
+ logger_1.logger.info(`Detected float16 vector storage. Retrying vectorSearch with float16 element type.`);
706
+ return this.vectorSearch({ ...finalArgs, vectorElementType: 'float16' });
707
+ }
708
+ const isUnsupported = msg.includes("vector_distance") ||
709
+ msg.includes("type vector") ||
710
+ msg.includes("limit of 1998") ||
711
+ err?.number === 195;
712
+ if (!isUnsupported) {
713
+ throw err;
714
+ }
715
+ logger_1.logger.warn(`Native VECTOR_DISTANCE not supported by SQL Server instance. Falling back to in-memory similarity search.`);
716
+ let fallbackSql = `SELECT * FROM ${this.tableName} WITH (NOLOCK) `;
717
+ const fallbackParams = {};
718
+ const fallbackWhereClauses = [];
719
+ fallbackWhereClauses.push(`[${field}] IS NOT NULL`);
720
+ const fallbackWhereSql = parseWhere(this.modelName, finalArgs.where, fallbackParams, "vf_");
721
+ if (fallbackWhereSql) {
722
+ fallbackWhereClauses.push(fallbackWhereSql);
723
+ }
724
+ fallbackSql += `WHERE ${fallbackWhereClauses.join(" AND ")}`;
725
+ const rows = await this.executor(fallbackSql, fallbackParams);
726
+ const scored = rows.map((row) => {
727
+ let distance = 1.0;
728
+ try {
729
+ const rowVector = typeof row[field] === "string"
730
+ ? JSON.parse(row[field])
731
+ : row[field];
732
+ if (Array.isArray(rowVector)) {
733
+ let dotProduct = 0;
734
+ let normA = 0;
735
+ let normB = 0;
736
+ for (let i = 0; i < finalArgs.vector.length; i++) {
737
+ const valA = finalArgs.vector[i] || 0;
738
+ const valB = rowVector[i] || 0;
739
+ dotProduct += valA * valB;
740
+ normA += valA * valA;
741
+ normB += valB * valB;
742
+ }
743
+ const similarity = (normA === 0 || normB === 0) ? 0 : dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
744
+ distance = 1.0 - similarity;
745
+ }
746
+ }
747
+ catch { /* skip */ }
748
+ return {
749
+ ...row,
750
+ distance
751
+ };
752
+ });
753
+ const results = scored
754
+ .sort((a, b) => a.distance - b.distance)
755
+ .slice(0, take);
756
+ if (finalArgs.include && results.length > 0) {
757
+ await resolveIncludes(this.modelName, results, finalArgs.include, this.executor);
758
+ }
759
+ return results;
760
+ }
761
+ });
762
+ }
763
+ async createMany(args) {
764
+ return this.orm._executeMiddleware({ model: this.modelName, action: 'createMany', args }, async (params) => {
765
+ const { args: finalArgs } = params;
766
+ if (!finalArgs.data || finalArgs.data.length === 0)
767
+ return { count: 0 };
768
+ 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] || {};
772
+ 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
+ const now = new Date();
804
+ for (const item of finalArgs.data) {
805
+ const rowData = { ...item };
806
+ if (!rowData.id && fields.id?.ts === "string")
807
+ rowData.id = (0, crypto_1.randomUUID)();
808
+ if (!rowData.createdAt && fields.createdAt)
809
+ rowData.createdAt = now;
810
+ if (!rowData.updatedAt && fields.updatedAt)
811
+ rowData.updatedAt = now;
812
+ const rowValues = fieldNames.map(col => {
813
+ const v = rowData[col];
814
+ if (v === undefined)
815
+ return null;
816
+ return v;
817
+ });
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 };
823
+ }
824
+ catch (err) {
825
+ logger_1.logger.warn(`Bulk insert failed, falling back to sequential inserts: ${err.message}`);
826
+ let count = 0;
827
+ for (const item of finalArgs.data) {
828
+ try {
829
+ await this.create({ data: item });
830
+ count++;
831
+ }
832
+ catch (innerErr) {
833
+ if (finalArgs.skipDuplicates)
834
+ continue;
835
+ throw innerErr;
836
+ }
837
+ }
838
+ return { count };
839
+ }
840
+ });
841
+ }
842
+ async aggregate(args) {
843
+ return this.orm._executeMiddleware({ model: this.modelName, action: 'aggregate', args }, async (params) => {
844
+ const { args: finalArgs } = params;
845
+ const selects = [];
846
+ const resultObj = {};
847
+ if (finalArgs._sum) {
848
+ resultObj._sum = {};
849
+ for (const key of Object.keys(finalArgs._sum)) {
850
+ selects.push(`SUM(${key}) as sum_${key}`);
851
+ }
852
+ }
853
+ if (finalArgs._avg) {
854
+ resultObj._avg = {};
855
+ for (const key of Object.keys(finalArgs._avg)) {
856
+ selects.push(`AVG(${key}) as avg_${key}`);
857
+ }
858
+ }
859
+ if (finalArgs._min) {
860
+ resultObj._min = {};
861
+ for (const key of Object.keys(finalArgs._min)) {
862
+ selects.push(`MIN(${key}) as min_${key}`);
863
+ }
864
+ }
865
+ if (finalArgs._max) {
866
+ resultObj._max = {};
867
+ for (const key of Object.keys(finalArgs._max)) {
868
+ selects.push(`MAX(${key}) as max_${key}`);
869
+ }
870
+ }
871
+ if (finalArgs._count) {
872
+ resultObj._count = {};
873
+ if (finalArgs._count === true || finalArgs._count._all) {
874
+ selects.push(`COUNT(*) as count_all`);
875
+ }
876
+ else {
877
+ for (const key of Object.keys(finalArgs._count)) {
878
+ selects.push(`COUNT(${key}) as count_${key}`);
879
+ }
880
+ }
881
+ }
882
+ if (selects.length === 0) {
883
+ throw new Error("Aggregate requires at least one aggregator field");
884
+ }
885
+ let sqlText = `SELECT ${selects.join(", ")} FROM ${this.tableName} WITH (NOLOCK)`;
886
+ const p = {};
887
+ const whereSql = parseWhere(this.modelName, finalArgs?.where, p);
888
+ if (whereSql) {
889
+ sqlText += ` WHERE ${whereSql}`;
890
+ }
891
+ const rows = await this.executor(sqlText, p);
892
+ const row = rows[0] || {};
893
+ if (finalArgs._sum) {
894
+ for (const key of Object.keys(finalArgs._sum)) {
895
+ resultObj._sum[key] = row[`sum_${key}`] !== undefined ? row[`sum_${key}`] : null;
896
+ }
897
+ }
898
+ if (finalArgs._avg) {
899
+ for (const key of Object.keys(finalArgs._avg)) {
900
+ resultObj._avg[key] = row[`avg_${key}`] !== undefined ? row[`avg_${key}`] : null;
901
+ }
902
+ }
903
+ if (finalArgs._min) {
904
+ for (const key of Object.keys(finalArgs._min)) {
905
+ resultObj._min[key] = row[`min_${key}`] !== undefined ? row[`min_${key}`] : null;
906
+ }
907
+ }
908
+ if (finalArgs._max) {
909
+ for (const key of Object.keys(finalArgs._max)) {
910
+ resultObj._max[key] = row[`max_${key}`] !== undefined ? row[`max_${key}`] : null;
911
+ }
912
+ }
913
+ if (finalArgs._count) {
914
+ if (finalArgs._count === true || finalArgs._count._all) {
915
+ resultObj._count._all = row[`count_all`] || 0;
916
+ }
917
+ else {
918
+ for (const key of Object.keys(finalArgs._count)) {
919
+ resultObj._count[key] = row[`count_${key}`] || 0;
920
+ }
921
+ }
922
+ }
923
+ return resultObj;
924
+ });
925
+ }
926
+ async groupBy(args) {
927
+ return this.orm._executeMiddleware({ model: this.modelName, action: 'groupBy', args }, async (params) => {
928
+ const { args: finalArgs } = params;
929
+ const byFields = finalArgs.by || [];
930
+ if (byFields.length === 0) {
931
+ throw new Error("groupBy requires 'by' fields");
932
+ }
933
+ const selects = [...byFields];
934
+ if (finalArgs._count) {
935
+ if (finalArgs._count === true || finalArgs._count._all) {
936
+ selects.push(`COUNT(*) as count_all`);
937
+ }
938
+ else {
939
+ for (const key of Object.keys(finalArgs._count)) {
940
+ selects.push(`COUNT(${key}) as count_${key}`);
941
+ }
942
+ }
943
+ }
944
+ if (finalArgs._sum) {
945
+ for (const key of Object.keys(finalArgs._sum)) {
946
+ selects.push(`SUM(${key}) as sum_${key}`);
947
+ }
948
+ }
949
+ if (finalArgs._avg) {
950
+ for (const key of Object.keys(finalArgs._avg)) {
951
+ selects.push(`AVG(${key}) as avg_${key}`);
952
+ }
953
+ }
954
+ if (finalArgs._min) {
955
+ for (const key of Object.keys(finalArgs._min)) {
956
+ selects.push(`MIN(${key}) as min_${key}`);
957
+ }
958
+ }
959
+ if (finalArgs._max) {
960
+ for (const key of Object.keys(finalArgs._max)) {
961
+ selects.push(`MAX(${key}) as max_${key}`);
962
+ }
963
+ }
964
+ let sqlText = `SELECT ${selects.join(", ")} FROM ${this.tableName} WITH (NOLOCK)`;
965
+ const p = {};
966
+ const whereSql = parseWhere(this.modelName, finalArgs?.where, p);
967
+ if (whereSql) {
968
+ sqlText += ` WHERE ${whereSql}`;
969
+ }
970
+ sqlText += ` GROUP BY ${byFields.join(", ")}`;
971
+ const rows = await this.executor(sqlText, p);
972
+ return rows.map((row) => {
973
+ const item = {};
974
+ byFields.forEach((field) => {
975
+ item[field] = row[field];
976
+ });
977
+ if (finalArgs._count) {
978
+ item._count = {};
979
+ if (finalArgs._count === true || finalArgs._count._all) {
980
+ item._count._all = row[`count_all`] || 0;
981
+ }
982
+ else {
983
+ for (const key of Object.keys(finalArgs._count)) {
984
+ item._count[key] = row[`count_${key}`] || 0;
985
+ }
986
+ }
987
+ }
988
+ if (finalArgs._sum) {
989
+ item._sum = {};
990
+ for (const key of Object.keys(finalArgs._sum)) {
991
+ item._sum[key] = row[`sum_${key}`] !== undefined ? row[`sum_${key}`] : null;
992
+ }
993
+ }
994
+ if (finalArgs._avg) {
995
+ item._avg = {};
996
+ for (const key of Object.keys(finalArgs._avg)) {
997
+ item._avg[key] = row[`avg_${key}`] !== undefined ? row[`avg_${key}`] : null;
998
+ }
999
+ }
1000
+ if (finalArgs._min) {
1001
+ item._min = {};
1002
+ for (const key of Object.keys(finalArgs._min)) {
1003
+ item._min[key] = row[`min_${key}`] !== undefined ? row[`min_${key}`] : null;
1004
+ }
1005
+ }
1006
+ if (finalArgs._max) {
1007
+ item._max = {};
1008
+ for (const key of Object.keys(finalArgs._max)) {
1009
+ item._max[key] = row[`max_${key}`] !== undefined ? row[`max_${key}`] : null;
1010
+ }
1011
+ }
1012
+ return item;
1013
+ });
1014
+ });
1015
+ }
1016
+ async upsert(args) {
1017
+ return this.orm._executeMiddleware({ model: this.modelName, action: 'upsert', args }, async (params) => {
1018
+ const { args: finalArgs } = params;
1019
+ const { where, create: createData, update: updateData, include } = finalArgs;
1020
+ // Filter out relation fields from data
1021
+ const cleanCreate = { ...createData };
1022
+ const cleanUpdate = { ...updateData };
1023
+ for (const key of Object.keys(cleanCreate)) {
1024
+ if (an5Metadata_1.relationMap[this.modelName]?.[key])
1025
+ delete cleanCreate[key];
1026
+ }
1027
+ for (const key of Object.keys(cleanUpdate)) {
1028
+ if (an5Metadata_1.relationMap[this.modelName]?.[key])
1029
+ delete cleanUpdate[key];
1030
+ }
1031
+ if (!cleanCreate.id && an5Metadata_1.modelFields[this.modelName]?.id?.ts === "string") {
1032
+ cleanCreate.id = (0, crypto_1.randomUUID)();
1033
+ }
1034
+ const now = new Date();
1035
+ if (!cleanCreate.createdAt && an5Metadata_1.modelFields[this.modelName]?.createdAt)
1036
+ cleanCreate.createdAt = now;
1037
+ if (!cleanCreate.updatedAt && an5Metadata_1.modelFields[this.modelName]?.updatedAt)
1038
+ cleanCreate.updatedAt = now;
1039
+ if (!cleanUpdate.updatedAt && an5Metadata_1.modelFields[this.modelName]?.updatedAt)
1040
+ cleanUpdate.updatedAt = now;
1041
+ const p = {};
1042
+ const whereSql = parseWhere(this.modelName, where, p, "upw_");
1043
+ const allKeys = Array.from(new Set([...Object.keys(cleanCreate), ...Object.keys(cleanUpdate)]));
1044
+ for (const k of allKeys) {
1045
+ if (cleanCreate[k] !== undefined)
1046
+ p[`c_${k}`] = cleanCreate[k];
1047
+ if (cleanUpdate[k] !== undefined)
1048
+ p[`u_${k}`] = cleanUpdate[k];
1049
+ }
1050
+ const sourceSelect = allKeys.map(k => {
1051
+ const val = cleanCreate[k] !== undefined ? `@c_${k}` : (cleanUpdate[k] !== undefined ? `@u_${k}` : "NULL");
1052
+ return `${val} as [${k}]`;
1053
+ }).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 ");
1068
+ const sqlText = `
1069
+ MERGE INTO ${this.tableName} WITH (HOLDLOCK) AS target
1070
+ USING (SELECT ${sourceSelect}) AS source
1071
+ ON (${onClause})
1072
+ WHEN MATCHED THEN
1073
+ UPDATE SET ${updateSets}
1074
+ WHEN NOT MATCHED THEN
1075
+ INSERT (${insertCols}) VALUES (${insertVals})
1076
+ OUTPUT inserted.*;
1077
+ `;
1078
+ try {
1079
+ const rows = await this.executor(sqlText, p);
1080
+ const result = rows[0];
1081
+ if (include && result) {
1082
+ await resolveIncludes(this.modelName, [result], include, this.executor);
1083
+ }
1084
+ return result;
1085
+ }
1086
+ catch (err) {
1087
+ 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
+ }
1095
+ }
1096
+ });
1097
+ }
1098
+ }
1099
+ function addNoLockToQuery(sql) {
1100
+ // If it's not a SELECT query, don't modify it
1101
+ if (!/^\s*SELECT/i.test(sql)) {
1102
+ return sql;
1103
+ }
1104
+ const tableNames = Object.values(an5Metadata_1.modelToTable);
1105
+ let modifiedSql = sql;
1106
+ for (const table of tableNames) {
1107
+ const escapedTable = table.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
1108
+ const regex = new RegExp(`\\b(FROM|JOIN)\\s+${escapedTable}\\b`, 'gi');
1109
+ modifiedSql = modifiedSql.replace(regex, (match, prefix, offset) => {
1110
+ const afterMatch = modifiedSql.slice(offset + match.length).trim();
1111
+ // If it's already followed by WITH (NOLOCK) or NOLOCK or similar, keep it.
1112
+ if (/^(WITH\s*\(NOLOCK\)|NOLOCK)/i.test(afterMatch)) {
1113
+ return match;
1114
+ }
1115
+ // Check if there is an alias (e.g. "FROM chunks c" or "FROM chunks as c")
1116
+ const aliasMatch = afterMatch.match(/^(?:as\s+)?([a-zA-Z0-9_]+)/i);
1117
+ if (aliasMatch) {
1118
+ const alias = aliasMatch[1];
1119
+ const keywords = ["where", "on", "join", "group", "order", "limit", "left", "right", "inner", "and", "or"];
1120
+ if (!keywords.includes(alias.toLowerCase())) {
1121
+ // It's an alias! Let's check if the alias itself is followed by WITH (NOLOCK)
1122
+ const afterAlias = afterMatch.slice(aliasMatch[0].length).trim();
1123
+ if (/^(WITH\s*\(NOLOCK\)|NOLOCK)/i.test(afterAlias)) {
1124
+ return match; // already has NOLOCK after the alias
1125
+ }
1126
+ }
1127
+ }
1128
+ return `${prefix} ${table} WITH (NOLOCK)`;
1129
+ });
1130
+ }
1131
+ return modifiedSql;
1132
+ }
1133
+ // Proxied AN5 ORM client class
1134
+ class An5ORM {
1135
+ constructor(customExecutor) {
1136
+ this.customExecutor = customExecutor;
1137
+ this.middlewares = [];
1138
+ // Add default logging middleware
1139
+ this.$use(async (params, next) => {
1140
+ const start = Date.now();
1141
+ try {
1142
+ const result = await next(params);
1143
+ const duration = Date.now() - start;
1144
+ if (process.env.DEBUG_ORM === "true") {
1145
+ logger_1.logger.info(`ORM [${params.model || 'raw'}.${params.action}] executed in ${duration}ms`);
1146
+ }
1147
+ return result;
1148
+ }
1149
+ catch (err) {
1150
+ const duration = Date.now() - start;
1151
+ logger_1.logger.error(`ORM [${params.model || 'raw'}.${params.action}] failed after ${duration}ms`, err);
1152
+ throw err;
1153
+ }
1154
+ });
1155
+ 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);
1180
+ }
1181
+ 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
+ );
1187
+ }
1188
+ }
1189
+ return target[prop];
1190
+ }
1191
+ });
1192
+ }
1193
+ $use(middleware) {
1194
+ this.middlewares.push(middleware);
1195
+ }
1196
+ async _executeMiddleware(params, finalAction) {
1197
+ let index = -1;
1198
+ const dispatch = async (i, currentParams) => {
1199
+ if (i <= index)
1200
+ throw new Error("next() called multiple times");
1201
+ index = i;
1202
+ let fn = this.middlewares[i];
1203
+ if (i === this.middlewares.length) {
1204
+ return finalAction(currentParams);
1205
+ }
1206
+ if (!fn)
1207
+ return;
1208
+ return fn(currentParams, (p) => dispatch(i + 1, p));
1209
+ };
1210
+ return dispatch(0, params);
1211
+ }
1212
+ async $connect() { }
1213
+ async $disconnect() {
1214
+ if (adapter) {
1215
+ await adapter.$disconnect();
1216
+ adapter = null;
1217
+ }
1218
+ }
1219
+ async $queryRaw(queryParts, ...values) {
1220
+ const executor = this.customExecutor || execQuery;
1221
+ let queryText = "";
1222
+ const params = {};
1223
+ if (Array.isArray(queryParts) && queryParts.raw !== undefined) {
1224
+ const strings = queryParts;
1225
+ for (let i = 0; i < strings.length; i++) {
1226
+ queryText += strings[i];
1227
+ if (i < values.length) {
1228
+ const paramName = `p_${i}`;
1229
+ queryText += `@${paramName}`;
1230
+ params[paramName] = values[i];
1231
+ }
1232
+ }
1233
+ }
1234
+ else if (typeof queryParts === "string") {
1235
+ queryText = queryParts;
1236
+ if (values && values.length > 0) {
1237
+ values.forEach((val, idx) => {
1238
+ const paramName = `p_${idx}`;
1239
+ params[paramName] = val;
1240
+ });
1241
+ }
1242
+ }
1243
+ else {
1244
+ throw new Error("Invalid query format for $queryRaw");
1245
+ }
1246
+ queryText = addNoLockToQuery(queryText);
1247
+ return executor(queryText, params);
1248
+ }
1249
+ async $queryRawUnsafe(queryText, ...values) {
1250
+ const executor = this.customExecutor || execQuery;
1251
+ const params = {};
1252
+ if (values && values.length > 0) {
1253
+ values.forEach((val, idx) => {
1254
+ const paramName = `p_${idx}`;
1255
+ params[paramName] = val;
1256
+ });
1257
+ }
1258
+ const modifiedQueryText = addNoLockToQuery(queryText);
1259
+ const result = await executor(modifiedQueryText, params);
1260
+ return result;
1261
+ }
1262
+ async $executeRaw(queryParts, ...values) {
1263
+ const executor = this.customExecutor || execQuery;
1264
+ let queryText = "";
1265
+ const params = {};
1266
+ if (Array.isArray(queryParts) && queryParts.raw !== undefined) {
1267
+ const strings = queryParts;
1268
+ for (let i = 0; i < strings.length; i++) {
1269
+ queryText += strings[i];
1270
+ if (i < values.length) {
1271
+ const paramName = `p_${i}`;
1272
+ queryText += `@${paramName}`;
1273
+ params[paramName] = values[i];
1274
+ }
1275
+ }
1276
+ }
1277
+ else if (typeof queryParts === "string") {
1278
+ queryText = queryParts;
1279
+ if (values && values.length > 0) {
1280
+ values.forEach((val, idx) => {
1281
+ const paramName = `p_${idx}`;
1282
+ params[paramName] = val;
1283
+ });
1284
+ }
1285
+ }
1286
+ else {
1287
+ throw new Error("Invalid query format for $executeRaw");
1288
+ }
1289
+ queryText = addNoLockToQuery(queryText);
1290
+ const result = (await executor(queryText, params));
1291
+ return result.rowsAffected?.[0] || 0;
1292
+ }
1293
+ async $executeRawUnsafe(queryText, ...values) {
1294
+ const executor = this.customExecutor || execQuery;
1295
+ const params = {};
1296
+ if (values && values.length > 0) {
1297
+ values.forEach((val, idx) => {
1298
+ const paramName = `p_${idx}`;
1299
+ params[paramName] = val;
1300
+ });
1301
+ }
1302
+ const modifiedQueryText = addNoLockToQuery(queryText);
1303
+ const result = (await executor(modifiedQueryText, params));
1304
+ return result.rowsAffected?.[0] || 0;
1305
+ }
1306
+ async $transaction(fn, options) {
1307
+ if (Array.isArray(fn)) {
1308
+ return Promise.all(fn);
1309
+ }
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);
1340
+ }
1341
+ throw err;
1342
+ }
1343
+ }
1344
+ }
1345
+ exports.An5ORM = An5ORM;
1346
+ exports.an5Orm = new An5ORM();
1347
+ exports.default = exports.an5Orm;
1348
+ //# sourceMappingURL=an5Orm.js.map