@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/README.md +138 -0
- package/dist/an5Orm.d.ts +29 -0
- package/dist/an5Orm.js +1348 -0
- package/dist/cleanup.d.ts +2 -0
- package/dist/cleanup.js +117 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +11 -0
- package/dist/migrate.d.ts +11 -0
- package/dist/migrate.js +298 -0
- package/dist/pull.d.ts +2 -0
- package/dist/pull.js +336 -0
- package/dist/push.d.ts +2 -0
- package/dist/push.js +258 -0
- package/dist/scripts/release-cli.d.ts +3 -0
- package/dist/scripts/release-cli.js +116 -0
- package/dist/seed.d.ts +2 -0
- package/dist/seed.js +401 -0
- package/generator/src/code-generator.ts +234 -0
- package/generator/src/dotnet-generator.ts +544 -0
- package/generator/src/index.ts +92 -0
- package/generator/src/metadata-generator.ts +79 -0
- package/generator/src/parser.ts +202 -0
- package/generator/src/python-generator.ts +90 -0
- package/generator/src/types.ts +29 -0
- package/package.json +49 -0
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
|