@an5/adapters 0.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 +263 -0
- package/dotnet/An5Adapter.cs +275 -0
- package/dotnet/Base/Core.cs +82 -0
- package/dotnet/Mssql/MssqlEngine.cs +144 -0
- package/dotnet/Postgres/PostgresEngine.cs +121 -0
- package/package.json +46 -0
- package/python/an5_adapter.py +97 -0
- package/python/base/__init__.py +3 -0
- package/python/base/dialects.py +8 -0
- package/python/base/metadata.py +9 -0
- package/python/base/sql.py +121 -0
- package/python/mssql/__init__.py +1 -0
- package/python/mssql/provider.py +62 -0
- package/python/postgres/__init__.py +1 -0
- package/python/postgres/provider.py +15 -0
- package/python/table_client.py +209 -0
- package/typescript/an5Adapter.d.ts +117 -0
- package/typescript/an5Adapter.js +447 -0
- package/typescript/an5Adapter.ts +474 -0
- package/typescript/base/index.d.ts +3 -0
- package/typescript/base/index.js +19 -0
- package/typescript/base/index.ts +3 -0
- package/typescript/base/metadata.d.ts +14 -0
- package/typescript/base/metadata.js +17 -0
- package/typescript/base/metadata.ts +28 -0
- package/typescript/base/sql.d.ts +4 -0
- package/typescript/base/sql.js +143 -0
- package/typescript/base/sql.ts +98 -0
- package/typescript/base/types.d.ts +21 -0
- package/typescript/base/types.js +2 -0
- package/typescript/base/types.ts +24 -0
- package/typescript/config.d.ts +17 -0
- package/typescript/config.js +92 -0
- package/typescript/config.ts +95 -0
- package/typescript/googlesheets/adapter.d.ts +31 -0
- package/typescript/googlesheets/adapter.js +138 -0
- package/typescript/googlesheets/adapter.ts +167 -0
- package/typescript/googlesheets/an5SheetsAdapter.d.ts +3 -0
- package/typescript/googlesheets/an5SheetsAdapter.js +8 -0
- package/typescript/googlesheets/an5SheetsAdapter.ts +3 -0
- package/typescript/googlesheets/config.d.ts +17 -0
- package/typescript/googlesheets/config.js +29 -0
- package/typescript/googlesheets/config.ts +33 -0
- package/typescript/googlesheets/helpers.d.ts +9 -0
- package/typescript/googlesheets/helpers.js +155 -0
- package/typescript/googlesheets/helpers.ts +124 -0
- package/typescript/googlesheets/index.d.ts +2 -0
- package/typescript/googlesheets/index.js +9 -0
- package/typescript/googlesheets/index.ts +2 -0
- package/typescript/googlesheets/parseConnectionString.d.ts +11 -0
- package/typescript/googlesheets/parseConnectionString.js +44 -0
- package/typescript/googlesheets/parseConnectionString.ts +44 -0
- package/typescript/googlesheets/retry.d.ts +3 -0
- package/typescript/googlesheets/retry.js +26 -0
- package/typescript/googlesheets/retry.ts +23 -0
- package/typescript/googlesheets/sqlExecutor.d.ts +12 -0
- package/typescript/googlesheets/sqlExecutor.js +306 -0
- package/typescript/googlesheets/sqlExecutor.ts +301 -0
- package/typescript/googlesheets/tableClient.d.ts +78 -0
- package/typescript/googlesheets/tableClient.js +394 -0
- package/typescript/googlesheets/tableClient.ts +434 -0
- package/typescript/googlesheets/types.d.ts +4 -0
- package/typescript/googlesheets/types.js +3 -0
- package/typescript/googlesheets/types.ts +6 -0
- package/typescript/index.d.ts +2 -0
- package/typescript/index.js +14 -0
- package/typescript/index.ts +15 -0
- package/typescript/mssql/engine.d.ts +16 -0
- package/typescript/mssql/engine.js +138 -0
- package/typescript/mssql/engine.ts +132 -0
- package/typescript/mssql/index.d.ts +1 -0
- package/typescript/mssql/index.js +17 -0
- package/typescript/mssql/index.ts +1 -0
- package/typescript/mysql/engine.d.ts +13 -0
- package/typescript/mysql/engine.js +77 -0
- package/typescript/mysql/engine.ts +80 -0
- package/typescript/mysql/index.d.ts +1 -0
- package/typescript/mysql/index.js +17 -0
- package/typescript/mysql/index.ts +1 -0
- package/typescript/postgres/engine.d.ts +13 -0
- package/typescript/postgres/engine.js +86 -0
- package/typescript/postgres/engine.ts +87 -0
- package/typescript/postgres/index.d.ts +1 -0
- package/typescript/postgres/index.js +17 -0
- package/typescript/postgres/index.ts +1 -0
- package/typescript/sqlite/engine.d.ts +13 -0
- package/typescript/sqlite/engine.js +58 -0
- package/typescript/sqlite/engine.ts +55 -0
- package/typescript/sqlite/index.d.ts +1 -0
- package/typescript/sqlite/index.js +17 -0
- package/typescript/sqlite/index.ts +1 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SheetsTableClient = void 0;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
const metadata_1 = require("../base/metadata");
|
|
6
|
+
const helpers_1 = require("./helpers");
|
|
7
|
+
const retry_1 = require("./retry");
|
|
8
|
+
// ─── Table Client ─────────────────────────────────────────────────────────────
|
|
9
|
+
class SheetsTableClient {
|
|
10
|
+
constructor(adapter, modelName, sheetMapping) {
|
|
11
|
+
this.adapter = adapter;
|
|
12
|
+
this.modelName = modelName;
|
|
13
|
+
this.sheetMapping = sheetMapping;
|
|
14
|
+
}
|
|
15
|
+
get sheetName() {
|
|
16
|
+
return (0, helpers_1.resolveSheetName)(this.modelName, this.sheetMapping);
|
|
17
|
+
}
|
|
18
|
+
get escSheetName() {
|
|
19
|
+
return (0, helpers_1.esc)(this.sheetName);
|
|
20
|
+
}
|
|
21
|
+
get fields() {
|
|
22
|
+
return (0, metadata_1.getFieldsForModel)(this.modelName);
|
|
23
|
+
}
|
|
24
|
+
async ensureSheetExists() {
|
|
25
|
+
const existing = await this.adapter.getSheetMeta(this.sheetName);
|
|
26
|
+
if (existing)
|
|
27
|
+
return;
|
|
28
|
+
const api = await this.adapter.getSheets();
|
|
29
|
+
await (0, retry_1.withRetry)(() => api.spreadsheets.batchUpdate({
|
|
30
|
+
spreadsheetId: this.adapter.config.spreadsheetId,
|
|
31
|
+
requestBody: {
|
|
32
|
+
requests: [{ addSheet: { properties: { title: this.sheetName } } }],
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
35
|
+
this.adapter.invalidateCache();
|
|
36
|
+
}
|
|
37
|
+
async readAllRows() {
|
|
38
|
+
const api = await this.adapter.getSheets();
|
|
39
|
+
const res = await (0, retry_1.withRetry)(() => api.spreadsheets.values.get({
|
|
40
|
+
spreadsheetId: this.adapter.config.spreadsheetId,
|
|
41
|
+
range: `${this.escSheetName}!A:ZZ`,
|
|
42
|
+
}));
|
|
43
|
+
const values = res.data.values || [];
|
|
44
|
+
if (values.length < 1)
|
|
45
|
+
return { headers: [], rows: [] };
|
|
46
|
+
const headers = values[0];
|
|
47
|
+
const rows = [];
|
|
48
|
+
for (let i = 1; i < values.length; i++) {
|
|
49
|
+
const row = { __row: i + 1 };
|
|
50
|
+
for (let j = 0; j < headers.length; j++) {
|
|
51
|
+
row[headers[j]] = (0, helpers_1.coerceCell)(values[i][j], this.fields[headers[j]]);
|
|
52
|
+
}
|
|
53
|
+
rows.push(row);
|
|
54
|
+
}
|
|
55
|
+
return { headers, rows };
|
|
56
|
+
}
|
|
57
|
+
async getOrCreateHeaders(cols) {
|
|
58
|
+
const api = await this.adapter.getSheets();
|
|
59
|
+
await this.ensureSheetExists();
|
|
60
|
+
const res = await (0, retry_1.withRetry)(() => api.spreadsheets.values.get({
|
|
61
|
+
spreadsheetId: this.adapter.config.spreadsheetId,
|
|
62
|
+
range: `${this.escSheetName}!A1:ZZ1`,
|
|
63
|
+
}));
|
|
64
|
+
const existing = (res.data.values || [])[0] || [];
|
|
65
|
+
if (existing.length > 0)
|
|
66
|
+
return existing;
|
|
67
|
+
await (0, retry_1.withRetry)(() => api.spreadsheets.values.update({
|
|
68
|
+
spreadsheetId: this.adapter.config.spreadsheetId,
|
|
69
|
+
range: `${this.escSheetName}!A1`,
|
|
70
|
+
valueInputOption: 'RAW',
|
|
71
|
+
requestBody: { values: [cols] },
|
|
72
|
+
}));
|
|
73
|
+
return cols;
|
|
74
|
+
}
|
|
75
|
+
rowToValues(data, headers) {
|
|
76
|
+
return headers.map(h => {
|
|
77
|
+
const v = data[h];
|
|
78
|
+
if (v instanceof Date)
|
|
79
|
+
return v.toISOString();
|
|
80
|
+
if (v === null || v === undefined)
|
|
81
|
+
return '';
|
|
82
|
+
if (typeof v === 'boolean')
|
|
83
|
+
return v ? 'TRUE' : 'FALSE';
|
|
84
|
+
if (typeof v === 'object')
|
|
85
|
+
return JSON.stringify(v);
|
|
86
|
+
return String(v);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async deleteRowByIndex(rowIdx, sheetIdVal) {
|
|
90
|
+
const api = await this.adapter.getSheets();
|
|
91
|
+
await (0, retry_1.withRetry)(() => api.spreadsheets.batchUpdate({
|
|
92
|
+
spreadsheetId: this.adapter.config.spreadsheetId,
|
|
93
|
+
requestBody: {
|
|
94
|
+
requests: [{
|
|
95
|
+
deleteDimension: {
|
|
96
|
+
range: {
|
|
97
|
+
sheetId: sheetIdVal,
|
|
98
|
+
dimension: 'ROWS',
|
|
99
|
+
startIndex: rowIdx - 1,
|
|
100
|
+
endIndex: rowIdx,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
}],
|
|
104
|
+
},
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
// ── CRUD ────────────────────────────────────────────────────────────────────
|
|
108
|
+
async findMany(args) {
|
|
109
|
+
const { headers, rows } = await this.readAllRows();
|
|
110
|
+
if (headers.length === 0)
|
|
111
|
+
return [];
|
|
112
|
+
let filtered = rows.filter(r => (0, helpers_1.matchWhere)(r, args?.where));
|
|
113
|
+
const orderSql = (0, helpers_1.buildOrderBy)(args?.orderBy);
|
|
114
|
+
if (orderSql)
|
|
115
|
+
filtered = (0, helpers_1.sortRows)(filtered, orderSql);
|
|
116
|
+
const skip = args?.skip ?? 0;
|
|
117
|
+
if (args?.take !== undefined) {
|
|
118
|
+
filtered = filtered.slice(skip, skip + args.take);
|
|
119
|
+
}
|
|
120
|
+
else if (skip > 0) {
|
|
121
|
+
filtered = filtered.slice(skip);
|
|
122
|
+
}
|
|
123
|
+
const select = args?.select;
|
|
124
|
+
if (select) {
|
|
125
|
+
const selectedKeys = Object.keys(select).filter(k => select[k] === true);
|
|
126
|
+
return filtered.map(r => {
|
|
127
|
+
const obj = {};
|
|
128
|
+
for (const k of selectedKeys)
|
|
129
|
+
obj[k] = r[k];
|
|
130
|
+
return obj;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return filtered.map(r => {
|
|
134
|
+
const { __row, ...rest } = r;
|
|
135
|
+
return rest;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
async findFirst(args) {
|
|
139
|
+
const rows = await this.findMany({ ...args, take: 1 });
|
|
140
|
+
return rows[0] ?? null;
|
|
141
|
+
}
|
|
142
|
+
async findUnique(args) {
|
|
143
|
+
return this.findFirst({ where: args.where });
|
|
144
|
+
}
|
|
145
|
+
async count(args) {
|
|
146
|
+
const rows = await this.findMany({ where: args?.where });
|
|
147
|
+
return rows.length;
|
|
148
|
+
}
|
|
149
|
+
async create(args) {
|
|
150
|
+
const fields = this.fields;
|
|
151
|
+
const idFieldName = Object.prototype.hasOwnProperty.call(fields, 'id')
|
|
152
|
+
? 'id'
|
|
153
|
+
: Object.keys(fields).find(name => name.endsWith('_id') || name.endsWith('Id') || name.toLowerCase() === 'id');
|
|
154
|
+
const data = { ...args.data };
|
|
155
|
+
if (idFieldName) {
|
|
156
|
+
const fieldDef = fields[idFieldName];
|
|
157
|
+
const rawType = typeof fieldDef === 'string' ? fieldDef : (fieldDef?.ts || fieldDef?.sql || '');
|
|
158
|
+
const isStringType = ['string', 'uuid', 'uniqueidentifier', 'nvarchar', 'varchar', 'text'].includes(rawType.toLowerCase());
|
|
159
|
+
if (isStringType && !data[idFieldName]) {
|
|
160
|
+
data[idFieldName] = (0, crypto_1.randomUUID)();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const cols = Object.keys(data).filter(k => data[k] !== undefined);
|
|
164
|
+
const headers = await this.getOrCreateHeaders(cols);
|
|
165
|
+
for (const h of headers) {
|
|
166
|
+
if (data[h] === undefined)
|
|
167
|
+
data[h] = null;
|
|
168
|
+
}
|
|
169
|
+
await this.adapter.appendRange(`${this.escSheetName}!A:A`, [
|
|
170
|
+
this.rowToValues(data, headers),
|
|
171
|
+
]);
|
|
172
|
+
return data;
|
|
173
|
+
}
|
|
174
|
+
async createMany(args) {
|
|
175
|
+
if (args.data.length === 0)
|
|
176
|
+
return { count: 0 };
|
|
177
|
+
const allKeys = new Set();
|
|
178
|
+
for (const row of args.data) {
|
|
179
|
+
Object.keys(row).forEach(k => allKeys.add(k));
|
|
180
|
+
}
|
|
181
|
+
const cols = [...allKeys];
|
|
182
|
+
const fields = this.fields;
|
|
183
|
+
const idFieldName = Object.prototype.hasOwnProperty.call(fields, 'id')
|
|
184
|
+
? 'id'
|
|
185
|
+
: Object.keys(fields).find(name => name.endsWith('_id') || name.endsWith('Id') || name.toLowerCase() === 'id');
|
|
186
|
+
const prepared = [];
|
|
187
|
+
for (const item of args.data) {
|
|
188
|
+
const data = { ...item };
|
|
189
|
+
if (idFieldName) {
|
|
190
|
+
const fieldDef = fields[idFieldName];
|
|
191
|
+
const rawType = typeof fieldDef === 'string' ? fieldDef : (fieldDef?.ts || fieldDef?.sql || '');
|
|
192
|
+
const isStringType = ['string', 'uuid', 'uniqueidentifier', 'nvarchar', 'varchar', 'text'].includes(rawType.toLowerCase());
|
|
193
|
+
if (isStringType && !data[idFieldName]) {
|
|
194
|
+
data[idFieldName] = (0, crypto_1.randomUUID)();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
prepared.push(data);
|
|
198
|
+
}
|
|
199
|
+
const headers = await this.getOrCreateHeaders(cols);
|
|
200
|
+
const batchValues = prepared.map(data => {
|
|
201
|
+
for (const h of headers) {
|
|
202
|
+
if (data[h] === undefined)
|
|
203
|
+
data[h] = null;
|
|
204
|
+
}
|
|
205
|
+
return this.rowToValues(data, headers);
|
|
206
|
+
});
|
|
207
|
+
try {
|
|
208
|
+
await this.adapter.appendRange(`${this.escSheetName}!A:A`, batchValues);
|
|
209
|
+
return { count: batchValues.length };
|
|
210
|
+
}
|
|
211
|
+
catch (e) {
|
|
212
|
+
if (!args.skipDuplicates)
|
|
213
|
+
throw e;
|
|
214
|
+
let count = 0;
|
|
215
|
+
for (const row of args.data) {
|
|
216
|
+
try {
|
|
217
|
+
await this.create({ data: row });
|
|
218
|
+
count++;
|
|
219
|
+
}
|
|
220
|
+
catch { /* skip */ }
|
|
221
|
+
}
|
|
222
|
+
return { count };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async update(args) {
|
|
226
|
+
const { headers, rows } = await this.readAllRows();
|
|
227
|
+
const matching = rows.filter(r => (0, helpers_1.matchWhere)(r, args.where));
|
|
228
|
+
if (matching.length === 0)
|
|
229
|
+
throw new Error('No record found matching where clause');
|
|
230
|
+
const target = matching[0];
|
|
231
|
+
const updated = { ...target, ...args.data, __row: target.__row };
|
|
232
|
+
await this.adapter.writeRange(`${this.escSheetName}!A${target.__row}`, [
|
|
233
|
+
this.rowToValues(updated, headers),
|
|
234
|
+
]);
|
|
235
|
+
const { __row, ...rest } = updated;
|
|
236
|
+
return rest;
|
|
237
|
+
}
|
|
238
|
+
async updateMany(args) {
|
|
239
|
+
const { headers, rows } = await this.readAllRows();
|
|
240
|
+
const matching = rows.filter(r => (0, helpers_1.matchWhere)(r, args.where));
|
|
241
|
+
for (const target of matching) {
|
|
242
|
+
const updated = { ...target, ...args.data, __row: target.__row };
|
|
243
|
+
await this.adapter.writeRange(`${this.escSheetName}!A${target.__row}`, [
|
|
244
|
+
this.rowToValues(updated, headers),
|
|
245
|
+
]);
|
|
246
|
+
}
|
|
247
|
+
return { count: matching.length };
|
|
248
|
+
}
|
|
249
|
+
async delete(args) {
|
|
250
|
+
const { headers, rows } = await this.readAllRows();
|
|
251
|
+
const matching = rows.filter(r => (0, helpers_1.matchWhere)(r, args.where));
|
|
252
|
+
if (matching.length === 0)
|
|
253
|
+
throw new Error('No record found matching where clause');
|
|
254
|
+
const target = matching[0];
|
|
255
|
+
const meta = await this.adapter.getSheetMeta(this.sheetName);
|
|
256
|
+
if (!meta)
|
|
257
|
+
throw new Error(`Sheet "${this.sheetName}" not found`);
|
|
258
|
+
await this.deleteRowByIndex(target.__row, meta.sheetId);
|
|
259
|
+
const { __row, ...rest } = target;
|
|
260
|
+
return rest;
|
|
261
|
+
}
|
|
262
|
+
async deleteMany(args) {
|
|
263
|
+
const { headers, rows } = await this.readAllRows();
|
|
264
|
+
const matching = rows.filter(r => (0, helpers_1.matchWhere)(r, args?.where));
|
|
265
|
+
if (matching.length === 0)
|
|
266
|
+
return { count: 0 };
|
|
267
|
+
const meta = await this.adapter.getSheetMeta(this.sheetName);
|
|
268
|
+
if (!meta)
|
|
269
|
+
throw new Error(`Sheet "${this.sheetName}" not found`);
|
|
270
|
+
const sorted = [...matching].sort((a, b) => b.__row - a.__row);
|
|
271
|
+
for (const target of sorted) {
|
|
272
|
+
await this.deleteRowByIndex(target.__row, meta.sheetId);
|
|
273
|
+
}
|
|
274
|
+
return { count: matching.length };
|
|
275
|
+
}
|
|
276
|
+
async deleteAll() {
|
|
277
|
+
const meta = await this.adapter.getSheetMeta(this.sheetName);
|
|
278
|
+
if (!meta)
|
|
279
|
+
return;
|
|
280
|
+
const api = await this.adapter.getSheets();
|
|
281
|
+
await (0, retry_1.withRetry)(() => api.spreadsheets.batchUpdate({
|
|
282
|
+
spreadsheetId: this.adapter.config.spreadsheetId,
|
|
283
|
+
requestBody: {
|
|
284
|
+
requests: [{
|
|
285
|
+
deleteDimension: {
|
|
286
|
+
range: {
|
|
287
|
+
sheetId: meta.sheetId,
|
|
288
|
+
dimension: 'ROWS',
|
|
289
|
+
startIndex: 1,
|
|
290
|
+
endIndex: 999999,
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
}],
|
|
294
|
+
},
|
|
295
|
+
}));
|
|
296
|
+
}
|
|
297
|
+
async clear() {
|
|
298
|
+
const api = await this.adapter.getSheets();
|
|
299
|
+
await (0, retry_1.withRetry)(() => api.spreadsheets.values.clear({
|
|
300
|
+
spreadsheetId: this.adapter.config.spreadsheetId,
|
|
301
|
+
range: `${this.escSheetName}!A2:ZZ`,
|
|
302
|
+
}));
|
|
303
|
+
}
|
|
304
|
+
async upsert(args) {
|
|
305
|
+
const existing = await this.findFirst({ where: args.where });
|
|
306
|
+
if (existing) {
|
|
307
|
+
return this.update({ where: args.where, data: args.update });
|
|
308
|
+
}
|
|
309
|
+
return this.create({ data: args.create });
|
|
310
|
+
}
|
|
311
|
+
async aggregate(args) {
|
|
312
|
+
const rows = await this.findMany({ where: args?.where });
|
|
313
|
+
const result = {};
|
|
314
|
+
if (args._count !== undefined)
|
|
315
|
+
result._count = rows.length;
|
|
316
|
+
if (args._sum) {
|
|
317
|
+
for (const f of Object.keys(args._sum)) {
|
|
318
|
+
result[`_sum_${f}`] = rows.reduce((s, r) => s + (Number(r[f]) || 0), 0);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (args._avg) {
|
|
322
|
+
for (const f of Object.keys(args._avg)) {
|
|
323
|
+
const sum = rows.reduce((s, r) => s + (Number(r[f]) || 0), 0);
|
|
324
|
+
result[`_avg_${f}`] = rows.length > 0 ? sum / rows.length : 0;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (args._min) {
|
|
328
|
+
for (const f of Object.keys(args._min)) {
|
|
329
|
+
const nums = rows.map((r) => Number(r[f])).filter(n => !isNaN(n));
|
|
330
|
+
result[`_min_${f}`] = nums.length > 0 ? Math.min(...nums) : null;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (args._max) {
|
|
334
|
+
for (const f of Object.keys(args._max)) {
|
|
335
|
+
const nums = rows.map((r) => Number(r[f])).filter(n => !isNaN(n));
|
|
336
|
+
result[`_max_${f}`] = nums.length > 0 ? Math.max(...nums) : null;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
341
|
+
async groupBy(args) {
|
|
342
|
+
const rows = await this.findMany({ where: args?.where });
|
|
343
|
+
const byCols = args.by || [];
|
|
344
|
+
const groups = {};
|
|
345
|
+
for (const row of rows) {
|
|
346
|
+
const key = byCols.map(c => row[c]).join('|');
|
|
347
|
+
if (!groups[key])
|
|
348
|
+
groups[key] = [];
|
|
349
|
+
groups[key].push(row);
|
|
350
|
+
}
|
|
351
|
+
return Object.entries(groups).map(([key, group]) => {
|
|
352
|
+
const entry = {};
|
|
353
|
+
const parts = key.split('|');
|
|
354
|
+
byCols.forEach((c, i) => { entry[c] = parts[i]; });
|
|
355
|
+
entry._count = group.length;
|
|
356
|
+
return entry;
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
async vectorSearch(args) {
|
|
360
|
+
const rows = await this.findMany({ where: args.where });
|
|
361
|
+
const vectorField = args.vectorField || 'embedding';
|
|
362
|
+
const metric = args.distanceMetric || 'cosine';
|
|
363
|
+
const scored = [];
|
|
364
|
+
for (const raw of rows) {
|
|
365
|
+
const row = raw;
|
|
366
|
+
const rawVec = row[vectorField];
|
|
367
|
+
if (!rawVec)
|
|
368
|
+
continue;
|
|
369
|
+
let vec = [];
|
|
370
|
+
try {
|
|
371
|
+
vec = typeof rawVec === 'string' ? JSON.parse(rawVec) : rawVec;
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
if (!Array.isArray(vec) || vec.length !== args.vector.length)
|
|
377
|
+
continue;
|
|
378
|
+
let dot = 0, m1 = 0, m2 = 0;
|
|
379
|
+
for (let i = 0; i < args.vector.length; i++) {
|
|
380
|
+
dot += args.vector[i] * vec[i];
|
|
381
|
+
m1 += args.vector[i] ** 2;
|
|
382
|
+
m2 += vec[i] ** 2;
|
|
383
|
+
}
|
|
384
|
+
const cosine = m1 && m2 ? dot / (Math.sqrt(m1) * Math.sqrt(m2)) : 0;
|
|
385
|
+
const dist = metric === 'cosine' ? 1 - cosine
|
|
386
|
+
: metric === 'dot' ? -dot
|
|
387
|
+
: Math.sqrt(args.vector.reduce((s, v, i) => s + (v - vec[i]) ** 2, 0));
|
|
388
|
+
scored.push({ row, dist });
|
|
389
|
+
}
|
|
390
|
+
scored.sort((a, b) => a.dist - b.dist);
|
|
391
|
+
return scored.slice(0, args.take ?? 10).map(s => ({ ...s.row, distance: s.dist }));
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
exports.SheetsTableClient = SheetsTableClient;
|