@an5/orm 1.0.8 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +174 -175
- package/dist/an5Metadata.js +64 -0
- package/dist/an5Orm.d.ts +92 -3
- package/dist/an5Orm.js +697 -206
- package/dist/config.d.ts +9 -1
- package/dist/config.js +13 -18
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -1
- package/dist/generator/src/api.d.ts +35 -0
- package/dist/generator/src/api.js +58 -0
- package/dist/generator/src/code-generator.d.ts +14 -13
- package/dist/generator/src/code-generator.js +249 -225
- package/dist/generator/src/config.d.ts +162 -0
- package/dist/generator/src/config.js +354 -0
- package/dist/generator/src/dialect.d.ts +88 -0
- package/dist/generator/src/dialect.js +318 -0
- package/dist/generator/src/dotnet-generator.d.ts +21 -11
- package/dist/generator/src/dotnet-generator.js +1053 -546
- package/dist/generator/src/field-types.d.ts +96 -0
- package/dist/generator/src/field-types.js +433 -0
- package/dist/generator/src/golang-generator.d.ts +36 -0
- package/dist/generator/src/golang-generator.js +1229 -0
- package/dist/generator/src/index.d.ts +1 -1
- package/dist/generator/src/index.js +137 -88
- package/dist/generator/src/metadata-generator.d.ts +11 -8
- package/dist/generator/src/metadata-generator.js +105 -90
- package/dist/generator/src/parser.d.ts +37 -11
- package/dist/generator/src/parser.js +208 -206
- package/dist/generator/src/push-apply.d.ts +37 -0
- package/dist/generator/src/push-apply.js +109 -0
- package/dist/generator/src/push-schema.d.ts +93 -0
- package/dist/generator/src/push-schema.js +189 -0
- package/dist/generator/src/python-generator.d.ts +37 -9
- package/dist/generator/src/python-generator.js +428 -105
- package/dist/generator/src/rust-generator.d.ts +38 -0
- package/dist/generator/src/rust-generator.js +971 -0
- package/dist/generator/src/suggest.d.ts +17 -0
- package/dist/generator/src/suggest.js +45 -0
- package/dist/generator/src/type-kinds.d.ts +27 -0
- package/dist/generator/src/type-kinds.js +72 -0
- package/dist/generator/src/types.d.ts +60 -27
- package/dist/generator/src/types.js +41 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +19 -8
- package/dist/logger.js +1 -1
- package/dist/migration-core.d.ts +102 -0
- package/dist/migration-core.js +731 -0
- package/dist/orm.d.ts +147 -0
- package/dist/orm.js +1654 -0
- package/dist/provider-support.d.ts +27 -0
- package/dist/provider-support.js +50 -0
- package/dist/scripts/release-cli.d.ts +2 -2
- package/dist/scripts/release-cli.js +115 -115
- package/dist/sql-utils.d.ts +3 -1
- package/dist/sql-utils.js +94 -24
- package/generator/src/api.ts +45 -0
- package/generator/src/code-generator.ts +259 -230
- package/generator/src/config.ts +496 -0
- package/generator/src/dialect.ts +415 -0
- package/generator/src/dotnet-generator.ts +1071 -544
- package/generator/src/field-types.ts +473 -0
- package/generator/src/golang-generator.ts +1267 -0
- package/generator/src/index.ts +146 -116
- package/generator/src/metadata-generator.ts +116 -101
- package/generator/src/parser.ts +234 -202
- package/generator/src/push-apply.ts +148 -0
- package/generator/src/push-schema.ts +250 -0
- package/generator/src/python-generator.ts +480 -114
- package/generator/src/rust-generator.ts +974 -0
- package/generator/src/suggest.ts +41 -0
- package/generator/src/type-kinds.ts +78 -0
- package/generator/src/types.ts +77 -29
- package/package.json +32 -6
package/dist/orm.js
ADDED
|
@@ -0,0 +1,1654 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.an5Orm = exports.An5ORM = exports.ViewClient = exports.TableClient = void 0;
|
|
4
|
+
const adapters_1 = require("@an5/adapters");
|
|
5
|
+
const crypto_1 = require("crypto");
|
|
6
|
+
const logger_1 = require("./logger");
|
|
7
|
+
const errors_1 = require("./errors");
|
|
8
|
+
const metadata_1 = require("./metadata");
|
|
9
|
+
const sql_utils_1 = require("./sql-utils");
|
|
10
|
+
function aggSelect(fn, key) {
|
|
11
|
+
return `${fn}(${(0, sql_utils_1.quoteIdentifier)(key)}) as ${aggAlias(fn.toLowerCase(), key)}`;
|
|
12
|
+
}
|
|
13
|
+
function aggAlias(fn, key) {
|
|
14
|
+
return (0, sql_utils_1.sanitizeParamName)(`${fn}_${key}`);
|
|
15
|
+
}
|
|
16
|
+
let defaultExecutor = null;
|
|
17
|
+
function getDefaultExecutor() {
|
|
18
|
+
if (!defaultExecutor) {
|
|
19
|
+
const adapter = (0, adapters_1.createAn5Adapter)({ connectionString: process.env.DATABASE_URL });
|
|
20
|
+
defaultExecutor = (0, adapters_1.executorFromAdapter)(adapter);
|
|
21
|
+
}
|
|
22
|
+
return defaultExecutor;
|
|
23
|
+
}
|
|
24
|
+
function normalizeAffectedCount(result) {
|
|
25
|
+
if (typeof result === "number")
|
|
26
|
+
return result;
|
|
27
|
+
if (!result)
|
|
28
|
+
return 0;
|
|
29
|
+
if (Array.isArray(result.rowsAffected))
|
|
30
|
+
return Number(result.rowsAffected[0] ?? 0);
|
|
31
|
+
if (typeof result.rowsAffected === "number")
|
|
32
|
+
return result.rowsAffected;
|
|
33
|
+
if (typeof result.count === "number")
|
|
34
|
+
return result.count;
|
|
35
|
+
if (Array.isArray(result) && result.length === 1)
|
|
36
|
+
return normalizeAffectedCount(result[0]);
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
const FILTER_OPERATOR_KEYS = new Set(["in", "notIn", "contains", "startsWith", "endsWith", "not", "gte", "lte", "gt", "lt"]);
|
|
40
|
+
function flattenSimpleEqualityWhere(where) {
|
|
41
|
+
if (!where || typeof where !== "object" || where instanceof Date || Array.isArray(where)) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const flat = {};
|
|
45
|
+
for (const [key, value] of Object.entries(where)) {
|
|
46
|
+
if (value === null || value === undefined || Array.isArray(value))
|
|
47
|
+
return null;
|
|
48
|
+
if (value instanceof Date || typeof value !== "object") {
|
|
49
|
+
flat[key] = value;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (!key.includes("_"))
|
|
53
|
+
return null;
|
|
54
|
+
for (const [innerKey, innerValue] of Object.entries(value)) {
|
|
55
|
+
if (FILTER_OPERATOR_KEYS.has(innerKey))
|
|
56
|
+
return null;
|
|
57
|
+
if (innerValue === null || innerValue === undefined || (typeof innerValue === "object" && !(innerValue instanceof Date))) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
flat[innerKey] = innerValue;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return Object.keys(flat).length > 0 ? flat : null;
|
|
64
|
+
}
|
|
65
|
+
function asArray(value) {
|
|
66
|
+
if (value === undefined)
|
|
67
|
+
return [];
|
|
68
|
+
return Array.isArray(value) ? value : [value];
|
|
69
|
+
}
|
|
70
|
+
function hasOwn(obj, key) {
|
|
71
|
+
return !!obj && Object.prototype.hasOwnProperty.call(obj, key);
|
|
72
|
+
}
|
|
73
|
+
function appendUpdateSet(sets, params, key, val) {
|
|
74
|
+
const col = (0, sql_utils_1.quoteIdentifier)(key);
|
|
75
|
+
const safeKey = (0, sql_utils_1.sanitizeParamName)(key);
|
|
76
|
+
if (val && typeof val === "object" && !(val instanceof Date)) {
|
|
77
|
+
const ops = [
|
|
78
|
+
['increment', '+'],
|
|
79
|
+
['decrement', '-'],
|
|
80
|
+
['multiply', '*'],
|
|
81
|
+
['divide', '/'],
|
|
82
|
+
];
|
|
83
|
+
for (const [opKey, symbol] of ops) {
|
|
84
|
+
if (val[opKey] !== undefined) {
|
|
85
|
+
const paramName = `${safeKey}_${opKey.slice(0, 3)}`;
|
|
86
|
+
sets.push(`${col} = ${col} ${symbol} @${paramName}`);
|
|
87
|
+
params[paramName] = val[opKey];
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (val.set !== undefined) {
|
|
92
|
+
const paramName = `${safeKey}_set`;
|
|
93
|
+
sets.push(`${col} = @${paramName}`);
|
|
94
|
+
params[paramName] = val.set;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
sets.push(`${col} = @${safeKey}`);
|
|
99
|
+
params[safeKey] = val;
|
|
100
|
+
}
|
|
101
|
+
function normalizeByFields(by) {
|
|
102
|
+
if (typeof by === "string")
|
|
103
|
+
return [by];
|
|
104
|
+
return Array.isArray(by) ? by.filter((field) => typeof field === "string" && field.length > 0) : [];
|
|
105
|
+
}
|
|
106
|
+
function selectedAggregateFields(fields) {
|
|
107
|
+
if (!fields || typeof fields !== "object")
|
|
108
|
+
return [];
|
|
109
|
+
return Object.keys(fields).filter((key) => fields[key]);
|
|
110
|
+
}
|
|
111
|
+
function aggregateExpression(kind, field) {
|
|
112
|
+
const upper = kind.toUpperCase();
|
|
113
|
+
if (upper === "COUNT" && field === "_all")
|
|
114
|
+
return "COUNT(*)";
|
|
115
|
+
return `${upper}(${(0, sql_utils_1.quoteIdentifier)(field)})`;
|
|
116
|
+
}
|
|
117
|
+
function addHavingPredicate(conditions, params, expr, paramBase, filter) {
|
|
118
|
+
if (filter && typeof filter === "object" && !(filter instanceof Date)) {
|
|
119
|
+
for (const [op, value] of Object.entries(filter)) {
|
|
120
|
+
const p = (0, sql_utils_1.sanitizeParamName)(`having_${paramBase}_${op}`);
|
|
121
|
+
if (op === "equals") {
|
|
122
|
+
conditions.push(`${expr} = @${p}`);
|
|
123
|
+
params[p] = value;
|
|
124
|
+
}
|
|
125
|
+
else if (op === "gt" || op === "gte" || op === "lt" || op === "lte") {
|
|
126
|
+
const symbol = op === "gt" ? ">" : op === "gte" ? ">=" : op === "lt" ? "<" : "<=";
|
|
127
|
+
conditions.push(`${expr} ${symbol} @${p}`);
|
|
128
|
+
params[p] = value;
|
|
129
|
+
}
|
|
130
|
+
else if (op === "not") {
|
|
131
|
+
if (value && typeof value === "object" && !(value instanceof Date)) {
|
|
132
|
+
const nested = [];
|
|
133
|
+
addHavingPredicate(nested, params, expr, `${paramBase}_not`, value);
|
|
134
|
+
if (nested.length > 0)
|
|
135
|
+
conditions.push(`NOT (${nested.join(" AND ")})`);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
conditions.push(`${expr} <> @${p}`);
|
|
139
|
+
params[p] = value;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else if ((op === "in" || op === "notIn") && Array.isArray(value)) {
|
|
143
|
+
if (value.length === 0) {
|
|
144
|
+
conditions.push(op === "in" ? "1 = 0" : "1 = 1");
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
const placeholders = value.map((item, idx) => {
|
|
148
|
+
const itemParam = (0, sql_utils_1.sanitizeParamName)(`${p}_${idx}`);
|
|
149
|
+
params[itemParam] = item;
|
|
150
|
+
return `@${itemParam}`;
|
|
151
|
+
});
|
|
152
|
+
conditions.push(`${expr} ${op === "in" ? "IN" : "NOT IN"} (${placeholders.join(", ")})`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const p = (0, sql_utils_1.sanitizeParamName)(`having_${paramBase}`);
|
|
159
|
+
conditions.push(`${expr} = @${p}`);
|
|
160
|
+
params[p] = filter;
|
|
161
|
+
}
|
|
162
|
+
function buildHavingSql(having, params) {
|
|
163
|
+
if (!having || typeof having !== "object")
|
|
164
|
+
return "";
|
|
165
|
+
const conditions = [];
|
|
166
|
+
const aggregateKeys = {
|
|
167
|
+
_count: "COUNT",
|
|
168
|
+
_sum: "SUM",
|
|
169
|
+
_avg: "AVG",
|
|
170
|
+
_min: "MIN",
|
|
171
|
+
_max: "MAX",
|
|
172
|
+
};
|
|
173
|
+
for (const [key, sqlFn] of Object.entries(aggregateKeys)) {
|
|
174
|
+
const fields = having[key];
|
|
175
|
+
if (!fields || typeof fields !== "object")
|
|
176
|
+
continue;
|
|
177
|
+
for (const [field, filter] of Object.entries(fields)) {
|
|
178
|
+
addHavingPredicate(conditions, params, aggregateExpression(sqlFn, field), `${key}_${field}`, filter);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return conditions.length > 0 ? conditions.join(" AND ") : "";
|
|
182
|
+
}
|
|
183
|
+
function projectFields(row, select) {
|
|
184
|
+
if (!row || !select)
|
|
185
|
+
return row;
|
|
186
|
+
const projected = {};
|
|
187
|
+
for (const [key, val] of Object.entries(select)) {
|
|
188
|
+
if (val) {
|
|
189
|
+
projected[key] = row[key];
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (row._count) {
|
|
193
|
+
projected._count = row._count;
|
|
194
|
+
}
|
|
195
|
+
return projected;
|
|
196
|
+
}
|
|
197
|
+
function collectRelationSelections(modelName, select, metadata) {
|
|
198
|
+
if (!select || typeof select !== "object")
|
|
199
|
+
return undefined;
|
|
200
|
+
const modelRelations = metadata.relationMap[modelName] || {};
|
|
201
|
+
const relationSelections = {};
|
|
202
|
+
for (const [key, value] of Object.entries(select)) {
|
|
203
|
+
if (!value)
|
|
204
|
+
continue;
|
|
205
|
+
if (key === "_count" || modelRelations[key]) {
|
|
206
|
+
relationSelections[key] = value;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return Object.keys(relationSelections).length > 0 ? relationSelections : undefined;
|
|
210
|
+
}
|
|
211
|
+
function mergeIncludes(primary, secondary) {
|
|
212
|
+
if (!primary)
|
|
213
|
+
return secondary;
|
|
214
|
+
if (!secondary)
|
|
215
|
+
return primary;
|
|
216
|
+
return { ...secondary, ...primary };
|
|
217
|
+
}
|
|
218
|
+
function relationOrderClause(orderBy, fallbackKey) {
|
|
219
|
+
const sql = (0, sql_utils_1.buildOrderBy)(orderBy);
|
|
220
|
+
return sql ? sql.replace(/^\s*ORDER\s+BY\s+/i, "") : `${(0, sql_utils_1.quoteIdentifier)(fallbackKey)} ASC`;
|
|
221
|
+
}
|
|
222
|
+
function requiredRelationKeys(modelName, include, metadata) {
|
|
223
|
+
if (!include || typeof include !== "object")
|
|
224
|
+
return [];
|
|
225
|
+
const modelRelations = metadata.relationMap[modelName] || {};
|
|
226
|
+
const keys = new Set();
|
|
227
|
+
for (const [key, value] of Object.entries(include)) {
|
|
228
|
+
if (!value)
|
|
229
|
+
continue;
|
|
230
|
+
const relation = modelRelations[key];
|
|
231
|
+
if (!relation) {
|
|
232
|
+
if (key === "_count") {
|
|
233
|
+
for (const rel of Object.values(modelRelations)) {
|
|
234
|
+
keys.add(rel.localKey);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
keys.add(relation.relationType === "one" ? relation.foreignKey : relation.localKey);
|
|
240
|
+
}
|
|
241
|
+
return Array.from(keys);
|
|
242
|
+
}
|
|
243
|
+
// Helper to batch-query and resolve relationships
|
|
244
|
+
async function resolveIncludes(modelName, rows, include, executor, metadata) {
|
|
245
|
+
if (!rows || rows.length === 0 || !include)
|
|
246
|
+
return;
|
|
247
|
+
const modelRelations = metadata.relationMap[modelName];
|
|
248
|
+
if (!modelRelations)
|
|
249
|
+
return;
|
|
250
|
+
for (const [key, value] of Object.entries(include)) {
|
|
251
|
+
if (!value)
|
|
252
|
+
continue;
|
|
253
|
+
const relation = modelRelations[key];
|
|
254
|
+
if (!relation) {
|
|
255
|
+
if (key === "_count" && value) {
|
|
256
|
+
const countFields = value === true
|
|
257
|
+
? Object.keys(modelRelations)
|
|
258
|
+
: Object.keys(value.select || {}).filter((field) => value.select[field]);
|
|
259
|
+
for (const countField of countFields) {
|
|
260
|
+
const rel = modelRelations[countField];
|
|
261
|
+
if (rel) {
|
|
262
|
+
const relTable = (0, sql_utils_1.quoteTableIdentifier)(metadata.modelToTable[rel.modelName] || rel.modelName);
|
|
263
|
+
const localKeys = rows.map(r => r[rel.localKey]).filter(Boolean);
|
|
264
|
+
if (localKeys.length === 0) {
|
|
265
|
+
rows.forEach(r => { r._count = { ...r._count, [countField]: 0 }; });
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
const sqlText = `
|
|
269
|
+
SELECT ${(0, sql_utils_1.quoteIdentifier)(rel.foreignKey)} as parentId, COUNT(*) as count
|
|
270
|
+
FROM ${relTable} WITH (NOLOCK)
|
|
271
|
+
WHERE ${(0, sql_utils_1.quoteIdentifier)(rel.foreignKey)} IN (${localKeys.map((_, i) => `@lk_${i}`).join(", ")})
|
|
272
|
+
GROUP BY ${(0, sql_utils_1.quoteIdentifier)(rel.foreignKey)}
|
|
273
|
+
`;
|
|
274
|
+
const countParams = {};
|
|
275
|
+
localKeys.forEach((lk, i) => { countParams[`lk_${i}`] = lk; });
|
|
276
|
+
const counts = await executor(sqlText, countParams);
|
|
277
|
+
const countMap = new Map(counts.map((c) => [c.parentId, c.count]));
|
|
278
|
+
rows.forEach(r => {
|
|
279
|
+
if (!r._count)
|
|
280
|
+
r._count = {};
|
|
281
|
+
r._count[countField] = countMap.get(r[rel.localKey]) || 0;
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const relTable = (0, sql_utils_1.quoteTableIdentifier)(metadata.modelToTable[relation.modelName] || relation.modelName);
|
|
289
|
+
const isMany = relation.relationType === "many";
|
|
290
|
+
const matchKey = relation.relationType === "one" ? relation.foreignKey : relation.localKey;
|
|
291
|
+
const searchKey = relation.relationType === "one" ? relation.localKey : relation.foreignKey;
|
|
292
|
+
const keys = rows.map(r => r[matchKey]).filter(Boolean);
|
|
293
|
+
if (keys.length === 0) {
|
|
294
|
+
rows.forEach(r => { r[key] = isMany ? [] : null; });
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
const uniqueKeys = Array.from(new Set(keys));
|
|
298
|
+
let relCols = "*";
|
|
299
|
+
const nestedInclude = value && typeof value === "object"
|
|
300
|
+
? mergeIncludes(value.include, collectRelationSelections(relation.modelName, value.select, metadata))
|
|
301
|
+
: undefined;
|
|
302
|
+
if (value && typeof value === "object" && value.select) {
|
|
303
|
+
const subSelect = value.select;
|
|
304
|
+
const subRelations = metadata.relationMap[relation.modelName] || {};
|
|
305
|
+
const selectedSubCols = Object.keys(subSelect)
|
|
306
|
+
.filter(k => subSelect[k] && !subRelations[k])
|
|
307
|
+
.map(k => (0, sql_utils_1.quoteIdentifier)(k));
|
|
308
|
+
if (selectedSubCols.length > 0) {
|
|
309
|
+
for (const requiredKey of [searchKey, ...requiredRelationKeys(relation.modelName, nestedInclude, metadata)]) {
|
|
310
|
+
const quotedRequiredKey = (0, sql_utils_1.quoteIdentifier)(requiredKey);
|
|
311
|
+
if (!selectedSubCols.includes(quotedRequiredKey)) {
|
|
312
|
+
selectedSubCols.push(quotedRequiredKey);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
relCols = selectedSubCols.join(", ");
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
const filters = [`${(0, sql_utils_1.quoteIdentifier)(searchKey)} IN (${uniqueKeys.map((_, i) => `@k_${i}`).join(", ")})`];
|
|
319
|
+
let sqlText = "";
|
|
320
|
+
const subParams = {};
|
|
321
|
+
uniqueKeys.forEach((k, i) => { subParams[`k_${i}`] = k; });
|
|
322
|
+
let subArgs = undefined;
|
|
323
|
+
if (value && typeof value === "object") {
|
|
324
|
+
subArgs = value;
|
|
325
|
+
const subWhereSql = (0, sql_utils_1.parseWhere)(relation.modelName, subArgs.where, subParams, `rel_${key}_`, {
|
|
326
|
+
relationMap: metadata.relationMap,
|
|
327
|
+
modelToTable: metadata.modelToTable,
|
|
328
|
+
});
|
|
329
|
+
if (subWhereSql) {
|
|
330
|
+
filters.push(subWhereSql);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const hasRelationPagination = isMany && subArgs && (subArgs.take !== undefined || subArgs.skip !== undefined);
|
|
334
|
+
if (hasRelationPagination) {
|
|
335
|
+
const rn = (0, sql_utils_1.quoteIdentifier)("__an5_rn");
|
|
336
|
+
const skip = (0, sql_utils_1.toNonNegativeInt)(subArgs.skip);
|
|
337
|
+
const take = subArgs.take !== undefined ? (0, sql_utils_1.toNonNegativeInt)(subArgs.take, 1) : undefined;
|
|
338
|
+
const whereSql = filters.join(" AND ");
|
|
339
|
+
sqlText = `SELECT * FROM (
|
|
340
|
+
SELECT ${relCols}, ROW_NUMBER() OVER (PARTITION BY ${(0, sql_utils_1.quoteIdentifier)(searchKey)} ORDER BY ${relationOrderClause(subArgs.orderBy, searchKey)}) AS ${rn}
|
|
341
|
+
FROM ${relTable} WITH (NOLOCK)
|
|
342
|
+
WHERE ${whereSql}
|
|
343
|
+
) AS [an5_rel]
|
|
344
|
+
WHERE ${rn} > ${skip}`;
|
|
345
|
+
if (take !== undefined) {
|
|
346
|
+
sqlText += ` AND ${rn} <= ${skip + take}`;
|
|
347
|
+
}
|
|
348
|
+
sqlText += ` ORDER BY ${(0, sql_utils_1.quoteIdentifier)(searchKey)}, ${rn}`;
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
sqlText = `SELECT ${relCols} FROM ${relTable} WITH (NOLOCK) WHERE ${filters.join(" AND ")}`;
|
|
352
|
+
if (subArgs?.orderBy) {
|
|
353
|
+
sqlText += (0, sql_utils_1.buildOrderBy)(subArgs.orderBy);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const relatedRows = await executor(sqlText, subParams);
|
|
357
|
+
relatedRows.forEach((r) => { delete r.__an5_rn; });
|
|
358
|
+
if (nestedInclude) {
|
|
359
|
+
await resolveIncludes(relation.modelName, relatedRows, nestedInclude, executor, metadata);
|
|
360
|
+
}
|
|
361
|
+
const outputRows = value && typeof value === "object" && value.select
|
|
362
|
+
? relatedRows.map((r) => projectFields(r, value.select))
|
|
363
|
+
: relatedRows;
|
|
364
|
+
const groupMap = new Map();
|
|
365
|
+
relatedRows.forEach((r, idx) => {
|
|
366
|
+
const k = r[searchKey];
|
|
367
|
+
if (!groupMap.has(k))
|
|
368
|
+
groupMap.set(k, []);
|
|
369
|
+
groupMap.get(k).push(outputRows[idx]);
|
|
370
|
+
});
|
|
371
|
+
rows.forEach(r => {
|
|
372
|
+
const k = r[matchKey];
|
|
373
|
+
const matches = groupMap.get(k) || [];
|
|
374
|
+
if (isMany) {
|
|
375
|
+
r[key] = matches;
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
r[key] = matches[0] || null;
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function adapterFromExecutor(executor) {
|
|
384
|
+
return new adapters_1.An5Adapter({
|
|
385
|
+
engine: {
|
|
386
|
+
dialect: "mssql",
|
|
387
|
+
connect: async () => { },
|
|
388
|
+
disconnect: async () => { },
|
|
389
|
+
exec: async (q, p) => executor(q, p),
|
|
390
|
+
executeRaw: async (q, p) => (executor.executeRaw ? executor.executeRaw(q, p) : normalizeAffectedCount(await executor(q, p))),
|
|
391
|
+
beginTransaction: async () => { throw new Error("Transaction not supported"); },
|
|
392
|
+
},
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
// Table query executor client class
|
|
396
|
+
class TableClient extends adapters_1.AdapterTableClient {
|
|
397
|
+
constructor(modelName, rawTableName, executor, orm) {
|
|
398
|
+
super(adapterFromExecutor(executor), modelName);
|
|
399
|
+
this.executor = executor;
|
|
400
|
+
this.orm = orm;
|
|
401
|
+
this.rawTableName = (0, sql_utils_1.quoteTableIdentifier)(rawTableName);
|
|
402
|
+
}
|
|
403
|
+
async executeRaw(queryText, params) {
|
|
404
|
+
if (this.executor.executeRaw) {
|
|
405
|
+
return this.executor.executeRaw(queryText, params);
|
|
406
|
+
}
|
|
407
|
+
return normalizeAffectedCount(await this.executor(queryText, params));
|
|
408
|
+
}
|
|
409
|
+
async findMany(args) {
|
|
410
|
+
return this.orm._executeMiddleware({ model: this.modelName, action: 'findMany', args }, async (params) => {
|
|
411
|
+
const { args: finalArgs } = params;
|
|
412
|
+
const hasSkip = finalArgs?.skip !== undefined && finalArgs?.skip !== null;
|
|
413
|
+
const selectedRelations = collectRelationSelections(this.modelName, finalArgs?.select, this.orm.metadata);
|
|
414
|
+
const include = mergeIncludes(finalArgs?.include, selectedRelations);
|
|
415
|
+
let cols = "*";
|
|
416
|
+
if (finalArgs?.select) {
|
|
417
|
+
const modelRelations = this.orm.metadata.relationMap[this.modelName] || {};
|
|
418
|
+
const selectedColNames = Object.keys(finalArgs.select)
|
|
419
|
+
.filter(k => finalArgs.select[k] && !modelRelations[k] && k !== "_count");
|
|
420
|
+
for (const key of requiredRelationKeys(this.modelName, include, this.orm.metadata)) {
|
|
421
|
+
if (!selectedColNames.includes(key)) {
|
|
422
|
+
selectedColNames.push(key);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
const selectedCols = selectedColNames.map(k => (0, sql_utils_1.quoteIdentifier)(k));
|
|
426
|
+
if (selectedCols.length > 0) {
|
|
427
|
+
cols = selectedCols.join(", ");
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
let sqlText = "SELECT";
|
|
431
|
+
if (finalArgs?.take && !hasSkip) {
|
|
432
|
+
sqlText += ` TOP (${(0, sql_utils_1.toNonNegativeInt)(finalArgs.take, 1)})`;
|
|
433
|
+
}
|
|
434
|
+
sqlText += ` ${cols} FROM ${this.rawTableName} WITH (NOLOCK)`;
|
|
435
|
+
const p = {};
|
|
436
|
+
const whereSql = this.orm.parseWhere(this.modelName, finalArgs?.where, p);
|
|
437
|
+
if (whereSql) {
|
|
438
|
+
sqlText += ` WHERE ${whereSql}`;
|
|
439
|
+
}
|
|
440
|
+
if (finalArgs?.orderBy) {
|
|
441
|
+
sqlText += (0, sql_utils_1.buildOrderBy)(finalArgs.orderBy);
|
|
442
|
+
}
|
|
443
|
+
else if (hasSkip) {
|
|
444
|
+
// OFFSET requires an ORDER BY clause in SQL Server
|
|
445
|
+
sqlText += " ORDER BY (SELECT NULL)";
|
|
446
|
+
}
|
|
447
|
+
if (hasSkip) {
|
|
448
|
+
sqlText += ` OFFSET ${(0, sql_utils_1.toNonNegativeInt)(finalArgs.skip)} ROWS`;
|
|
449
|
+
if (finalArgs?.take) {
|
|
450
|
+
sqlText += ` FETCH NEXT ${(0, sql_utils_1.toNonNegativeInt)(finalArgs.take, 1)} ROWS ONLY`;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const rows = await this.executor(sqlText, p);
|
|
454
|
+
if (include) {
|
|
455
|
+
await resolveIncludes(this.modelName, rows, include, this.executor, this.orm.metadata);
|
|
456
|
+
}
|
|
457
|
+
if (finalArgs?.select) {
|
|
458
|
+
rows.forEach((r, idx) => {
|
|
459
|
+
rows[idx] = projectFields(r, finalArgs.select);
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
return rows;
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
async findFirst(args) {
|
|
466
|
+
return this.orm._executeMiddleware({ model: this.modelName, action: 'findFirst', args }, async (params) => {
|
|
467
|
+
const rows = await this.findMany({ ...params.args, take: 1 });
|
|
468
|
+
return rows[0] || null;
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
async findUnique(args) {
|
|
472
|
+
return this.orm._executeMiddleware({ model: this.modelName, action: 'findUnique', args }, async (params) => {
|
|
473
|
+
return this.findFirst(params.args);
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
async count(args) {
|
|
477
|
+
return this.orm._executeMiddleware({ model: this.modelName, action: 'count', args }, async (params) => {
|
|
478
|
+
return super.count(params.args);
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
scopedRelationWhere(relation, parentId, where) {
|
|
482
|
+
const parentWhere = { [relation.foreignKey]: parentId };
|
|
483
|
+
if (!where || Object.keys(where).length === 0)
|
|
484
|
+
return parentWhere;
|
|
485
|
+
return { AND: [where, parentWhere] };
|
|
486
|
+
}
|
|
487
|
+
async handleNestedWrites(data, parentId) {
|
|
488
|
+
const modelRelations = this.orm.metadata.relationMap[this.modelName] || {};
|
|
489
|
+
for (const [key, value] of Object.entries(data)) {
|
|
490
|
+
const relation = modelRelations[key];
|
|
491
|
+
if (!relation || !value || typeof value !== "object" || (value instanceof Date))
|
|
492
|
+
continue;
|
|
493
|
+
const relTableClient = this.orm[relation.modelName];
|
|
494
|
+
if (!relTableClient)
|
|
495
|
+
continue;
|
|
496
|
+
const nestedOps = value;
|
|
497
|
+
if (relation.relationType !== "many")
|
|
498
|
+
continue;
|
|
499
|
+
if (hasOwn(nestedOps, "set")) {
|
|
500
|
+
await relTableClient.updateMany({
|
|
501
|
+
where: { [relation.foreignKey]: parentId },
|
|
502
|
+
data: { [relation.foreignKey]: null },
|
|
503
|
+
});
|
|
504
|
+
for (const item of asArray(nestedOps.set)) {
|
|
505
|
+
await relTableClient.update({
|
|
506
|
+
where: item,
|
|
507
|
+
data: { [relation.foreignKey]: parentId }
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
// Handle deleteMany (an5Orm-style)
|
|
512
|
+
if (hasOwn(nestedOps, "deleteMany")) {
|
|
513
|
+
const deleteWhere = Array.isArray(nestedOps.deleteMany) ? { OR: nestedOps.deleteMany } : nestedOps.deleteMany;
|
|
514
|
+
// Scope deletion to parent
|
|
515
|
+
const scopedWhere = this.scopedRelationWhere(relation, parentId, deleteWhere);
|
|
516
|
+
await relTableClient.deleteMany({ where: scopedWhere });
|
|
517
|
+
}
|
|
518
|
+
if (hasOwn(nestedOps, "delete")) {
|
|
519
|
+
const deleteWhere = Array.isArray(nestedOps.delete) ? { OR: nestedOps.delete } : nestedOps.delete;
|
|
520
|
+
await relTableClient.deleteMany({
|
|
521
|
+
where: this.scopedRelationWhere(relation, parentId, deleteWhere),
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
if (hasOwn(nestedOps, "disconnect")) {
|
|
525
|
+
const disconnectWhere = Array.isArray(nestedOps.disconnect) ? { OR: nestedOps.disconnect } : nestedOps.disconnect;
|
|
526
|
+
await relTableClient.updateMany({
|
|
527
|
+
where: this.scopedRelationWhere(relation, parentId, disconnectWhere),
|
|
528
|
+
data: { [relation.foreignKey]: null },
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
if (hasOwn(nestedOps, "update")) {
|
|
532
|
+
for (const item of asArray(nestedOps.update)) {
|
|
533
|
+
await relTableClient.update({
|
|
534
|
+
where: this.scopedRelationWhere(relation, parentId, item.where),
|
|
535
|
+
data: item.data,
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
if (hasOwn(nestedOps, "upsert")) {
|
|
540
|
+
for (const item of asArray(nestedOps.upsert)) {
|
|
541
|
+
await relTableClient.upsert({
|
|
542
|
+
where: this.scopedRelationWhere(relation, parentId, item.where),
|
|
543
|
+
create: { ...item.create, [relation.foreignKey]: parentId },
|
|
544
|
+
update: item.update,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
// Handle create
|
|
549
|
+
if (hasOwn(nestedOps, "create")) {
|
|
550
|
+
for (const item of asArray(nestedOps.create)) {
|
|
551
|
+
// Inject parent ID
|
|
552
|
+
const itemData = { ...item, [relation.foreignKey]: parentId };
|
|
553
|
+
await relTableClient.create({ data: itemData });
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
// Handle connect
|
|
557
|
+
if (hasOwn(nestedOps, "connect")) {
|
|
558
|
+
for (const item of asArray(nestedOps.connect)) {
|
|
559
|
+
await relTableClient.update({
|
|
560
|
+
where: item,
|
|
561
|
+
data: { [relation.foreignKey]: parentId }
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
async create(args) {
|
|
568
|
+
return this.orm._executeMiddleware({ model: this.modelName, action: 'create', args }, async (params) => {
|
|
569
|
+
const { args: finalArgs } = params;
|
|
570
|
+
try {
|
|
571
|
+
const data = { ...finalArgs.data };
|
|
572
|
+
// Extract nested writes
|
|
573
|
+
const nestedData = {};
|
|
574
|
+
const modelRelations = this.orm.metadata.relationMap[this.modelName] || {};
|
|
575
|
+
for (const key of Object.keys(data)) {
|
|
576
|
+
if (modelRelations[key]) {
|
|
577
|
+
nestedData[key] = data[key];
|
|
578
|
+
delete data[key];
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
if (!data.id && this.orm.metadata.modelFields[this.modelName]?.id?.ts === "string") {
|
|
582
|
+
data.id = (0, crypto_1.randomUUID)();
|
|
583
|
+
}
|
|
584
|
+
const now = new Date();
|
|
585
|
+
if (!data.createdAt && this.orm.metadata.modelFields[this.modelName]?.createdAt)
|
|
586
|
+
data.createdAt = now;
|
|
587
|
+
if (!data.updatedAt && this.orm.metadata.modelFields[this.modelName]?.updatedAt)
|
|
588
|
+
data.updatedAt = now;
|
|
589
|
+
// Handle one-relation connect where we hold the FK
|
|
590
|
+
for (const [key, value] of Object.entries(nestedData)) {
|
|
591
|
+
const rel = modelRelations[key];
|
|
592
|
+
const nestedValue = value && typeof value === "object" ? value : {};
|
|
593
|
+
if (rel && rel.relationType === "one" && (nestedValue.connect || nestedValue.set)) {
|
|
594
|
+
const connectObj = nestedValue.connect || nestedValue.set;
|
|
595
|
+
const targetId = connectObj[rel.localKey] || connectObj.id || Object.values(connectObj)[0];
|
|
596
|
+
data[rel.foreignKey] = targetId;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
const keys = Object.keys(data);
|
|
600
|
+
const columns = keys.map(k => (0, sql_utils_1.quoteIdentifier)(k)).join(", ");
|
|
601
|
+
const safeKeys = keys.map(k => (0, sql_utils_1.sanitizeParamName)(k));
|
|
602
|
+
const placeholders = safeKeys.map(k => `@${k}`).join(", ");
|
|
603
|
+
const safeData = {};
|
|
604
|
+
keys.forEach((k, i) => { safeData[safeKeys[i]] = data[k]; });
|
|
605
|
+
const sqlText = `INSERT INTO ${this.rawTableName} (${columns}) OUTPUT inserted.* VALUES (${placeholders})`;
|
|
606
|
+
const rows = await this.executor(sqlText, safeData);
|
|
607
|
+
const createdRow = rows[0];
|
|
608
|
+
if (!createdRow) {
|
|
609
|
+
throw new Error("Failed to insert record and retrieve output");
|
|
610
|
+
}
|
|
611
|
+
const pkField = ("id" in createdRow) ? "id" : Object.keys(createdRow).find(k => k.endsWith("_id"));
|
|
612
|
+
const insertedId = pkField ? createdRow[pkField] : undefined;
|
|
613
|
+
// Process other nested writes
|
|
614
|
+
await this.handleNestedWrites(nestedData, insertedId);
|
|
615
|
+
const created = await this.findUnique({
|
|
616
|
+
where: pkField ? { [pkField]: insertedId } : {},
|
|
617
|
+
include: finalArgs.include
|
|
618
|
+
});
|
|
619
|
+
if (!created) {
|
|
620
|
+
throw new Error(`Failed to retrieve newly created record with ID ${insertedId}`);
|
|
621
|
+
}
|
|
622
|
+
return created;
|
|
623
|
+
}
|
|
624
|
+
catch (error) {
|
|
625
|
+
const msg = String(error?.message || '').toLowerCase();
|
|
626
|
+
const errNumber = error?.number;
|
|
627
|
+
if (msg.includes('duplicate') ||
|
|
628
|
+
msg.includes('unique') ||
|
|
629
|
+
errNumber === 2627 ||
|
|
630
|
+
errNumber === 2601) {
|
|
631
|
+
throw new errors_1.An5ClientKnownRequestError("Unique constraint failed", {
|
|
632
|
+
code: "P2002",
|
|
633
|
+
clientVersion: "mock",
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
if (msg.includes('foreign key') || errNumber === 547) {
|
|
637
|
+
throw new errors_1.An5ClientKnownRequestError("Foreign key constraint failed", {
|
|
638
|
+
code: "P2003",
|
|
639
|
+
clientVersion: "mock",
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
if (msg.includes('not found') || errNumber === 404) {
|
|
643
|
+
throw new errors_1.An5ClientKnownRequestError("Record not found", {
|
|
644
|
+
code: "P2025",
|
|
645
|
+
clientVersion: "mock",
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
throw error;
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
async update(args) {
|
|
653
|
+
return this.orm._executeMiddleware({ model: this.modelName, action: 'update', args }, async (params) => {
|
|
654
|
+
const { args: finalArgs } = params;
|
|
655
|
+
const data = { ...finalArgs.data };
|
|
656
|
+
if (this.orm.metadata.modelFields[this.modelName]?.updatedAt) {
|
|
657
|
+
data.updatedAt = new Date();
|
|
658
|
+
}
|
|
659
|
+
// Extract nested writes
|
|
660
|
+
const nestedData = {};
|
|
661
|
+
const modelRelations = this.orm.metadata.relationMap[this.modelName] || {};
|
|
662
|
+
for (const key of Object.keys(data)) {
|
|
663
|
+
if (modelRelations[key]) {
|
|
664
|
+
nestedData[key] = data[key];
|
|
665
|
+
delete data[key];
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
delete data.id;
|
|
669
|
+
// Handle one-relation connect where we hold the FK
|
|
670
|
+
for (const [key, value] of Object.entries(nestedData)) {
|
|
671
|
+
const rel = modelRelations[key];
|
|
672
|
+
const nestedValue = value && typeof value === "object" ? value : {};
|
|
673
|
+
if (rel && rel.relationType === "one") {
|
|
674
|
+
if (nestedValue.connect || nestedValue.set) {
|
|
675
|
+
const connectObj = nestedValue.connect || nestedValue.set;
|
|
676
|
+
const targetId = connectObj[rel.localKey] || connectObj.id || Object.values(connectObj)[0];
|
|
677
|
+
data[rel.foreignKey] = targetId;
|
|
678
|
+
}
|
|
679
|
+
else if (hasOwn(nestedValue, "disconnect") || value === null) {
|
|
680
|
+
data[rel.foreignKey] = null;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
const sets = [];
|
|
685
|
+
const p = {};
|
|
686
|
+
for (const key of Object.keys(data)) {
|
|
687
|
+
const val = data[key];
|
|
688
|
+
appendUpdateSet(sets, p, key, val);
|
|
689
|
+
}
|
|
690
|
+
const whereParams = {};
|
|
691
|
+
const whereSql = this.orm.parseWhere(this.modelName, finalArgs.where, whereParams, "w_");
|
|
692
|
+
Object.assign(p, whereParams);
|
|
693
|
+
const existing = await this.findUnique({ where: finalArgs.where });
|
|
694
|
+
if (!existing)
|
|
695
|
+
throw new Error("Record not found to update");
|
|
696
|
+
const parentId = existing.id;
|
|
697
|
+
if (sets.length > 0) {
|
|
698
|
+
const sqlText = `UPDATE ${this.tableName} SET ${sets.join(", ")} WHERE ${whereSql}`;
|
|
699
|
+
await this.executor(sqlText, p);
|
|
700
|
+
}
|
|
701
|
+
// Process nested writes
|
|
702
|
+
await this.handleNestedWrites(nestedData, parentId);
|
|
703
|
+
const updated = await this.findUnique({ where: finalArgs.where, include: finalArgs.include });
|
|
704
|
+
if (!updated) {
|
|
705
|
+
throw new Error(`Failed to retrieve updated record`);
|
|
706
|
+
}
|
|
707
|
+
return updated;
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
async updateMany(args) {
|
|
711
|
+
return this.orm._executeMiddleware({ model: this.modelName, action: 'updateMany', args }, async (params) => {
|
|
712
|
+
const { args: finalArgs } = params;
|
|
713
|
+
const data = { ...finalArgs.data };
|
|
714
|
+
if (this.orm.metadata.modelFields[this.modelName]?.updatedAt) {
|
|
715
|
+
data.updatedAt = new Date();
|
|
716
|
+
}
|
|
717
|
+
delete data.id;
|
|
718
|
+
const sets = [];
|
|
719
|
+
const p = {};
|
|
720
|
+
for (const key of Object.keys(data)) {
|
|
721
|
+
const val = data[key];
|
|
722
|
+
if (this.orm.metadata.relationMap[this.modelName]?.[key])
|
|
723
|
+
continue;
|
|
724
|
+
appendUpdateSet(sets, p, key, val);
|
|
725
|
+
}
|
|
726
|
+
if (sets.length === 0) {
|
|
727
|
+
return { count: 0 };
|
|
728
|
+
}
|
|
729
|
+
const whereParams = {};
|
|
730
|
+
const whereSql = this.orm.parseWhere(this.modelName, finalArgs.where, whereParams, "w_");
|
|
731
|
+
Object.assign(p, whereParams);
|
|
732
|
+
const sqlText = `UPDATE ${this.rawTableName} SET ${sets.join(", ")}${whereSql ? ` WHERE ${whereSql}` : ""}`;
|
|
733
|
+
const count = await this.executeRaw(sqlText, p);
|
|
734
|
+
return { count };
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
async delete(args) {
|
|
738
|
+
return this.orm._executeMiddleware({ model: this.modelName, action: 'delete', args }, async (params) => {
|
|
739
|
+
const { args: finalArgs } = params;
|
|
740
|
+
return super.delete({ where: finalArgs?.where || finalArgs });
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
async deleteMany(args) {
|
|
744
|
+
return this.orm._executeMiddleware({ model: this.modelName, action: 'deleteMany', args }, async (params) => {
|
|
745
|
+
const { args: finalArgs } = params;
|
|
746
|
+
return super.deleteMany({ where: finalArgs?.where || finalArgs });
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
async vectorSearch(args) {
|
|
750
|
+
return this.orm._executeMiddleware({ model: this.modelName, action: 'vectorSearch', args }, async (params) => {
|
|
751
|
+
const { args: finalArgs } = params;
|
|
752
|
+
const field = finalArgs.vectorField || "embedding";
|
|
753
|
+
const METRICS = ["cosine", "euclidean", "dot"];
|
|
754
|
+
const ELEMENT_TYPES = ["float32", "float16", "uint8"];
|
|
755
|
+
const metric = METRICS.includes(finalArgs.distanceMetric) ? finalArgs.distanceMetric : "cosine";
|
|
756
|
+
const elementType = ELEMENT_TYPES.includes(finalArgs.vectorElementType) ? finalArgs.vectorElementType : "float32";
|
|
757
|
+
const take = (0, sql_utils_1.toNonNegativeInt)(finalArgs.take, 10);
|
|
758
|
+
const dim = Number(finalArgs.vector?.length) || 0;
|
|
759
|
+
const vectorJson = JSON.stringify(finalArgs.vector);
|
|
760
|
+
const col = (0, sql_utils_1.quoteIdentifier)(field);
|
|
761
|
+
try {
|
|
762
|
+
if (dim > 1998) {
|
|
763
|
+
throw new Error("Vector dimension exceeds SQL Server limit of 1998");
|
|
764
|
+
}
|
|
765
|
+
let sqlText = `SELECT TOP (${take}) *, `;
|
|
766
|
+
sqlText += `VECTOR_DISTANCE('${metric}', CAST(${col} AS VECTOR(${dim}, ${elementType})), CAST(@query_vector AS VECTOR(${dim}, ${elementType}))) AS distance `;
|
|
767
|
+
sqlText += `FROM ${this.tableName} WITH (NOLOCK) `;
|
|
768
|
+
const p = {
|
|
769
|
+
query_vector: vectorJson
|
|
770
|
+
};
|
|
771
|
+
const whereClauses = [];
|
|
772
|
+
whereClauses.push(`${col} IS NOT NULL`);
|
|
773
|
+
const whereSql = this.orm.parseWhere(this.modelName, finalArgs.where, p, "v_");
|
|
774
|
+
if (whereSql) {
|
|
775
|
+
whereClauses.push(whereSql);
|
|
776
|
+
}
|
|
777
|
+
sqlText += `WHERE ${whereClauses.join(" AND ")} `;
|
|
778
|
+
sqlText += `ORDER BY distance ASC`;
|
|
779
|
+
const rows = await this.executor(sqlText, p);
|
|
780
|
+
if (finalArgs.include) {
|
|
781
|
+
await resolveIncludes(this.modelName, rows, finalArgs.include, this.executor, this.orm.metadata);
|
|
782
|
+
}
|
|
783
|
+
return rows;
|
|
784
|
+
}
|
|
785
|
+
catch (err) {
|
|
786
|
+
const msg = (String(err?.message || "") + " " + String(err?.originalError?.message || "")).toLowerCase();
|
|
787
|
+
// Handle specific float16 to float32 conversion error by retrying with float16
|
|
788
|
+
if (msg.includes("float16") && msg.includes("float32") && msg.includes("conversion") && !finalArgs.vectorElementType) {
|
|
789
|
+
logger_1.logger.info(`Detected float16 vector storage. Retrying vectorSearch with float16 element type.`);
|
|
790
|
+
return this.vectorSearch({ ...finalArgs, vectorElementType: 'float16' });
|
|
791
|
+
}
|
|
792
|
+
const isUnsupported = msg.includes("vector_distance") ||
|
|
793
|
+
msg.includes("type vector") ||
|
|
794
|
+
msg.includes("type \"vector\"") ||
|
|
795
|
+
msg.includes("data type vector") ||
|
|
796
|
+
msg.includes("incorrect syntax") ||
|
|
797
|
+
msg.includes("syntax near") ||
|
|
798
|
+
msg.includes("not a recognized built-in function") ||
|
|
799
|
+
msg.includes("not a defined system type") ||
|
|
800
|
+
msg.includes("limit of 1998") ||
|
|
801
|
+
err?.number === 195 ||
|
|
802
|
+
err?.number === 102 ||
|
|
803
|
+
err?.number === 319 ||
|
|
804
|
+
err?.originalError?.number === 319;
|
|
805
|
+
if (!isUnsupported) {
|
|
806
|
+
throw err;
|
|
807
|
+
}
|
|
808
|
+
logger_1.logger.warn(`Native VECTOR_DISTANCE not supported by SQL Server instance. Falling back to in-memory similarity search.`);
|
|
809
|
+
let fallbackSql = `SELECT * FROM ${this.tableName} WITH (NOLOCK) `;
|
|
810
|
+
const fallbackParams = {};
|
|
811
|
+
const fallbackWhereClauses = [];
|
|
812
|
+
fallbackWhereClauses.push(`${col} IS NOT NULL`);
|
|
813
|
+
const fallbackWhereSql = this.orm.parseWhere(this.modelName, finalArgs.where, fallbackParams, "vf_");
|
|
814
|
+
if (fallbackWhereSql) {
|
|
815
|
+
fallbackWhereClauses.push(fallbackWhereSql);
|
|
816
|
+
}
|
|
817
|
+
fallbackSql += `WHERE ${fallbackWhereClauses.join(" AND ")}`;
|
|
818
|
+
const rows = await this.executor(fallbackSql, fallbackParams);
|
|
819
|
+
const scored = rows.map((row) => {
|
|
820
|
+
let distance = 1.0;
|
|
821
|
+
try {
|
|
822
|
+
const rowVector = typeof row[field] === "string"
|
|
823
|
+
? JSON.parse(row[field])
|
|
824
|
+
: row[field];
|
|
825
|
+
if (Array.isArray(rowVector)) {
|
|
826
|
+
let dotProduct = 0;
|
|
827
|
+
let normA = 0;
|
|
828
|
+
let normB = 0;
|
|
829
|
+
for (let i = 0; i < finalArgs.vector.length; i++) {
|
|
830
|
+
const valA = finalArgs.vector[i] || 0;
|
|
831
|
+
const valB = rowVector[i] || 0;
|
|
832
|
+
dotProduct += valA * valB;
|
|
833
|
+
normA += valA * valA;
|
|
834
|
+
normB += valB * valB;
|
|
835
|
+
}
|
|
836
|
+
const similarity = (normA === 0 || normB === 0) ? 0 : dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
837
|
+
distance = 1.0 - similarity;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
catch { /* skip */ }
|
|
841
|
+
return {
|
|
842
|
+
...row,
|
|
843
|
+
distance
|
|
844
|
+
};
|
|
845
|
+
});
|
|
846
|
+
const results = scored
|
|
847
|
+
.sort((a, b) => a.distance - b.distance)
|
|
848
|
+
.slice(0, take);
|
|
849
|
+
if (finalArgs.include && results.length > 0) {
|
|
850
|
+
await resolveIncludes(this.modelName, results, finalArgs.include, this.executor, this.orm.metadata);
|
|
851
|
+
}
|
|
852
|
+
return results;
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
async createMany(args) {
|
|
857
|
+
return this.orm._executeMiddleware({ model: this.modelName, action: 'createMany', args }, async (params) => {
|
|
858
|
+
const { args: finalArgs } = params;
|
|
859
|
+
if (!finalArgs.data || finalArgs.data.length === 0)
|
|
860
|
+
return { count: 0 };
|
|
861
|
+
try {
|
|
862
|
+
const fields = this.orm.metadata.modelFields[this.modelName] || {};
|
|
863
|
+
const fieldNames = Object.keys(fields);
|
|
864
|
+
const now = new Date();
|
|
865
|
+
const rows = finalArgs.data.map((item) => {
|
|
866
|
+
const rowData = { ...item };
|
|
867
|
+
if (!rowData.id && fields.id?.ts === "string")
|
|
868
|
+
rowData.id = (0, crypto_1.randomUUID)();
|
|
869
|
+
if (!rowData.createdAt && fields.createdAt)
|
|
870
|
+
rowData.createdAt = now;
|
|
871
|
+
if (!rowData.updatedAt && fields.updatedAt)
|
|
872
|
+
rowData.updatedAt = now;
|
|
873
|
+
return rowData;
|
|
874
|
+
});
|
|
875
|
+
const cols = fieldNames.filter(col => rows.some((r) => r[col] !== undefined));
|
|
876
|
+
if (cols.length === 0)
|
|
877
|
+
throw new Error("No insertable columns");
|
|
878
|
+
const params = {};
|
|
879
|
+
const rowPlaceholders = [];
|
|
880
|
+
rows.forEach((row, rowIdx) => {
|
|
881
|
+
const vals = cols.map(col => {
|
|
882
|
+
const p = (0, sql_utils_1.sanitizeParamName)(`r${rowIdx}_${col}`);
|
|
883
|
+
params[p] = row[col] ?? null;
|
|
884
|
+
return `@${p}`;
|
|
885
|
+
});
|
|
886
|
+
rowPlaceholders.push(`(${vals.join(", ")})`);
|
|
887
|
+
});
|
|
888
|
+
const sqlText = `INSERT INTO ${this.tableName} (${cols.map(sql_utils_1.quoteIdentifier).join(", ")}) VALUES ${rowPlaceholders.join(", ")}`;
|
|
889
|
+
await this.executeRaw(sqlText, params);
|
|
890
|
+
return { count: rows.length };
|
|
891
|
+
}
|
|
892
|
+
catch (err) {
|
|
893
|
+
logger_1.logger.warn(`Bulk insert failed, falling back to sequential inserts: ${err.message}`);
|
|
894
|
+
let count = 0;
|
|
895
|
+
for (const item of finalArgs.data) {
|
|
896
|
+
try {
|
|
897
|
+
await this.create({ data: item });
|
|
898
|
+
count++;
|
|
899
|
+
}
|
|
900
|
+
catch (innerErr) {
|
|
901
|
+
if (finalArgs.skipDuplicates)
|
|
902
|
+
continue;
|
|
903
|
+
throw innerErr;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
return { count };
|
|
907
|
+
}
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
async aggregate(args) {
|
|
911
|
+
return this.orm._executeMiddleware({ model: this.modelName, action: 'aggregate', args }, async (params) => {
|
|
912
|
+
const { args: finalArgs } = params;
|
|
913
|
+
const selects = [];
|
|
914
|
+
const resultObj = {};
|
|
915
|
+
if (finalArgs._sum) {
|
|
916
|
+
resultObj._sum = {};
|
|
917
|
+
for (const key of selectedAggregateFields(finalArgs._sum)) {
|
|
918
|
+
selects.push(aggSelect("SUM", key));
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
if (finalArgs._avg) {
|
|
922
|
+
resultObj._avg = {};
|
|
923
|
+
for (const key of selectedAggregateFields(finalArgs._avg)) {
|
|
924
|
+
selects.push(aggSelect("AVG", key));
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
if (finalArgs._min) {
|
|
928
|
+
resultObj._min = {};
|
|
929
|
+
for (const key of selectedAggregateFields(finalArgs._min)) {
|
|
930
|
+
selects.push(aggSelect("MIN", key));
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
if (finalArgs._max) {
|
|
934
|
+
resultObj._max = {};
|
|
935
|
+
for (const key of selectedAggregateFields(finalArgs._max)) {
|
|
936
|
+
selects.push(aggSelect("MAX", key));
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
if (finalArgs._count) {
|
|
940
|
+
resultObj._count = {};
|
|
941
|
+
if (finalArgs._count === true || finalArgs._count._all) {
|
|
942
|
+
selects.push(`COUNT(*) as count_all`);
|
|
943
|
+
}
|
|
944
|
+
else {
|
|
945
|
+
for (const key of selectedAggregateFields(finalArgs._count)) {
|
|
946
|
+
selects.push(aggSelect("COUNT", key));
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
if (selects.length === 0) {
|
|
951
|
+
throw new Error("Aggregate requires at least one aggregator field");
|
|
952
|
+
}
|
|
953
|
+
let sqlText = `SELECT ${selects.join(", ")} FROM ${this.tableName} WITH (NOLOCK)`;
|
|
954
|
+
const p = {};
|
|
955
|
+
const whereSql = this.orm.parseWhere(this.modelName, finalArgs?.where, p);
|
|
956
|
+
if (whereSql) {
|
|
957
|
+
sqlText += ` WHERE ${whereSql}`;
|
|
958
|
+
}
|
|
959
|
+
const rows = await this.executor(sqlText, p);
|
|
960
|
+
const row = rows[0] || {};
|
|
961
|
+
if (finalArgs._sum) {
|
|
962
|
+
for (const key of selectedAggregateFields(finalArgs._sum)) {
|
|
963
|
+
resultObj._sum[key] = row[aggAlias("sum", key)] !== undefined ? row[aggAlias("sum", key)] : null;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
if (finalArgs._avg) {
|
|
967
|
+
for (const key of selectedAggregateFields(finalArgs._avg)) {
|
|
968
|
+
resultObj._avg[key] = row[aggAlias("avg", key)] !== undefined ? row[aggAlias("avg", key)] : null;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
if (finalArgs._min) {
|
|
972
|
+
for (const key of selectedAggregateFields(finalArgs._min)) {
|
|
973
|
+
resultObj._min[key] = row[aggAlias("min", key)] !== undefined ? row[aggAlias("min", key)] : null;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
if (finalArgs._max) {
|
|
977
|
+
for (const key of selectedAggregateFields(finalArgs._max)) {
|
|
978
|
+
resultObj._max[key] = row[aggAlias("max", key)] !== undefined ? row[aggAlias("max", key)] : null;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
if (finalArgs._count) {
|
|
982
|
+
if (finalArgs._count === true || finalArgs._count._all) {
|
|
983
|
+
resultObj._count._all = row[`count_all`] || 0;
|
|
984
|
+
}
|
|
985
|
+
else {
|
|
986
|
+
for (const key of selectedAggregateFields(finalArgs._count)) {
|
|
987
|
+
resultObj._count[key] = row[aggAlias("count", key)] || 0;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
return resultObj;
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
async groupBy(args) {
|
|
995
|
+
return this.orm._executeMiddleware({ model: this.modelName, action: 'groupBy', args }, async (params) => {
|
|
996
|
+
const { args: finalArgs } = params;
|
|
997
|
+
const byFields = normalizeByFields(finalArgs.by);
|
|
998
|
+
if (byFields.length === 0) {
|
|
999
|
+
throw new Error("groupBy requires 'by' fields");
|
|
1000
|
+
}
|
|
1001
|
+
const selects = byFields.map((f) => (0, sql_utils_1.quoteIdentifier)(f));
|
|
1002
|
+
if (finalArgs._count) {
|
|
1003
|
+
if (finalArgs._count === true || finalArgs._count._all) {
|
|
1004
|
+
selects.push(`COUNT(*) as count_all`);
|
|
1005
|
+
}
|
|
1006
|
+
else {
|
|
1007
|
+
for (const key of selectedAggregateFields(finalArgs._count)) {
|
|
1008
|
+
selects.push(aggSelect("COUNT", key));
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
if (finalArgs._sum) {
|
|
1013
|
+
for (const key of selectedAggregateFields(finalArgs._sum)) {
|
|
1014
|
+
selects.push(aggSelect("SUM", key));
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
if (finalArgs._avg) {
|
|
1018
|
+
for (const key of selectedAggregateFields(finalArgs._avg)) {
|
|
1019
|
+
selects.push(aggSelect("AVG", key));
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
if (finalArgs._min) {
|
|
1023
|
+
for (const key of selectedAggregateFields(finalArgs._min)) {
|
|
1024
|
+
selects.push(aggSelect("MIN", key));
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
if (finalArgs._max) {
|
|
1028
|
+
for (const key of selectedAggregateFields(finalArgs._max)) {
|
|
1029
|
+
selects.push(aggSelect("MAX", key));
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
let sqlText = `SELECT ${selects.join(", ")} FROM ${this.tableName} WITH (NOLOCK)`;
|
|
1033
|
+
const p = {};
|
|
1034
|
+
const whereSql = this.orm.parseWhere(this.modelName, finalArgs?.where, p);
|
|
1035
|
+
if (whereSql) {
|
|
1036
|
+
sqlText += ` WHERE ${whereSql}`;
|
|
1037
|
+
}
|
|
1038
|
+
sqlText += ` GROUP BY ${byFields.map((f) => (0, sql_utils_1.quoteIdentifier)(f)).join(", ")}`;
|
|
1039
|
+
const havingSql = buildHavingSql(finalArgs?.having, p);
|
|
1040
|
+
if (havingSql) {
|
|
1041
|
+
sqlText += ` HAVING ${havingSql}`;
|
|
1042
|
+
}
|
|
1043
|
+
const hasSkip = finalArgs?.skip !== undefined && finalArgs?.skip !== null;
|
|
1044
|
+
const hasTake = finalArgs?.take !== undefined && finalArgs?.take !== null;
|
|
1045
|
+
if (finalArgs?.orderBy) {
|
|
1046
|
+
sqlText += (0, sql_utils_1.buildOrderBy)(finalArgs.orderBy);
|
|
1047
|
+
}
|
|
1048
|
+
else if (hasSkip || hasTake) {
|
|
1049
|
+
sqlText += ` ORDER BY ${byFields.map((f) => (0, sql_utils_1.quoteIdentifier)(f)).join(", ")}`;
|
|
1050
|
+
}
|
|
1051
|
+
if (hasSkip || hasTake) {
|
|
1052
|
+
sqlText += ` OFFSET ${(0, sql_utils_1.toNonNegativeInt)(finalArgs.skip)} ROWS`;
|
|
1053
|
+
if (hasTake) {
|
|
1054
|
+
sqlText += ` FETCH NEXT ${(0, sql_utils_1.toNonNegativeInt)(finalArgs.take, 1)} ROWS ONLY`;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
const rows = await this.executor(sqlText, p);
|
|
1058
|
+
return rows.map((row) => {
|
|
1059
|
+
const item = {};
|
|
1060
|
+
byFields.forEach((field) => {
|
|
1061
|
+
item[field] = row[field];
|
|
1062
|
+
});
|
|
1063
|
+
if (finalArgs._count) {
|
|
1064
|
+
item._count = {};
|
|
1065
|
+
if (finalArgs._count === true || finalArgs._count._all) {
|
|
1066
|
+
item._count._all = row[`count_all`] || 0;
|
|
1067
|
+
}
|
|
1068
|
+
else {
|
|
1069
|
+
for (const key of selectedAggregateFields(finalArgs._count)) {
|
|
1070
|
+
item._count[key] = row[aggAlias("count", key)] || 0;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
if (finalArgs._sum) {
|
|
1075
|
+
item._sum = {};
|
|
1076
|
+
for (const key of selectedAggregateFields(finalArgs._sum)) {
|
|
1077
|
+
item._sum[key] = row[aggAlias("sum", key)] !== undefined ? row[aggAlias("sum", key)] : null;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
if (finalArgs._avg) {
|
|
1081
|
+
item._avg = {};
|
|
1082
|
+
for (const key of selectedAggregateFields(finalArgs._avg)) {
|
|
1083
|
+
item._avg[key] = row[aggAlias("avg", key)] !== undefined ? row[aggAlias("avg", key)] : null;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
if (finalArgs._min) {
|
|
1087
|
+
item._min = {};
|
|
1088
|
+
for (const key of selectedAggregateFields(finalArgs._min)) {
|
|
1089
|
+
item._min[key] = row[aggAlias("min", key)] !== undefined ? row[aggAlias("min", key)] : null;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
if (finalArgs._max) {
|
|
1093
|
+
item._max = {};
|
|
1094
|
+
for (const key of selectedAggregateFields(finalArgs._max)) {
|
|
1095
|
+
item._max[key] = row[aggAlias("max", key)] !== undefined ? row[aggAlias("max", key)] : null;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
return item;
|
|
1099
|
+
});
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
async sequentialUpsert(finalArgs) {
|
|
1103
|
+
const existing = await this.findUnique({ where: finalArgs.where });
|
|
1104
|
+
if (existing) {
|
|
1105
|
+
return this.update({ where: finalArgs.where, data: finalArgs.update, include: finalArgs.include });
|
|
1106
|
+
}
|
|
1107
|
+
return this.create({ data: finalArgs.create, include: finalArgs.include });
|
|
1108
|
+
}
|
|
1109
|
+
async upsert(args) {
|
|
1110
|
+
return this.orm._executeMiddleware({ model: this.modelName, action: 'upsert', args }, async (params) => {
|
|
1111
|
+
const { args: finalArgs } = params;
|
|
1112
|
+
const { where, create: createData, update: updateData, include } = finalArgs;
|
|
1113
|
+
// Filter out relation fields from data
|
|
1114
|
+
const cleanCreate = { ...createData };
|
|
1115
|
+
const cleanUpdate = { ...updateData };
|
|
1116
|
+
for (const key of Object.keys(cleanCreate)) {
|
|
1117
|
+
if (this.orm.metadata.relationMap[this.modelName]?.[key])
|
|
1118
|
+
delete cleanCreate[key];
|
|
1119
|
+
}
|
|
1120
|
+
for (const key of Object.keys(cleanUpdate)) {
|
|
1121
|
+
if (this.orm.metadata.relationMap[this.modelName]?.[key])
|
|
1122
|
+
delete cleanUpdate[key];
|
|
1123
|
+
}
|
|
1124
|
+
if (!cleanCreate.id && this.orm.metadata.modelFields[this.modelName]?.id?.ts === "string") {
|
|
1125
|
+
cleanCreate.id = (0, crypto_1.randomUUID)();
|
|
1126
|
+
}
|
|
1127
|
+
const now = new Date();
|
|
1128
|
+
if (!cleanCreate.createdAt && this.orm.metadata.modelFields[this.modelName]?.createdAt)
|
|
1129
|
+
cleanCreate.createdAt = now;
|
|
1130
|
+
if (!cleanCreate.updatedAt && this.orm.metadata.modelFields[this.modelName]?.updatedAt)
|
|
1131
|
+
cleanCreate.updatedAt = now;
|
|
1132
|
+
if (!cleanUpdate.updatedAt && this.orm.metadata.modelFields[this.modelName]?.updatedAt)
|
|
1133
|
+
cleanUpdate.updatedAt = now;
|
|
1134
|
+
const atomicWhere = flattenSimpleEqualityWhere(where);
|
|
1135
|
+
if (!atomicWhere || Object.keys(cleanUpdate).length === 0) {
|
|
1136
|
+
return this.sequentialUpsert(finalArgs);
|
|
1137
|
+
}
|
|
1138
|
+
const p = {};
|
|
1139
|
+
const allKeys = Array.from(new Set([...Object.keys(cleanCreate), ...Object.keys(cleanUpdate)]));
|
|
1140
|
+
const cParam = (k) => `c_${(0, sql_utils_1.sanitizeParamName)(k)}`;
|
|
1141
|
+
const uParam = (k) => `u_${(0, sql_utils_1.sanitizeParamName)(k)}`;
|
|
1142
|
+
for (const k of allKeys) {
|
|
1143
|
+
if (cleanCreate[k] !== undefined)
|
|
1144
|
+
p[cParam(k)] = cleanCreate[k];
|
|
1145
|
+
if (cleanUpdate[k] !== undefined)
|
|
1146
|
+
p[uParam(k)] = cleanUpdate[k];
|
|
1147
|
+
}
|
|
1148
|
+
for (const [k, v] of Object.entries(atomicWhere)) {
|
|
1149
|
+
p[`upw_${(0, sql_utils_1.sanitizeParamName)(k)}`] = v;
|
|
1150
|
+
}
|
|
1151
|
+
const sourceSelect = allKeys.map(k => {
|
|
1152
|
+
const val = cleanCreate[k] !== undefined ? `@${cParam(k)}` : (cleanUpdate[k] !== undefined ? `@${uParam(k)}` : "NULL");
|
|
1153
|
+
return `${val} as ${(0, sql_utils_1.quoteIdentifier)(k)}`;
|
|
1154
|
+
}).join(", ");
|
|
1155
|
+
const updateSets = Object.keys(cleanUpdate).map(k => `target.${(0, sql_utils_1.quoteIdentifier)(k)} = source.${(0, sql_utils_1.quoteIdentifier)(k)}`).join(", ");
|
|
1156
|
+
const insertCols = Object.keys(cleanCreate).map(k => (0, sql_utils_1.quoteIdentifier)(k)).join(", ");
|
|
1157
|
+
const insertVals = Object.keys(cleanCreate).map(k => `source.${(0, sql_utils_1.quoteIdentifier)(k)}`).join(", ");
|
|
1158
|
+
const onClause = Object.keys(atomicWhere)
|
|
1159
|
+
.map(k => `target.${(0, sql_utils_1.quoteIdentifier)(k)} = @upw_${(0, sql_utils_1.sanitizeParamName)(k)}`)
|
|
1160
|
+
.join(" AND ");
|
|
1161
|
+
const sqlText = `
|
|
1162
|
+
MERGE INTO ${this.tableName} WITH (HOLDLOCK) AS target
|
|
1163
|
+
USING (SELECT ${sourceSelect}) AS source
|
|
1164
|
+
ON (${onClause})
|
|
1165
|
+
WHEN MATCHED THEN
|
|
1166
|
+
UPDATE SET ${updateSets}
|
|
1167
|
+
WHEN NOT MATCHED THEN
|
|
1168
|
+
INSERT (${insertCols}) VALUES (${insertVals})
|
|
1169
|
+
OUTPUT inserted.*;
|
|
1170
|
+
`;
|
|
1171
|
+
try {
|
|
1172
|
+
const rows = await this.executor(sqlText, p);
|
|
1173
|
+
const result = rows[0];
|
|
1174
|
+
if (include && result) {
|
|
1175
|
+
await resolveIncludes(this.modelName, [result], include, this.executor, this.orm.metadata);
|
|
1176
|
+
}
|
|
1177
|
+
return result;
|
|
1178
|
+
}
|
|
1179
|
+
catch (err) {
|
|
1180
|
+
logger_1.logger.warn(`Atomic upsert failed, falling back to sequential: ${err.message}`);
|
|
1181
|
+
return this.sequentialUpsert(finalArgs);
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
exports.TableClient = TableClient;
|
|
1187
|
+
function addNoLockToQuery(sql, metadata) {
|
|
1188
|
+
// If it's not a SELECT query, don't modify it
|
|
1189
|
+
if (!/^\s*SELECT/i.test(sql)) {
|
|
1190
|
+
return sql;
|
|
1191
|
+
}
|
|
1192
|
+
const tableNames = Object.values(metadata.modelToTable);
|
|
1193
|
+
let modifiedSql = sql;
|
|
1194
|
+
for (const table of tableNames) {
|
|
1195
|
+
const escapedTable = table.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
1196
|
+
const regex = new RegExp(`\\b(FROM|JOIN)\\s+${escapedTable}\\b`, 'gi');
|
|
1197
|
+
modifiedSql = modifiedSql.replace(regex, (match, prefix, offset) => {
|
|
1198
|
+
const afterMatch = modifiedSql.slice(offset + match.length).trim();
|
|
1199
|
+
// If it's already followed by WITH (NOLOCK) or NOLOCK or similar, keep it.
|
|
1200
|
+
if (/^(WITH\s*\(NOLOCK\)|NOLOCK)/i.test(afterMatch)) {
|
|
1201
|
+
return match;
|
|
1202
|
+
}
|
|
1203
|
+
// Check if there is an alias (e.g. "FROM chunks c" or "FROM chunks as c")
|
|
1204
|
+
const aliasMatch = afterMatch.match(/^(?:as\s+)?([a-zA-Z0-9_]+)/i);
|
|
1205
|
+
if (aliasMatch) {
|
|
1206
|
+
const alias = aliasMatch[1];
|
|
1207
|
+
const keywords = ["where", "on", "join", "group", "order", "limit", "left", "right", "inner", "and", "or"];
|
|
1208
|
+
if (!keywords.includes(alias.toLowerCase())) {
|
|
1209
|
+
// It's an alias! Let's check if the alias itself is followed by WITH (NOLOCK)
|
|
1210
|
+
const afterAlias = afterMatch.slice(aliasMatch[0].length).trim();
|
|
1211
|
+
if (/^(WITH\s*\(NOLOCK\)|NOLOCK)/i.test(afterAlias)) {
|
|
1212
|
+
return match; // already has NOLOCK after the alias
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
return `${prefix} ${table} WITH (NOLOCK)`;
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
return modifiedSql;
|
|
1220
|
+
}
|
|
1221
|
+
// Lazy-load generated schema metadata from the ORM's own generated copy so
|
|
1222
|
+
// `new An5ORM()` resolves schema models out of the box without the core ever
|
|
1223
|
+
// importing from the generated client package (client is generated FROM the ORM).
|
|
1224
|
+
class ViewClient {
|
|
1225
|
+
constructor(viewName, rawTableName, executor, orm) {
|
|
1226
|
+
this.viewName = viewName;
|
|
1227
|
+
this.rawTableName = rawTableName;
|
|
1228
|
+
this.executor = executor;
|
|
1229
|
+
this.orm = orm;
|
|
1230
|
+
this.tableClient = new TableClient(viewName, rawTableName, executor, orm);
|
|
1231
|
+
}
|
|
1232
|
+
async findMany(args) { return this.tableClient.findMany(args); }
|
|
1233
|
+
async findFirst(args) { return this.tableClient.findFirst(args); }
|
|
1234
|
+
async findUnique(args) { return this.tableClient.findUnique(args); }
|
|
1235
|
+
async count(args) { return this.tableClient.count(args); }
|
|
1236
|
+
async aggregate(args) { return this.tableClient.aggregate(args); }
|
|
1237
|
+
async groupBy(args) { return this.tableClient.groupBy(args); }
|
|
1238
|
+
async vectorSearch(args) { return this.tableClient.vectorSearch(args); }
|
|
1239
|
+
async create() {
|
|
1240
|
+
throw new errors_1.An5ClientKnownRequestError("Views are read-only and do not support create operations.", { code: "P2000", clientVersion: "1.0.8" });
|
|
1241
|
+
}
|
|
1242
|
+
async createMany() {
|
|
1243
|
+
throw new errors_1.An5ClientKnownRequestError("Views are read-only and do not support createMany operations.", { code: "P2000", clientVersion: "1.0.8" });
|
|
1244
|
+
}
|
|
1245
|
+
async update() {
|
|
1246
|
+
throw new errors_1.An5ClientKnownRequestError("Views are read-only and do not support update operations.", { code: "P2000", clientVersion: "1.0.8" });
|
|
1247
|
+
}
|
|
1248
|
+
async updateMany() {
|
|
1249
|
+
throw new errors_1.An5ClientKnownRequestError("Views are read-only and do not support updateMany operations.", { code: "P2000", clientVersion: "1.0.8" });
|
|
1250
|
+
}
|
|
1251
|
+
async delete() {
|
|
1252
|
+
throw new errors_1.An5ClientKnownRequestError("Views are read-only and do not support delete operations.", { code: "P2000", clientVersion: "1.0.8" });
|
|
1253
|
+
}
|
|
1254
|
+
async deleteMany() {
|
|
1255
|
+
throw new errors_1.An5ClientKnownRequestError("Views are read-only and do not support deleteMany operations.", { code: "P2000", clientVersion: "1.0.8" });
|
|
1256
|
+
}
|
|
1257
|
+
async upsert() {
|
|
1258
|
+
throw new errors_1.An5ClientKnownRequestError("Views are read-only and do not support upsert operations.", { code: "P2000", clientVersion: "1.0.8" });
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
exports.ViewClient = ViewClient;
|
|
1262
|
+
function buildProcCallSql(procName, params) {
|
|
1263
|
+
const p = {};
|
|
1264
|
+
const targetProc = (0, sql_utils_1.quoteTableIdentifier)(procName);
|
|
1265
|
+
if (!params) {
|
|
1266
|
+
return { sqlText: `EXEC ${targetProc};`, p };
|
|
1267
|
+
}
|
|
1268
|
+
if (Array.isArray(params)) {
|
|
1269
|
+
const argList = params.map((val, idx) => {
|
|
1270
|
+
const key = `p_${idx}`;
|
|
1271
|
+
p[key] = val;
|
|
1272
|
+
return `@${key}`;
|
|
1273
|
+
}).join(", ");
|
|
1274
|
+
return { sqlText: `EXEC ${targetProc} ${argList};`, p };
|
|
1275
|
+
}
|
|
1276
|
+
const argList = Object.entries(params).map(([key, val], idx) => {
|
|
1277
|
+
const paramKey = (0, sql_utils_1.sanitizeParamName)(`p_${key}_${idx}`);
|
|
1278
|
+
p[paramKey] = val;
|
|
1279
|
+
return `@${(0, sql_utils_1.sanitizeParamName)(key)} = @${paramKey}`;
|
|
1280
|
+
}).join(", ");
|
|
1281
|
+
return { sqlText: `EXEC ${targetProc} ${argList};`, p };
|
|
1282
|
+
}
|
|
1283
|
+
function buildFunctionCallSql(fnName, params) {
|
|
1284
|
+
const p = {};
|
|
1285
|
+
const targetFn = (0, sql_utils_1.quoteTableIdentifier)(fnName);
|
|
1286
|
+
if (!params) {
|
|
1287
|
+
return { sqlText: `SELECT * FROM ${targetFn}();`, p };
|
|
1288
|
+
}
|
|
1289
|
+
if (Array.isArray(params)) {
|
|
1290
|
+
const argList = params.map((val, idx) => {
|
|
1291
|
+
const key = `p_${idx}`;
|
|
1292
|
+
p[key] = val;
|
|
1293
|
+
return `@${key}`;
|
|
1294
|
+
}).join(", ");
|
|
1295
|
+
return { sqlText: `SELECT * FROM ${targetFn}(${argList});`, p };
|
|
1296
|
+
}
|
|
1297
|
+
const argList = Object.entries(params).map(([key, val], idx) => {
|
|
1298
|
+
const paramKey = (0, sql_utils_1.sanitizeParamName)(`p_${key}_${idx}`);
|
|
1299
|
+
p[paramKey] = val;
|
|
1300
|
+
return `@${paramKey}`;
|
|
1301
|
+
}).join(", ");
|
|
1302
|
+
return { sqlText: `SELECT * FROM ${targetFn}(${argList});`, p };
|
|
1303
|
+
}
|
|
1304
|
+
let autoMetadata = null;
|
|
1305
|
+
function loadAutoMetadata() {
|
|
1306
|
+
if (autoMetadata)
|
|
1307
|
+
return autoMetadata;
|
|
1308
|
+
const candidates = [
|
|
1309
|
+
"an5-client/typescript/an5Metadata",
|
|
1310
|
+
"../an5Client/typescript/an5Metadata"
|
|
1311
|
+
];
|
|
1312
|
+
for (const cand of candidates) {
|
|
1313
|
+
try {
|
|
1314
|
+
const m = require(cand);
|
|
1315
|
+
if (m && m.modelToTable && Object.keys(m.modelToTable).length > 0) {
|
|
1316
|
+
autoMetadata = {
|
|
1317
|
+
modelToTable: m.modelToTable || {},
|
|
1318
|
+
relationMap: m.relationMap || {},
|
|
1319
|
+
modelFields: m.modelFields || {},
|
|
1320
|
+
};
|
|
1321
|
+
return autoMetadata;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
catch { }
|
|
1325
|
+
}
|
|
1326
|
+
autoMetadata = metadata_1.DEFAULT_METADATA;
|
|
1327
|
+
return autoMetadata;
|
|
1328
|
+
}
|
|
1329
|
+
// Proxied AN5 ORM client class
|
|
1330
|
+
class An5ORM {
|
|
1331
|
+
constructor(customExecutor, metadata, inTransaction = false, transactionControl) {
|
|
1332
|
+
this.inTransaction = inTransaction;
|
|
1333
|
+
this.transactionControl = transactionControl;
|
|
1334
|
+
this.middlewares = [];
|
|
1335
|
+
this.eventListeners = new Map();
|
|
1336
|
+
this.slowQueryThresholdMs = Number(process.env.SLOW_QUERY_THRESHOLD_MS ?? 500);
|
|
1337
|
+
if (customExecutor && typeof customExecutor === "object" && typeof customExecutor.exec === "function") {
|
|
1338
|
+
this.customExecutor = (0, adapters_1.executorFromAdapter)(customExecutor);
|
|
1339
|
+
}
|
|
1340
|
+
else if (typeof customExecutor === "function") {
|
|
1341
|
+
this.customExecutor = customExecutor;
|
|
1342
|
+
}
|
|
1343
|
+
this.metadata = metadata ?? loadAutoMetadata();
|
|
1344
|
+
if (this.metadata) {
|
|
1345
|
+
(0, adapters_1.setAdapterMetadata)({ modelToTable: this.metadata.modelToTable, modelFields: this.metadata.modelFields });
|
|
1346
|
+
}
|
|
1347
|
+
// Add default logging and telemetry event middleware
|
|
1348
|
+
this.$use(async (params, next) => {
|
|
1349
|
+
const start = Date.now();
|
|
1350
|
+
try {
|
|
1351
|
+
const result = await next(params);
|
|
1352
|
+
const duration = Date.now() - start;
|
|
1353
|
+
// Emit 'query' telemetry event
|
|
1354
|
+
const queryEvt = {
|
|
1355
|
+
timestamp: new Date(),
|
|
1356
|
+
query: params.args?.queryText || `${params.model || "raw"}.${params.action}`,
|
|
1357
|
+
params: params.args,
|
|
1358
|
+
duration,
|
|
1359
|
+
model: params.model,
|
|
1360
|
+
action: params.action,
|
|
1361
|
+
};
|
|
1362
|
+
this.$emit("query", queryEvt);
|
|
1363
|
+
// Check slow query threshold
|
|
1364
|
+
if (this.slowQueryThresholdMs > 0 && duration >= this.slowQueryThresholdMs) {
|
|
1365
|
+
this.$emit("warn", {
|
|
1366
|
+
timestamp: new Date(),
|
|
1367
|
+
message: `Slow query detected: [${params.model || "raw"}.${params.action}] took ${duration}ms (threshold: ${this.slowQueryThresholdMs}ms)`,
|
|
1368
|
+
level: "warn",
|
|
1369
|
+
duration,
|
|
1370
|
+
query: queryEvt.query,
|
|
1371
|
+
params: params.args,
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
if (process.env.DEBUG_ORM === "true") {
|
|
1375
|
+
logger_1.logger.info(`ORM [${params.model || "raw"}.${params.action}] executed in ${duration}ms`);
|
|
1376
|
+
}
|
|
1377
|
+
return result;
|
|
1378
|
+
}
|
|
1379
|
+
catch (err) {
|
|
1380
|
+
const duration = Date.now() - start;
|
|
1381
|
+
this.$emit("error", {
|
|
1382
|
+
timestamp: new Date(),
|
|
1383
|
+
message: `ORM query failed: [${params.model || "raw"}.${params.action}] after ${duration}ms`,
|
|
1384
|
+
level: "error",
|
|
1385
|
+
duration,
|
|
1386
|
+
error: err,
|
|
1387
|
+
});
|
|
1388
|
+
logger_1.logger.error(`ORM [${params.model || "raw"}.${params.action}] failed after ${duration}ms`, err);
|
|
1389
|
+
throw err;
|
|
1390
|
+
}
|
|
1391
|
+
});
|
|
1392
|
+
return new Proxy(this, {
|
|
1393
|
+
get(target, prop, receiver) {
|
|
1394
|
+
if (typeof prop === "string" && prop in target && typeof target[prop] === "function") {
|
|
1395
|
+
return target[prop].bind(target);
|
|
1396
|
+
}
|
|
1397
|
+
if (!(prop in target) && typeof prop === "string" && !prop.startsWith("_")) {
|
|
1398
|
+
let modelName = prop;
|
|
1399
|
+
let tableName = target.metadata.modelToTable[prop];
|
|
1400
|
+
if (!tableName) {
|
|
1401
|
+
const lowerProp = prop.toLowerCase();
|
|
1402
|
+
for (const [mName, tName] of Object.entries(target.metadata.modelToTable)) {
|
|
1403
|
+
const lowerModel = mName.toLowerCase();
|
|
1404
|
+
const lowerTable = tName.toLowerCase();
|
|
1405
|
+
if (lowerModel === lowerProp ||
|
|
1406
|
+
lowerTable === lowerProp ||
|
|
1407
|
+
lowerModel + "s" === lowerProp ||
|
|
1408
|
+
lowerModel + "es" === lowerProp ||
|
|
1409
|
+
lowerTable + "s" === lowerProp ||
|
|
1410
|
+
lowerTable + "es" === lowerProp) {
|
|
1411
|
+
modelName = mName;
|
|
1412
|
+
tableName = tName;
|
|
1413
|
+
break;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
if (!tableName) {
|
|
1418
|
+
tableName = prop;
|
|
1419
|
+
}
|
|
1420
|
+
target[prop] = new TableClient(modelName, tableName, target.getExecutor(), receiver);
|
|
1421
|
+
}
|
|
1422
|
+
return target[prop];
|
|
1423
|
+
}
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
$on(event, listener) {
|
|
1427
|
+
if (!this.eventListeners.has(event)) {
|
|
1428
|
+
this.eventListeners.set(event, new Set());
|
|
1429
|
+
}
|
|
1430
|
+
this.eventListeners.get(event).add(listener);
|
|
1431
|
+
}
|
|
1432
|
+
$off(event, listener) {
|
|
1433
|
+
const listeners = this.eventListeners.get(event);
|
|
1434
|
+
if (listeners) {
|
|
1435
|
+
listeners.delete(listener);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
$emit(event, payload) {
|
|
1439
|
+
const listeners = this.eventListeners.get(event);
|
|
1440
|
+
if (listeners) {
|
|
1441
|
+
listeners.forEach((fn) => {
|
|
1442
|
+
try {
|
|
1443
|
+
fn(payload);
|
|
1444
|
+
}
|
|
1445
|
+
catch (err) {
|
|
1446
|
+
logger_1.logger.error(`Error in ORM event listener for '${event}':`, err instanceof Error ? err : new Error(String(err)));
|
|
1447
|
+
}
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
getExecutor() {
|
|
1452
|
+
return this.customExecutor || getDefaultExecutor();
|
|
1453
|
+
}
|
|
1454
|
+
table(name) {
|
|
1455
|
+
const rawTable = this.metadata.modelToTable[name] || name;
|
|
1456
|
+
return new TableClient(name, rawTable, this.getExecutor(), this);
|
|
1457
|
+
}
|
|
1458
|
+
view(name) {
|
|
1459
|
+
const rawTable = this.metadata.modelToTable[name] || name;
|
|
1460
|
+
return new ViewClient(name, rawTable, this.getExecutor(), this);
|
|
1461
|
+
}
|
|
1462
|
+
$view(name) {
|
|
1463
|
+
return this.view(name);
|
|
1464
|
+
}
|
|
1465
|
+
async $queryProc(procName, params) {
|
|
1466
|
+
const executor = this.getExecutor();
|
|
1467
|
+
const { sqlText, p } = buildProcCallSql(procName, params);
|
|
1468
|
+
return executor(sqlText, p);
|
|
1469
|
+
}
|
|
1470
|
+
async $executeProc(procName, params) {
|
|
1471
|
+
const executor = this.getExecutor();
|
|
1472
|
+
const { sqlText, p } = buildProcCallSql(procName, params);
|
|
1473
|
+
if (executor.executeRaw) {
|
|
1474
|
+
return executor.executeRaw(sqlText, p);
|
|
1475
|
+
}
|
|
1476
|
+
return normalizeAffectedCount(await executor(sqlText, p));
|
|
1477
|
+
}
|
|
1478
|
+
async $queryFunction(fnName, params) {
|
|
1479
|
+
const executor = this.getExecutor();
|
|
1480
|
+
const { sqlText, p } = buildFunctionCallSql(fnName, params);
|
|
1481
|
+
return executor(sqlText, p);
|
|
1482
|
+
}
|
|
1483
|
+
$use(middleware) {
|
|
1484
|
+
this.middlewares.push(middleware);
|
|
1485
|
+
}
|
|
1486
|
+
parseWhere(modelName, where, params, prefix = "") {
|
|
1487
|
+
return (0, sql_utils_1.parseWhere)(modelName, where, params, prefix, {
|
|
1488
|
+
relationMap: this.metadata.relationMap,
|
|
1489
|
+
modelToTable: this.metadata.modelToTable,
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
async _executeMiddleware(params, finalAction) {
|
|
1493
|
+
let index = -1;
|
|
1494
|
+
const dispatch = async (i, currentParams) => {
|
|
1495
|
+
if (i <= index)
|
|
1496
|
+
throw new Error("next() called multiple times");
|
|
1497
|
+
index = i;
|
|
1498
|
+
let fn = this.middlewares[i];
|
|
1499
|
+
if (i === this.middlewares.length) {
|
|
1500
|
+
return finalAction(currentParams);
|
|
1501
|
+
}
|
|
1502
|
+
if (!fn)
|
|
1503
|
+
return;
|
|
1504
|
+
return fn(currentParams, (p) => dispatch(i + 1, p));
|
|
1505
|
+
};
|
|
1506
|
+
return dispatch(0, params);
|
|
1507
|
+
}
|
|
1508
|
+
async $connect() { }
|
|
1509
|
+
async $disconnect() {
|
|
1510
|
+
defaultExecutor = null;
|
|
1511
|
+
}
|
|
1512
|
+
async $queryRaw(queryParts, ...values) {
|
|
1513
|
+
const executor = this.getExecutor();
|
|
1514
|
+
let queryText = "";
|
|
1515
|
+
const params = {};
|
|
1516
|
+
if (Array.isArray(queryParts) && queryParts.raw !== undefined) {
|
|
1517
|
+
const strings = queryParts;
|
|
1518
|
+
for (let i = 0; i < strings.length; i++) {
|
|
1519
|
+
queryText += strings[i];
|
|
1520
|
+
if (i < values.length) {
|
|
1521
|
+
const paramName = `p_${i}`;
|
|
1522
|
+
queryText += `@${paramName}`;
|
|
1523
|
+
params[paramName] = values[i];
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
else if (typeof queryParts === "string") {
|
|
1528
|
+
queryText = queryParts;
|
|
1529
|
+
if (values && values.length > 0) {
|
|
1530
|
+
values.forEach((val, idx) => {
|
|
1531
|
+
const paramName = `p_${idx}`;
|
|
1532
|
+
params[paramName] = val;
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
else {
|
|
1537
|
+
throw new Error("Invalid query format for $queryRaw");
|
|
1538
|
+
}
|
|
1539
|
+
queryText = addNoLockToQuery(queryText, this.metadata);
|
|
1540
|
+
return executor(queryText, params);
|
|
1541
|
+
}
|
|
1542
|
+
async $queryRawUnsafe(queryText, ...values) {
|
|
1543
|
+
const executor = this.getExecutor();
|
|
1544
|
+
const params = {};
|
|
1545
|
+
if (values && values.length > 0) {
|
|
1546
|
+
values.forEach((val, idx) => {
|
|
1547
|
+
const paramName = `p_${idx}`;
|
|
1548
|
+
params[paramName] = val;
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
const modifiedQueryText = addNoLockToQuery(queryText, this.metadata);
|
|
1552
|
+
const result = await executor(modifiedQueryText, params);
|
|
1553
|
+
return result;
|
|
1554
|
+
}
|
|
1555
|
+
async $executeRaw(queryParts, ...values) {
|
|
1556
|
+
const executor = this.getExecutor();
|
|
1557
|
+
let queryText = "";
|
|
1558
|
+
const params = {};
|
|
1559
|
+
if (Array.isArray(queryParts) && queryParts.raw !== undefined) {
|
|
1560
|
+
const strings = queryParts;
|
|
1561
|
+
for (let i = 0; i < strings.length; i++) {
|
|
1562
|
+
queryText += strings[i];
|
|
1563
|
+
if (i < values.length) {
|
|
1564
|
+
const paramName = `p_${i}`;
|
|
1565
|
+
queryText += `@${paramName}`;
|
|
1566
|
+
params[paramName] = values[i];
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
else if (typeof queryParts === "string") {
|
|
1571
|
+
queryText = queryParts;
|
|
1572
|
+
if (values && values.length > 0) {
|
|
1573
|
+
values.forEach((val, idx) => {
|
|
1574
|
+
const paramName = `p_${idx}`;
|
|
1575
|
+
params[paramName] = val;
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
else {
|
|
1580
|
+
throw new Error("Invalid query format for $executeRaw");
|
|
1581
|
+
}
|
|
1582
|
+
queryText = addNoLockToQuery(queryText, this.metadata);
|
|
1583
|
+
if (executor.executeRaw) {
|
|
1584
|
+
return executor.executeRaw(queryText, params);
|
|
1585
|
+
}
|
|
1586
|
+
return normalizeAffectedCount(await executor(queryText, params));
|
|
1587
|
+
}
|
|
1588
|
+
async $executeRawUnsafe(queryText, ...values) {
|
|
1589
|
+
const executor = this.getExecutor();
|
|
1590
|
+
const params = {};
|
|
1591
|
+
if (values && values.length > 0) {
|
|
1592
|
+
values.forEach((val, idx) => {
|
|
1593
|
+
const paramName = `p_${idx}`;
|
|
1594
|
+
params[paramName] = val;
|
|
1595
|
+
});
|
|
1596
|
+
}
|
|
1597
|
+
const modifiedQueryText = addNoLockToQuery(queryText, this.metadata);
|
|
1598
|
+
if (executor.executeRaw) {
|
|
1599
|
+
return executor.executeRaw(modifiedQueryText, params);
|
|
1600
|
+
}
|
|
1601
|
+
return normalizeAffectedCount(await executor(modifiedQueryText, params));
|
|
1602
|
+
}
|
|
1603
|
+
async $transaction(fn, options) {
|
|
1604
|
+
if (Array.isArray(fn)) {
|
|
1605
|
+
return Promise.all(fn);
|
|
1606
|
+
}
|
|
1607
|
+
const executor = this.getExecutor();
|
|
1608
|
+
if (!executor.transaction) {
|
|
1609
|
+
if (this.inTransaction) {
|
|
1610
|
+
const txClient = new An5ORM(executor, this.metadata, true);
|
|
1611
|
+
return fn(txClient);
|
|
1612
|
+
}
|
|
1613
|
+
throw new Error("Transactions require an executor with transaction support");
|
|
1614
|
+
}
|
|
1615
|
+
return executor.transaction(async (txExecutor) => {
|
|
1616
|
+
const txClient = new An5ORM(txExecutor, this.metadata, true);
|
|
1617
|
+
return fn(txClient);
|
|
1618
|
+
}, options);
|
|
1619
|
+
}
|
|
1620
|
+
async $begin() {
|
|
1621
|
+
if (this.inTransaction) {
|
|
1622
|
+
throw new Error("Interactive transactions cannot be nested");
|
|
1623
|
+
}
|
|
1624
|
+
const executor = this.getExecutor();
|
|
1625
|
+
if (!executor.beginTransaction) {
|
|
1626
|
+
throw new Error("Interactive transactions require an executor with beginTransaction support");
|
|
1627
|
+
}
|
|
1628
|
+
const tx = await executor.beginTransaction();
|
|
1629
|
+
return new An5ORM(tx.executor, this.metadata, true, {
|
|
1630
|
+
commit: tx.commit,
|
|
1631
|
+
rollback: tx.rollback,
|
|
1632
|
+
});
|
|
1633
|
+
}
|
|
1634
|
+
async $commit() {
|
|
1635
|
+
if (!this.transactionControl) {
|
|
1636
|
+
throw new Error("$commit can only be called on an interactive transaction client");
|
|
1637
|
+
}
|
|
1638
|
+
const control = this.transactionControl;
|
|
1639
|
+
this.transactionControl = undefined;
|
|
1640
|
+
await control.commit();
|
|
1641
|
+
}
|
|
1642
|
+
async $rollback() {
|
|
1643
|
+
if (!this.transactionControl) {
|
|
1644
|
+
throw new Error("$rollback can only be called on an interactive transaction client");
|
|
1645
|
+
}
|
|
1646
|
+
const control = this.transactionControl;
|
|
1647
|
+
this.transactionControl = undefined;
|
|
1648
|
+
await control.rollback();
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
exports.An5ORM = An5ORM;
|
|
1652
|
+
exports.an5Orm = new An5ORM();
|
|
1653
|
+
exports.default = exports.an5Orm;
|
|
1654
|
+
//# sourceMappingURL=orm.js.map
|