@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,167 @@
1
+ import { google, sheets_v4 } from 'googleapis';
2
+ import { An5SheetsAdapterConfig, resolveConfig } from './config';
3
+ import { execQuery } from './sqlExecutor';
4
+ import { SheetsTableClient } from './tableClient';
5
+ import { SheetMeta } from './types';
6
+ import { withRetry } from './retry';
7
+
8
+ // ─── Adapter ──────────────────────────────────────────────────────────────────
9
+
10
+ export class An5SheetsAdapter {
11
+ private sheets: sheets_v4.Sheets | null = null;
12
+ config: ReturnType<typeof resolveConfig>;
13
+ private sheetsCache: Promise<SheetMeta[]> | null = null;
14
+
15
+ constructor(config: An5SheetsAdapterConfig) {
16
+ this.config = resolveConfig(config);
17
+ }
18
+
19
+ async getSheets(): Promise<sheets_v4.Sheets> {
20
+ if (!this.sheets) {
21
+ const auth = new google.auth.JWT({
22
+ email: this.config.clientEmail,
23
+ key: this.config.privateKey,
24
+ scopes: ['https://www.googleapis.com/auth/spreadsheets'],
25
+ });
26
+ this.sheets = google.sheets({ version: 'v4', auth });
27
+ }
28
+ return this.sheets;
29
+ }
30
+
31
+ private async getApi() {
32
+ return this.getSheets();
33
+ }
34
+
35
+ invalidateCache(): void {
36
+ this.sheetsCache = null;
37
+ }
38
+
39
+ async listSheets(): Promise<string[]> {
40
+ const api = await this.getApi();
41
+ const res = await withRetry(() =>
42
+ api.spreadsheets.get({
43
+ spreadsheetId: this.config.spreadsheetId,
44
+ fields: 'sheets.properties.title',
45
+ })
46
+ );
47
+ return (res.data.sheets || []).map(s => s.properties?.title || '');
48
+ }
49
+
50
+ async getSheetMeta(name: string): Promise<SheetMeta | null> {
51
+ const api = await this.getApi();
52
+ if (this.sheetsCache == null) {
53
+ this.sheetsCache = withRetry(async () => {
54
+ const res = await api.spreadsheets.get({
55
+ spreadsheetId: this.config.spreadsheetId,
56
+ fields: 'sheets.properties',
57
+ });
58
+ return (res.data.sheets || []).map(s => ({
59
+ sheetId: s.properties?.sheetId ?? -1,
60
+ title: s.properties?.title ?? '',
61
+ })).filter(m => m.sheetId >= 0);
62
+ });
63
+ }
64
+ const all = await this.sheetsCache;
65
+ return all.find(m => m.title === name) || null;
66
+ }
67
+
68
+ async deleteSheet(name: string): Promise<void> {
69
+ const meta = await this.getSheetMeta(name);
70
+ if (!meta) throw new Error(`Sheet "${name}" not found`);
71
+ const api = await this.getApi();
72
+ await withRetry(() =>
73
+ api.spreadsheets.batchUpdate({
74
+ spreadsheetId: this.config.spreadsheetId,
75
+ requestBody: {
76
+ requests: [{ deleteSheet: { sheetId: meta.sheetId } }],
77
+ },
78
+ })
79
+ );
80
+ this.invalidateCache();
81
+ }
82
+
83
+ async $connect(): Promise<void> {
84
+ await this.getSheets();
85
+ }
86
+
87
+ async $disconnect(): Promise<void> {
88
+ this.sheets = null;
89
+ this.sheetsCache = null;
90
+ }
91
+
92
+ async exec<T = any>(query: string, params?: Record<string, any>): Promise<T[]> {
93
+ return execQuery(this, query, params, undefined, undefined) as Promise<T[]>;
94
+ }
95
+
96
+ async $queryRawUnsafe<T = any>(query: string, ...values: any[]): Promise<T[]> {
97
+ const params: Record<string, any> = {};
98
+ values.forEach((v, i) => { params[`p_${i}`] = v; });
99
+ const resolved = query.replace(/@p_(\d+)/g, (_, i) => `@p_${i}`);
100
+ return execQuery(this, resolved, params, undefined, undefined) as Promise<T[]>;
101
+ }
102
+
103
+ async $executeRaw(query: string, ...values: any[]): Promise<number> {
104
+ const params: Record<string, any> = {};
105
+ values.forEach((v, i) => { params[`p_${i}`] = v; });
106
+ const resolved = query.replace(/@p_(\d+)/g, (_, i) => `@p_${i}`);
107
+ return execQuery(this, resolved, params, undefined, undefined) as Promise<number>;
108
+ }
109
+
110
+ async $executeRawUnsafe(query: string, ...values: any[]): Promise<number> {
111
+ return this.$executeRaw(query, ...values);
112
+ }
113
+
114
+ async $transaction<R>(fn: (tx: An5SheetsAdapter) => Promise<R>, _options?: { timeout?: number }): Promise<R>;
115
+ async $transaction<R>(list: Promise<R>[]): Promise<R[]>;
116
+ async $transaction(fn: any, _options?: any): Promise<any> {
117
+ if (typeof fn === 'function') return fn(this);
118
+ return Promise.all(fn);
119
+ }
120
+
121
+ table<T = any>(modelName: string): SheetsTableClient<T> {
122
+ return new SheetsTableClient<T>(this, modelName, this.config.sheetMapping);
123
+ }
124
+
125
+ // ── Raw range access ──────────────────────────────────────────────────────
126
+
127
+ async readRange<T = any>(range: string): Promise<T[][]> {
128
+ const api = await this.getApi();
129
+ const res = await withRetry(() =>
130
+ api.spreadsheets.values.get({
131
+ spreadsheetId: this.config.spreadsheetId,
132
+ range,
133
+ })
134
+ );
135
+ return (res.data.values || []) as T[][];
136
+ }
137
+
138
+ async writeRange(range: string, values: any[][]): Promise<void> {
139
+ const api = await this.getApi();
140
+ await withRetry(() =>
141
+ api.spreadsheets.values.update({
142
+ spreadsheetId: this.config.spreadsheetId,
143
+ range,
144
+ valueInputOption: 'RAW',
145
+ requestBody: { values },
146
+ })
147
+ );
148
+ }
149
+
150
+ async appendRange(range: string, values: any[][]): Promise<void> {
151
+ const api = await this.getApi();
152
+ await withRetry(() =>
153
+ api.spreadsheets.values.append({
154
+ spreadsheetId: this.config.spreadsheetId,
155
+ range,
156
+ valueInputOption: 'RAW',
157
+ insertDataOption: 'INSERT_ROWS',
158
+ requestBody: { values },
159
+ })
160
+ );
161
+ }
162
+ }
163
+ // ─── Factory ──────────────────────────────────────────────────────────────────
164
+
165
+ export function createAn5SheetsAdapter(config: An5SheetsAdapterConfig): An5SheetsAdapter {
166
+ return new An5SheetsAdapter(config);
167
+ }
@@ -0,0 +1,3 @@
1
+ export { An5SheetsAdapter, createAn5SheetsAdapter } from './adapter';
2
+ export { SheetsTableClient } from './tableClient';
3
+ export { An5SheetsAdapterConfig } from './config';
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SheetsTableClient = exports.createAn5SheetsAdapter = exports.An5SheetsAdapter = void 0;
4
+ var adapter_1 = require("./adapter");
5
+ Object.defineProperty(exports, "An5SheetsAdapter", { enumerable: true, get: function () { return adapter_1.An5SheetsAdapter; } });
6
+ Object.defineProperty(exports, "createAn5SheetsAdapter", { enumerable: true, get: function () { return adapter_1.createAn5SheetsAdapter; } });
7
+ var tableClient_1 = require("./tableClient");
8
+ Object.defineProperty(exports, "SheetsTableClient", { enumerable: true, get: function () { return tableClient_1.SheetsTableClient; } });
@@ -0,0 +1,3 @@
1
+ export { An5SheetsAdapter, createAn5SheetsAdapter } from './adapter';
2
+ export { SheetsTableClient } from './tableClient';
3
+ export { An5SheetsAdapterConfig } from './config';
@@ -0,0 +1,17 @@
1
+ export interface An5SheetsAdapterConfig {
2
+ spreadsheetId: string;
3
+ clientEmail?: string;
4
+ privateKey?: string;
5
+ credentials?: {
6
+ client_email: string;
7
+ private_key: string;
8
+ };
9
+ sheetMapping?: Record<string, string>;
10
+ }
11
+ export declare function normalizeKey(key: string): string;
12
+ export declare function resolveConfig(config: An5SheetsAdapterConfig): {
13
+ spreadsheetId: string;
14
+ clientEmail: string;
15
+ privateKey: string;
16
+ sheetMapping: Record<string, string> | undefined;
17
+ };
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ // ─── Config ───────────────────────────────────────────────────────────────────
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.normalizeKey = normalizeKey;
5
+ exports.resolveConfig = resolveConfig;
6
+ function normalizeKey(key) {
7
+ if (key.includes('PRIVATE KEY'))
8
+ return key;
9
+ const lines = key.split('\\n').join('\n');
10
+ if (lines.includes('PRIVATE KEY'))
11
+ return lines;
12
+ return `-----BEGIN PRIVATE KEY-----\n${lines}\n-----END PRIVATE KEY-----`;
13
+ }
14
+ function resolveConfig(config) {
15
+ if (config.credentials) {
16
+ return {
17
+ spreadsheetId: config.spreadsheetId,
18
+ clientEmail: config.credentials.client_email,
19
+ privateKey: normalizeKey(config.credentials.private_key),
20
+ sheetMapping: config.sheetMapping,
21
+ };
22
+ }
23
+ return {
24
+ spreadsheetId: config.spreadsheetId,
25
+ clientEmail: config.clientEmail,
26
+ privateKey: normalizeKey(config.privateKey),
27
+ sheetMapping: config.sheetMapping,
28
+ };
29
+ }
@@ -0,0 +1,33 @@
1
+ // ─── Config ───────────────────────────────────────────────────────────────────
2
+
3
+ export interface An5SheetsAdapterConfig {
4
+ spreadsheetId: string;
5
+ clientEmail?: string;
6
+ privateKey?: string;
7
+ credentials?: { client_email: string; private_key: string };
8
+ sheetMapping?: Record<string, string>;
9
+ }
10
+
11
+ export function normalizeKey(key: string): string {
12
+ if (key.includes('PRIVATE KEY')) return key;
13
+ const lines = key.split('\\n').join('\n');
14
+ if (lines.includes('PRIVATE KEY')) return lines;
15
+ return `-----BEGIN PRIVATE KEY-----\n${lines}\n-----END PRIVATE KEY-----`;
16
+ }
17
+
18
+ export function resolveConfig(config: An5SheetsAdapterConfig) {
19
+ if (config.credentials) {
20
+ return {
21
+ spreadsheetId: config.spreadsheetId,
22
+ clientEmail: config.credentials.client_email,
23
+ privateKey: normalizeKey(config.credentials.private_key),
24
+ sheetMapping: config.sheetMapping,
25
+ };
26
+ }
27
+ return {
28
+ spreadsheetId: config.spreadsheetId,
29
+ clientEmail: config.clientEmail!,
30
+ privateKey: normalizeKey(config.privateKey!),
31
+ sheetMapping: config.sheetMapping,
32
+ };
33
+ }
@@ -0,0 +1,9 @@
1
+ export declare function esc(name: string): string;
2
+ export declare function resolveSheetName(modelName: string, mapping?: Record<string, string>): string;
3
+ export declare function matchWhere(row: Record<string, any>, where: any): boolean;
4
+ export declare function buildOrderBy(orderBy: any): string;
5
+ export declare function sortRows(rows: Record<string, any>[], orderBy: string): Record<string, any>[];
6
+ export declare function coerceCell(raw: any, fieldMeta?: {
7
+ ts?: string;
8
+ sql?: string;
9
+ }): any;
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.esc = esc;
4
+ exports.resolveSheetName = resolveSheetName;
5
+ exports.matchWhere = matchWhere;
6
+ exports.buildOrderBy = buildOrderBy;
7
+ exports.sortRows = sortRows;
8
+ exports.coerceCell = coerceCell;
9
+ const metadata_1 = require("../base/metadata");
10
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
11
+ function esc(name) {
12
+ return name.includes(' ') || /[^\w]/.test(name) ? `'${name.replace(/'/g, "''")}'` : name;
13
+ }
14
+ function resolveSheetName(modelName, mapping) {
15
+ if (mapping?.[modelName])
16
+ return mapping[modelName];
17
+ const modelToTable = (0, metadata_1.getModelToTable)();
18
+ return modelToTable[modelName] || modelName;
19
+ }
20
+ function matchWhere(row, where) {
21
+ if (!where)
22
+ return true;
23
+ const cleanWhere = {};
24
+ for (const [key, value] of Object.entries(where)) {
25
+ if (key.includes('_') &&
26
+ value && typeof value === 'object' &&
27
+ !(value instanceof Date) &&
28
+ !value.in && !value.contains &&
29
+ !value.not && !value.gte &&
30
+ !value.lte && !value.gt && !value.lt) {
31
+ Object.assign(cleanWhere, value);
32
+ }
33
+ else {
34
+ cleanWhere[key] = value;
35
+ }
36
+ }
37
+ for (const [key, value] of Object.entries(cleanWhere)) {
38
+ if (key === 'OR' && Array.isArray(value)) {
39
+ if (!value.some((v) => matchWhere(row, v)))
40
+ return false;
41
+ continue;
42
+ }
43
+ if (key === 'AND' && Array.isArray(value)) {
44
+ if (!value.every((v) => matchWhere(row, v)))
45
+ return false;
46
+ continue;
47
+ }
48
+ const cellVal = row[key];
49
+ if (value === null) {
50
+ if (cellVal !== null && cellVal !== undefined)
51
+ return false;
52
+ }
53
+ else if (typeof value === 'object' && !(value instanceof Date)) {
54
+ const v = value;
55
+ if (v.not !== undefined) {
56
+ if (v.not === null) {
57
+ if (cellVal === null || cellVal === undefined)
58
+ return false;
59
+ }
60
+ else if (cellVal == v.not)
61
+ return false;
62
+ }
63
+ if (v.equals !== undefined && cellVal != v.equals)
64
+ return false;
65
+ if (v.contains !== undefined && (!cellVal || !String(cellVal).includes(v.contains)))
66
+ return false;
67
+ if (v.startsWith !== undefined && (!cellVal || !String(cellVal).startsWith(v.startsWith)))
68
+ return false;
69
+ if (v.endsWith !== undefined && (!cellVal || !String(cellVal).endsWith(v.endsWith)))
70
+ return false;
71
+ if (v.gte !== undefined && !(Number(cellVal) >= Number(v.gte)))
72
+ return false;
73
+ if (v.lte !== undefined && !(Number(cellVal) <= Number(v.lte)))
74
+ return false;
75
+ if (v.gt !== undefined && !(Number(cellVal) > Number(v.gt)))
76
+ return false;
77
+ if (v.lt !== undefined && !(Number(cellVal) < Number(v.lt)))
78
+ return false;
79
+ if (v.in !== undefined) {
80
+ if (!Array.isArray(v.in) || !v.in.includes(cellVal))
81
+ return false;
82
+ }
83
+ }
84
+ else {
85
+ if (cellVal != value)
86
+ return false;
87
+ }
88
+ }
89
+ return true;
90
+ }
91
+ function buildOrderBy(orderBy) {
92
+ if (!orderBy)
93
+ return '';
94
+ const entries = Array.isArray(orderBy) ? orderBy : [orderBy];
95
+ const parts = [];
96
+ for (const entry of entries) {
97
+ for (const [key, dir] of Object.entries(entry)) {
98
+ parts.push(`${key} ${dir.toUpperCase()}`);
99
+ }
100
+ }
101
+ return parts.join(', ');
102
+ }
103
+ function sortRows(rows, orderBy) {
104
+ if (!orderBy)
105
+ return rows;
106
+ const clauses = orderBy.split(',').map(c => c.trim());
107
+ return [...rows].sort((a, b) => {
108
+ for (const clause of clauses) {
109
+ const [field, dir] = clause.split(/\s+/);
110
+ const direction = dir?.toUpperCase() === 'DESC' ? -1 : 1;
111
+ const aVal = a[field];
112
+ const bVal = b[field];
113
+ if (aVal == null && bVal == null)
114
+ continue;
115
+ if (aVal == null)
116
+ return 1 * direction;
117
+ if (bVal == null)
118
+ return -1 * direction;
119
+ if (aVal < bVal)
120
+ return -1 * direction;
121
+ if (aVal > bVal)
122
+ return 1 * direction;
123
+ }
124
+ return 0;
125
+ });
126
+ }
127
+ function coerceCell(raw, fieldMeta) {
128
+ if (raw === undefined || raw === '')
129
+ return null;
130
+ if (raw instanceof Date)
131
+ return raw.toISOString();
132
+ if (fieldMeta) {
133
+ const tsType = (fieldMeta.ts || '').toLowerCase().replace('?', '');
134
+ if (['number', 'int', 'float', 'double', 'decimal'].includes(tsType)) {
135
+ const n = Number(raw);
136
+ return isNaN(n) ? raw : n;
137
+ }
138
+ if (tsType === 'boolean' || tsType === 'bool') {
139
+ if (raw === 'true' || raw === true)
140
+ return true;
141
+ if (raw === 'false' || raw === false)
142
+ return false;
143
+ }
144
+ }
145
+ if (typeof raw === 'string' && /^-?\d+\.?\d*$/.test(raw.trim()) && !/^0\d/.test(raw.trim())) {
146
+ const n = Number(raw);
147
+ if (!isNaN(n) && String(n) === raw.trim())
148
+ return n;
149
+ }
150
+ if (raw === 'true')
151
+ return true;
152
+ if (raw === 'false')
153
+ return false;
154
+ return raw;
155
+ }
@@ -0,0 +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} ${(dir as string).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
+ }
@@ -0,0 +1,2 @@
1
+ export { An5SheetsAdapter, SheetsTableClient, createAn5SheetsAdapter, An5SheetsAdapterConfig } from './an5SheetsAdapter';
2
+ export { parseSheetsConnectionString } from './parseConnectionString';
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseSheetsConnectionString = exports.createAn5SheetsAdapter = exports.SheetsTableClient = exports.An5SheetsAdapter = void 0;
4
+ var an5SheetsAdapter_1 = require("./an5SheetsAdapter");
5
+ Object.defineProperty(exports, "An5SheetsAdapter", { enumerable: true, get: function () { return an5SheetsAdapter_1.An5SheetsAdapter; } });
6
+ Object.defineProperty(exports, "SheetsTableClient", { enumerable: true, get: function () { return an5SheetsAdapter_1.SheetsTableClient; } });
7
+ Object.defineProperty(exports, "createAn5SheetsAdapter", { enumerable: true, get: function () { return an5SheetsAdapter_1.createAn5SheetsAdapter; } });
8
+ var parseConnectionString_1 = require("./parseConnectionString");
9
+ Object.defineProperty(exports, "parseSheetsConnectionString", { enumerable: true, get: function () { return parseConnectionString_1.parseSheetsConnectionString; } });
@@ -0,0 +1,2 @@
1
+ export { An5SheetsAdapter, SheetsTableClient, createAn5SheetsAdapter, An5SheetsAdapterConfig } from './an5SheetsAdapter';
2
+ export { parseSheetsConnectionString } from './parseConnectionString';
@@ -0,0 +1,11 @@
1
+ import type { An5SheetsAdapterConfig } from './config';
2
+ /**
3
+ * Parse a `googlesheets://` connection string into An5SheetsAdapterConfig.
4
+ *
5
+ * Format:
6
+ * googlesheets://spreadsheetId;clientEmail=xxx;privateKey=xxx
7
+ * googlesheets://spreadsheetId;clientEmail=xxx;privateKey=xxx;sheetMapping=users:Users,orders:Orders
8
+ *
9
+ * The privateKey must be URL-encoded (use encodeURIComponent on the raw key).
10
+ */
11
+ export declare function parseSheetsConnectionString(url: string): An5SheetsAdapterConfig;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseSheetsConnectionString = parseSheetsConnectionString;
4
+ /**
5
+ * Parse a `googlesheets://` connection string into An5SheetsAdapterConfig.
6
+ *
7
+ * Format:
8
+ * googlesheets://spreadsheetId;clientEmail=xxx;privateKey=xxx
9
+ * googlesheets://spreadsheetId;clientEmail=xxx;privateKey=xxx;sheetMapping=users:Users,orders:Orders
10
+ *
11
+ * The privateKey must be URL-encoded (use encodeURIComponent on the raw key).
12
+ */
13
+ function parseSheetsConnectionString(url) {
14
+ const clean = url.replace(/^googlesheets:\/\//, '');
15
+ const parts = clean.split(';');
16
+ const spreadsheetId = parts[0].trim();
17
+ const config = {};
18
+ for (let i = 1; i < parts.length; i++) {
19
+ const part = parts[i].trim();
20
+ if (!part)
21
+ continue;
22
+ const eqIdx = part.indexOf('=');
23
+ if (eqIdx === -1)
24
+ continue;
25
+ const key = part.slice(0, eqIdx).trim().toLowerCase();
26
+ const value = decodeURIComponent(part.slice(eqIdx + 1).trim());
27
+ config[key] = value;
28
+ }
29
+ const result = { spreadsheetId };
30
+ if (config.clientemail)
31
+ result.clientEmail = config.clientemail;
32
+ if (config.privatekey)
33
+ result.privateKey = config.privatekey;
34
+ if (config.sheetmapping) {
35
+ const mapping = {};
36
+ for (const pair of config.sheetmapping.split(',')) {
37
+ const [k, v] = pair.split(':');
38
+ if (k && v)
39
+ mapping[k.trim()] = v.trim();
40
+ }
41
+ result.sheetMapping = mapping;
42
+ }
43
+ return result;
44
+ }