@an5/adapters 0.2.1 → 0.2.2

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 (39) hide show
  1. package/README.md +263 -263
  2. package/dotnet/An5Adapter.cs +275 -275
  3. package/package.json +54 -53
  4. package/python/base/metadata.py +10 -10
  5. package/python/table_client.py +216 -216
  6. package/typescript/an5Adapter.d.ts +4 -2
  7. package/typescript/an5Adapter.js +58 -21
  8. package/typescript/browser.d.ts +5 -0
  9. package/typescript/browser.js +12 -0
  10. package/typescript/googlesheets/adapter.js +3 -3
  11. package/typescript/googlesheets/an5SheetsAdapter.d.ts +1 -1
  12. package/typescript/googlesheets/index.d.ts +2 -1
  13. package/typescript/index.d.ts +4 -2
  14. package/typescript/{an5Adapter.ts → src/an5Adapter.ts} +505 -474
  15. package/typescript/{base → src/base}/index.ts +3 -3
  16. package/typescript/{base → src/base}/metadata.ts +28 -28
  17. package/typescript/{base → src/base}/sql.ts +98 -98
  18. package/typescript/{base → src/base}/types.ts +24 -24
  19. package/typescript/src/browser.ts +6 -0
  20. package/typescript/{config.ts → src/config.ts} +95 -95
  21. package/typescript/{googlesheets → src/googlesheets}/adapter.ts +242 -241
  22. package/typescript/{googlesheets → src/googlesheets}/an5SheetsAdapter.ts +3 -3
  23. package/typescript/{googlesheets → src/googlesheets}/config.ts +44 -44
  24. package/typescript/{googlesheets → src/googlesheets}/helpers.ts +124 -124
  25. package/typescript/{googlesheets → src/googlesheets}/index.ts +3 -2
  26. package/typescript/{googlesheets → src/googlesheets}/parseConnectionString.ts +44 -44
  27. package/typescript/{googlesheets → src/googlesheets}/retry.ts +32 -32
  28. package/typescript/{googlesheets → src/googlesheets}/sqlExecutor.ts +301 -301
  29. package/typescript/{googlesheets → src/googlesheets}/tableClient.ts +435 -435
  30. package/typescript/{googlesheets → src/googlesheets}/types.ts +6 -6
  31. package/typescript/{index.ts → src/index.ts} +19 -15
  32. package/typescript/{mssql → src/mssql}/engine.ts +132 -132
  33. package/typescript/{mysql → src/mssql}/index.ts +1 -1
  34. package/typescript/{mysql → src/mysql}/engine.ts +80 -80
  35. package/typescript/{postgres → src/mysql}/index.ts +1 -1
  36. package/typescript/{postgres → src/postgres}/engine.ts +87 -87
  37. package/typescript/{sqlite → src/postgres}/index.ts +1 -1
  38. package/typescript/{sqlite → src/sqlite}/engine.ts +55 -55
  39. package/typescript/{mssql → src/sqlite}/index.ts +1 -1
@@ -1,124 +1,124 @@
1
- import { getModelToTable } from '../base/metadata';
2
-
3
- // ─── Helpers ──────────────────────────────────────────────────────────────────
4
-
5
- export function esc(name: string): string {
6
- return name.includes(' ') || /[^\w]/.test(name) ? `'${name.replace(/'/g, "''")}'` : name;
7
- }
8
-
9
- export function resolveSheetName(modelName: string, mapping?: Record<string, string>): string {
10
- if (mapping?.[modelName]) return mapping[modelName];
11
- const modelToTable = getModelToTable();
12
- return modelToTable[modelName] || modelName;
13
- }
14
-
15
- export function matchWhere(row: Record<string, any>, where: any): boolean {
16
- if (!where) return true;
17
-
18
- const cleanWhere: Record<string, any> = {};
19
- for (const [key, value] of Object.entries(where)) {
20
- if (
21
- key.includes('_') &&
22
- value && typeof value === 'object' &&
23
- !(value instanceof Date) &&
24
- !(value as any).in && !(value as any).contains &&
25
- !(value as any).not && !(value as any).gte &&
26
- !(value as any).lte && !(value as any).gt && !(value as any).lt
27
- ) {
28
- Object.assign(cleanWhere, value);
29
- } else {
30
- cleanWhere[key] = value;
31
- }
32
- }
33
-
34
- for (const [key, value] of Object.entries(cleanWhere)) {
35
- if (key === 'OR' && Array.isArray(value)) {
36
- if (!value.some((v: any) => matchWhere(row, v))) return false;
37
- continue;
38
- }
39
- if (key === 'AND' && Array.isArray(value)) {
40
- if (!value.every((v: any) => matchWhere(row, v))) return false;
41
- continue;
42
- }
43
-
44
- const cellVal = row[key];
45
-
46
- if (value === null) {
47
- if (cellVal !== null && cellVal !== undefined) return false;
48
- } else if (typeof value === 'object' && !(value instanceof Date)) {
49
- const v = value as any;
50
- if (v.not !== undefined) {
51
- if (v.not === null) { if (cellVal === null || cellVal === undefined) return false; }
52
- else if (cellVal == v.not) return false;
53
- }
54
- if (v.equals !== undefined && cellVal != v.equals) return false;
55
- if (v.contains !== undefined && (!cellVal || !String(cellVal).includes(v.contains))) return false;
56
- if (v.startsWith !== undefined && (!cellVal || !String(cellVal).startsWith(v.startsWith))) return false;
57
- if (v.endsWith !== undefined && (!cellVal || !String(cellVal).endsWith(v.endsWith))) return false;
58
- if (v.gte !== undefined && !(Number(cellVal) >= Number(v.gte))) return false;
59
- if (v.lte !== undefined && !(Number(cellVal) <= Number(v.lte))) return false;
60
- if (v.gt !== undefined && !(Number(cellVal) > Number(v.gt))) return false;
61
- if (v.lt !== undefined && !(Number(cellVal) < Number(v.lt))) return false;
62
- if (v.in !== undefined) {
63
- if (!Array.isArray(v.in) || !v.in.includes(cellVal)) return false;
64
- }
65
- } else {
66
- if (cellVal != value) return false;
67
- }
68
- }
69
- return true;
70
- }
71
-
72
- export function buildOrderBy(orderBy: any): string {
73
- if (!orderBy) return '';
74
- const entries = Array.isArray(orderBy) ? orderBy : [orderBy];
75
- const parts: string[] = [];
76
- for (const entry of entries) {
77
- for (const [key, dir] of Object.entries(entry)) {
78
- parts.push(`${key} ${String(dir || 'asc').toUpperCase()}`);
79
- }
80
- }
81
- return parts.join(', ');
82
- }
83
-
84
- export function sortRows(rows: Record<string, any>[], orderBy: string): Record<string, any>[] {
85
- if (!orderBy) return rows;
86
- const clauses = orderBy.split(',').map(c => c.trim());
87
- return [...rows].sort((a, b) => {
88
- for (const clause of clauses) {
89
- const [field, dir] = clause.split(/\s+/);
90
- const direction = dir?.toUpperCase() === 'DESC' ? -1 : 1;
91
- const aVal = a[field];
92
- const bVal = b[field];
93
- if (aVal == null && bVal == null) continue;
94
- if (aVal == null) return 1 * direction;
95
- if (bVal == null) return -1 * direction;
96
- if (aVal < bVal) return -1 * direction;
97
- if (aVal > bVal) return 1 * direction;
98
- }
99
- return 0;
100
- });
101
- }
102
-
103
- export function coerceCell(raw: any, fieldMeta?: { ts?: string; sql?: string }): any {
104
- if (raw === undefined || raw === '') return null;
105
- if (raw instanceof Date) return raw.toISOString();
106
- if (fieldMeta) {
107
- const tsType = (fieldMeta.ts || '').toLowerCase().replace('?', '');
108
- if (['number', 'int', 'float', 'double', 'decimal'].includes(tsType)) {
109
- const n = Number(raw);
110
- return isNaN(n) ? raw : n;
111
- }
112
- if (tsType === 'boolean' || tsType === 'bool') {
113
- if (raw === 'true' || raw === true) return true;
114
- if (raw === 'false' || raw === false) return false;
115
- }
116
- }
117
- if (typeof raw === 'string' && /^-?\d+\.?\d*$/.test(raw.trim()) && !/^0\d/.test(raw.trim())) {
118
- const n = Number(raw);
119
- if (!isNaN(n) && String(n) === raw.trim()) return n;
120
- }
121
- if (raw === 'true') return true;
122
- if (raw === 'false') return false;
123
- return raw;
124
- }
1
+ import { getModelToTable } from '../base/metadata';
2
+
3
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
4
+
5
+ export function esc(name: string): string {
6
+ return name.includes(' ') || /[^\w]/.test(name) ? `'${name.replace(/'/g, "''")}'` : name;
7
+ }
8
+
9
+ export function resolveSheetName(modelName: string, mapping?: Record<string, string>): string {
10
+ if (mapping?.[modelName]) return mapping[modelName];
11
+ const modelToTable = getModelToTable();
12
+ return modelToTable[modelName] || modelName;
13
+ }
14
+
15
+ export function matchWhere(row: Record<string, any>, where: any): boolean {
16
+ if (!where) return true;
17
+
18
+ const cleanWhere: Record<string, any> = {};
19
+ for (const [key, value] of Object.entries(where)) {
20
+ if (
21
+ key.includes('_') &&
22
+ value && typeof value === 'object' &&
23
+ !(value instanceof Date) &&
24
+ !(value as any).in && !(value as any).contains &&
25
+ !(value as any).not && !(value as any).gte &&
26
+ !(value as any).lte && !(value as any).gt && !(value as any).lt
27
+ ) {
28
+ Object.assign(cleanWhere, value);
29
+ } else {
30
+ cleanWhere[key] = value;
31
+ }
32
+ }
33
+
34
+ for (const [key, value] of Object.entries(cleanWhere)) {
35
+ if (key === 'OR' && Array.isArray(value)) {
36
+ if (!value.some((v: any) => matchWhere(row, v))) return false;
37
+ continue;
38
+ }
39
+ if (key === 'AND' && Array.isArray(value)) {
40
+ if (!value.every((v: any) => matchWhere(row, v))) return false;
41
+ continue;
42
+ }
43
+
44
+ const cellVal = row[key];
45
+
46
+ if (value === null) {
47
+ if (cellVal !== null && cellVal !== undefined) return false;
48
+ } else if (typeof value === 'object' && !(value instanceof Date)) {
49
+ const v = value as any;
50
+ if (v.not !== undefined) {
51
+ if (v.not === null) { if (cellVal === null || cellVal === undefined) return false; }
52
+ else if (cellVal == v.not) return false;
53
+ }
54
+ if (v.equals !== undefined && cellVal != v.equals) return false;
55
+ if (v.contains !== undefined && (!cellVal || !String(cellVal).includes(v.contains))) return false;
56
+ if (v.startsWith !== undefined && (!cellVal || !String(cellVal).startsWith(v.startsWith))) return false;
57
+ if (v.endsWith !== undefined && (!cellVal || !String(cellVal).endsWith(v.endsWith))) return false;
58
+ if (v.gte !== undefined && !(Number(cellVal) >= Number(v.gte))) return false;
59
+ if (v.lte !== undefined && !(Number(cellVal) <= Number(v.lte))) return false;
60
+ if (v.gt !== undefined && !(Number(cellVal) > Number(v.gt))) return false;
61
+ if (v.lt !== undefined && !(Number(cellVal) < Number(v.lt))) return false;
62
+ if (v.in !== undefined) {
63
+ if (!Array.isArray(v.in) || !v.in.includes(cellVal)) return false;
64
+ }
65
+ } else {
66
+ if (cellVal != value) return false;
67
+ }
68
+ }
69
+ return true;
70
+ }
71
+
72
+ export function buildOrderBy(orderBy: any): string {
73
+ if (!orderBy) return '';
74
+ const entries = Array.isArray(orderBy) ? orderBy : [orderBy];
75
+ const parts: string[] = [];
76
+ for (const entry of entries) {
77
+ for (const [key, dir] of Object.entries(entry)) {
78
+ parts.push(`${key} ${String(dir || 'asc').toUpperCase()}`);
79
+ }
80
+ }
81
+ return parts.join(', ');
82
+ }
83
+
84
+ export function sortRows(rows: Record<string, any>[], orderBy: string): Record<string, any>[] {
85
+ if (!orderBy) return rows;
86
+ const clauses = orderBy.split(',').map(c => c.trim());
87
+ return [...rows].sort((a, b) => {
88
+ for (const clause of clauses) {
89
+ const [field, dir] = clause.split(/\s+/);
90
+ const direction = dir?.toUpperCase() === 'DESC' ? -1 : 1;
91
+ const aVal = a[field];
92
+ const bVal = b[field];
93
+ if (aVal == null && bVal == null) continue;
94
+ if (aVal == null) return 1 * direction;
95
+ if (bVal == null) return -1 * direction;
96
+ if (aVal < bVal) return -1 * direction;
97
+ if (aVal > bVal) return 1 * direction;
98
+ }
99
+ return 0;
100
+ });
101
+ }
102
+
103
+ export function coerceCell(raw: any, fieldMeta?: { ts?: string; sql?: string }): any {
104
+ if (raw === undefined || raw === '') return null;
105
+ if (raw instanceof Date) return raw.toISOString();
106
+ if (fieldMeta) {
107
+ const tsType = (fieldMeta.ts || '').toLowerCase().replace('?', '');
108
+ if (['number', 'int', 'float', 'double', 'decimal'].includes(tsType)) {
109
+ const n = Number(raw);
110
+ return isNaN(n) ? raw : n;
111
+ }
112
+ if (tsType === 'boolean' || tsType === 'bool') {
113
+ if (raw === 'true' || raw === true) return true;
114
+ if (raw === 'false' || raw === false) return false;
115
+ }
116
+ }
117
+ if (typeof raw === 'string' && /^-?\d+\.?\d*$/.test(raw.trim()) && !/^0\d/.test(raw.trim())) {
118
+ const n = Number(raw);
119
+ if (!isNaN(n) && String(n) === raw.trim()) return n;
120
+ }
121
+ if (raw === 'true') return true;
122
+ if (raw === 'false') return false;
123
+ return raw;
124
+ }
@@ -1,2 +1,3 @@
1
- export { An5SheetsAdapter, SheetsTableClient, createAn5SheetsAdapter, An5SheetsAdapterConfig } from './an5SheetsAdapter';
2
- export { parseSheetsConnectionString } from './parseConnectionString';
1
+ export { An5SheetsAdapter, SheetsTableClient, createAn5SheetsAdapter } from './an5SheetsAdapter';
2
+ export type { An5SheetsAdapterConfig } from './an5SheetsAdapter';
3
+ export { parseSheetsConnectionString } from './parseConnectionString';
@@ -1,44 +1,44 @@
1
- import type { An5SheetsAdapterConfig } from './config';
2
-
3
- /**
4
- * Parse a `googlesheets://` connection string into An5SheetsAdapterConfig.
5
- *
6
- * Format:
7
- * googlesheets://spreadsheetId;clientEmail=xxx;privateKey=xxx
8
- * googlesheets://spreadsheetId;clientEmail=xxx;privateKey=xxx;sheetMapping=users:Users,orders:Orders
9
- *
10
- * The privateKey must be URL-encoded (use encodeURIComponent on the raw key).
11
- */
12
- export function parseSheetsConnectionString(url: string): An5SheetsAdapterConfig {
13
- const clean = url.replace(/^googlesheets:\/\//, '');
14
- const parts = clean.split(';');
15
-
16
- const spreadsheetId = parts[0].trim();
17
- const config: Record<string, string> = {};
18
-
19
- for (let i = 1; i < parts.length; i++) {
20
- const part = parts[i].trim();
21
- if (!part) continue;
22
- const eqIdx = part.indexOf('=');
23
- if (eqIdx === -1) continue;
24
- const key = part.slice(0, eqIdx).trim().toLowerCase();
25
- const value = decodeURIComponent(part.slice(eqIdx + 1).trim());
26
- config[key] = value;
27
- }
28
-
29
- const result: An5SheetsAdapterConfig = { spreadsheetId };
30
-
31
- if (config.clientemail) result.clientEmail = config.clientemail;
32
- if (config.privatekey) result.privateKey = config.privatekey;
33
-
34
- if (config.sheetmapping) {
35
- const mapping: Record<string, string> = {};
36
- for (const pair of config.sheetmapping.split(',')) {
37
- const [k, v] = pair.split(':');
38
- if (k && v) mapping[k.trim()] = v.trim();
39
- }
40
- result.sheetMapping = mapping;
41
- }
42
-
43
- return result;
44
- }
1
+ import type { An5SheetsAdapterConfig } from './config';
2
+
3
+ /**
4
+ * Parse a `googlesheets://` connection string into An5SheetsAdapterConfig.
5
+ *
6
+ * Format:
7
+ * googlesheets://spreadsheetId;clientEmail=xxx;privateKey=xxx
8
+ * googlesheets://spreadsheetId;clientEmail=xxx;privateKey=xxx;sheetMapping=users:Users,orders:Orders
9
+ *
10
+ * The privateKey must be URL-encoded (use encodeURIComponent on the raw key).
11
+ */
12
+ export function parseSheetsConnectionString(url: string): An5SheetsAdapterConfig {
13
+ const clean = url.replace(/^googlesheets:\/\//, '');
14
+ const parts = clean.split(';');
15
+
16
+ const spreadsheetId = parts[0].trim();
17
+ const config: Record<string, string> = {};
18
+
19
+ for (let i = 1; i < parts.length; i++) {
20
+ const part = parts[i].trim();
21
+ if (!part) continue;
22
+ const eqIdx = part.indexOf('=');
23
+ if (eqIdx === -1) continue;
24
+ const key = part.slice(0, eqIdx).trim().toLowerCase();
25
+ const value = decodeURIComponent(part.slice(eqIdx + 1).trim());
26
+ config[key] = value;
27
+ }
28
+
29
+ const result: An5SheetsAdapterConfig = { spreadsheetId };
30
+
31
+ if (config.clientemail) result.clientEmail = config.clientemail;
32
+ if (config.privatekey) result.privateKey = config.privatekey;
33
+
34
+ if (config.sheetmapping) {
35
+ const mapping: Record<string, string> = {};
36
+ for (const pair of config.sheetmapping.split(',')) {
37
+ const [k, v] = pair.split(':');
38
+ if (k && v) mapping[k.trim()] = v.trim();
39
+ }
40
+ result.sheetMapping = mapping;
41
+ }
42
+
43
+ return result;
44
+ }
@@ -1,32 +1,32 @@
1
- // ─── Retry helper ─────────────────────────────────────────────────────────────
2
-
3
- export const MAX_RETRIES = 3;
4
- export const BASE_DELAY = 1000;
5
-
6
- function isRateLimitError(err: any): boolean {
7
- const code = err?.code || err?.status || err?.response?.status;
8
- if (code === 429) return true;
9
- if (code === 500 || code === 503) return true;
10
- if (code === 403) {
11
- const reason = err?.errors?.[0]?.reason || '';
12
- if (reason === 'rateLimitExceeded') return true;
13
- if (typeof err?.body === 'string' && err.body.includes('rateLimitExceeded')) return true;
14
- if (typeof err?.message === 'string' && err.message.includes('rateLimitExceeded')) return true;
15
- }
16
- return false;
17
- }
18
-
19
- export async function withRetry<T>(fn: () => Promise<T>, attempt = 1): Promise<T> {
20
- try {
21
- return await fn();
22
- } catch (err: any) {
23
- if (isRateLimitError(err) && attempt <= MAX_RETRIES) {
24
- const delay = err.retryAfter
25
- ? err.retryAfter * 1000
26
- : BASE_DELAY * Math.pow(2, attempt - 1) + Math.random() * 500;
27
- await new Promise(r => setTimeout(r, delay));
28
- return withRetry(fn, attempt + 1);
29
- }
30
- throw err;
31
- }
32
- }
1
+ // ─── Retry helper ─────────────────────────────────────────────────────────────
2
+
3
+ export const MAX_RETRIES = 3;
4
+ export const BASE_DELAY = 1000;
5
+
6
+ function isRateLimitError(err: any): boolean {
7
+ const code = err?.code || err?.status || err?.response?.status;
8
+ if (code === 429) return true;
9
+ if (code === 500 || code === 503) return true;
10
+ if (code === 403) {
11
+ const reason = err?.errors?.[0]?.reason || '';
12
+ if (reason === 'rateLimitExceeded') return true;
13
+ if (typeof err?.body === 'string' && err.body.includes('rateLimitExceeded')) return true;
14
+ if (typeof err?.message === 'string' && err.message.includes('rateLimitExceeded')) return true;
15
+ }
16
+ return false;
17
+ }
18
+
19
+ export async function withRetry<T>(fn: () => Promise<T>, attempt = 1): Promise<T> {
20
+ try {
21
+ return await fn();
22
+ } catch (err: any) {
23
+ if (isRateLimitError(err) && attempt <= MAX_RETRIES) {
24
+ const delay = err.retryAfter
25
+ ? err.retryAfter * 1000
26
+ : BASE_DELAY * Math.pow(2, attempt - 1) + Math.random() * 500;
27
+ await new Promise(r => setTimeout(r, delay));
28
+ return withRetry(fn, attempt + 1);
29
+ }
30
+ throw err;
31
+ }
32
+ }