@an5/adapters 0.2.5 → 0.2.6
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 +189 -305
- package/dist/an5Adapter.d.ts +85 -7
- package/dist/an5Adapter.js +736 -64
- package/dist/base/metadata.d.ts +10 -0
- package/dist/base/metadata.js +107 -0
- package/dist/base/sql.d.ts +19 -1
- package/dist/base/sql.js +152 -24
- package/dist/base/types.d.ts +4 -1
- package/dist/browser.d.ts +4 -1
- package/dist/browser.js +9 -2
- package/dist/googlesheets/adapter.d.ts +1 -0
- package/dist/googlesheets/adapter.js +9 -2
- package/dist/googlesheets/config.d.ts +4 -4
- package/dist/googlesheets/helpers.js +2 -0
- package/dist/googlesheets/parseConnectionString.js +5 -2
- package/dist/googlesheets/sqlExecutor.js +29 -24
- package/dist/googlesheets/tableClient.js +14 -5
- package/dist/index.d.ts +3 -1
- package/dist/index.js +7 -1
- package/dist/mssql/engine.js +8 -4
- package/dist/mysql/engine.js +1 -1
- package/dist/postgres/engine.js +1 -1
- package/dist/sqlite/browserEngine.d.ts +27 -0
- package/dist/sqlite/browserEngine.js +161 -0
- package/dist/sqlite/engine.js +15 -3
- package/dist/sqlite/index.d.ts +1 -0
- package/dist/sqlite/index.js +1 -0
- package/dist/typed.d.ts +5 -0
- package/dist/typed.js +2 -0
- package/dotnet/An5Adapter.cs +1089 -275
- package/dotnet/Base/Core.cs +340 -82
- package/dotnet/Mssql/MssqlEngine.cs +145 -144
- package/dotnet/Postgres/PostgresEngine.cs +121 -121
- package/golang/an5_adapter.go +314 -0
- package/golang/base/base.go +100 -0
- package/golang/base/metadata.go +116 -0
- package/golang/base/where.go +311 -0
- package/golang/go.mod +3 -0
- package/golang/mssql/mssql.go +32 -0
- package/golang/postgres/postgres.go +37 -0
- package/golang/sqlite/sqlite.go +14 -0
- package/golang/sqlite_test.go +42 -0
- package/golang/table_client.go +1329 -0
- package/package.json +17 -7
- package/python/an5_adapter.py +119 -97
- package/python/base/__init__.py +3 -3
- package/python/base/dialects.py +11 -8
- package/python/base/metadata.py +35 -10
- package/python/base/sql.py +172 -121
- package/python/mssql/__init__.py +1 -1
- package/python/mssql/provider.py +62 -62
- package/python/postgres/__init__.py +1 -1
- package/python/postgres/provider.py +15 -15
- package/python/table_client.py +619 -216
- package/typescript/src/an5Adapter.ts +1220 -505
- package/typescript/src/base/index.ts +3 -3
- package/typescript/src/base/metadata.ts +148 -28
- package/typescript/src/base/sql.ts +264 -98
- package/typescript/src/base/types.ts +27 -24
- package/typescript/src/browser.ts +10 -6
- package/typescript/src/config.ts +95 -95
- package/typescript/src/googlesheets/adapter.ts +269 -259
- package/typescript/src/googlesheets/an5SheetsAdapter.ts +3 -3
- package/typescript/src/googlesheets/config.ts +47 -47
- package/typescript/src/googlesheets/helpers.ts +144 -143
- package/typescript/src/googlesheets/index.ts +3 -3
- package/typescript/src/googlesheets/parseConnectionString.ts +48 -46
- package/typescript/src/googlesheets/retry.ts +126 -126
- package/typescript/src/googlesheets/sqlExecutor.ts +304 -301
- package/typescript/src/googlesheets/tableClient.ts +461 -455
- package/typescript/src/googlesheets/types.ts +6 -6
- package/typescript/src/index.ts +31 -27
- package/typescript/src/mssql/engine.ts +134 -132
- package/typescript/src/mssql/index.ts +1 -1
- package/typescript/src/mysql/engine.ts +80 -80
- package/typescript/src/mysql/index.ts +1 -1
- package/typescript/src/postgres/engine.ts +87 -87
- package/typescript/src/postgres/index.ts +1 -1
- package/typescript/src/sqlite/browserEngine.ts +192 -0
- package/typescript/src/sqlite/engine.ts +66 -55
- package/typescript/src/sqlite/index.ts +3 -1
- package/typescript/src/typed.ts +52 -0
package/dist/an5Adapter.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createAdapter = exports.AdapterTableClient = exports.An5AdapterTx = exports.An5Adapter = exports.setAdapterMetadata = void 0;
|
|
3
|
+
exports.createAdapter = exports.AdapterTableClient = exports.An5AdapterTx = exports.ViewClient = exports.An5Adapter = exports.createAdapterProxy = exports.setAdapterMetadata = void 0;
|
|
4
4
|
exports.createAn5Adapter = createAn5Adapter;
|
|
5
|
+
exports.createBrowserSqliteAdapter = createBrowserSqliteAdapter;
|
|
6
|
+
exports.executorFromAdapter = executorFromAdapter;
|
|
5
7
|
const uuid_1 = require("./base/uuid");
|
|
6
8
|
const googlesheets_1 = require("./googlesheets");
|
|
7
9
|
const parseConnectionString_1 = require("./googlesheets/parseConnectionString");
|
|
@@ -9,9 +11,164 @@ const sql_1 = require("./base/sql");
|
|
|
9
11
|
const metadata_1 = require("./base/metadata");
|
|
10
12
|
var metadata_2 = require("./base/metadata");
|
|
11
13
|
Object.defineProperty(exports, "setAdapterMetadata", { enumerable: true, get: function () { return metadata_2.setAdapterMetadata; } });
|
|
14
|
+
Object.defineProperty(exports, "createAdapterProxy", { enumerable: true, get: function () { return metadata_2.createAdapterProxy; } });
|
|
12
15
|
function isSheetsConfig(config) {
|
|
13
16
|
return config.spreadsheetId !== undefined;
|
|
14
17
|
}
|
|
18
|
+
function sanitizeParamName(name) {
|
|
19
|
+
const cleaned = String(name).replace(/[^A-Za-z0-9_]/g, '_');
|
|
20
|
+
return /^[A-Za-z]/.test(cleaned) ? cleaned : `p_${cleaned}`;
|
|
21
|
+
}
|
|
22
|
+
function appendUpdateSet(sets, params, col, val, dialect) {
|
|
23
|
+
const quoted = (0, sql_1.quote)(col, dialect);
|
|
24
|
+
const safeCol = sanitizeParamName(col);
|
|
25
|
+
if (val && typeof val === 'object' && !(val instanceof Date)) {
|
|
26
|
+
if (val.increment !== undefined) {
|
|
27
|
+
sets.push(`${quoted} = ${quoted} + @s_${safeCol}_inc`);
|
|
28
|
+
params[`s_${safeCol}_inc`] = val.increment;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (val.decrement !== undefined) {
|
|
32
|
+
sets.push(`${quoted} = ${quoted} - @s_${safeCol}_dec`);
|
|
33
|
+
params[`s_${safeCol}_dec`] = val.decrement;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (val.multiply !== undefined) {
|
|
37
|
+
sets.push(`${quoted} = ${quoted} * @s_${safeCol}_mul`);
|
|
38
|
+
params[`s_${safeCol}_mul`] = val.multiply;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (val.divide !== undefined) {
|
|
42
|
+
sets.push(`${quoted} = ${quoted} / @s_${safeCol}_div`);
|
|
43
|
+
params[`s_${safeCol}_div`] = val.divide;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (val.set !== undefined) {
|
|
47
|
+
sets.push(`${quoted} = @s_${safeCol}_set`);
|
|
48
|
+
params[`s_${safeCol}_set`] = val.set;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
sets.push(`${quoted} = @s_${safeCol}`);
|
|
53
|
+
params[`s_${safeCol}`] = val;
|
|
54
|
+
}
|
|
55
|
+
function selectedAggregateFields(fields) {
|
|
56
|
+
if (!fields || typeof fields !== 'object')
|
|
57
|
+
return [];
|
|
58
|
+
return Object.keys(fields).filter(key => fields[key]);
|
|
59
|
+
}
|
|
60
|
+
function normalizeByFields(by) {
|
|
61
|
+
if (typeof by === 'string')
|
|
62
|
+
return [by];
|
|
63
|
+
return Array.isArray(by) ? by.filter(field => typeof field === 'string' && field.length > 0) : [];
|
|
64
|
+
}
|
|
65
|
+
function toNonNegativeInt(value, fallback = 0) {
|
|
66
|
+
const n = Number.parseInt(String(value), 10);
|
|
67
|
+
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
68
|
+
}
|
|
69
|
+
function projectFields(row, select) {
|
|
70
|
+
if (!row || !select || typeof select !== 'object')
|
|
71
|
+
return row;
|
|
72
|
+
const projected = {};
|
|
73
|
+
for (const [key, val] of Object.entries(select)) {
|
|
74
|
+
if (val) {
|
|
75
|
+
projected[key] = row[key];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (row._count) {
|
|
79
|
+
projected._count = row._count;
|
|
80
|
+
}
|
|
81
|
+
return projected;
|
|
82
|
+
}
|
|
83
|
+
async function resolveIncludes(modelName, rows, include, adapter) {
|
|
84
|
+
if (!rows || rows.length === 0 || !include || typeof include !== 'object')
|
|
85
|
+
return;
|
|
86
|
+
const modelRelations = (0, metadata_1.getRelationsForModel)(modelName);
|
|
87
|
+
for (const [key, value] of Object.entries(include)) {
|
|
88
|
+
if (!value)
|
|
89
|
+
continue;
|
|
90
|
+
if (key === '_count') {
|
|
91
|
+
for (const row of rows) {
|
|
92
|
+
if (!row._count)
|
|
93
|
+
row._count = {};
|
|
94
|
+
}
|
|
95
|
+
for (const [relKey, relation] of Object.entries(modelRelations)) {
|
|
96
|
+
if (relation.relationType === 'many') {
|
|
97
|
+
const uniqueKeys = Array.from(new Set(rows.map(r => r[relation.localKey]).filter(k => k !== undefined && k !== null)));
|
|
98
|
+
if (uniqueKeys.length > 0) {
|
|
99
|
+
const relClient = adapter.table(relation.modelName);
|
|
100
|
+
const relatedRows = await relClient.findMany({
|
|
101
|
+
where: { [relation.foreignKey]: { in: uniqueKeys } },
|
|
102
|
+
});
|
|
103
|
+
const countMap = new Map();
|
|
104
|
+
relatedRows.forEach((r) => {
|
|
105
|
+
const k = r[relation.foreignKey];
|
|
106
|
+
countMap.set(k, (countMap.get(k) || 0) + 1);
|
|
107
|
+
});
|
|
108
|
+
rows.forEach(r => {
|
|
109
|
+
const k = r[relation.localKey];
|
|
110
|
+
r._count[relKey] = countMap.get(k) || 0;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
rows.forEach(r => { r._count[relKey] = 0; });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const relation = modelRelations[key];
|
|
121
|
+
if (!relation)
|
|
122
|
+
continue;
|
|
123
|
+
const isMany = relation.relationType === 'many';
|
|
124
|
+
const localKey = relation.localKey;
|
|
125
|
+
const foreignKey = relation.foreignKey;
|
|
126
|
+
const joinKey = isMany ? localKey : foreignKey;
|
|
127
|
+
const matchKey = isMany ? foreignKey : localKey;
|
|
128
|
+
const uniqueKeys = Array.from(new Set(rows.map(r => r[joinKey]).filter(k => k !== undefined && k !== null)));
|
|
129
|
+
if (uniqueKeys.length === 0) {
|
|
130
|
+
rows.forEach(r => { r[key] = isMany ? [] : null; });
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const subArgs = typeof value === 'object' ? { ...value } : {};
|
|
134
|
+
const nestedInclude = subArgs.include;
|
|
135
|
+
const nestedSelect = subArgs.select;
|
|
136
|
+
const subWhere = {
|
|
137
|
+
[matchKey]: { in: uniqueKeys },
|
|
138
|
+
...(subArgs.where || {}),
|
|
139
|
+
};
|
|
140
|
+
const relClient = adapter.table(relation.modelName);
|
|
141
|
+
const relatedRows = await relClient.findMany({
|
|
142
|
+
where: subWhere,
|
|
143
|
+
orderBy: subArgs.orderBy,
|
|
144
|
+
take: subArgs.take,
|
|
145
|
+
skip: subArgs.skip,
|
|
146
|
+
});
|
|
147
|
+
if (nestedInclude) {
|
|
148
|
+
await resolveIncludes(relation.modelName, relatedRows, nestedInclude, adapter);
|
|
149
|
+
}
|
|
150
|
+
const outputRows = nestedSelect && typeof nestedSelect === 'object'
|
|
151
|
+
? relatedRows.map((r) => projectFields(r, nestedSelect))
|
|
152
|
+
: relatedRows;
|
|
153
|
+
const groupMap = new Map();
|
|
154
|
+
relatedRows.forEach((r, idx) => {
|
|
155
|
+
const k = r[isMany ? foreignKey : localKey];
|
|
156
|
+
if (!groupMap.has(k))
|
|
157
|
+
groupMap.set(k, []);
|
|
158
|
+
groupMap.get(k).push(outputRows[idx]);
|
|
159
|
+
});
|
|
160
|
+
rows.forEach(r => {
|
|
161
|
+
const k = r[isMany ? localKey : foreignKey];
|
|
162
|
+
const matches = groupMap.get(k) || [];
|
|
163
|
+
if (isMany) {
|
|
164
|
+
r[key] = matches;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
r[key] = matches[0] || null;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
15
172
|
// ─── An5Adapter ────────────────────────────────────────────────────────────────────
|
|
16
173
|
class An5Adapter {
|
|
17
174
|
get dialect() {
|
|
@@ -32,14 +189,27 @@ class An5Adapter {
|
|
|
32
189
|
this._engineType = null;
|
|
33
190
|
this._engineConfig = null;
|
|
34
191
|
this.sheetsAdapter = null;
|
|
192
|
+
this._listeners = {};
|
|
193
|
+
this._middlewares = [];
|
|
35
194
|
if (isSheetsConfig(adapterConfig)) {
|
|
36
195
|
this.sheetsAdapter = new googlesheets_1.An5SheetsAdapter(adapterConfig);
|
|
37
|
-
return;
|
|
196
|
+
return (0, metadata_1.createAdapterProxy)(this, (name) => this.table(name));
|
|
197
|
+
}
|
|
198
|
+
if (adapterConfig.engine) {
|
|
199
|
+
this._engine = adapterConfig.engine;
|
|
200
|
+
this._engineType = this._engine?.dialect || 'sqlite';
|
|
201
|
+
return (0, metadata_1.createAdapterProxy)(this, (name) => this.table(name));
|
|
38
202
|
}
|
|
39
|
-
|
|
203
|
+
if (adapterConfig.db || adapterConfig.driver) {
|
|
204
|
+
const { SqliteBrowserEngine } = require('./sqlite/browserEngine.js');
|
|
205
|
+
this._engine = new SqliteBrowserEngine(adapterConfig);
|
|
206
|
+
this._engineType = 'sqlite';
|
|
207
|
+
return (0, metadata_1.createAdapterProxy)(this, (name) => this.table(name));
|
|
208
|
+
}
|
|
209
|
+
const cs = (adapterConfig.connectionString || '').trim();
|
|
40
210
|
if (cs.startsWith('googlesheets://')) {
|
|
41
211
|
this.sheetsAdapter = new googlesheets_1.An5SheetsAdapter((0, parseConnectionString_1.parseSheetsConnectionString)(cs));
|
|
42
|
-
return;
|
|
212
|
+
return (0, metadata_1.createAdapterProxy)(this, (name) => this.table(name));
|
|
43
213
|
}
|
|
44
214
|
this._engineConfig = adapterConfig;
|
|
45
215
|
if (cs.startsWith('postgres://') || cs.startsWith('postgresql://')) {
|
|
@@ -54,27 +224,28 @@ class An5Adapter {
|
|
|
54
224
|
else {
|
|
55
225
|
this._engineType = 'mssql';
|
|
56
226
|
}
|
|
227
|
+
return (0, metadata_1.createAdapterProxy)(this, (name) => this.table(name));
|
|
57
228
|
}
|
|
58
229
|
async requireEngine() {
|
|
59
230
|
if (!this._engine && this._engineType) {
|
|
60
231
|
switch (this._engineType) {
|
|
61
232
|
case 'postgres': {
|
|
62
|
-
const { PostgresEngine } = require('./postgres.js');
|
|
233
|
+
const { PostgresEngine } = require('./postgres/index.js');
|
|
63
234
|
this._engine = new PostgresEngine(this._engineConfig);
|
|
64
235
|
break;
|
|
65
236
|
}
|
|
66
237
|
case 'mysql': {
|
|
67
|
-
const { MysqlEngine } = require('./mysql.js');
|
|
238
|
+
const { MysqlEngine } = require('./mysql/index.js');
|
|
68
239
|
this._engine = new MysqlEngine(this._engineConfig);
|
|
69
240
|
break;
|
|
70
241
|
}
|
|
71
242
|
case 'sqlite': {
|
|
72
|
-
const { SqliteEngine } = require('./sqlite.js');
|
|
243
|
+
const { SqliteEngine } = require('./sqlite/index.js');
|
|
73
244
|
this._engine = new SqliteEngine(this._engineConfig);
|
|
74
245
|
break;
|
|
75
246
|
}
|
|
76
247
|
case 'mssql': {
|
|
77
|
-
const { MssqlEngine } = require('./mssql.js');
|
|
248
|
+
const { MssqlEngine } = require('./mssql/index.js');
|
|
78
249
|
this._engine = new MssqlEngine(this._engineConfig);
|
|
79
250
|
break;
|
|
80
251
|
}
|
|
@@ -164,11 +335,107 @@ class An5Adapter {
|
|
|
164
335
|
throw err;
|
|
165
336
|
}
|
|
166
337
|
}
|
|
338
|
+
async $begin() {
|
|
339
|
+
if (this.sheetsAdapter) {
|
|
340
|
+
throw new Error('Interactive transactions are not supported by the Google Sheets adapter');
|
|
341
|
+
}
|
|
342
|
+
const engine = await this.requireEngine();
|
|
343
|
+
return new An5AdapterTx(await engine.beginTransaction(), this.dialect);
|
|
344
|
+
}
|
|
167
345
|
table(modelName) {
|
|
168
346
|
if (this.sheetsAdapter)
|
|
169
347
|
return this.sheetsAdapter.table(modelName);
|
|
170
348
|
return new AdapterTableClient(this, modelName);
|
|
171
349
|
}
|
|
350
|
+
$on(event, callback) {
|
|
351
|
+
if (!this._listeners[event])
|
|
352
|
+
this._listeners[event] = [];
|
|
353
|
+
this._listeners[event].push(callback);
|
|
354
|
+
return this;
|
|
355
|
+
}
|
|
356
|
+
$off(event, callback) {
|
|
357
|
+
if (!this._listeners[event])
|
|
358
|
+
return this;
|
|
359
|
+
this._listeners[event] = this._listeners[event].filter(cb => cb !== callback);
|
|
360
|
+
return this;
|
|
361
|
+
}
|
|
362
|
+
$emit(event, ...args) {
|
|
363
|
+
if (this._listeners[event]) {
|
|
364
|
+
for (const cb of this._listeners[event]) {
|
|
365
|
+
try {
|
|
366
|
+
cb(...args);
|
|
367
|
+
}
|
|
368
|
+
catch { }
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
$use(middleware) {
|
|
373
|
+
this._middlewares.push(middleware);
|
|
374
|
+
return this;
|
|
375
|
+
}
|
|
376
|
+
view(viewName) {
|
|
377
|
+
return new ViewClient(this, viewName);
|
|
378
|
+
}
|
|
379
|
+
async $queryProc(procName, params) {
|
|
380
|
+
if (this.sheetsAdapter)
|
|
381
|
+
throw new Error('Stored procedures are not supported by the Google Sheets adapter');
|
|
382
|
+
const paramObj = {};
|
|
383
|
+
let sql = '';
|
|
384
|
+
if (Array.isArray(params)) {
|
|
385
|
+
const placeholders = params.map((v, i) => {
|
|
386
|
+
paramObj[`p_${i}`] = v;
|
|
387
|
+
return `@p_${i}`;
|
|
388
|
+
});
|
|
389
|
+
sql = this.dialect === 'postgres' || this.dialect === 'mysql'
|
|
390
|
+
? `CALL ${procName}(${placeholders.join(', ')})`
|
|
391
|
+
: `EXEC ${procName} ${placeholders.join(', ')}`;
|
|
392
|
+
}
|
|
393
|
+
else if (params && typeof params === 'object') {
|
|
394
|
+
const pairs = Object.entries(params).map(([k, v]) => {
|
|
395
|
+
paramObj[`p_${k}`] = v;
|
|
396
|
+
return `@${k} = @p_${k}`;
|
|
397
|
+
});
|
|
398
|
+
sql = this.dialect === 'postgres' || this.dialect === 'mysql'
|
|
399
|
+
? `CALL ${procName}(${Object.keys(params).map(k => `@p_${k}`).join(', ')})`
|
|
400
|
+
: `EXEC ${procName} ${pairs.join(', ')}`;
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
sql = this.dialect === 'postgres' || this.dialect === 'mysql'
|
|
404
|
+
? `CALL ${procName}()`
|
|
405
|
+
: `EXEC ${procName}`;
|
|
406
|
+
}
|
|
407
|
+
return this.exec(sql, paramObj);
|
|
408
|
+
}
|
|
409
|
+
async $executeProc(procName, params) {
|
|
410
|
+
if (this.sheetsAdapter)
|
|
411
|
+
throw new Error('Stored procedures are not supported by the Google Sheets adapter');
|
|
412
|
+
const paramObj = {};
|
|
413
|
+
let sql = '';
|
|
414
|
+
if (Array.isArray(params)) {
|
|
415
|
+
const placeholders = params.map((v, i) => {
|
|
416
|
+
paramObj[`p_${i}`] = v;
|
|
417
|
+
return `@p_${i}`;
|
|
418
|
+
});
|
|
419
|
+
sql = this.dialect === 'postgres' || this.dialect === 'mysql'
|
|
420
|
+
? `CALL ${procName}(${placeholders.join(', ')})`
|
|
421
|
+
: `EXEC ${procName} ${placeholders.join(', ')}`;
|
|
422
|
+
}
|
|
423
|
+
else if (params && typeof params === 'object') {
|
|
424
|
+
const pairs = Object.entries(params).map(([k, v]) => {
|
|
425
|
+
paramObj[`p_${k}`] = v;
|
|
426
|
+
return `@${k} = @p_${k}`;
|
|
427
|
+
});
|
|
428
|
+
sql = this.dialect === 'postgres' || this.dialect === 'mysql'
|
|
429
|
+
? `CALL ${procName}(${Object.keys(params).map(k => `@p_${k}`).join(', ')})`
|
|
430
|
+
: `EXEC ${procName} ${pairs.join(', ')}`;
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
sql = this.dialect === 'postgres' || this.dialect === 'mysql'
|
|
434
|
+
? `CALL ${procName}()`
|
|
435
|
+
: `EXEC ${procName}`;
|
|
436
|
+
}
|
|
437
|
+
return this._executeRaw(sql, paramObj);
|
|
438
|
+
}
|
|
172
439
|
async readRange(range) {
|
|
173
440
|
return this.requireSheetsAdapter().readRange(range);
|
|
174
441
|
}
|
|
@@ -186,21 +453,92 @@ class An5Adapter {
|
|
|
186
453
|
}
|
|
187
454
|
}
|
|
188
455
|
exports.An5Adapter = An5Adapter;
|
|
456
|
+
// ─── View Client (Read-Only) ────────────────────────────────────────────────────────
|
|
457
|
+
class ViewClient {
|
|
458
|
+
constructor(adapter, viewName) {
|
|
459
|
+
this.adapter = adapter;
|
|
460
|
+
this.viewName = viewName;
|
|
461
|
+
this.tableClient = new AdapterTableClient(adapter, viewName);
|
|
462
|
+
}
|
|
463
|
+
get dialect() { return this.tableClient.dialect; }
|
|
464
|
+
get tableName() { return this.tableClient.tableName; }
|
|
465
|
+
async findMany(args) {
|
|
466
|
+
return this.tableClient.findMany(args);
|
|
467
|
+
}
|
|
468
|
+
async findFirst(args) {
|
|
469
|
+
return this.tableClient.findFirst(args);
|
|
470
|
+
}
|
|
471
|
+
async findUnique(args) {
|
|
472
|
+
return this.tableClient.findUnique(args);
|
|
473
|
+
}
|
|
474
|
+
async count(args) {
|
|
475
|
+
return this.tableClient.count(args);
|
|
476
|
+
}
|
|
477
|
+
async aggregate(args) {
|
|
478
|
+
return this.tableClient.aggregate(args);
|
|
479
|
+
}
|
|
480
|
+
async groupBy(args) {
|
|
481
|
+
return this.tableClient.groupBy(args);
|
|
482
|
+
}
|
|
483
|
+
async vectorSearch(args) {
|
|
484
|
+
return this.tableClient.vectorSearch(args);
|
|
485
|
+
}
|
|
486
|
+
async create() {
|
|
487
|
+
throw new Error(`View '${this.viewName}' is read-only. Mutation operations (create) are not allowed.`);
|
|
488
|
+
}
|
|
489
|
+
async createMany() {
|
|
490
|
+
throw new Error(`View '${this.viewName}' is read-only. Mutation operations (createMany) are not allowed.`);
|
|
491
|
+
}
|
|
492
|
+
async update() {
|
|
493
|
+
throw new Error(`View '${this.viewName}' is read-only. Mutation operations (update) are not allowed.`);
|
|
494
|
+
}
|
|
495
|
+
async updateMany() {
|
|
496
|
+
throw new Error(`View '${this.viewName}' is read-only. Mutation operations (updateMany) are not allowed.`);
|
|
497
|
+
}
|
|
498
|
+
async delete() {
|
|
499
|
+
throw new Error(`View '${this.viewName}' is read-only. Mutation operations (delete) are not allowed.`);
|
|
500
|
+
}
|
|
501
|
+
async deleteMany() {
|
|
502
|
+
throw new Error(`View '${this.viewName}' is read-only. Mutation operations (deleteMany) are not allowed.`);
|
|
503
|
+
}
|
|
504
|
+
async upsert() {
|
|
505
|
+
throw new Error(`View '${this.viewName}' is read-only. Mutation operations (upsert) are not allowed.`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
exports.ViewClient = ViewClient;
|
|
189
509
|
// ─── Transaction-scoped adapter ────────────────────────────────────────────────────
|
|
190
510
|
class An5AdapterTx {
|
|
191
511
|
constructor(handle, dialect) {
|
|
192
512
|
this.handle = handle;
|
|
193
513
|
this.dialect = dialect;
|
|
514
|
+
this.closed = false;
|
|
515
|
+
return (0, metadata_1.createAdapterProxy)(this, (name) => this.table(name));
|
|
194
516
|
}
|
|
195
517
|
async exec(query, params) {
|
|
518
|
+
this.ensureOpen();
|
|
196
519
|
return this.handle.exec(query, params);
|
|
197
520
|
}
|
|
198
521
|
async _executeRaw(query, params) {
|
|
522
|
+
this.ensureOpen();
|
|
199
523
|
return this.handle.executeRaw(query, params);
|
|
200
524
|
}
|
|
525
|
+
async $commit() {
|
|
526
|
+
this.ensureOpen();
|
|
527
|
+
this.closed = true;
|
|
528
|
+
await this.handle.commit();
|
|
529
|
+
}
|
|
530
|
+
async $rollback() {
|
|
531
|
+
this.ensureOpen();
|
|
532
|
+
this.closed = true;
|
|
533
|
+
await this.handle.rollback();
|
|
534
|
+
}
|
|
201
535
|
table(modelName) {
|
|
202
536
|
return new AdapterTableClient(this, modelName);
|
|
203
537
|
}
|
|
538
|
+
ensureOpen() {
|
|
539
|
+
if (this.closed)
|
|
540
|
+
throw new Error('Transaction is already closed');
|
|
541
|
+
}
|
|
204
542
|
}
|
|
205
543
|
exports.An5AdapterTx = An5AdapterTx;
|
|
206
544
|
// ─── Table Client ──────────────────────────────────────────────────────────────────
|
|
@@ -235,6 +573,14 @@ class AdapterTableClient {
|
|
|
235
573
|
get nolock() {
|
|
236
574
|
return this.dialect === 'mssql' ? ' WITH (NOLOCK)' : '';
|
|
237
575
|
}
|
|
576
|
+
/** Build the shared context used by parseWhere to resolve relations and self references. */
|
|
577
|
+
whereContext() {
|
|
578
|
+
return {
|
|
579
|
+
relationMap: (0, metadata_1.getRelationMap)(),
|
|
580
|
+
modelToTable: (0, metadata_1.getModelToTable)(),
|
|
581
|
+
selfRef: this.tableName,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
238
584
|
async doExec(query, params) {
|
|
239
585
|
return this.adapter.exec(query, params);
|
|
240
586
|
}
|
|
@@ -243,49 +589,92 @@ class AdapterTableClient {
|
|
|
243
589
|
}
|
|
244
590
|
async findMany(args) {
|
|
245
591
|
const params = {};
|
|
246
|
-
const
|
|
592
|
+
const whereCtx = this.whereContext();
|
|
593
|
+
const whereSql = (0, sql_1.parseWhere)(this.modelName, args?.where, params, this.dialect, '', whereCtx);
|
|
247
594
|
const orderSql = (0, sql_1.buildOrderBy)(args?.orderBy, this.dialect);
|
|
248
595
|
const take = args?.take;
|
|
249
|
-
const skip = args?.skip
|
|
596
|
+
const skip = args?.skip;
|
|
597
|
+
const hasSkip = skip !== undefined && skip !== null;
|
|
598
|
+
let cols = "*";
|
|
599
|
+
if (args?.select && typeof args.select === "object") {
|
|
600
|
+
const relationKeys = (0, metadata_1.getRelationsForModel)(this.modelName);
|
|
601
|
+
const hasRelationSelect = Object.keys(args.select).some(k => k === '_count' || relationKeys[k]);
|
|
602
|
+
const fields = (0, metadata_1.getFieldsForModel)(this.modelName);
|
|
603
|
+
const selectedKeys = Object.keys(args.select).filter(k => args.select[k] === true && (Object.keys(fields).length === 0 || fields[k]));
|
|
604
|
+
if (selectedKeys.length > 0 && !hasRelationSelect) {
|
|
605
|
+
cols = selectedKeys.map(k => (0, sql_1.quote)(k, this.dialect)).join(", ");
|
|
606
|
+
}
|
|
607
|
+
}
|
|
250
608
|
let query;
|
|
251
|
-
if (take !== undefined) {
|
|
609
|
+
if (take !== undefined && !hasSkip) {
|
|
610
|
+
if (this.dialect === 'postgres' || this.dialect === 'mysql' || this.dialect === 'sqlite') {
|
|
611
|
+
query = `SELECT ${cols} FROM ${this.tableName}`;
|
|
612
|
+
if (whereSql)
|
|
613
|
+
query += ` WHERE ${whereSql}`;
|
|
614
|
+
if (orderSql)
|
|
615
|
+
query += ` ${orderSql}`;
|
|
616
|
+
query += ` LIMIT ${take}`;
|
|
617
|
+
}
|
|
618
|
+
else {
|
|
619
|
+
query = `SELECT TOP (${take}) ${cols} FROM ${this.tableName}${this.nolock}`;
|
|
620
|
+
if (whereSql)
|
|
621
|
+
query += ` WHERE ${whereSql}`;
|
|
622
|
+
if (orderSql)
|
|
623
|
+
query += ` ${orderSql}`;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
else if (hasSkip) {
|
|
252
627
|
if (this.dialect === 'postgres' || this.dialect === 'mysql' || this.dialect === 'sqlite') {
|
|
253
|
-
query = `SELECT
|
|
628
|
+
query = `SELECT ${cols} FROM ${this.tableName}`;
|
|
254
629
|
if (whereSql)
|
|
255
630
|
query += ` WHERE ${whereSql}`;
|
|
256
631
|
if (orderSql)
|
|
257
632
|
query += ` ${orderSql}`;
|
|
258
|
-
query += ` LIMIT ${take} OFFSET ${skip}`;
|
|
633
|
+
query += ` LIMIT ${take ?? 'ALL'} OFFSET ${skip}`;
|
|
259
634
|
}
|
|
260
635
|
else {
|
|
261
636
|
// mssql requires ORDER BY before OFFSET/FETCH
|
|
262
|
-
query = `SELECT
|
|
637
|
+
query = `SELECT ${cols} FROM ${this.tableName}${this.nolock}`;
|
|
263
638
|
if (whereSql)
|
|
264
639
|
query += ` WHERE ${whereSql}`;
|
|
265
640
|
query += ` ${orderSql || 'ORDER BY (SELECT NULL)'}`;
|
|
266
|
-
query += ` OFFSET ${skip} ROWS
|
|
641
|
+
query += ` OFFSET ${skip} ROWS`;
|
|
642
|
+
if (take !== undefined) {
|
|
643
|
+
query += ` FETCH NEXT ${take} ROWS ONLY`;
|
|
644
|
+
}
|
|
267
645
|
}
|
|
268
646
|
}
|
|
269
647
|
else {
|
|
270
|
-
query = `SELECT
|
|
648
|
+
query = `SELECT ${cols} FROM ${this.tableName}${this.nolock}`;
|
|
271
649
|
if (whereSql)
|
|
272
650
|
query += ` WHERE ${whereSql}`;
|
|
273
651
|
if (orderSql)
|
|
274
652
|
query += ` ${orderSql}`;
|
|
275
653
|
}
|
|
276
|
-
|
|
654
|
+
const rows = await this.doExec(query, params);
|
|
655
|
+
if (args?.include) {
|
|
656
|
+
await resolveIncludes(this.modelName, rows, args.include, this.adapter);
|
|
657
|
+
}
|
|
658
|
+
if (args?.select && typeof args.select === 'object') {
|
|
659
|
+
const relationKeys = (0, metadata_1.getRelationsForModel)(this.modelName);
|
|
660
|
+
const hasRelationSelect = Object.keys(args.select).some(k => k === '_count' || relationKeys[k]);
|
|
661
|
+
if (hasRelationSelect) {
|
|
662
|
+
await resolveIncludes(this.modelName, rows, args.select, this.adapter);
|
|
663
|
+
}
|
|
664
|
+
return rows.map((r) => projectFields(r, args.select));
|
|
665
|
+
}
|
|
666
|
+
return rows;
|
|
277
667
|
}
|
|
278
668
|
async findFirst(args) {
|
|
279
669
|
const rows = await this.findMany({ ...args, take: 1 });
|
|
280
670
|
return rows[0] ?? null;
|
|
281
671
|
}
|
|
282
672
|
async findUnique(args) {
|
|
283
|
-
return this.findFirst({ where: args.where });
|
|
673
|
+
return this.findFirst({ where: args.where, select: args.select, include: args.include });
|
|
284
674
|
}
|
|
285
675
|
async count(args) {
|
|
286
676
|
const params = {};
|
|
287
|
-
const whereSql = (0, sql_1.parseWhere)(this.modelName, args?.where, params, this.dialect);
|
|
288
|
-
// Add NOLOCK for mssql count too
|
|
677
|
+
const whereSql = (0, sql_1.parseWhere)(this.modelName, args?.where, params, this.dialect, '', this.whereContext());
|
|
289
678
|
let query = `SELECT COUNT(*) AS cnt FROM ${this.tableName}${this.nolock}`;
|
|
290
679
|
if (whereSql)
|
|
291
680
|
query += ` WHERE ${whereSql}`;
|
|
@@ -293,11 +682,20 @@ class AdapterTableClient {
|
|
|
293
682
|
return Number(rows[0]?.cnt ?? rows[0]?.CNT ?? 0);
|
|
294
683
|
}
|
|
295
684
|
async create(args) {
|
|
685
|
+
const modelRelations = (0, metadata_1.getRelationsForModel)(this.modelName);
|
|
686
|
+
const rawData = { ...args.data };
|
|
687
|
+
const relationWrites = {};
|
|
688
|
+
for (const key of Object.keys(rawData)) {
|
|
689
|
+
if (modelRelations[key] && typeof rawData[key] === 'object' && rawData[key] !== null && !(rawData[key] instanceof Date)) {
|
|
690
|
+
relationWrites[key] = rawData[key];
|
|
691
|
+
delete rawData[key];
|
|
692
|
+
}
|
|
693
|
+
}
|
|
296
694
|
const fields = (0, metadata_1.getFieldsForModel)(this.modelName);
|
|
297
695
|
const idFieldName = Object.prototype.hasOwnProperty.call(fields, 'id')
|
|
298
696
|
? 'id'
|
|
299
697
|
: Object.keys(fields).find((name) => name.endsWith('_id') || name.endsWith('Id') || name.toLowerCase() === 'id');
|
|
300
|
-
const data = { ...
|
|
698
|
+
const data = { ...rawData };
|
|
301
699
|
if (idFieldName) {
|
|
302
700
|
const fieldDef = fields[idFieldName];
|
|
303
701
|
const rawType = typeof fieldDef === 'string' ? fieldDef : (fieldDef?.ts || fieldDef?.sql || fieldDef?.type || '');
|
|
@@ -316,12 +714,41 @@ class AdapterTableClient {
|
|
|
316
714
|
}
|
|
317
715
|
const query = `INSERT INTO ${this.tableName} (${cols.map(c => (0, sql_1.quote)(c, this.dialect)).join(', ')}) VALUES (${vals.join(', ')})`;
|
|
318
716
|
await this.doExec(query, params);
|
|
319
|
-
|
|
717
|
+
const createdRecord = (await this.findFirst({ where: idFieldName ? { [idFieldName]: data[idFieldName] } : data }));
|
|
718
|
+
// Handle nested relation writes
|
|
719
|
+
for (const [relKey, relWrite] of Object.entries(relationWrites)) {
|
|
720
|
+
const relation = modelRelations[relKey];
|
|
721
|
+
if (!relation)
|
|
722
|
+
continue;
|
|
723
|
+
const childClient = this.adapter.table(relation.modelName);
|
|
724
|
+
if (relWrite.create) {
|
|
725
|
+
const createItems = Array.isArray(relWrite.create) ? relWrite.create : [relWrite.create];
|
|
726
|
+
for (const item of createItems) {
|
|
727
|
+
await childClient.create({
|
|
728
|
+
data: {
|
|
729
|
+
...item,
|
|
730
|
+
[relation.foreignKey]: createdRecord[relation.localKey],
|
|
731
|
+
},
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
if (args.include) {
|
|
737
|
+
await resolveIncludes(this.modelName, [createdRecord], args.include, this.adapter);
|
|
738
|
+
}
|
|
739
|
+
if (args.select) {
|
|
740
|
+
await resolveIncludes(this.modelName, [createdRecord], args.select, this.adapter);
|
|
741
|
+
return projectFields(createdRecord, args.select);
|
|
742
|
+
}
|
|
743
|
+
return createdRecord;
|
|
320
744
|
}
|
|
321
745
|
async createMany(args) {
|
|
322
746
|
if (args.data.length === 0)
|
|
323
747
|
return { count: 0 };
|
|
324
|
-
const
|
|
748
|
+
const firstRow = args.data[0];
|
|
749
|
+
if (firstRow === undefined)
|
|
750
|
+
return { count: 0 };
|
|
751
|
+
const firstCols = Object.keys(firstRow).filter(k => firstRow[k] !== undefined);
|
|
325
752
|
// Bulk INSERT for the common case (same columns, no skipDuplicates)
|
|
326
753
|
if (firstCols.length > 0 && !args.skipDuplicates) {
|
|
327
754
|
const params = {};
|
|
@@ -359,29 +786,80 @@ class AdapterTableClient {
|
|
|
359
786
|
return { count };
|
|
360
787
|
}
|
|
361
788
|
async update(args) {
|
|
362
|
-
const
|
|
363
|
-
const
|
|
364
|
-
const
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
789
|
+
const modelRelations = (0, metadata_1.getRelationsForModel)(this.modelName);
|
|
790
|
+
const rawData = { ...args.data };
|
|
791
|
+
const relationWrites = {};
|
|
792
|
+
for (const key of Object.keys(rawData)) {
|
|
793
|
+
if (modelRelations[key] && typeof rawData[key] === 'object' && rawData[key] !== null && !(rawData[key] instanceof Date)) {
|
|
794
|
+
relationWrites[key] = rawData[key];
|
|
795
|
+
delete rawData[key];
|
|
796
|
+
}
|
|
370
797
|
}
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
798
|
+
const setCols = Object.keys(rawData).filter(k => rawData[k] !== undefined);
|
|
799
|
+
if (setCols.length > 0) {
|
|
800
|
+
const params = {};
|
|
801
|
+
const whereSql = (0, sql_1.parseWhere)(this.modelName, args.where, params, this.dialect, 'w_', this.whereContext());
|
|
802
|
+
const sets = [];
|
|
803
|
+
for (const col of setCols) {
|
|
804
|
+
appendUpdateSet(sets, params, col, rawData[col], this.dialect);
|
|
805
|
+
}
|
|
806
|
+
if (sets.length > 0) {
|
|
807
|
+
const query = `UPDATE ${this.tableName} SET ${sets.join(', ')}${whereSql ? ` WHERE ${whereSql}` : ''}`;
|
|
808
|
+
await this.doExecuteRaw(query, params);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
const updatedRecord = (await this.findFirst({ where: args.where }));
|
|
812
|
+
// Handle nested writes
|
|
813
|
+
for (const [relKey, relWrite] of Object.entries(relationWrites)) {
|
|
814
|
+
const relation = modelRelations[relKey];
|
|
815
|
+
if (!relation)
|
|
816
|
+
continue;
|
|
817
|
+
const childClient = this.adapter.table(relation.modelName);
|
|
818
|
+
if (relWrite.create) {
|
|
819
|
+
const createItems = Array.isArray(relWrite.create) ? relWrite.create : [relWrite.create];
|
|
820
|
+
for (const item of createItems) {
|
|
821
|
+
await childClient.create({
|
|
822
|
+
data: {
|
|
823
|
+
...item,
|
|
824
|
+
[relation.foreignKey]: updatedRecord[relation.localKey],
|
|
825
|
+
},
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
if (relWrite.update) {
|
|
830
|
+
const updateObj = relWrite.update;
|
|
831
|
+
await childClient.update({
|
|
832
|
+
where: updateObj.where,
|
|
833
|
+
data: updateObj.data,
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
if (relWrite.disconnect) {
|
|
837
|
+
const disc = relWrite.disconnect;
|
|
838
|
+
await childClient.updateMany({
|
|
839
|
+
where: disc,
|
|
840
|
+
data: { [relation.foreignKey]: null },
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
if (args.include) {
|
|
845
|
+
await resolveIncludes(this.modelName, [updatedRecord], args.include, this.adapter);
|
|
846
|
+
}
|
|
847
|
+
if (args.select) {
|
|
848
|
+
await resolveIncludes(this.modelName, [updatedRecord], args.select, this.adapter);
|
|
849
|
+
return projectFields(updatedRecord, args.select);
|
|
850
|
+
}
|
|
851
|
+
return updatedRecord;
|
|
374
852
|
}
|
|
375
853
|
async updateMany(args) {
|
|
376
854
|
const params = {};
|
|
377
|
-
const whereSql = (0, sql_1.parseWhere)(this.modelName, args.where, params, this.dialect, 'w_');
|
|
855
|
+
const whereSql = (0, sql_1.parseWhere)(this.modelName, args.where, params, this.dialect, 'w_', this.whereContext());
|
|
378
856
|
const setCols = Object.keys(args.data).filter(k => args.data[k] !== undefined);
|
|
379
857
|
const sets = [];
|
|
380
858
|
for (const col of setCols) {
|
|
381
|
-
|
|
382
|
-
params[p] = args.data[col];
|
|
383
|
-
sets.push(`${(0, sql_1.quote)(col, this.dialect)} = @${p}`);
|
|
859
|
+
appendUpdateSet(sets, params, col, args.data[col], this.dialect);
|
|
384
860
|
}
|
|
861
|
+
if (sets.length === 0)
|
|
862
|
+
return { count: 0 };
|
|
385
863
|
const query = `UPDATE ${this.tableName} SET ${sets.join(', ')}${whereSql ? ` WHERE ${whereSql}` : ''}`;
|
|
386
864
|
const count = await this.doExecuteRaw(query, params);
|
|
387
865
|
return { count };
|
|
@@ -389,13 +867,13 @@ class AdapterTableClient {
|
|
|
389
867
|
async delete(args) {
|
|
390
868
|
const existing = await this.findFirst({ where: args.where });
|
|
391
869
|
const params = {};
|
|
392
|
-
const whereSql = (0, sql_1.parseWhere)(this.modelName, args.where, params, this.dialect);
|
|
870
|
+
const whereSql = (0, sql_1.parseWhere)(this.modelName, args.where, params, this.dialect, '', this.whereContext());
|
|
393
871
|
await this.doExecuteRaw(`DELETE FROM ${this.tableName} WHERE ${whereSql}`, params);
|
|
394
872
|
return existing;
|
|
395
873
|
}
|
|
396
874
|
async deleteMany(args) {
|
|
397
875
|
const params = {};
|
|
398
|
-
const whereSql = (0, sql_1.parseWhere)(this.modelName, args?.where, params, this.dialect);
|
|
876
|
+
const whereSql = (0, sql_1.parseWhere)(this.modelName, args?.where, params, this.dialect, '', this.whereContext());
|
|
399
877
|
const query = `DELETE FROM ${this.tableName}${whereSql ? ` WHERE ${whereSql}` : ''}`;
|
|
400
878
|
const count = await this.doExecuteRaw(query, params);
|
|
401
879
|
return { count };
|
|
@@ -408,37 +886,173 @@ class AdapterTableClient {
|
|
|
408
886
|
}
|
|
409
887
|
async aggregate(args) {
|
|
410
888
|
const params = {};
|
|
411
|
-
const whereSql = (0, sql_1.parseWhere)(this.modelName, args?.where, params, this.dialect);
|
|
889
|
+
const whereSql = (0, sql_1.parseWhere)(this.modelName, args?.where, params, this.dialect, '', this.whereContext());
|
|
412
890
|
const aggs = [];
|
|
413
|
-
if (args._count)
|
|
414
|
-
aggs.push('COUNT(*) AS
|
|
891
|
+
if (args._count) {
|
|
892
|
+
aggs.push('COUNT(*) AS cnt_all');
|
|
893
|
+
if (typeof args._count === 'object') {
|
|
894
|
+
for (const f of selectedAggregateFields(args._count)) {
|
|
895
|
+
if (f !== '_all')
|
|
896
|
+
aggs.push(`COUNT(${(0, sql_1.quote)(f, this.dialect)}) AS cnt_${sanitizeParamName(f)}`);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
415
900
|
if (args._sum)
|
|
416
|
-
for (const f of
|
|
417
|
-
aggs.push(`SUM(${(0, sql_1.quote)(f, this.dialect)}) AS
|
|
901
|
+
for (const f of selectedAggregateFields(args._sum))
|
|
902
|
+
aggs.push(`SUM(${(0, sql_1.quote)(f, this.dialect)}) AS sum_${sanitizeParamName(f)}`);
|
|
418
903
|
if (args._avg)
|
|
419
|
-
for (const f of
|
|
420
|
-
aggs.push(`AVG(${(0, sql_1.quote)(f, this.dialect)}) AS
|
|
904
|
+
for (const f of selectedAggregateFields(args._avg))
|
|
905
|
+
aggs.push(`AVG(${(0, sql_1.quote)(f, this.dialect)}) AS avg_${sanitizeParamName(f)}`);
|
|
421
906
|
if (args._min)
|
|
422
|
-
for (const f of
|
|
423
|
-
aggs.push(`MIN(${(0, sql_1.quote)(f, this.dialect)}) AS
|
|
907
|
+
for (const f of selectedAggregateFields(args._min))
|
|
908
|
+
aggs.push(`MIN(${(0, sql_1.quote)(f, this.dialect)}) AS min_${sanitizeParamName(f)}`);
|
|
424
909
|
if (args._max)
|
|
425
|
-
for (const f of
|
|
426
|
-
aggs.push(`MAX(${(0, sql_1.quote)(f, this.dialect)}) AS
|
|
427
|
-
|
|
910
|
+
for (const f of selectedAggregateFields(args._max))
|
|
911
|
+
aggs.push(`MAX(${(0, sql_1.quote)(f, this.dialect)}) AS max_${sanitizeParamName(f)}`);
|
|
912
|
+
if (aggs.length === 0)
|
|
913
|
+
throw new Error('Aggregate requires at least one aggregator field');
|
|
914
|
+
const query = `SELECT ${aggs.join(', ')} FROM ${this.tableName}${this.nolock}${whereSql ? ` WHERE ${whereSql}` : ''}`;
|
|
428
915
|
const rows = await this.doExec(query, params);
|
|
429
|
-
|
|
916
|
+
const row = rows[0] || {};
|
|
917
|
+
const result = {};
|
|
918
|
+
if (args._count) {
|
|
919
|
+
const allCount = Number(row.cnt_all ?? row._count ?? 0);
|
|
920
|
+
result._count = { _all: allCount };
|
|
921
|
+
if (typeof args._count === 'object') {
|
|
922
|
+
for (const f of selectedAggregateFields(args._count)) {
|
|
923
|
+
if (f === '_all')
|
|
924
|
+
continue;
|
|
925
|
+
const k = `cnt_${sanitizeParamName(f)}`;
|
|
926
|
+
result._count[f] = row[k] !== undefined ? Number(row[k]) : allCount;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
if (args._sum) {
|
|
931
|
+
result._sum = {};
|
|
932
|
+
for (const f of selectedAggregateFields(args._sum)) {
|
|
933
|
+
const k = `sum_${sanitizeParamName(f)}`;
|
|
934
|
+
result._sum[f] = row[k] !== null && row[k] !== undefined ? Number(row[k]) : null;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
if (args._avg) {
|
|
938
|
+
result._avg = {};
|
|
939
|
+
for (const f of selectedAggregateFields(args._avg)) {
|
|
940
|
+
const k = `avg_${sanitizeParamName(f)}`;
|
|
941
|
+
result._avg[f] = row[k] !== null && row[k] !== undefined ? Number(row[k]) : null;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
if (args._min) {
|
|
945
|
+
result._min = {};
|
|
946
|
+
for (const f of selectedAggregateFields(args._min)) {
|
|
947
|
+
const k = `min_${sanitizeParamName(f)}`;
|
|
948
|
+
result._min[f] = row[k] ?? null;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
if (args._max) {
|
|
952
|
+
result._max = {};
|
|
953
|
+
for (const f of selectedAggregateFields(args._max)) {
|
|
954
|
+
const k = `max_${sanitizeParamName(f)}`;
|
|
955
|
+
result._max[f] = row[k] ?? null;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
return result;
|
|
430
959
|
}
|
|
431
960
|
async groupBy(args) {
|
|
432
961
|
const params = {};
|
|
433
962
|
const whereSql = (0, sql_1.parseWhere)(this.modelName, args?.where, params, this.dialect);
|
|
434
|
-
const
|
|
435
|
-
|
|
963
|
+
const byFields = normalizeByFields(args?.by);
|
|
964
|
+
if (byFields.length === 0)
|
|
965
|
+
throw new Error("groupBy requires 'by' fields");
|
|
966
|
+
const byCols = byFields.map((b) => (0, sql_1.quote)(b, this.dialect)).join(', ');
|
|
967
|
+
const aggs = ['COUNT(*) AS _count'];
|
|
968
|
+
if (args?._sum)
|
|
969
|
+
for (const f of selectedAggregateFields(args._sum))
|
|
970
|
+
aggs.push(`SUM(${(0, sql_1.quote)(f, this.dialect)}) AS _sum_${f}`);
|
|
971
|
+
if (args?._avg)
|
|
972
|
+
for (const f of selectedAggregateFields(args._avg))
|
|
973
|
+
aggs.push(`AVG(${(0, sql_1.quote)(f, this.dialect)}) AS _avg_${f}`);
|
|
974
|
+
if (args?._min)
|
|
975
|
+
for (const f of selectedAggregateFields(args._min))
|
|
976
|
+
aggs.push(`MIN(${(0, sql_1.quote)(f, this.dialect)}) AS _min_${f}`);
|
|
977
|
+
if (args?._max)
|
|
978
|
+
for (const f of selectedAggregateFields(args._max))
|
|
979
|
+
aggs.push(`MAX(${(0, sql_1.quote)(f, this.dialect)}) AS _max_${f}`);
|
|
980
|
+
let query = `SELECT ${byCols}, ${aggs.join(', ')} FROM ${this.tableName}${whereSql ? ` WHERE ${whereSql}` : ''} GROUP BY ${byCols}`;
|
|
981
|
+
const orderSql = (0, sql_1.buildOrderBy)(args?.orderBy, this.dialect);
|
|
982
|
+
const hasSkip = args?.skip !== undefined && args?.skip !== null;
|
|
983
|
+
const hasTake = args?.take !== undefined && args?.take !== null;
|
|
984
|
+
if (orderSql)
|
|
985
|
+
query += ` ${orderSql}`;
|
|
986
|
+
else if (hasSkip || hasTake)
|
|
987
|
+
query += ` ORDER BY ${byCols}`;
|
|
988
|
+
if (hasSkip || hasTake) {
|
|
989
|
+
const skip = toNonNegativeInt(args?.skip);
|
|
990
|
+
const take = toNonNegativeInt(args?.take, 1);
|
|
991
|
+
if (this.dialect === 'postgres' || this.dialect === 'mysql' || this.dialect === 'sqlite') {
|
|
992
|
+
if (hasTake)
|
|
993
|
+
query += ` LIMIT ${take}`;
|
|
994
|
+
query += ` OFFSET ${skip}`;
|
|
995
|
+
}
|
|
996
|
+
else {
|
|
997
|
+
query += ` OFFSET ${skip} ROWS`;
|
|
998
|
+
if (hasTake)
|
|
999
|
+
query += ` FETCH NEXT ${take} ROWS ONLY`;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
436
1002
|
return this.doExec(query, params);
|
|
437
1003
|
}
|
|
438
1004
|
async vectorSearch(args) {
|
|
439
|
-
const rows = await this.findMany({ where: args.where });
|
|
440
1005
|
const vectorField = args.vectorField || 'embedding';
|
|
441
|
-
const
|
|
1006
|
+
const METRICS = ['cosine', 'euclidean', 'dot'];
|
|
1007
|
+
const ELEMENT_TYPES = ['float32', 'float16', 'uint8'];
|
|
1008
|
+
const metric = METRICS.includes(args.distanceMetric) ? args.distanceMetric : 'cosine';
|
|
1009
|
+
const elementType = ELEMENT_TYPES.includes(args.vectorElementType) ? args.vectorElementType : 'float32';
|
|
1010
|
+
const take = args.take ?? 10;
|
|
1011
|
+
const dim = Array.isArray(args.vector) ? args.vector.length : 0;
|
|
1012
|
+
const vectorJson = JSON.stringify(args.vector);
|
|
1013
|
+
const col = (0, sql_1.quote)(vectorField, this.dialect);
|
|
1014
|
+
const params = { query_vector: vectorJson };
|
|
1015
|
+
// 1. Try native dialect execution
|
|
1016
|
+
if (this.dialect === 'postgres') {
|
|
1017
|
+
const op = metric === 'cosine' ? '<=>' : metric === 'dot' ? '<#>' : '<->';
|
|
1018
|
+
const whereSql = (0, sql_1.parseWhere)(this.modelName, args.where, params, this.dialect, '', this.whereContext());
|
|
1019
|
+
const query = `SELECT *, (${col} ${op} @query_vector::vector) AS distance FROM ${this.tableName}${whereSql ? ` WHERE ${whereSql}` : ''} ORDER BY distance ASC LIMIT ${take}`;
|
|
1020
|
+
try {
|
|
1021
|
+
return await this.doExec(query, params);
|
|
1022
|
+
}
|
|
1023
|
+
catch (err) {
|
|
1024
|
+
const msg = String(err?.message || '').toLowerCase();
|
|
1025
|
+
if (!msg.includes('vector') && !msg.includes('operator does not exist'))
|
|
1026
|
+
throw err;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
else if (this.dialect === 'mssql') {
|
|
1030
|
+
const distFn = metric === 'cosine' ? 'cosine' : metric === 'euclidean' ? 'euclidean' : 'dot';
|
|
1031
|
+
const whereSql = (0, sql_1.parseWhere)(this.modelName, args.where, params, this.dialect, '', this.whereContext());
|
|
1032
|
+
const query = `SELECT TOP (${take}) *, VECTOR_DISTANCE('${distFn}', ${col}, CAST(@query_vector AS VECTOR(${dim}, ${elementType}))) AS distance FROM ${this.tableName}${this.nolock}${whereSql ? ` WHERE ${whereSql}` : ''} ORDER BY distance ASC`;
|
|
1033
|
+
try {
|
|
1034
|
+
return await this.doExec(query, params);
|
|
1035
|
+
}
|
|
1036
|
+
catch (err) {
|
|
1037
|
+
const msg = String(err?.message || '').toLowerCase();
|
|
1038
|
+
const isUnsupported = msg.includes('vector_distance') ||
|
|
1039
|
+
msg.includes('syntax near') ||
|
|
1040
|
+
msg.includes('not a recognized built-in function') ||
|
|
1041
|
+
msg.includes('not a defined system type') ||
|
|
1042
|
+
msg.includes('type "vector"') ||
|
|
1043
|
+
msg.includes('pgvector') ||
|
|
1044
|
+
msg.includes('operator does not exist') ||
|
|
1045
|
+
msg.includes('limit of 1998') ||
|
|
1046
|
+
err?.number === 195 ||
|
|
1047
|
+
err?.number === 102 ||
|
|
1048
|
+
err?.number === 319 ||
|
|
1049
|
+
err?.originalError?.number === 319;
|
|
1050
|
+
if (!isUnsupported)
|
|
1051
|
+
throw err;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
// 2. Fallback: in-memory similarity computation when native vector support is unavailable
|
|
1055
|
+
const rows = await this.findMany({ where: args.where });
|
|
442
1056
|
const scored = [];
|
|
443
1057
|
for (const row of rows) {
|
|
444
1058
|
const raw = row[vectorField];
|
|
@@ -455,30 +1069,88 @@ class AdapterTableClient {
|
|
|
455
1069
|
continue;
|
|
456
1070
|
let dot = 0, m1 = 0, m2 = 0;
|
|
457
1071
|
for (let i = 0; i < args.vector.length; i++) {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
1072
|
+
const av = args.vector[i] ?? 0;
|
|
1073
|
+
const bv = vec[i] ?? 0;
|
|
1074
|
+
dot += av * bv;
|
|
1075
|
+
m1 += av ** 2;
|
|
1076
|
+
m2 += bv ** 2;
|
|
461
1077
|
}
|
|
462
1078
|
const cosine = m1 && m2 ? dot / (Math.sqrt(m1) * Math.sqrt(m2)) : 0;
|
|
463
1079
|
const dist = metric === 'cosine'
|
|
464
1080
|
? 1 - cosine
|
|
465
1081
|
: metric === 'dot'
|
|
466
1082
|
? -dot
|
|
467
|
-
: Math.sqrt(args.vector.reduce((s, v, i) => s + (v - vec[i]) ** 2, 0));
|
|
1083
|
+
: Math.sqrt(args.vector.reduce((s, v, i) => s + (v - (vec[i] ?? 0)) ** 2, 0));
|
|
468
1084
|
scored.push({ row, dist });
|
|
469
1085
|
}
|
|
470
1086
|
scored.sort((a, b) => a.dist - b.dist);
|
|
471
|
-
return scored.slice(0,
|
|
1087
|
+
return scored.slice(0, take).map(s => ({ ...s.row, distance: s.dist }));
|
|
472
1088
|
}
|
|
473
1089
|
}
|
|
474
1090
|
exports.AdapterTableClient = AdapterTableClient;
|
|
475
|
-
// ─── Factory ──────────────────────────────────────────────────────────────────────
|
|
476
1091
|
function createAn5Adapter(config) {
|
|
477
1092
|
if (isSheetsConfig(config))
|
|
478
1093
|
return new googlesheets_1.An5SheetsAdapter(config);
|
|
479
|
-
if (config.connectionString.trim().startsWith('googlesheets://')) {
|
|
1094
|
+
if (config.connectionString && config.connectionString.trim().startsWith('googlesheets://')) {
|
|
480
1095
|
return new googlesheets_1.An5SheetsAdapter((0, parseConnectionString_1.parseSheetsConnectionString)(config.connectionString));
|
|
481
1096
|
}
|
|
482
1097
|
return new An5Adapter(config);
|
|
483
1098
|
}
|
|
484
1099
|
exports.createAdapter = createAn5Adapter;
|
|
1100
|
+
function createBrowserSqliteAdapter(config) {
|
|
1101
|
+
const { SqliteBrowserEngine } = require('./sqlite/browserEngine.js');
|
|
1102
|
+
return new An5Adapter({ engine: new SqliteBrowserEngine(config) });
|
|
1103
|
+
}
|
|
1104
|
+
function normalizeAffectedCount(result) {
|
|
1105
|
+
if (typeof result === 'number')
|
|
1106
|
+
return result;
|
|
1107
|
+
if (!result)
|
|
1108
|
+
return 0;
|
|
1109
|
+
if (Array.isArray(result.rowsAffected))
|
|
1110
|
+
return Number(result.rowsAffected[0] ?? 0);
|
|
1111
|
+
if (typeof result.rowsAffected === 'number')
|
|
1112
|
+
return result.rowsAffected;
|
|
1113
|
+
if (typeof result.count === 'number')
|
|
1114
|
+
return result.count;
|
|
1115
|
+
return 0;
|
|
1116
|
+
}
|
|
1117
|
+
function executorFromAdapter(adapterLike) {
|
|
1118
|
+
if (!adapterLike)
|
|
1119
|
+
return adapterLike;
|
|
1120
|
+
if (typeof adapterLike === 'function' && adapterLike.executeRaw) {
|
|
1121
|
+
return adapterLike;
|
|
1122
|
+
}
|
|
1123
|
+
return Object.assign(async (queryText, params) => {
|
|
1124
|
+
return typeof adapterLike === 'function'
|
|
1125
|
+
? adapterLike(queryText, params)
|
|
1126
|
+
: adapterLike.exec(queryText, params);
|
|
1127
|
+
}, {
|
|
1128
|
+
executeRaw: async (queryText, params) => {
|
|
1129
|
+
if (typeof adapterLike._executeRaw === 'function') {
|
|
1130
|
+
return adapterLike._executeRaw(queryText, params);
|
|
1131
|
+
}
|
|
1132
|
+
if (typeof adapterLike.executeRaw === 'function') {
|
|
1133
|
+
return adapterLike.executeRaw(queryText, params);
|
|
1134
|
+
}
|
|
1135
|
+
const res = typeof adapterLike === 'function'
|
|
1136
|
+
? await adapterLike(queryText, params)
|
|
1137
|
+
: await adapterLike.exec(queryText, params);
|
|
1138
|
+
return normalizeAffectedCount(res);
|
|
1139
|
+
},
|
|
1140
|
+
transaction: adapterLike.$transaction
|
|
1141
|
+
? async (fn, options) => {
|
|
1142
|
+
return adapterLike.$transaction(async (tx) => fn(executorFromAdapter(tx)), options);
|
|
1143
|
+
}
|
|
1144
|
+
: undefined,
|
|
1145
|
+
beginTransaction: adapterLike.$begin
|
|
1146
|
+
? async () => {
|
|
1147
|
+
const tx = await adapterLike.$begin();
|
|
1148
|
+
return {
|
|
1149
|
+
executor: executorFromAdapter(tx),
|
|
1150
|
+
commit: () => tx.$commit(),
|
|
1151
|
+
rollback: () => tx.$rollback(),
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
: undefined,
|
|
1155
|
+
});
|
|
1156
|
+
}
|