@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,301 @@
1
+ import type { An5SheetsAdapter } from './adapter';
2
+ import { esc } from './helpers';
3
+ import { withRetry } from './retry';
4
+
5
+ // ─── SQL query executor ───────────────────────────────────────────────────────
6
+
7
+ export type ExecResult = any[] | number;
8
+
9
+ export async function execQuery(adapter: An5SheetsAdapter, query: string, params: Record<string, any> | undefined, _modelMap?: Record<string, string>, _sheetId?: string): Promise<ExecResult> {
10
+ const q = query.trim();
11
+ const upper = q.toUpperCase();
12
+
13
+ if (upper.startsWith('SELECT')) return execSelect(adapter, q, params);
14
+ if (upper.startsWith('INSERT')) return execInsert(adapter, q, params);
15
+ if (upper.startsWith('UPDATE')) return execUpdate(adapter, q, params);
16
+ if (upper.startsWith('DELETE')) return execDelete(adapter, q, params);
17
+
18
+ // Fallback: treat as raw range or sheet name
19
+ const range = q.includes('!') ? q : esc(q) + '!A:ZZ';
20
+ const raw = await adapter.readRange(range);
21
+ return raw;
22
+ }
23
+
24
+ export function substParams(sql: string, params?: Record<string, any>): { text: string; values: any[] } {
25
+ if (!params) return { text: sql, values: [] };
26
+ const values: any[] = [];
27
+ const text = sql.replace(/@(\w+)/g, (_, name) => {
28
+ if (params[name] !== undefined) {
29
+ const idx = values.length;
30
+ values.push(params[name]);
31
+ return `?${idx}`;
32
+ }
33
+ return `@${name}`;
34
+ });
35
+ return { text, values };
36
+ }
37
+
38
+ export function matchRowSQL(row: Record<string, any>, whereText: string, values: any[]): boolean {
39
+ if (!whereText) return true;
40
+ const trimmed = whereText.trim();
41
+
42
+ // Handle simple AND conditions: field = ?0 AND field2 = ?1
43
+ const parts = trimmed.split(/\s+AND\s+/i);
44
+ for (const part of parts) {
45
+ const m = part.match(/^(.+?)\s*(=|!=|<>|>|<|>=|<=|LIKE|CONTAINS)\s*(.+)$/i);
46
+ if (!m) continue;
47
+ const field = m[1].trim().replace(/[[\]]/g, '');
48
+ const op = m[2].toUpperCase();
49
+ const valRef = m[3].trim();
50
+
51
+ const val = valRef.startsWith('?') ? values[parseInt(valRef.slice(1))] : valRef.replace(/^['"]|['"]$/g, '');
52
+ const cell = row[field];
53
+
54
+ switch (op) {
55
+ case '=': if (cell != val) return false; break;
56
+ case '!=': case '<>': if (cell == val) return false; break;
57
+ case '>': if (!(Number(cell) > Number(val))) return false; break;
58
+ case '<': if (!(Number(cell) < Number(val))) return false; break;
59
+ case '>=': if (!(Number(cell) >= Number(val))) return false; break;
60
+ case '<=': if (!(Number(cell) <= Number(val))) return false; break;
61
+ case 'LIKE': if (!cell || !String(cell).toLowerCase().includes(String(val).toLowerCase().replace(/%/g, ''))) return false; break;
62
+ case 'CONTAINS': if (!cell || !String(cell).includes(String(val))) return false; break;
63
+ }
64
+ }
65
+ return true;
66
+ }
67
+
68
+ export function extractColsAndVals(sql: string, params?: Record<string, any>): { cols: string[]; vals: any[] } {
69
+ const cols: string[] = [];
70
+ const vals: any[] = [];
71
+
72
+ const colMatch = sql.match(/\(([^)]+)\)\s*VALUES/i);
73
+ if (colMatch) {
74
+ const rawCols = colMatch[1].split(',').map(c => c.trim().replace(/[[\]]/g, ''));
75
+ // Find actual values
76
+ const valMatch = sql.substring(colMatch.index! + colMatch[0].length).match(/\(([^)]+)\)/);
77
+ if (valMatch) {
78
+ const rawVals = valMatch[1].split(',').map(v => v.trim());
79
+ for (let i = 0; i < rawCols.length; i++) {
80
+ cols.push(rawCols[i]);
81
+ const rv = rawVals[i];
82
+ if (rv.startsWith('@') && params && params[rv.slice(1)] !== undefined) {
83
+ vals.push(params[rv.slice(1)]);
84
+ } else {
85
+ vals.push(rv.replace(/^['"]|['"]$/g, ''));
86
+ }
87
+ }
88
+ }
89
+ }
90
+ return { cols, vals };
91
+ }
92
+
93
+ async function execSelect(adapter: An5SheetsAdapter, query: string, params?: Record<string, any>): Promise<any[]> {
94
+ const m = query.match(/SELECT\s+(.+?)\s+FROM\s+(?:'([^']+)'|"([^"]+)"|(\S+))\s*(?:WHERE\s+(.+))?$/i);
95
+ if (!m) {
96
+ // Try reading as raw range
97
+ const raw = await adapter.readRange(esc(query.replace(/^SELECT\s+\*\s+FROM\s+/i, '').trim()) + '!A:ZZ');
98
+ const headers = raw[0] || [];
99
+ return raw.slice(1).map(r => {
100
+ const obj: any = {};
101
+ headers.forEach((h: any, i: number) => { obj[h] = r[i] ?? null; });
102
+ return obj;
103
+ });
104
+ }
105
+
106
+ const sheetName = m[2] || m[3] || m[4];
107
+ const whereClause = m[5];
108
+
109
+ const raw = await adapter.readRange(esc(sheetName) + '!A:ZZ');
110
+ if (raw.length < 1) return [];
111
+
112
+ const headers = raw[0] as string[];
113
+ const fieldList = m[1].trim();
114
+ const selectAll = fieldList === '*';
115
+
116
+ const rows = raw.slice(1).map(row => {
117
+ const obj: any = {};
118
+ headers.forEach((h, i) => { obj[h] = row[i] ?? null; });
119
+ return obj;
120
+ });
121
+
122
+ if (!whereClause) return selectAll ? rows : rows.map(r => pickFields(r, fieldList, headers));
123
+ const { text, values } = substParams(query, params);
124
+ // Extract just the WHERE part
125
+ const whereParts = query.match(/WHERE\s+(.+)$/i);
126
+ const whereText = whereParts ? whereParts[1] : '';
127
+ const { text: whereSubst, values: whereVals } = substParams(whereText, params);
128
+
129
+ const result = rows.filter(r => matchRowSQL(r, whereSubst, whereVals));
130
+ return selectAll ? result : result.map(r => pickFields(r, fieldList, headers));
131
+ }
132
+
133
+ function pickFields(row: any, fieldList: string, headers: string[]): any {
134
+ const fields = fieldList.split(',').map(f => f.trim().replace(/[[\]]/g, ''));
135
+ const obj: any = {};
136
+ fields.forEach(f => { obj[f] = row[f]; });
137
+ return obj;
138
+ }
139
+
140
+ async function execInsert(adapter: An5SheetsAdapter, query: string, params?: Record<string, any>): Promise<number> {
141
+ const m = query.match(/INSERT\s+INTO\s+(?:'([^']+)'|"([^"]+)"|(\S+))\s*(.*)/i);
142
+ if (!m) throw new Error('Invalid INSERT syntax');
143
+
144
+ const sheetName = m[1] || m[2] || m[3];
145
+ const { cols, vals } = extractColsAndVals(query, params);
146
+ if (cols.length === 0) throw new Error('Cannot extract columns from INSERT');
147
+
148
+ // Ensure headers exist
149
+ const raw = await adapter.readRange(esc(sheetName) + '!A1:ZZ1');
150
+ const existingHeaders = (raw[0] || []) as string[];
151
+ let headers = existingHeaders.length > 0 ? existingHeaders : cols;
152
+
153
+ if (existingHeaders.length === 0) {
154
+ // Write headers first
155
+ const api = await adapter.getSheets();
156
+ await withRetry(() =>
157
+ api.spreadsheets.values.update({
158
+ spreadsheetId: adapter.config.spreadsheetId,
159
+ range: esc(sheetName) + '!A1',
160
+ valueInputOption: 'RAW',
161
+ requestBody: { values: [cols] },
162
+ })
163
+ );
164
+ headers = cols;
165
+ }
166
+
167
+ const rowData: any = {};
168
+ cols.forEach((c, i) => { rowData[c] = vals[i]; });
169
+ for (const h of headers) { if (rowData[h] === undefined) rowData[h] = null; }
170
+
171
+ const rowVals = headers.map(h => {
172
+ const v = rowData[h];
173
+ if (v === null || v === undefined) return '';
174
+ if (v instanceof Date) return v.toISOString();
175
+ return String(v);
176
+ });
177
+
178
+ await adapter.appendRange(esc(sheetName) + '!A:A', [rowVals]);
179
+ return 1;
180
+ }
181
+
182
+ async function execUpdate(adapter: An5SheetsAdapter, query: string, params?: Record<string, any>): Promise<number> {
183
+ const m = query.match(/UPDATE\s+(?:'([^']+)'|"([^"]+)"|(\S+))\s+SET\s+(.+?)(?:\s+WHERE\s+(.+))?$/i);
184
+ if (!m) throw new Error('Invalid UPDATE syntax');
185
+
186
+ const sheetName = m[1] || m[2] || m[3];
187
+ const setClause = m[4];
188
+ const whereClause = m[5];
189
+
190
+ // Parse SET clause
191
+ const setPairs = setClause.split(',').map(p => p.trim());
192
+ const setFields: string[] = [];
193
+ const setVals: any[] = [];
194
+ for (const pair of setPairs) {
195
+ const sm = pair.match(/^(\w+)\s*=\s*(.+)$/i);
196
+ if (!sm) continue;
197
+ setFields.push(sm[1].replace(/[[\]]/g, ''));
198
+ const rv = sm[2].trim();
199
+ if (rv.startsWith('@') && params && params[rv.slice(1)] !== undefined) {
200
+ setVals.push(params[rv.slice(1)]);
201
+ } else {
202
+ setVals.push(rv.replace(/^['"]|['"]$/g, ''));
203
+ }
204
+ }
205
+
206
+ // Read data
207
+ const range = esc(sheetName) + '!A:ZZ';
208
+ const raw = await adapter.readRange(range);
209
+ if (raw.length < 1) return 0;
210
+
211
+ const headers = raw[0] as string[];
212
+ const rows = raw.slice(1);
213
+
214
+ // Determine which rows to update
215
+ const { text: whereSubst, values: whereVals } = substParams(whereClause || '', params);
216
+ let count = 0;
217
+ for (let i = 0; i < rows.length; i++) {
218
+ const row: any = {};
219
+ headers.forEach((h, j) => { row[h] = rows[i][j] ?? null; });
220
+ row.__row = i + 2;
221
+
222
+ if (whereClause && !matchRowSQL(row, whereSubst, whereVals)) continue;
223
+
224
+ // Apply SET values
225
+ setFields.forEach((f, idx) => { row[f] = setVals[idx]; });
226
+ const updated = headers.map(h => {
227
+ const v = row[h];
228
+ if (v === null || v === undefined) return '';
229
+ if (v instanceof Date) return v.toISOString();
230
+ return String(v);
231
+ });
232
+
233
+ await adapter.writeRange(esc(sheetName) + `!A${row.__row}`, [updated]);
234
+ count++;
235
+ }
236
+ return count;
237
+ }
238
+
239
+ async function execDelete(adapter: An5SheetsAdapter, query: string, params?: Record<string, any>): Promise<number> {
240
+ const m = query.match(/DELETE\s+FROM\s+(?:'([^']+)'|"([^"]+)"|(\S+))\s*(?:WHERE\s+(.+))?$/i);
241
+ if (!m) throw new Error('Invalid DELETE syntax');
242
+
243
+ const sheetName = m[1] || m[2] || m[3];
244
+ const whereClause = m[4];
245
+
246
+ const meta = await adapter.getSheetMeta(sheetName);
247
+ if (!meta) return 0;
248
+
249
+ if (!whereClause) {
250
+ // Delete all rows (keep headers)
251
+ const api = await adapter.getSheets();
252
+ await withRetry(() =>
253
+ api.spreadsheets.batchUpdate({
254
+ spreadsheetId: adapter.config.spreadsheetId,
255
+ requestBody: {
256
+ requests: [{
257
+ deleteDimension: {
258
+ range: { sheetId: meta.sheetId, dimension: 'ROWS', startIndex: 1, endIndex: 999999 },
259
+ },
260
+ }],
261
+ },
262
+ })
263
+ );
264
+ return -1; // unknown count
265
+ }
266
+
267
+ // Read and find matching rows
268
+ const raw = await adapter.readRange(esc(sheetName) + '!A:ZZ');
269
+ if (raw.length < 1) return 0;
270
+
271
+ const headers = raw[0] as string[];
272
+ const { text: whereSubst, values: whereVals } = substParams(whereClause, params);
273
+
274
+ const toDelete: number[] = [];
275
+ for (let i = 1; i < raw.length; i++) {
276
+ const row: any = {};
277
+ headers.forEach((h, j) => { row[h] = raw[i][j] ?? null; });
278
+ if (matchRowSQL(row, whereSubst, whereVals)) {
279
+ toDelete.push(i + 1);
280
+ }
281
+ }
282
+
283
+ // Delete from bottom to top
284
+ toDelete.sort((a, b) => b - a);
285
+ for (const rowIdx of toDelete) {
286
+ const api = await adapter.getSheets();
287
+ await withRetry(() =>
288
+ api.spreadsheets.batchUpdate({
289
+ spreadsheetId: adapter.config.spreadsheetId,
290
+ requestBody: {
291
+ requests: [{
292
+ deleteDimension: {
293
+ range: { sheetId: meta.sheetId, dimension: 'ROWS', startIndex: rowIdx - 1, endIndex: rowIdx },
294
+ },
295
+ }],
296
+ },
297
+ })
298
+ );
299
+ }
300
+ return toDelete.length;
301
+ }
@@ -0,0 +1,78 @@
1
+ import type { An5SheetsAdapter } from './adapter';
2
+ export declare class SheetsTableClient<T = any> {
3
+ private adapter;
4
+ private modelName;
5
+ private sheetMapping?;
6
+ constructor(adapter: An5SheetsAdapter, modelName: string, sheetMapping?: Record<string, string> | undefined);
7
+ private get sheetName();
8
+ private get escSheetName();
9
+ private get fields();
10
+ private ensureSheetExists;
11
+ private readAllRows;
12
+ private getOrCreateHeaders;
13
+ private rowToValues;
14
+ private deleteRowByIndex;
15
+ findMany(args?: {
16
+ where?: any;
17
+ orderBy?: any;
18
+ skip?: number;
19
+ take?: number;
20
+ select?: any;
21
+ }): Promise<T[]>;
22
+ findFirst(args?: {
23
+ where?: any;
24
+ orderBy?: any;
25
+ select?: any;
26
+ }): Promise<T | null>;
27
+ findUnique(args: {
28
+ where: any;
29
+ }): Promise<T | null>;
30
+ count(args?: {
31
+ where?: any;
32
+ }): Promise<number>;
33
+ create(args: {
34
+ data: Partial<T>;
35
+ }): Promise<T>;
36
+ createMany(args: {
37
+ data: Partial<T>[];
38
+ skipDuplicates?: boolean;
39
+ }): Promise<{
40
+ count: number;
41
+ }>;
42
+ update(args: {
43
+ where: any;
44
+ data: Partial<T>;
45
+ }): Promise<T>;
46
+ updateMany(args: {
47
+ where?: any;
48
+ data: Partial<T>;
49
+ }): Promise<{
50
+ count: number;
51
+ }>;
52
+ delete(args: {
53
+ where: any;
54
+ }): Promise<T>;
55
+ deleteMany(args?: {
56
+ where?: any;
57
+ }): Promise<{
58
+ count: number;
59
+ }>;
60
+ deleteAll(): Promise<void>;
61
+ clear(): Promise<void>;
62
+ upsert(args: {
63
+ where: any;
64
+ create: Partial<T>;
65
+ update: Partial<T>;
66
+ }): Promise<T>;
67
+ aggregate(args: any): Promise<any>;
68
+ groupBy(args: any): Promise<any[]>;
69
+ vectorSearch(args: {
70
+ vector: number[];
71
+ take?: number;
72
+ where?: any;
73
+ vectorField?: string;
74
+ distanceMetric?: 'cosine' | 'euclidean' | 'dot';
75
+ }): Promise<(T & {
76
+ distance: number;
77
+ })[]>;
78
+ }