@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,143 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.quote = quote;
4
+ exports.parseWhere = parseWhere;
5
+ exports.buildOrderBy = buildOrderBy;
6
+ // ─── Quoting ───────────────────────────────────────────────────────────────────────
7
+ function quote(name, dialect) {
8
+ if (dialect === 'postgres') {
9
+ if (name.startsWith('[') && name.endsWith(']'))
10
+ return `"${name.slice(1, -1)}"`;
11
+ if (name.startsWith('"'))
12
+ return name;
13
+ return `"${name}"`;
14
+ }
15
+ if (dialect === 'mysql') {
16
+ if (name.startsWith('[') && name.endsWith(']'))
17
+ return `\`${name.slice(1, -1)}\``;
18
+ if (name.startsWith('`'))
19
+ return name;
20
+ return `\`${name}\``;
21
+ }
22
+ // mssql / sqlite
23
+ return name.startsWith('[') ? name : `[${name}]`;
24
+ }
25
+ // ─── Where clause builder ──────────────────────────────────────────────────────────
26
+ function parseWhere(modelName, where, params, dialect, prefix = '') {
27
+ if (!where)
28
+ return '';
29
+ const conditions = [];
30
+ const cleanWhere = {};
31
+ for (const [key, value] of Object.entries(where)) {
32
+ if (key.includes('_') &&
33
+ value && typeof value === 'object' &&
34
+ !(value instanceof Date) &&
35
+ !value.in && !value.contains &&
36
+ !value.not && !value.gte &&
37
+ !value.lte && !value.gt && !value.lt) {
38
+ Object.assign(cleanWhere, value);
39
+ }
40
+ else {
41
+ cleanWhere[key] = value;
42
+ }
43
+ }
44
+ for (const [key, value] of Object.entries(cleanWhere)) {
45
+ if (key === 'OR' && Array.isArray(value)) {
46
+ const sub = value.map((v, i) => parseWhere(modelName, v, params, dialect, `${prefix}or_${i}_`)).filter(Boolean);
47
+ if (sub.length > 0)
48
+ conditions.push(`(${sub.join(' OR ')})`);
49
+ }
50
+ else if (key === 'AND' && Array.isArray(value)) {
51
+ const sub = value.map((v, i) => parseWhere(modelName, v, params, dialect, `${prefix}and_${i}_`)).filter(Boolean);
52
+ if (sub.length > 0)
53
+ conditions.push(`(${sub.join(' AND ')})`);
54
+ }
55
+ else {
56
+ const pname = `${prefix}${key}`;
57
+ const col = quote(key, dialect);
58
+ if (value === null) {
59
+ conditions.push(`${col} IS NULL`);
60
+ }
61
+ else if (typeof value === 'object' && !(value instanceof Date)) {
62
+ const v = value;
63
+ if (v.not !== undefined) {
64
+ if (v.not === null) {
65
+ conditions.push(`${col} IS NOT NULL`);
66
+ }
67
+ else {
68
+ const p = `${pname}_not`;
69
+ params[p] = v.not;
70
+ conditions.push(`${col} <> @${p}`);
71
+ }
72
+ }
73
+ if (v.equals !== undefined) {
74
+ const p = `${pname}_eq`;
75
+ params[p] = v.equals;
76
+ conditions.push(`${col} = @${p}`);
77
+ }
78
+ if (v.contains !== undefined) {
79
+ const p = `${pname}_co`;
80
+ params[p] = `%${v.contains}%`;
81
+ conditions.push(`${col} LIKE @${p}`);
82
+ }
83
+ if (v.startsWith !== undefined) {
84
+ const p = `${pname}_sw`;
85
+ params[p] = `${v.startsWith}%`;
86
+ conditions.push(`${col} LIKE @${p}`);
87
+ }
88
+ if (v.endsWith !== undefined) {
89
+ const p = `${pname}_ew`;
90
+ params[p] = `%${v.endsWith}`;
91
+ conditions.push(`${col} LIKE @${p}`);
92
+ }
93
+ if (v.gte !== undefined) {
94
+ const p = `${pname}_gte`;
95
+ params[p] = v.gte;
96
+ conditions.push(`${col} >= @${p}`);
97
+ }
98
+ if (v.lte !== undefined) {
99
+ const p = `${pname}_lte`;
100
+ params[p] = v.lte;
101
+ conditions.push(`${col} <= @${p}`);
102
+ }
103
+ if (v.gt !== undefined) {
104
+ const p = `${pname}_gt`;
105
+ params[p] = v.gt;
106
+ conditions.push(`${col} > @${p}`);
107
+ }
108
+ if (v.lt !== undefined) {
109
+ const p = `${pname}_lt`;
110
+ params[p] = v.lt;
111
+ conditions.push(`${col} < @${p}`);
112
+ }
113
+ if (v.in !== undefined) {
114
+ if (Array.isArray(v.in) && v.in.length > 0) {
115
+ const ps = v.in.map((x, i) => { const p = `${pname}_in${i}`; params[p] = x; return `@${p}`; });
116
+ conditions.push(`${col} IN (${ps.join(', ')})`);
117
+ }
118
+ else {
119
+ conditions.push('1=0');
120
+ }
121
+ }
122
+ }
123
+ else {
124
+ params[pname] = value;
125
+ conditions.push(`${col} = @${pname}`);
126
+ }
127
+ }
128
+ }
129
+ return conditions.join(' AND ');
130
+ }
131
+ // ─── Order by builder ──────────────────────────────────────────────────────────────
132
+ function buildOrderBy(orderBy, dialect) {
133
+ if (!orderBy)
134
+ return '';
135
+ const entries = Array.isArray(orderBy) ? orderBy : [orderBy];
136
+ const parts = [];
137
+ for (const entry of entries) {
138
+ for (const [key, dir] of Object.entries(entry)) {
139
+ parts.push(`${quote(key, dialect)} ${dir.toUpperCase()}`);
140
+ }
141
+ }
142
+ return parts.length > 0 ? `ORDER BY ${parts.join(', ')}` : '';
143
+ }
@@ -0,0 +1,98 @@
1
+ import type { Dialect } from './types';
2
+
3
+ // ─── Quoting ───────────────────────────────────────────────────────────────────────
4
+
5
+ export function quote(name: string, dialect: Dialect): string {
6
+ if (dialect === 'postgres') {
7
+ if (name.startsWith('[') && name.endsWith(']')) return `"${name.slice(1, -1)}"`;
8
+ if (name.startsWith('"')) return name;
9
+ return `"${name}"`;
10
+ }
11
+ if (dialect === 'mysql') {
12
+ if (name.startsWith('[') && name.endsWith(']')) return `\`${name.slice(1, -1)}\``;
13
+ if (name.startsWith('`')) return name;
14
+ return `\`${name}\``;
15
+ }
16
+ // mssql / sqlite
17
+ return name.startsWith('[') ? name : `[${name}]`;
18
+ }
19
+
20
+ // ─── Where clause builder ──────────────────────────────────────────────────────────
21
+
22
+ export function parseWhere(modelName: string, where: any, params: Record<string, any>, dialect: Dialect, prefix = ''): string {
23
+ if (!where) return '';
24
+ const conditions: string[] = [];
25
+
26
+ const cleanWhere: Record<string, any> = {};
27
+ for (const [key, value] of Object.entries(where)) {
28
+ if (
29
+ key.includes('_') &&
30
+ value && typeof value === 'object' &&
31
+ !(value instanceof Date) &&
32
+ !(value as any).in && !(value as any).contains &&
33
+ !(value as any).not && !(value as any).gte &&
34
+ !(value as any).lte && !(value as any).gt && !(value as any).lt
35
+ ) {
36
+ Object.assign(cleanWhere, value);
37
+ } else {
38
+ cleanWhere[key] = value;
39
+ }
40
+ }
41
+
42
+ for (const [key, value] of Object.entries(cleanWhere)) {
43
+ if (key === 'OR' && Array.isArray(value)) {
44
+ const sub = value.map((v, i) => parseWhere(modelName, v, params, dialect, `${prefix}or_${i}_`)).filter(Boolean);
45
+ if (sub.length > 0) conditions.push(`(${sub.join(' OR ')})`);
46
+ } else if (key === 'AND' && Array.isArray(value)) {
47
+ const sub = value.map((v, i) => parseWhere(modelName, v, params, dialect, `${prefix}and_${i}_`)).filter(Boolean);
48
+ if (sub.length > 0) conditions.push(`(${sub.join(' AND ')})`);
49
+ } else {
50
+ const pname = `${prefix}${key}`;
51
+ const col = quote(key, dialect);
52
+
53
+ if (value === null) {
54
+ conditions.push(`${col} IS NULL`);
55
+ } else if (typeof value === 'object' && !(value instanceof Date)) {
56
+ const v = value as any;
57
+ if (v.not !== undefined) {
58
+ if (v.not === null) { conditions.push(`${col} IS NOT NULL`); }
59
+ else { const p = `${pname}_not`; params[p] = v.not; conditions.push(`${col} <> @${p}`); }
60
+ }
61
+ if (v.equals !== undefined) { const p = `${pname}_eq`; params[p] = v.equals; conditions.push(`${col} = @${p}`); }
62
+ if (v.contains !== undefined) { const p = `${pname}_co`; params[p] = `%${v.contains}%`; conditions.push(`${col} LIKE @${p}`); }
63
+ if (v.startsWith !== undefined) { const p = `${pname}_sw`; params[p] = `${v.startsWith}%`; conditions.push(`${col} LIKE @${p}`); }
64
+ if (v.endsWith !== undefined) { const p = `${pname}_ew`; params[p] = `%${v.endsWith}`; conditions.push(`${col} LIKE @${p}`); }
65
+ if (v.gte !== undefined) { const p = `${pname}_gte`; params[p] = v.gte; conditions.push(`${col} >= @${p}`); }
66
+ if (v.lte !== undefined) { const p = `${pname}_lte`; params[p] = v.lte; conditions.push(`${col} <= @${p}`); }
67
+ if (v.gt !== undefined) { const p = `${pname}_gt`; params[p] = v.gt; conditions.push(`${col} > @${p}`); }
68
+ if (v.lt !== undefined) { const p = `${pname}_lt`; params[p] = v.lt; conditions.push(`${col} < @${p}`); }
69
+ if (v.in !== undefined) {
70
+ if (Array.isArray(v.in) && v.in.length > 0) {
71
+ const ps = v.in.map((x: any, i: number) => { const p = `${pname}_in${i}`; params[p] = x; return `@${p}`; });
72
+ conditions.push(`${col} IN (${ps.join(', ')})`);
73
+ } else {
74
+ conditions.push('1=0');
75
+ }
76
+ }
77
+ } else {
78
+ params[pname] = value;
79
+ conditions.push(`${col} = @${pname}`);
80
+ }
81
+ }
82
+ }
83
+ return conditions.join(' AND ');
84
+ }
85
+
86
+ // ─── Order by builder ──────────────────────────────────────────────────────────────
87
+
88
+ export function buildOrderBy(orderBy: any, dialect: Dialect): string {
89
+ if (!orderBy) return '';
90
+ const entries = Array.isArray(orderBy) ? orderBy : [orderBy];
91
+ const parts: string[] = [];
92
+ for (const entry of entries) {
93
+ for (const [key, dir] of Object.entries(entry)) {
94
+ parts.push(`${quote(key, dialect)} ${(dir as string).toUpperCase()}`);
95
+ }
96
+ }
97
+ return parts.length > 0 ? `ORDER BY ${parts.join(', ')}` : '';
98
+ }
@@ -0,0 +1,21 @@
1
+ export type Dialect = 'mssql' | 'postgres' | 'mysql' | 'sqlite' | 'googlesheets';
2
+ export interface An5AdapterConfig {
3
+ connectionString: string;
4
+ poolMax?: number;
5
+ requestTimeout?: number;
6
+ connectionTimeout?: number;
7
+ }
8
+ export interface TransactionHandle {
9
+ exec<T>(query: string, params?: Record<string, any>): Promise<T[]>;
10
+ executeRaw(query: string, params?: Record<string, any>): Promise<number>;
11
+ commit(): Promise<void>;
12
+ rollback(): Promise<void>;
13
+ }
14
+ export interface QueryEngine {
15
+ dialect: Dialect;
16
+ exec<T>(query: string, params?: Record<string, any>): Promise<T[]>;
17
+ executeRaw(query: string, params?: Record<string, any>): Promise<number>;
18
+ connect(): Promise<void>;
19
+ disconnect(): Promise<void>;
20
+ beginTransaction(): Promise<TransactionHandle>;
21
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,24 @@
1
+ export type Dialect = 'mssql' | 'postgres' | 'mysql' | 'sqlite' | 'googlesheets';
2
+
3
+ export interface An5AdapterConfig {
4
+ connectionString: string;
5
+ poolMax?: number;
6
+ requestTimeout?: number;
7
+ connectionTimeout?: number;
8
+ }
9
+
10
+ export interface TransactionHandle {
11
+ exec<T>(query: string, params?: Record<string, any>): Promise<T[]>;
12
+ executeRaw(query: string, params?: Record<string, any>): Promise<number>;
13
+ commit(): Promise<void>;
14
+ rollback(): Promise<void>;
15
+ }
16
+
17
+ export interface QueryEngine {
18
+ dialect: Dialect;
19
+ exec<T>(query: string, params?: Record<string, any>): Promise<T[]>;
20
+ executeRaw(query: string, params?: Record<string, any>): Promise<number>;
21
+ connect(): Promise<void>;
22
+ disconnect(): Promise<void>;
23
+ beginTransaction(): Promise<TransactionHandle>;
24
+ }
@@ -0,0 +1,17 @@
1
+ export interface LlmConfigData {
2
+ provider: string;
3
+ apiKey: string;
4
+ model?: string;
5
+ endpoint?: string;
6
+ }
7
+ export interface EmbeddingConfigData {
8
+ provider: string;
9
+ apiKey: string;
10
+ model?: string;
11
+ endpoint?: string;
12
+ }
13
+ export declare function resetAdapter(): void;
14
+ export declare function getLlmConfig(): Promise<LlmConfigData | null>;
15
+ export declare function setLlmConfig(data: LlmConfigData): Promise<void>;
16
+ export declare function getEmbeddingConfig(): Promise<EmbeddingConfigData | null>;
17
+ export declare function setEmbeddingConfig(data: EmbeddingConfigData): Promise<void>;
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resetAdapter = resetAdapter;
4
+ exports.getLlmConfig = getLlmConfig;
5
+ exports.setLlmConfig = setLlmConfig;
6
+ exports.getEmbeddingConfig = getEmbeddingConfig;
7
+ exports.setEmbeddingConfig = setEmbeddingConfig;
8
+ const an5Adapter_1 = require("./an5Adapter");
9
+ let _adapter = null;
10
+ function getAdapter() {
11
+ if (_adapter)
12
+ return _adapter;
13
+ const url = process.env.DATABASE_URL;
14
+ if (!url)
15
+ return null;
16
+ try {
17
+ _adapter = (0, an5Adapter_1.createAn5Adapter)({ connectionString: url });
18
+ return _adapter;
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ function resetAdapter() {
25
+ _adapter = null;
26
+ }
27
+ async function getLlmConfig() {
28
+ const adapter = getAdapter();
29
+ if (!adapter)
30
+ return null;
31
+ try {
32
+ const rows = await adapter.table('LlmConfig').findMany({
33
+ where: { isActive: true },
34
+ });
35
+ if (rows.length > 0) {
36
+ const c = rows[0];
37
+ return { provider: c.provider, apiKey: c.apiKey, model: c.model, endpoint: c.endpoint };
38
+ }
39
+ }
40
+ catch { }
41
+ return null;
42
+ }
43
+ async function setLlmConfig(data) {
44
+ const adapter = getAdapter();
45
+ if (!adapter)
46
+ throw new Error('DATABASE_URL not configured');
47
+ const existing = await adapter.table('LlmConfig').findFirst({ where: { isActive: true } });
48
+ if (existing) {
49
+ await adapter.table('LlmConfig').update({
50
+ where: { id: existing.id },
51
+ data: { ...data, updatedAt: new Date() },
52
+ });
53
+ }
54
+ else {
55
+ await adapter.table('LlmConfig').create({
56
+ data: { ...data, isActive: true, createdAt: new Date(), updatedAt: new Date() },
57
+ });
58
+ }
59
+ }
60
+ async function getEmbeddingConfig() {
61
+ const adapter = getAdapter();
62
+ if (!adapter)
63
+ return null;
64
+ try {
65
+ const rows = await adapter.table('EmbeddingConfig').findMany({
66
+ where: { isActive: true },
67
+ });
68
+ if (rows.length > 0) {
69
+ const c = rows[0];
70
+ return { provider: c.provider, apiKey: c.apiKey, model: c.model, endpoint: c.endpoint };
71
+ }
72
+ }
73
+ catch { }
74
+ return null;
75
+ }
76
+ async function setEmbeddingConfig(data) {
77
+ const adapter = getAdapter();
78
+ if (!adapter)
79
+ throw new Error('DATABASE_URL not configured');
80
+ const existing = await adapter.table('EmbeddingConfig').findFirst({ where: { isActive: true } });
81
+ if (existing) {
82
+ await adapter.table('EmbeddingConfig').update({
83
+ where: { id: existing.id },
84
+ data: { ...data, updatedAt: new Date() },
85
+ });
86
+ }
87
+ else {
88
+ await adapter.table('EmbeddingConfig').create({
89
+ data: { ...data, isActive: true, createdAt: new Date(), updatedAt: new Date() },
90
+ });
91
+ }
92
+ }
@@ -0,0 +1,95 @@
1
+ import { createAn5Adapter, type AnyAdapter } from './an5Adapter';
2
+
3
+ export interface LlmConfigData {
4
+ provider: string;
5
+ apiKey: string;
6
+ model?: string;
7
+ endpoint?: string;
8
+ }
9
+
10
+ export interface EmbeddingConfigData {
11
+ provider: string;
12
+ apiKey: string;
13
+ model?: string;
14
+ endpoint?: string;
15
+ }
16
+
17
+ let _adapter: AnyAdapter | null = null;
18
+
19
+ function getAdapter(): AnyAdapter | null {
20
+ if (_adapter) return _adapter;
21
+ const url = process.env.DATABASE_URL;
22
+ if (!url) return null;
23
+ try {
24
+ _adapter = createAn5Adapter({ connectionString: url });
25
+ return _adapter;
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+
31
+ export function resetAdapter(): void {
32
+ _adapter = null;
33
+ }
34
+
35
+ export async function getLlmConfig(): Promise<LlmConfigData | null> {
36
+ const adapter = getAdapter();
37
+ if (!adapter) return null;
38
+ try {
39
+ const rows = await adapter.table<LlmConfigData>('LlmConfig').findMany({
40
+ where: { isActive: true },
41
+ });
42
+ if (rows.length > 0) {
43
+ const c = rows[0] as any;
44
+ return { provider: c.provider, apiKey: c.apiKey, model: c.model, endpoint: c.endpoint };
45
+ }
46
+ } catch {}
47
+ return null;
48
+ }
49
+
50
+ export async function setLlmConfig(data: LlmConfigData): Promise<void> {
51
+ const adapter = getAdapter();
52
+ if (!adapter) throw new Error('DATABASE_URL not configured');
53
+ const existing = await adapter.table('LlmConfig').findFirst({ where: { isActive: true } });
54
+ if (existing) {
55
+ await adapter.table('LlmConfig').update({
56
+ where: { id: (existing as any).id },
57
+ data: { ...data, updatedAt: new Date() },
58
+ });
59
+ } else {
60
+ await adapter.table('LlmConfig').create({
61
+ data: { ...data, isActive: true, createdAt: new Date(), updatedAt: new Date() },
62
+ });
63
+ }
64
+ }
65
+
66
+ export async function getEmbeddingConfig(): Promise<EmbeddingConfigData | null> {
67
+ const adapter = getAdapter();
68
+ if (!adapter) return null;
69
+ try {
70
+ const rows = await adapter.table<EmbeddingConfigData>('EmbeddingConfig').findMany({
71
+ where: { isActive: true },
72
+ });
73
+ if (rows.length > 0) {
74
+ const c = rows[0] as any;
75
+ return { provider: c.provider, apiKey: c.apiKey, model: c.model, endpoint: c.endpoint };
76
+ }
77
+ } catch {}
78
+ return null;
79
+ }
80
+
81
+ export async function setEmbeddingConfig(data: EmbeddingConfigData): Promise<void> {
82
+ const adapter = getAdapter();
83
+ if (!adapter) throw new Error('DATABASE_URL not configured');
84
+ const existing = await adapter.table('EmbeddingConfig').findFirst({ where: { isActive: true } });
85
+ if (existing) {
86
+ await adapter.table('EmbeddingConfig').update({
87
+ where: { id: (existing as any).id },
88
+ data: { ...data, updatedAt: new Date() },
89
+ });
90
+ } else {
91
+ await adapter.table('EmbeddingConfig').create({
92
+ data: { ...data, isActive: true, createdAt: new Date(), updatedAt: new Date() },
93
+ });
94
+ }
95
+ }
@@ -0,0 +1,31 @@
1
+ import { sheets_v4 } from 'googleapis';
2
+ import { An5SheetsAdapterConfig, resolveConfig } from './config';
3
+ import { SheetsTableClient } from './tableClient';
4
+ import { SheetMeta } from './types';
5
+ export declare class An5SheetsAdapter {
6
+ private sheets;
7
+ config: ReturnType<typeof resolveConfig>;
8
+ private sheetsCache;
9
+ constructor(config: An5SheetsAdapterConfig);
10
+ getSheets(): Promise<sheets_v4.Sheets>;
11
+ private getApi;
12
+ invalidateCache(): void;
13
+ listSheets(): Promise<string[]>;
14
+ getSheetMeta(name: string): Promise<SheetMeta | null>;
15
+ deleteSheet(name: string): Promise<void>;
16
+ $connect(): Promise<void>;
17
+ $disconnect(): Promise<void>;
18
+ exec<T = any>(query: string, params?: Record<string, any>): Promise<T[]>;
19
+ $queryRawUnsafe<T = any>(query: string, ...values: any[]): Promise<T[]>;
20
+ $executeRaw(query: string, ...values: any[]): Promise<number>;
21
+ $executeRawUnsafe(query: string, ...values: any[]): Promise<number>;
22
+ $transaction<R>(fn: (tx: An5SheetsAdapter) => Promise<R>, _options?: {
23
+ timeout?: number;
24
+ }): Promise<R>;
25
+ $transaction<R>(list: Promise<R>[]): Promise<R[]>;
26
+ table<T = any>(modelName: string): SheetsTableClient<T>;
27
+ readRange<T = any>(range: string): Promise<T[][]>;
28
+ writeRange(range: string, values: any[][]): Promise<void>;
29
+ appendRange(range: string, values: any[][]): Promise<void>;
30
+ }
31
+ export declare function createAn5SheetsAdapter(config: An5SheetsAdapterConfig): An5SheetsAdapter;
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.An5SheetsAdapter = void 0;
4
+ exports.createAn5SheetsAdapter = createAn5SheetsAdapter;
5
+ const googleapis_1 = require("googleapis");
6
+ const config_1 = require("./config");
7
+ const sqlExecutor_1 = require("./sqlExecutor");
8
+ const tableClient_1 = require("./tableClient");
9
+ const retry_1 = require("./retry");
10
+ // ─── Adapter ──────────────────────────────────────────────────────────────────
11
+ class An5SheetsAdapter {
12
+ constructor(config) {
13
+ this.sheets = null;
14
+ this.sheetsCache = null;
15
+ this.config = (0, config_1.resolveConfig)(config);
16
+ }
17
+ async getSheets() {
18
+ if (!this.sheets) {
19
+ const auth = new googleapis_1.google.auth.JWT({
20
+ email: this.config.clientEmail,
21
+ key: this.config.privateKey,
22
+ scopes: ['https://www.googleapis.com/auth/spreadsheets'],
23
+ });
24
+ this.sheets = googleapis_1.google.sheets({ version: 'v4', auth });
25
+ }
26
+ return this.sheets;
27
+ }
28
+ async getApi() {
29
+ return this.getSheets();
30
+ }
31
+ invalidateCache() {
32
+ this.sheetsCache = null;
33
+ }
34
+ async listSheets() {
35
+ const api = await this.getApi();
36
+ const res = await (0, retry_1.withRetry)(() => api.spreadsheets.get({
37
+ spreadsheetId: this.config.spreadsheetId,
38
+ fields: 'sheets.properties.title',
39
+ }));
40
+ return (res.data.sheets || []).map(s => s.properties?.title || '');
41
+ }
42
+ async getSheetMeta(name) {
43
+ const api = await this.getApi();
44
+ if (this.sheetsCache == null) {
45
+ this.sheetsCache = (0, retry_1.withRetry)(async () => {
46
+ const res = await api.spreadsheets.get({
47
+ spreadsheetId: this.config.spreadsheetId,
48
+ fields: 'sheets.properties',
49
+ });
50
+ return (res.data.sheets || []).map(s => ({
51
+ sheetId: s.properties?.sheetId ?? -1,
52
+ title: s.properties?.title ?? '',
53
+ })).filter(m => m.sheetId >= 0);
54
+ });
55
+ }
56
+ const all = await this.sheetsCache;
57
+ return all.find(m => m.title === name) || null;
58
+ }
59
+ async deleteSheet(name) {
60
+ const meta = await this.getSheetMeta(name);
61
+ if (!meta)
62
+ throw new Error(`Sheet "${name}" not found`);
63
+ const api = await this.getApi();
64
+ await (0, retry_1.withRetry)(() => api.spreadsheets.batchUpdate({
65
+ spreadsheetId: this.config.spreadsheetId,
66
+ requestBody: {
67
+ requests: [{ deleteSheet: { sheetId: meta.sheetId } }],
68
+ },
69
+ }));
70
+ this.invalidateCache();
71
+ }
72
+ async $connect() {
73
+ await this.getSheets();
74
+ }
75
+ async $disconnect() {
76
+ this.sheets = null;
77
+ this.sheetsCache = null;
78
+ }
79
+ async exec(query, params) {
80
+ return (0, sqlExecutor_1.execQuery)(this, query, params, undefined, undefined);
81
+ }
82
+ async $queryRawUnsafe(query, ...values) {
83
+ const params = {};
84
+ values.forEach((v, i) => { params[`p_${i}`] = v; });
85
+ const resolved = query.replace(/@p_(\d+)/g, (_, i) => `@p_${i}`);
86
+ return (0, sqlExecutor_1.execQuery)(this, resolved, params, undefined, undefined);
87
+ }
88
+ async $executeRaw(query, ...values) {
89
+ const params = {};
90
+ values.forEach((v, i) => { params[`p_${i}`] = v; });
91
+ const resolved = query.replace(/@p_(\d+)/g, (_, i) => `@p_${i}`);
92
+ return (0, sqlExecutor_1.execQuery)(this, resolved, params, undefined, undefined);
93
+ }
94
+ async $executeRawUnsafe(query, ...values) {
95
+ return this.$executeRaw(query, ...values);
96
+ }
97
+ async $transaction(fn, _options) {
98
+ if (typeof fn === 'function')
99
+ return fn(this);
100
+ return Promise.all(fn);
101
+ }
102
+ table(modelName) {
103
+ return new tableClient_1.SheetsTableClient(this, modelName, this.config.sheetMapping);
104
+ }
105
+ // ── Raw range access ──────────────────────────────────────────────────────
106
+ async readRange(range) {
107
+ const api = await this.getApi();
108
+ const res = await (0, retry_1.withRetry)(() => api.spreadsheets.values.get({
109
+ spreadsheetId: this.config.spreadsheetId,
110
+ range,
111
+ }));
112
+ return (res.data.values || []);
113
+ }
114
+ async writeRange(range, values) {
115
+ const api = await this.getApi();
116
+ await (0, retry_1.withRetry)(() => api.spreadsheets.values.update({
117
+ spreadsheetId: this.config.spreadsheetId,
118
+ range,
119
+ valueInputOption: 'RAW',
120
+ requestBody: { values },
121
+ }));
122
+ }
123
+ async appendRange(range, values) {
124
+ const api = await this.getApi();
125
+ await (0, retry_1.withRetry)(() => api.spreadsheets.values.append({
126
+ spreadsheetId: this.config.spreadsheetId,
127
+ range,
128
+ valueInputOption: 'RAW',
129
+ insertDataOption: 'INSERT_ROWS',
130
+ requestBody: { values },
131
+ }));
132
+ }
133
+ }
134
+ exports.An5SheetsAdapter = An5SheetsAdapter;
135
+ // ─── Factory ──────────────────────────────────────────────────────────────────
136
+ function createAn5SheetsAdapter(config) {
137
+ return new An5SheetsAdapter(config);
138
+ }