@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.
Files changed (91) hide show
  1. package/README.md +263 -0
  2. package/dotnet/An5Adapter.cs +275 -0
  3. package/dotnet/Base/Core.cs +82 -0
  4. package/dotnet/Mssql/MssqlEngine.cs +144 -0
  5. package/dotnet/Postgres/PostgresEngine.cs +121 -0
  6. package/package.json +46 -0
  7. package/python/an5_adapter.py +97 -0
  8. package/python/base/__init__.py +3 -0
  9. package/python/base/dialects.py +8 -0
  10. package/python/base/metadata.py +9 -0
  11. package/python/base/sql.py +121 -0
  12. package/python/mssql/__init__.py +1 -0
  13. package/python/mssql/provider.py +62 -0
  14. package/python/postgres/__init__.py +1 -0
  15. package/python/postgres/provider.py +15 -0
  16. package/python/table_client.py +209 -0
  17. package/typescript/an5Adapter.d.ts +117 -0
  18. package/typescript/an5Adapter.js +447 -0
  19. package/typescript/an5Adapter.ts +474 -0
  20. package/typescript/base/index.d.ts +3 -0
  21. package/typescript/base/index.js +19 -0
  22. package/typescript/base/index.ts +3 -0
  23. package/typescript/base/metadata.d.ts +14 -0
  24. package/typescript/base/metadata.js +17 -0
  25. package/typescript/base/metadata.ts +28 -0
  26. package/typescript/base/sql.d.ts +4 -0
  27. package/typescript/base/sql.js +143 -0
  28. package/typescript/base/sql.ts +98 -0
  29. package/typescript/base/types.d.ts +21 -0
  30. package/typescript/base/types.js +2 -0
  31. package/typescript/base/types.ts +24 -0
  32. package/typescript/config.d.ts +17 -0
  33. package/typescript/config.js +92 -0
  34. package/typescript/config.ts +95 -0
  35. package/typescript/googlesheets/adapter.d.ts +31 -0
  36. package/typescript/googlesheets/adapter.js +138 -0
  37. package/typescript/googlesheets/adapter.ts +167 -0
  38. package/typescript/googlesheets/an5SheetsAdapter.d.ts +3 -0
  39. package/typescript/googlesheets/an5SheetsAdapter.js +8 -0
  40. package/typescript/googlesheets/an5SheetsAdapter.ts +3 -0
  41. package/typescript/googlesheets/config.d.ts +17 -0
  42. package/typescript/googlesheets/config.js +29 -0
  43. package/typescript/googlesheets/config.ts +33 -0
  44. package/typescript/googlesheets/helpers.d.ts +9 -0
  45. package/typescript/googlesheets/helpers.js +155 -0
  46. package/typescript/googlesheets/helpers.ts +124 -0
  47. package/typescript/googlesheets/index.d.ts +2 -0
  48. package/typescript/googlesheets/index.js +9 -0
  49. package/typescript/googlesheets/index.ts +2 -0
  50. package/typescript/googlesheets/parseConnectionString.d.ts +11 -0
  51. package/typescript/googlesheets/parseConnectionString.js +44 -0
  52. package/typescript/googlesheets/parseConnectionString.ts +44 -0
  53. package/typescript/googlesheets/retry.d.ts +3 -0
  54. package/typescript/googlesheets/retry.js +26 -0
  55. package/typescript/googlesheets/retry.ts +23 -0
  56. package/typescript/googlesheets/sqlExecutor.d.ts +12 -0
  57. package/typescript/googlesheets/sqlExecutor.js +306 -0
  58. package/typescript/googlesheets/sqlExecutor.ts +301 -0
  59. package/typescript/googlesheets/tableClient.d.ts +78 -0
  60. package/typescript/googlesheets/tableClient.js +394 -0
  61. package/typescript/googlesheets/tableClient.ts +434 -0
  62. package/typescript/googlesheets/types.d.ts +4 -0
  63. package/typescript/googlesheets/types.js +3 -0
  64. package/typescript/googlesheets/types.ts +6 -0
  65. package/typescript/index.d.ts +2 -0
  66. package/typescript/index.js +14 -0
  67. package/typescript/index.ts +15 -0
  68. package/typescript/mssql/engine.d.ts +16 -0
  69. package/typescript/mssql/engine.js +138 -0
  70. package/typescript/mssql/engine.ts +132 -0
  71. package/typescript/mssql/index.d.ts +1 -0
  72. package/typescript/mssql/index.js +17 -0
  73. package/typescript/mssql/index.ts +1 -0
  74. package/typescript/mysql/engine.d.ts +13 -0
  75. package/typescript/mysql/engine.js +77 -0
  76. package/typescript/mysql/engine.ts +80 -0
  77. package/typescript/mysql/index.d.ts +1 -0
  78. package/typescript/mysql/index.js +17 -0
  79. package/typescript/mysql/index.ts +1 -0
  80. package/typescript/postgres/engine.d.ts +13 -0
  81. package/typescript/postgres/engine.js +86 -0
  82. package/typescript/postgres/engine.ts +87 -0
  83. package/typescript/postgres/index.d.ts +1 -0
  84. package/typescript/postgres/index.js +17 -0
  85. package/typescript/postgres/index.ts +1 -0
  86. package/typescript/sqlite/engine.d.ts +13 -0
  87. package/typescript/sqlite/engine.js +58 -0
  88. package/typescript/sqlite/engine.ts +55 -0
  89. package/typescript/sqlite/index.d.ts +1 -0
  90. package/typescript/sqlite/index.js +17 -0
  91. package/typescript/sqlite/index.ts +1 -0
@@ -0,0 +1,474 @@
1
+ import { randomUUID } from 'crypto';
2
+
3
+ import {
4
+ An5SheetsAdapter,
5
+ An5SheetsAdapterConfig,
6
+ SheetsTableClient,
7
+ } from './googlesheets';
8
+ import { parseSheetsConnectionString } from './googlesheets/parseConnectionString';
9
+ import type { An5AdapterConfig, Dialect, QueryEngine, TransactionHandle } from './base/types';
10
+ import { buildOrderBy, parseWhere, quote } from './base/sql';
11
+ import { getFieldsForModel, getModelToTable, setAdapterMetadata, type AdapterMetadata } from './base/metadata';
12
+ import { MssqlEngine } from './mssql';
13
+ import { PostgresEngine } from './postgres';
14
+ import { MysqlEngine } from './mysql';
15
+ import { SqliteEngine } from './sqlite';
16
+
17
+ export type { An5AdapterConfig, Dialect, QueryEngine, TransactionHandle } from './base/types';
18
+ export { setAdapterMetadata, type AdapterMetadata } from './base/metadata';
19
+
20
+ export type AnyAdapter = An5Adapter | An5SheetsAdapter;
21
+ export type AnyAdapterConfig = An5AdapterConfig | An5SheetsAdapterConfig | { connectionString: string };
22
+
23
+ function isSheetsConfig(config: AnyAdapterConfig): config is An5SheetsAdapterConfig {
24
+ return (config as any).spreadsheetId !== undefined;
25
+ }
26
+
27
+ // ─── An5Adapter ────────────────────────────────────────────────────────────────────
28
+
29
+ export class An5Adapter {
30
+ private engine: QueryEngine | null = null;
31
+ private sheetsAdapter: An5SheetsAdapter | null = null;
32
+
33
+ get dialect(): Dialect { return this.sheetsAdapter ? 'googlesheets' : this.requireEngine().dialect; }
34
+
35
+ constructor(adapterConfig: An5AdapterConfig | An5SheetsAdapterConfig) {
36
+ if (isSheetsConfig(adapterConfig)) {
37
+ this.sheetsAdapter = new An5SheetsAdapter(adapterConfig);
38
+ return;
39
+ }
40
+
41
+ const cs = adapterConfig.connectionString.trim();
42
+ if (cs.startsWith('googlesheets://')) {
43
+ this.sheetsAdapter = new An5SheetsAdapter(parseSheetsConnectionString(cs));
44
+ return;
45
+ }
46
+
47
+ if (cs.startsWith('postgres://') || cs.startsWith('postgresql://')) {
48
+ this.engine = new PostgresEngine(adapterConfig);
49
+ } else if (cs.startsWith('mysql://') || cs.startsWith('mariadb://')) {
50
+ this.engine = new MysqlEngine(adapterConfig);
51
+ } else if (cs.startsWith('sqlite://') || cs.endsWith('.sqlite') || cs.endsWith('.db')) {
52
+ this.engine = new SqliteEngine(adapterConfig);
53
+ } else {
54
+ this.engine = new MssqlEngine(adapterConfig);
55
+ }
56
+ }
57
+
58
+ private requireEngine(): QueryEngine {
59
+ if (!this.engine) throw new Error('SQL engine is not available for Google Sheets adapter');
60
+ return this.engine;
61
+ }
62
+
63
+ private requireSheetsAdapter(): An5SheetsAdapter {
64
+ if (!this.sheetsAdapter) throw new Error('Google Sheets methods require a googlesheets:// connection or Sheets config');
65
+ return this.sheetsAdapter;
66
+ }
67
+
68
+ async exec<T = any>(query: string, params?: Record<string, any>): Promise<T[]> {
69
+ if (this.sheetsAdapter) return this.sheetsAdapter.exec<T>(query, params);
70
+ return this.requireEngine().exec<T>(query, params);
71
+ }
72
+
73
+ /** INTERNAL: used by AdapterTableClient for DML statements needing row count */
74
+ async _executeRaw(query: string, params?: Record<string, any>): Promise<number> {
75
+ return this.requireEngine().executeRaw(query, params);
76
+ }
77
+
78
+ /** Execute a raw query with positional values → @p_0, @p_1, ... per dialect */
79
+ async $queryRawUnsafe<T = any>(query: string, ...values: any[]): Promise<T[]> {
80
+ if (this.sheetsAdapter) return this.sheetsAdapter.$queryRawUnsafe<T>(query, ...values);
81
+ const params: Record<string, any> = {};
82
+ values.forEach((v, i) => { params[`p_${i}`] = v; });
83
+ // Normalise external positional placeholders → @p_N so each engine can re-transform
84
+ let q = query;
85
+ if (this.dialect === 'postgres') {
86
+ let idx = 0;
87
+ q = q.replace(/\$\d+/g, () => `@p_${idx++}`);
88
+ } else if (this.dialect === 'mysql') {
89
+ let idx = 0;
90
+ q = q.replace(/\?/g, () => `@p_${idx++}`);
91
+ }
92
+ return this.exec<T>(q, params);
93
+ }
94
+
95
+ async $executeRaw(query: string, ...values: any[]): Promise<number> {
96
+ if (this.sheetsAdapter) return this.sheetsAdapter.$executeRaw(query, ...values);
97
+ const params: Record<string, any> = {};
98
+ values.forEach((v, i) => { params[`p_${i}`] = v; });
99
+ let q = query;
100
+ if (this.dialect === 'postgres') {
101
+ let idx = 0;
102
+ q = q.replace(/\$\d+/g, () => `@p_${idx++}`);
103
+ } else if (this.dialect === 'mysql') {
104
+ let idx = 0;
105
+ q = q.replace(/\?/g, () => `@p_${idx++}`);
106
+ }
107
+ return this.requireEngine().executeRaw(q, params);
108
+ }
109
+
110
+ async $executeRawUnsafe(query: string, ...values: any[]): Promise<number> {
111
+ return this.$executeRaw(query, ...values);
112
+ }
113
+
114
+ async $connect(): Promise<void> {
115
+ if (this.sheetsAdapter) return this.sheetsAdapter.$connect();
116
+ await this.requireEngine().connect();
117
+ }
118
+
119
+ async $disconnect(): Promise<void> {
120
+ if (this.sheetsAdapter) return this.sheetsAdapter.$disconnect();
121
+ await this.requireEngine().disconnect();
122
+ }
123
+
124
+ /** Real transaction with BEGIN / COMMIT / ROLLBACK */
125
+ async $transaction<R>(fn: (tx: An5AdapterTx) => Promise<R>, options?: { timeout?: number }): Promise<R>;
126
+ async $transaction<R>(list: Promise<R>[]): Promise<R[]>;
127
+ async $transaction(fn: any, _options?: any): Promise<any> {
128
+ if (this.sheetsAdapter) return this.sheetsAdapter.$transaction(fn, _options);
129
+ if (Array.isArray(fn)) return Promise.all(fn);
130
+ const handle = await this.requireEngine().beginTransaction();
131
+ const txAdapter = new An5AdapterTx(handle, this.dialect);
132
+ try {
133
+ const result = await fn(txAdapter);
134
+ await handle.commit();
135
+ return result;
136
+ } catch (err) {
137
+ await handle.rollback();
138
+ throw err;
139
+ }
140
+ }
141
+
142
+ table<T = any>(modelName: string): AdapterTableClient<T> | SheetsTableClient<T> {
143
+ if (this.sheetsAdapter) return this.sheetsAdapter.table<T>(modelName);
144
+ return new AdapterTableClient<T>(this, modelName);
145
+ }
146
+
147
+ async readRange<T = any>(range: string): Promise<T[][]> {
148
+ return this.requireSheetsAdapter().readRange<T>(range);
149
+ }
150
+
151
+ async writeRange(range: string, values: any[][]): Promise<void> {
152
+ await this.requireSheetsAdapter().writeRange(range, values);
153
+ }
154
+
155
+ async appendRange(range: string, values: any[][]): Promise<void> {
156
+ await this.requireSheetsAdapter().appendRange(range, values);
157
+ }
158
+
159
+ async listSheets(): Promise<string[]> {
160
+ return this.requireSheetsAdapter().listSheets();
161
+ }
162
+
163
+ async deleteSheet(name: string): Promise<void> {
164
+ await this.requireSheetsAdapter().deleteSheet(name);
165
+ }
166
+ }
167
+
168
+ // ─── Transaction-scoped adapter ────────────────────────────────────────────────────
169
+
170
+ export class An5AdapterTx {
171
+ constructor(
172
+ private readonly handle: TransactionHandle,
173
+ public readonly dialect: Dialect,
174
+ ) { }
175
+
176
+ async exec<T = any>(query: string, params?: Record<string, any>): Promise<T[]> {
177
+ return this.handle.exec<T>(query, params);
178
+ }
179
+
180
+ async _executeRaw(query: string, params?: Record<string, any>): Promise<number> {
181
+ return this.handle.executeRaw(query, params);
182
+ }
183
+
184
+ table<T = any>(modelName: string): AdapterTableClient<T> {
185
+ return new AdapterTableClient<T>(this, modelName);
186
+ }
187
+ }
188
+
189
+ // ─── Table Client ──────────────────────────────────────────────────────────────────
190
+
191
+ export class AdapterTableClient<T = any> {
192
+ constructor(
193
+ private readonly adapter: An5Adapter | An5AdapterTx,
194
+ private readonly modelName: string,
195
+ ) { }
196
+
197
+ private get dialect(): Dialect { return this.adapter.dialect; }
198
+
199
+ private get tableName(): string {
200
+ const name = this.modelName;
201
+ let t = name;
202
+ const modelToTable = getModelToTable();
203
+ if (modelToTable[name]) t = modelToTable[name];
204
+ else {
205
+ const camel = name.charAt(0).toLowerCase() + name.slice(1);
206
+ if (modelToTable[camel]) t = modelToTable[camel];
207
+ else {
208
+ const lower = name.toLowerCase();
209
+ if (modelToTable[lower]) t = modelToTable[lower];
210
+ }
211
+ }
212
+ if (t.startsWith('[') || t.startsWith('"') || t.startsWith('`')) return t;
213
+ if (t.includes('.')) return t.split('.').map(p => quote(p, this.dialect)).join('.');
214
+ return quote(t, this.dialect);
215
+ }
216
+
217
+ private get nolock(): string {
218
+ return this.dialect === 'mssql' ? ' WITH (NOLOCK)' : '';
219
+ }
220
+
221
+ private async doExec<U = any>(query: string, params?: Record<string, any>): Promise<U[]> {
222
+ return this.adapter.exec<U>(query, params);
223
+ }
224
+
225
+ private async doExecuteRaw(query: string, params?: Record<string, any>): Promise<number> {
226
+ return this.adapter._executeRaw(query, params);
227
+ }
228
+
229
+ async findMany(args?: { where?: any; orderBy?: any; skip?: number; take?: number; select?: any }): Promise<T[]> {
230
+ const params: Record<string, any> = {};
231
+ const whereSql = parseWhere(this.modelName, args?.where, params, this.dialect);
232
+ const orderSql = buildOrderBy(args?.orderBy, this.dialect);
233
+ const take = args?.take;
234
+ const skip = args?.skip ?? 0;
235
+
236
+ let query: string;
237
+ if (take !== undefined) {
238
+ if (this.dialect === 'postgres' || this.dialect === 'mysql' || this.dialect === 'sqlite') {
239
+ query = `SELECT * FROM ${this.tableName}`;
240
+ if (whereSql) query += ` WHERE ${whereSql}`;
241
+ if (orderSql) query += ` ${orderSql}`;
242
+ query += ` LIMIT ${take} OFFSET ${skip}`;
243
+ } else {
244
+ // mssql requires ORDER BY before OFFSET/FETCH
245
+ query = `SELECT * FROM ${this.tableName}${this.nolock}`;
246
+ if (whereSql) query += ` WHERE ${whereSql}`;
247
+ query += ` ${orderSql || 'ORDER BY (SELECT NULL)'}`;
248
+ query += ` OFFSET ${skip} ROWS FETCH NEXT ${take} ROWS ONLY`;
249
+ }
250
+ } else {
251
+ query = `SELECT * FROM ${this.tableName}${this.nolock}`;
252
+ if (whereSql) query += ` WHERE ${whereSql}`;
253
+ if (orderSql) query += ` ${orderSql}`;
254
+ }
255
+ return this.doExec<T>(query, params);
256
+ }
257
+
258
+ async findFirst(args?: { where?: any; orderBy?: any; select?: any }): Promise<T | null> {
259
+ const rows = await this.findMany({ ...args, take: 1 });
260
+ return rows[0] ?? null;
261
+ }
262
+
263
+ async findUnique(args: { where: any }): Promise<T | null> {
264
+ return this.findFirst({ where: args.where });
265
+ }
266
+
267
+ async count(args?: { where?: any }): Promise<number> {
268
+ const params: Record<string, any> = {};
269
+ const whereSql = parseWhere(this.modelName, args?.where, params, this.dialect);
270
+ // Add NOLOCK for mssql count too
271
+ let query = `SELECT COUNT(*) AS cnt FROM ${this.tableName}${this.nolock}`;
272
+ if (whereSql) query += ` WHERE ${whereSql}`;
273
+ const rows = await this.doExec<any>(query, params);
274
+ return Number(rows[0]?.cnt ?? rows[0]?.CNT ?? 0);
275
+ }
276
+
277
+ async create(args: { data: Partial<T> }): Promise<T> {
278
+ const fields = getFieldsForModel(this.modelName);
279
+ const idFieldName = Object.prototype.hasOwnProperty.call(fields, 'id')
280
+ ? 'id'
281
+ : Object.keys(fields).find((name) => name.endsWith('_id') || name.endsWith('Id') || name.toLowerCase() === 'id');
282
+
283
+ const data: any = { ...args.data };
284
+ if (idFieldName) {
285
+ const fieldDef: any = fields[idFieldName];
286
+ const rawType = typeof fieldDef === 'string' ? fieldDef : (fieldDef?.ts || fieldDef?.sql || fieldDef?.type || '');
287
+ const normalizedType = String(rawType).toLowerCase();
288
+ const isStringType = ['string', 'uuid', 'uniqueidentifier', 'nvarchar', 'varchar', 'text'].includes(normalizedType);
289
+ if (isStringType && !data[idFieldName]) data[idFieldName] = randomUUID();
290
+ }
291
+
292
+ const cols = Object.keys(data).filter(k => data[k] !== undefined);
293
+ const params: Record<string, any> = {};
294
+ const vals: string[] = [];
295
+
296
+ for (const col of cols) {
297
+ const p = `c_${col}`;
298
+ params[p] = data[col];
299
+ vals.push(`@${p}`);
300
+ }
301
+
302
+ const query = `INSERT INTO ${this.tableName} (${cols.map(c => quote(c, this.dialect)).join(', ')}) VALUES (${vals.join(', ')})`;
303
+ await this.doExec(query, params);
304
+ return (await this.findFirst({ where: idFieldName ? { [idFieldName]: data[idFieldName] } : data })) as T;
305
+ }
306
+
307
+ async createMany(args: { data: Partial<T>[]; skipDuplicates?: boolean }): Promise<{ count: number }> {
308
+ if (args.data.length === 0) return { count: 0 };
309
+
310
+ const firstCols = Object.keys(args.data[0]).filter(k => (args.data[0] as any)[k] !== undefined);
311
+
312
+ // Bulk INSERT for the common case (same columns, no skipDuplicates)
313
+ if (firstCols.length > 0 && !args.skipDuplicates) {
314
+ const params: Record<string, any> = {};
315
+ const rowPlaceholders: string[] = [];
316
+
317
+ for (let r = 0; r < args.data.length; r++) {
318
+ const row = args.data[r] as any;
319
+ const vals = firstCols.map(col => {
320
+ const p = `r${r}_${col}`;
321
+ params[p] = row[col] ?? null;
322
+ return `@${p}`;
323
+ });
324
+ rowPlaceholders.push(`(${vals.join(', ')})`);
325
+ }
326
+
327
+ const query = `INSERT INTO ${this.tableName} (${firstCols.map(c => quote(c, this.dialect)).join(', ')}) VALUES ${rowPlaceholders.join(', ')}`;
328
+ try {
329
+ await this.doExecuteRaw(query, params);
330
+ return { count: args.data.length };
331
+ } catch {
332
+ // fallback below if bulk fails (e.g. mixed column sets)
333
+ }
334
+ }
335
+
336
+ // Row-by-row fallback
337
+ let count = 0;
338
+ for (const row of args.data) {
339
+ try { await this.create({ data: row }); count++; }
340
+ catch (e) { if (!args.skipDuplicates) throw e; }
341
+ }
342
+ return { count };
343
+ }
344
+
345
+ async update(args: { where: any; data: Partial<T> }): Promise<T> {
346
+ const params: Record<string, any> = {};
347
+ const whereSql = parseWhere(this.modelName, args.where, params, this.dialect, 'w_');
348
+ const setCols = Object.keys(args.data).filter(k => (args.data as any)[k] !== undefined);
349
+ const sets: string[] = [];
350
+
351
+ for (const col of setCols) {
352
+ const p = `s_${col}`;
353
+ params[p] = (args.data as any)[col];
354
+ sets.push(`${quote(col, this.dialect)} = @${p}`);
355
+ }
356
+
357
+ const query = `UPDATE ${this.tableName} SET ${sets.join(', ')}${whereSql ? ` WHERE ${whereSql}` : ''}`;
358
+ await this.doExecuteRaw(query, params);
359
+ return (await this.findFirst({ where: args.where })) as T;
360
+ }
361
+
362
+ async updateMany(args: { where?: any; data: Partial<T> }): Promise<{ count: number }> {
363
+ const params: Record<string, any> = {};
364
+ const whereSql = parseWhere(this.modelName, args.where, params, this.dialect, 'w_');
365
+ const setCols = Object.keys(args.data).filter(k => (args.data as any)[k] !== undefined);
366
+ const sets: string[] = [];
367
+
368
+ for (const col of setCols) {
369
+ const p = `s_${col}`;
370
+ params[p] = (args.data as any)[col];
371
+ sets.push(`${quote(col, this.dialect)} = @${p}`);
372
+ }
373
+
374
+ const query = `UPDATE ${this.tableName} SET ${sets.join(', ')}${whereSql ? ` WHERE ${whereSql}` : ''}`;
375
+ const count = await this.doExecuteRaw(query, params);
376
+ return { count };
377
+ }
378
+
379
+ async delete(args: { where: any }): Promise<T> {
380
+ const existing = await this.findFirst({ where: args.where });
381
+ const params: Record<string, any> = {};
382
+ const whereSql = parseWhere(this.modelName, args.where, params, this.dialect);
383
+ await this.doExecuteRaw(`DELETE FROM ${this.tableName} WHERE ${whereSql}`, params);
384
+ return existing as T;
385
+ }
386
+
387
+ async deleteMany(args?: { where?: any }): Promise<{ count: number }> {
388
+ const params: Record<string, any> = {};
389
+ const whereSql = parseWhere(this.modelName, args?.where, params, this.dialect);
390
+ const query = `DELETE FROM ${this.tableName}${whereSql ? ` WHERE ${whereSql}` : ''}`;
391
+ const count = await this.doExecuteRaw(query, params);
392
+ return { count };
393
+ }
394
+
395
+ async upsert(args: { where: any; create: Partial<T>; update: Partial<T> }): Promise<T> {
396
+ const existing = await this.findFirst({ where: args.where });
397
+ return existing
398
+ ? this.update({ where: args.where, data: args.update })
399
+ : this.create({ data: args.create });
400
+ }
401
+
402
+ async aggregate(args: any): Promise<any> {
403
+ const params: Record<string, any> = {};
404
+ const whereSql = parseWhere(this.modelName, args?.where, params, this.dialect);
405
+ const aggs: string[] = [];
406
+
407
+ if (args._count) aggs.push('COUNT(*) AS _count');
408
+ if (args._sum) for (const f of Object.keys(args._sum)) aggs.push(`SUM(${quote(f, this.dialect)}) AS _sum_${f}`);
409
+ if (args._avg) for (const f of Object.keys(args._avg)) aggs.push(`AVG(${quote(f, this.dialect)}) AS _avg_${f}`);
410
+ if (args._min) for (const f of Object.keys(args._min)) aggs.push(`MIN(${quote(f, this.dialect)}) AS _min_${f}`);
411
+ if (args._max) for (const f of Object.keys(args._max)) aggs.push(`MAX(${quote(f, this.dialect)}) AS _max_${f}`);
412
+
413
+ const query = `SELECT ${aggs.join(', ')} FROM ${this.tableName}${whereSql ? ` WHERE ${whereSql}` : ''}`;
414
+ const rows = await this.doExec(query, params);
415
+ return rows[0] ?? {};
416
+ }
417
+
418
+ async groupBy(args: any): Promise<any[]> {
419
+ const params: Record<string, any> = {};
420
+ const whereSql = parseWhere(this.modelName, args?.where, params, this.dialect);
421
+ const byCols = (args.by || []).map((b: string) => quote(b, this.dialect)).join(', ');
422
+ const query = `SELECT ${byCols}, COUNT(*) AS _count FROM ${this.tableName}${whereSql ? ` WHERE ${whereSql}` : ''} GROUP BY ${byCols}`;
423
+ return this.doExec(query, params);
424
+ }
425
+
426
+ async vectorSearch(args: {
427
+ vector: number[];
428
+ take?: number;
429
+ where?: any;
430
+ vectorField?: string;
431
+ distanceMetric?: 'cosine' | 'euclidean' | 'dot';
432
+ }): Promise<(T & { distance: number })[]> {
433
+ const rows = await this.findMany({ where: args.where });
434
+ const vectorField = args.vectorField || 'embedding';
435
+ const metric = args.distanceMetric || 'cosine';
436
+
437
+ const scored: { row: T; dist: number }[] = [];
438
+ for (const row of rows) {
439
+ const raw = (row as any)[vectorField];
440
+ if (!raw) continue;
441
+ let vec: number[] = [];
442
+ try { vec = typeof raw === 'string' ? JSON.parse(raw) : raw; } catch { continue; }
443
+ if (!Array.isArray(vec) || vec.length !== args.vector.length) continue;
444
+
445
+ let dot = 0, m1 = 0, m2 = 0;
446
+ for (let i = 0; i < args.vector.length; i++) {
447
+ dot += args.vector[i] * vec[i];
448
+ m1 += args.vector[i] ** 2;
449
+ m2 += vec[i] ** 2;
450
+ }
451
+ const cosine = m1 && m2 ? dot / (Math.sqrt(m1) * Math.sqrt(m2)) : 0;
452
+ const dist = metric === 'cosine'
453
+ ? 1 - cosine
454
+ : metric === 'dot'
455
+ ? -dot
456
+ : Math.sqrt(args.vector.reduce((s, v, i) => s + (v - vec[i]) ** 2, 0));
457
+ scored.push({ row, dist });
458
+ }
459
+ scored.sort((a, b) => a.dist - b.dist);
460
+ return scored.slice(0, args.take ?? 10).map(s => ({ ...s.row as any, distance: s.dist }));
461
+ }
462
+ }
463
+
464
+ // ─── Factory ──────────────────────────────────────────────────────────────────────
465
+
466
+ export function createAn5Adapter(config: AnyAdapterConfig): AnyAdapter {
467
+ if (isSheetsConfig(config)) return new An5SheetsAdapter(config);
468
+ if (config.connectionString.trim().startsWith('googlesheets://')) {
469
+ return new An5SheetsAdapter(parseSheetsConnectionString(config.connectionString));
470
+ }
471
+ return new An5Adapter(config);
472
+ }
473
+
474
+ export const createAdapter = createAn5Adapter;
@@ -0,0 +1,3 @@
1
+ export * from './types';
2
+ export * from './sql';
3
+ export * from './metadata';
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types"), exports);
18
+ __exportStar(require("./sql"), exports);
19
+ __exportStar(require("./metadata"), exports);
@@ -0,0 +1,3 @@
1
+ export * from './types';
2
+ export * from './sql';
3
+ export * from './metadata';
@@ -0,0 +1,14 @@
1
+ export interface ModelFieldMeta {
2
+ name?: string;
3
+ ts?: string;
4
+ sql?: string;
5
+ isId?: boolean;
6
+ [key: string]: any;
7
+ }
8
+ export interface AdapterMetadata {
9
+ modelToTable?: Record<string, string>;
10
+ modelFields?: Record<string, any>;
11
+ }
12
+ export declare function setAdapterMetadata(metadata: AdapterMetadata): void;
13
+ export declare function getModelToTable(): Record<string, string>;
14
+ export declare function getFieldsForModel(modelName: string): any;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setAdapterMetadata = setAdapterMetadata;
4
+ exports.getModelToTable = getModelToTable;
5
+ exports.getFieldsForModel = getFieldsForModel;
6
+ let modelToTable = {};
7
+ let modelFields = {};
8
+ function setAdapterMetadata(metadata) {
9
+ modelToTable = { ...(metadata.modelToTable || {}) };
10
+ modelFields = { ...(metadata.modelFields || {}) };
11
+ }
12
+ function getModelToTable() {
13
+ return modelToTable;
14
+ }
15
+ function getFieldsForModel(modelName) {
16
+ return modelFields[modelName] || {};
17
+ }
@@ -0,0 +1,28 @@
1
+ export interface ModelFieldMeta {
2
+ name?: string;
3
+ ts?: string;
4
+ sql?: string;
5
+ isId?: boolean;
6
+ [key: string]: any;
7
+ }
8
+
9
+ export interface AdapterMetadata {
10
+ modelToTable?: Record<string, string>;
11
+ modelFields?: Record<string, any>;
12
+ }
13
+
14
+ let modelToTable: Record<string, string> = {};
15
+ let modelFields: Record<string, any> = {};
16
+
17
+ export function setAdapterMetadata(metadata: AdapterMetadata): void {
18
+ modelToTable = { ...(metadata.modelToTable || {}) };
19
+ modelFields = { ...(metadata.modelFields || {}) };
20
+ }
21
+
22
+ export function getModelToTable(): Record<string, string> {
23
+ return modelToTable;
24
+ }
25
+
26
+ export function getFieldsForModel(modelName: string): any {
27
+ return modelFields[modelName] || {};
28
+ }
@@ -0,0 +1,4 @@
1
+ import type { Dialect } from './types';
2
+ export declare function quote(name: string, dialect: Dialect): string;
3
+ export declare function parseWhere(modelName: string, where: any, params: Record<string, any>, dialect: Dialect, prefix?: string): string;
4
+ export declare function buildOrderBy(orderBy: any, dialect: Dialect): string;