@an5/adapters 0.2.4 → 0.2.6
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.
- package/README.md +189 -304
- package/dist/an5Adapter.d.ts +85 -7
- package/dist/an5Adapter.js +736 -64
- package/dist/base/metadata.d.ts +10 -0
- package/dist/base/metadata.js +107 -0
- package/dist/base/sql.d.ts +19 -1
- package/dist/base/sql.js +152 -24
- package/dist/base/types.d.ts +4 -1
- package/dist/browser.d.ts +5 -2
- package/dist/browser.js +10 -2
- package/dist/googlesheets/adapter.d.ts +1 -0
- package/dist/googlesheets/adapter.js +9 -2
- package/dist/googlesheets/config.d.ts +4 -4
- package/dist/googlesheets/helpers.js +2 -0
- package/dist/googlesheets/parseConnectionString.js +5 -2
- package/dist/googlesheets/sqlExecutor.js +29 -24
- package/dist/googlesheets/tableClient.js +14 -5
- package/dist/index.d.ts +5 -1
- package/dist/index.js +12 -1
- package/dist/mssql/engine.js +8 -4
- package/dist/mysql/engine.js +1 -1
- package/dist/postgres/engine.js +1 -1
- package/dist/sqlite/browserEngine.d.ts +27 -0
- package/dist/sqlite/browserEngine.js +161 -0
- package/dist/sqlite/engine.js +15 -3
- package/dist/sqlite/index.d.ts +1 -0
- package/dist/sqlite/index.js +1 -0
- package/dist/typed.d.ts +5 -0
- package/dist/typed.js +2 -0
- package/dotnet/An5Adapter.cs +1089 -275
- package/dotnet/Base/Core.cs +340 -82
- package/dotnet/Mssql/MssqlEngine.cs +145 -144
- package/dotnet/Postgres/PostgresEngine.cs +121 -121
- package/golang/an5_adapter.go +314 -0
- package/golang/base/base.go +100 -0
- package/golang/base/metadata.go +116 -0
- package/golang/base/where.go +311 -0
- package/golang/go.mod +3 -0
- package/golang/mssql/mssql.go +32 -0
- package/golang/postgres/postgres.go +37 -0
- package/golang/sqlite/sqlite.go +14 -0
- package/golang/sqlite_test.go +42 -0
- package/golang/table_client.go +1329 -0
- package/package.json +18 -8
- package/python/an5_adapter.py +119 -97
- package/python/base/__init__.py +3 -3
- package/python/base/dialects.py +11 -8
- package/python/base/metadata.py +35 -10
- package/python/base/sql.py +172 -121
- package/python/mssql/__init__.py +1 -1
- package/python/mssql/provider.py +62 -62
- package/python/postgres/__init__.py +1 -1
- package/python/postgres/provider.py +15 -15
- package/python/table_client.py +619 -216
- package/typescript/src/an5Adapter.ts +1220 -505
- package/typescript/src/base/index.ts +3 -3
- package/typescript/src/base/metadata.ts +148 -28
- package/typescript/src/base/sql.ts +264 -98
- package/typescript/src/base/types.ts +27 -24
- package/typescript/src/browser.ts +10 -6
- package/typescript/src/config.ts +95 -95
- package/typescript/src/googlesheets/adapter.ts +269 -259
- package/typescript/src/googlesheets/an5SheetsAdapter.ts +3 -3
- package/typescript/src/googlesheets/config.ts +47 -47
- package/typescript/src/googlesheets/helpers.ts +144 -143
- package/typescript/src/googlesheets/index.ts +3 -3
- package/typescript/src/googlesheets/parseConnectionString.ts +48 -46
- package/typescript/src/googlesheets/retry.ts +126 -126
- package/typescript/src/googlesheets/sqlExecutor.ts +304 -301
- package/typescript/src/googlesheets/tableClient.ts +461 -455
- package/typescript/src/googlesheets/types.ts +6 -6
- package/typescript/src/index.ts +32 -19
- package/typescript/src/mssql/engine.ts +134 -132
- package/typescript/src/mssql/index.ts +1 -1
- package/typescript/src/mysql/engine.ts +80 -80
- package/typescript/src/mysql/index.ts +1 -1
- package/typescript/src/postgres/engine.ts +87 -87
- package/typescript/src/postgres/index.ts +1 -1
- package/typescript/src/sqlite/browserEngine.ts +192 -0
- package/typescript/src/sqlite/engine.ts +66 -55
- package/typescript/src/sqlite/index.ts +3 -1
- package/typescript/src/typed.ts +52 -0
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
import type { An5AdapterConfig, Dialect, QueryEngine, TransactionHandle } from '../base/types';
|
|
2
|
-
|
|
3
|
-
// ─── PostgreSQL Engine ─────────────────────────────────────────────────────────────
|
|
4
|
-
|
|
5
|
-
let PgPool: any = null;
|
|
6
|
-
try { PgPool = require('pg').Pool; } catch { }
|
|
7
|
-
|
|
8
|
-
export class PostgresEngine implements QueryEngine {
|
|
9
|
-
dialect: Dialect = 'postgres';
|
|
10
|
-
private pool: any = null;
|
|
11
|
-
|
|
12
|
-
constructor(adapterConfig: An5AdapterConfig) {
|
|
13
|
-
if (!PgPool) throw new Error('pg package is required for PostgreSQL support. Run: npm install pg');
|
|
14
|
-
this.pool = new PgPool({
|
|
15
|
-
connectionString: adapterConfig.connectionString,
|
|
16
|
-
max: adapterConfig.poolMax ?? 10,
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** Convert @name → $N; identical names reuse the same $N */
|
|
21
|
-
private transform(query: string, params?: Record<string, any>): { text: string; values: any[] } {
|
|
22
|
-
if (!params || Object.keys(params).length === 0) return { text: query, values: [] };
|
|
23
|
-
const values: any[] = [];
|
|
24
|
-
const seen: Record<string, number> = {};
|
|
25
|
-
const text = query.replace(/@(\w+)/g, (_, name: string) => {
|
|
26
|
-
if (seen[name] !== undefined) return `$${seen[name]}`;
|
|
27
|
-
const idx = values.length + 1;
|
|
28
|
-
seen[name] = idx;
|
|
29
|
-
values.push(params[name] ?? null);
|
|
30
|
-
return `$${idx}`;
|
|
31
|
-
});
|
|
32
|
-
return { text, values };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async exec<T = any>(query: string, params?: Record<string, any>): Promise<T[]> {
|
|
36
|
-
const { text, values } = this.transform(query, params);
|
|
37
|
-
const result = await this.pool.query(text, values);
|
|
38
|
-
return result.rows as T[];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async executeRaw(query: string, params?: Record<string, any>): Promise<number> {
|
|
42
|
-
const { text, values } = this.transform(query, params);
|
|
43
|
-
const result = await this.pool.query(text, values);
|
|
44
|
-
return result.rowCount ?? 0;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async connect(): Promise<void> {
|
|
48
|
-
const client = await this.pool.connect();
|
|
49
|
-
client.release();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async disconnect(): Promise<void> { await this.pool.end(); }
|
|
53
|
-
|
|
54
|
-
async beginTransaction(): Promise<TransactionHandle> {
|
|
55
|
-
const client = await this.pool.connect();
|
|
56
|
-
await client.query('BEGIN');
|
|
57
|
-
|
|
58
|
-
const transform = (query: string, params?: Record<string, any>) => {
|
|
59
|
-
if (!params || Object.keys(params).length === 0) return { text: query, values: [] };
|
|
60
|
-
const values: any[] = [];
|
|
61
|
-
const seen: Record<string, number> = {};
|
|
62
|
-
const text = query.replace(/@(\w+)/g, (_, name: string) => {
|
|
63
|
-
if (seen[name] !== undefined) return `$${seen[name]}`;
|
|
64
|
-
const idx = values.length + 1;
|
|
65
|
-
seen[name] = idx;
|
|
66
|
-
values.push(params[name] ?? null);
|
|
67
|
-
return `$${idx}`;
|
|
68
|
-
});
|
|
69
|
-
return { text, values };
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
return {
|
|
73
|
-
exec: async <T>(q: string, p?: Record<string, any>) => {
|
|
74
|
-
const { text, values } = transform(q, p);
|
|
75
|
-
const r = await client.query(text, values);
|
|
76
|
-
return (r.rows || []) as T[];
|
|
77
|
-
},
|
|
78
|
-
executeRaw: async (q: string, p?: Record<string, any>) => {
|
|
79
|
-
const { text, values } = transform(q, p);
|
|
80
|
-
const r = await client.query(text, values);
|
|
81
|
-
return r.rowCount ?? 0;
|
|
82
|
-
},
|
|
83
|
-
commit: async () => { await client.query('COMMIT'); client.release(); },
|
|
84
|
-
rollback: async () => { await client.query('ROLLBACK'); client.release(); },
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
}
|
|
1
|
+
import type { An5AdapterConfig, Dialect, QueryEngine, TransactionHandle } from '../base/types';
|
|
2
|
+
|
|
3
|
+
// ─── PostgreSQL Engine ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
let PgPool: any = null;
|
|
6
|
+
try { PgPool = require('pg').Pool; } catch { }
|
|
7
|
+
|
|
8
|
+
export class PostgresEngine implements QueryEngine {
|
|
9
|
+
dialect: Dialect = 'postgres';
|
|
10
|
+
private pool: any = null;
|
|
11
|
+
|
|
12
|
+
constructor(adapterConfig: An5AdapterConfig) {
|
|
13
|
+
if (!PgPool) throw new Error('pg package is required for PostgreSQL support. Run: npm install pg');
|
|
14
|
+
this.pool = new PgPool({
|
|
15
|
+
connectionString: adapterConfig.connectionString ?? '',
|
|
16
|
+
max: adapterConfig.poolMax ?? 10,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Convert @name → $N; identical names reuse the same $N */
|
|
21
|
+
private transform(query: string, params?: Record<string, any>): { text: string; values: any[] } {
|
|
22
|
+
if (!params || Object.keys(params).length === 0) return { text: query, values: [] };
|
|
23
|
+
const values: any[] = [];
|
|
24
|
+
const seen: Record<string, number> = {};
|
|
25
|
+
const text = query.replace(/@(\w+)/g, (_, name: string) => {
|
|
26
|
+
if (seen[name] !== undefined) return `$${seen[name]}`;
|
|
27
|
+
const idx = values.length + 1;
|
|
28
|
+
seen[name] = idx;
|
|
29
|
+
values.push(params[name] ?? null);
|
|
30
|
+
return `$${idx}`;
|
|
31
|
+
});
|
|
32
|
+
return { text, values };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async exec<T = any>(query: string, params?: Record<string, any>): Promise<T[]> {
|
|
36
|
+
const { text, values } = this.transform(query, params);
|
|
37
|
+
const result = await this.pool.query(text, values);
|
|
38
|
+
return result.rows as T[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async executeRaw(query: string, params?: Record<string, any>): Promise<number> {
|
|
42
|
+
const { text, values } = this.transform(query, params);
|
|
43
|
+
const result = await this.pool.query(text, values);
|
|
44
|
+
return result.rowCount ?? 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async connect(): Promise<void> {
|
|
48
|
+
const client = await this.pool.connect();
|
|
49
|
+
client.release();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async disconnect(): Promise<void> { await this.pool.end(); }
|
|
53
|
+
|
|
54
|
+
async beginTransaction(): Promise<TransactionHandle> {
|
|
55
|
+
const client = await this.pool.connect();
|
|
56
|
+
await client.query('BEGIN');
|
|
57
|
+
|
|
58
|
+
const transform = (query: string, params?: Record<string, any>) => {
|
|
59
|
+
if (!params || Object.keys(params).length === 0) return { text: query, values: [] };
|
|
60
|
+
const values: any[] = [];
|
|
61
|
+
const seen: Record<string, number> = {};
|
|
62
|
+
const text = query.replace(/@(\w+)/g, (_, name: string) => {
|
|
63
|
+
if (seen[name] !== undefined) return `$${seen[name]}`;
|
|
64
|
+
const idx = values.length + 1;
|
|
65
|
+
seen[name] = idx;
|
|
66
|
+
values.push(params[name] ?? null);
|
|
67
|
+
return `$${idx}`;
|
|
68
|
+
});
|
|
69
|
+
return { text, values };
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
exec: async <T>(q: string, p?: Record<string, any>) => {
|
|
74
|
+
const { text, values } = transform(q, p);
|
|
75
|
+
const r = await client.query(text, values);
|
|
76
|
+
return (r.rows || []) as T[];
|
|
77
|
+
},
|
|
78
|
+
executeRaw: async (q: string, p?: Record<string, any>) => {
|
|
79
|
+
const { text, values } = transform(q, p);
|
|
80
|
+
const r = await client.query(text, values);
|
|
81
|
+
return r.rowCount ?? 0;
|
|
82
|
+
},
|
|
83
|
+
commit: async () => { await client.query('COMMIT'); client.release(); },
|
|
84
|
+
rollback: async () => { await client.query('ROLLBACK'); client.release(); },
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from './engine';
|
|
1
|
+
export * from './engine';
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import type { Dialect, QueryEngine, TransactionHandle } from '../base/types';
|
|
2
|
+
|
|
3
|
+
// ─── Browser SQLite Driver & Config ──────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
export interface SqliteDriver {
|
|
6
|
+
exec<T = any>(query: string, params?: Record<string, any>): Promise<T[]> | T[];
|
|
7
|
+
executeRaw?(query: string, params?: Record<string, any>): Promise<number> | number;
|
|
8
|
+
close?(): Promise<void> | void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SqliteBrowserConfig {
|
|
12
|
+
db?: any; // sql.js Database instance or SQLite WASM DB
|
|
13
|
+
driver?: SqliteDriver;
|
|
14
|
+
exec?: <T = any>(query: string, params?: Record<string, any>) => Promise<T[]> | T[];
|
|
15
|
+
executeRaw?: (query: string, params?: Record<string, any>) => Promise<number> | number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ─── Browser SQLite Engine ──────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
export class SqliteBrowserEngine implements QueryEngine {
|
|
21
|
+
dialect: Dialect = 'sqlite';
|
|
22
|
+
private db: any = null;
|
|
23
|
+
private driver: SqliteDriver | null = null;
|
|
24
|
+
private customExec: ((query: string, params?: Record<string, any>) => Promise<any[]> | any[]) | null = null;
|
|
25
|
+
private customExecuteRaw: ((query: string, params?: Record<string, any>) => Promise<number> | number) | null = null;
|
|
26
|
+
|
|
27
|
+
constructor(config?: SqliteBrowserConfig | any) {
|
|
28
|
+
if (!config) return;
|
|
29
|
+
|
|
30
|
+
if (config.exec && typeof config.exec === 'function') {
|
|
31
|
+
this.customExec = config.exec;
|
|
32
|
+
this.customExecuteRaw = config.executeRaw ?? null;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (config.driver) {
|
|
37
|
+
this.driver = config.driver;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Direct DB instance (e.g. sql.js Database or sqlite-wasm)
|
|
42
|
+
if (config.db) {
|
|
43
|
+
this.db = config.db;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// If config itself looks like a driver with exec()
|
|
48
|
+
if (typeof config.exec === 'function' && typeof config.run !== 'function' && typeof config.prepare !== 'function') {
|
|
49
|
+
this.driver = config as SqliteDriver;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// If config itself is a Database instance (has prepare, exec, run)
|
|
54
|
+
if (typeof config.prepare === 'function' || typeof config.run === 'function' || typeof config.exec === 'function') {
|
|
55
|
+
this.db = config;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private normalizeSqlJsParams(params?: Record<string, any>): Record<string, any> {
|
|
60
|
+
if (!params || typeof params !== 'object') return {};
|
|
61
|
+
const normalized: Record<string, any> = {};
|
|
62
|
+
for (const [k, v] of Object.entries(params)) {
|
|
63
|
+
const key = k.startsWith('@') || k.startsWith(':') || k.startsWith('$') ? k : `@${k}`;
|
|
64
|
+
normalized[key] = v;
|
|
65
|
+
}
|
|
66
|
+
return normalized;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async exec<T = any>(query: string, params?: Record<string, any>): Promise<T[]> {
|
|
70
|
+
if (this.customExec) {
|
|
71
|
+
return await this.customExec(query, params);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (this.driver) {
|
|
75
|
+
return await this.driver.exec<T>(query, params);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (this.db) {
|
|
79
|
+
// sql.js Database with prepare()
|
|
80
|
+
if (typeof this.db.prepare === 'function') {
|
|
81
|
+
const stmt = this.db.prepare(query);
|
|
82
|
+
try {
|
|
83
|
+
if (params && Object.keys(params).length > 0) {
|
|
84
|
+
stmt.bind(this.normalizeSqlJsParams(params));
|
|
85
|
+
}
|
|
86
|
+
const results: T[] = [];
|
|
87
|
+
while (stmt.step()) {
|
|
88
|
+
results.push(stmt.getAsObject() as T);
|
|
89
|
+
}
|
|
90
|
+
return results;
|
|
91
|
+
} finally {
|
|
92
|
+
stmt.free();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Fallback sql.js db.exec()
|
|
97
|
+
if (typeof this.db.exec === 'function') {
|
|
98
|
+
const res = this.db.exec(query);
|
|
99
|
+
if (!res || res.length === 0) return [];
|
|
100
|
+
const { columns, values } = res[0];
|
|
101
|
+
return values.map((row: any[]) => {
|
|
102
|
+
const obj: any = {};
|
|
103
|
+
columns.forEach((col: string, i: number) => {
|
|
104
|
+
obj[col] = row[i];
|
|
105
|
+
});
|
|
106
|
+
return obj as T;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
throw new Error('SqliteBrowserEngine: No SQLite database or driver provided. Pass a sql.js db instance or custom driver.');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async executeRaw(query: string, params?: Record<string, any>): Promise<number> {
|
|
115
|
+
if (this.customExecuteRaw) {
|
|
116
|
+
return await this.customExecuteRaw(query, params);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (this.driver && typeof this.driver.executeRaw === 'function') {
|
|
120
|
+
return await this.driver.executeRaw(query, params);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (this.db) {
|
|
124
|
+
if (typeof this.db.prepare === 'function') {
|
|
125
|
+
const stmt = this.db.prepare(query);
|
|
126
|
+
try {
|
|
127
|
+
if (params && Object.keys(params).length > 0) {
|
|
128
|
+
stmt.bind(this.normalizeSqlJsParams(params));
|
|
129
|
+
}
|
|
130
|
+
stmt.step();
|
|
131
|
+
if (typeof this.db.getRowsModified === 'function') {
|
|
132
|
+
return this.db.getRowsModified();
|
|
133
|
+
}
|
|
134
|
+
return 1;
|
|
135
|
+
} finally {
|
|
136
|
+
stmt.free();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (typeof this.db.run === 'function') {
|
|
141
|
+
this.db.run(query, this.normalizeSqlJsParams(params));
|
|
142
|
+
if (typeof this.db.getRowsModified === 'function') {
|
|
143
|
+
return this.db.getRowsModified();
|
|
144
|
+
}
|
|
145
|
+
return 1;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const rows = await this.exec(query, params);
|
|
150
|
+
return rows.length;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async connect(): Promise<void> {}
|
|
154
|
+
|
|
155
|
+
async disconnect(): Promise<void> {
|
|
156
|
+
if (this.driver && typeof this.driver.close === 'function') {
|
|
157
|
+
await this.driver.close();
|
|
158
|
+
} else if (this.db && typeof this.db.close === 'function') {
|
|
159
|
+
this.db.close();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async beginTransaction(): Promise<TransactionHandle> {
|
|
164
|
+
await this.executeRaw('BEGIN');
|
|
165
|
+
let done = false;
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
exec: async <T>(q: string, p?: Record<string, any>) => this.exec<T>(q, p),
|
|
169
|
+
executeRaw: async (q: string, p?: Record<string, any>) => this.executeRaw(q, p),
|
|
170
|
+
commit: async () => {
|
|
171
|
+
if (!done) {
|
|
172
|
+
await this.executeRaw('COMMIT');
|
|
173
|
+
done = true;
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
rollback: async () => {
|
|
177
|
+
if (!done) {
|
|
178
|
+
await this.executeRaw('ROLLBACK');
|
|
179
|
+
done = true;
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ─── Browser SQLite Adapter Factory ──────────────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
export function createBrowserSqliteAdapter(config: SqliteBrowserConfig | any): any {
|
|
189
|
+
const { An5Adapter } = require('../an5Adapter');
|
|
190
|
+
return new An5Adapter({ engine: new SqliteBrowserEngine(config) });
|
|
191
|
+
}
|
|
192
|
+
|
|
@@ -1,55 +1,66 @@
|
|
|
1
|
-
import type { An5AdapterConfig, Dialect, QueryEngine, TransactionHandle } from '../base/types';
|
|
2
|
-
|
|
3
|
-
// ─── SQLite Engine ─────────────────────────────────────────────────────────────────
|
|
4
|
-
|
|
5
|
-
let BetterSqlite3: any = null;
|
|
6
|
-
try { BetterSqlite3 = require('better-sqlite3'); } catch { }
|
|
7
|
-
|
|
8
|
-
export class SqliteEngine implements QueryEngine {
|
|
9
|
-
dialect: Dialect = 'sqlite';
|
|
10
|
-
private db: any = null;
|
|
11
|
-
private readonly filePath: string;
|
|
12
|
-
|
|
13
|
-
constructor(adapterConfig: An5AdapterConfig) {
|
|
14
|
-
if (!BetterSqlite3) throw new Error('better-sqlite3 package is required for SQLite support. Run: npm install better-sqlite3');
|
|
15
|
-
this.filePath = adapterConfig.connectionString
|
|
16
|
-
.replace(/^sqlite:\/\/\//i, '/')
|
|
17
|
-
.replace(/^sqlite:\/\//i, '');
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
private getDb(): any {
|
|
21
|
-
if (!this.db) this.db = new BetterSqlite3(this.filePath);
|
|
22
|
-
return this.db;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// better-sqlite3 natively supports @name parameters
|
|
26
|
-
async exec<T = any>(query: string, params?: Record<string, any>): Promise<T[]> {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
db
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
1
|
+
import type { An5AdapterConfig, Dialect, QueryEngine, TransactionHandle } from '../base/types';
|
|
2
|
+
|
|
3
|
+
// ─── SQLite Engine ─────────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
let BetterSqlite3: any = null;
|
|
6
|
+
try { BetterSqlite3 = require('better-sqlite3'); } catch { }
|
|
7
|
+
|
|
8
|
+
export class SqliteEngine implements QueryEngine {
|
|
9
|
+
dialect: Dialect = 'sqlite';
|
|
10
|
+
private db: any = null;
|
|
11
|
+
private readonly filePath: string;
|
|
12
|
+
|
|
13
|
+
constructor(adapterConfig: An5AdapterConfig) {
|
|
14
|
+
if (!BetterSqlite3) throw new Error('better-sqlite3 package is required for SQLite support. Run: npm install better-sqlite3');
|
|
15
|
+
this.filePath = (adapterConfig.connectionString ?? '')
|
|
16
|
+
.replace(/^sqlite:\/\/\//i, '/')
|
|
17
|
+
.replace(/^sqlite:\/\//i, '');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private getDb(): any {
|
|
21
|
+
if (!this.db) this.db = new BetterSqlite3(this.filePath);
|
|
22
|
+
return this.db;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// better-sqlite3 natively supports @name parameters
|
|
26
|
+
async exec<T = any>(query: string, params?: Record<string, any>): Promise<T[]> {
|
|
27
|
+
const stmt = this.getDb().prepare(query);
|
|
28
|
+
if (stmt.reader) {
|
|
29
|
+
return stmt.all(params ?? {}) as T[];
|
|
30
|
+
}
|
|
31
|
+
stmt.run(params ?? {});
|
|
32
|
+
return [] as T[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async executeRaw(query: string, params?: Record<string, any>): Promise<number> {
|
|
36
|
+
const info = this.getDb().prepare(query).run(params ?? {});
|
|
37
|
+
return info.changes ?? 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async connect(): Promise<void> { this.getDb(); }
|
|
41
|
+
|
|
42
|
+
async disconnect(): Promise<void> {
|
|
43
|
+
if (this.db) { this.db.close(); this.db = null; }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async beginTransaction(): Promise<TransactionHandle> {
|
|
47
|
+
const db = this.getDb();
|
|
48
|
+
db.prepare('BEGIN').run();
|
|
49
|
+
let done = false;
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
exec: async <T>(q: string, p?: Record<string, any>) => {
|
|
53
|
+
const stmt = db.prepare(q);
|
|
54
|
+
if (stmt.reader) {
|
|
55
|
+
return stmt.all(p ?? {}) as T[];
|
|
56
|
+
}
|
|
57
|
+
stmt.run(p ?? {});
|
|
58
|
+
return [] as T[];
|
|
59
|
+
},
|
|
60
|
+
executeRaw: async (q: string, p?: Record<string, any>) =>
|
|
61
|
+
(db.prepare(q).run(p ?? {}).changes ?? 0),
|
|
62
|
+
commit: async () => { if (!done) { db.prepare('COMMIT').run(); done = true; } },
|
|
63
|
+
rollback: async () => { if (!done) { db.prepare('ROLLBACK').run(); done = true; } },
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { AdapterTableClient, An5Adapter } from './an5Adapter';
|
|
2
|
+
|
|
3
|
+
// ─── Typed model access ──────────────────────────────────────────────────────
|
|
4
|
+
// The runtime adapter resolves `db.user` / `db.User` dynamically through a
|
|
5
|
+
// Proxy, which TypeScript normally types as `any` (An5Adapter declares
|
|
6
|
+
// `[key: string]: any`). These helpers let app code bind generated model
|
|
7
|
+
// delegate types (e.g. `UserTableClient` emitted by the schema generator) so
|
|
8
|
+
// `db.user` gets full autocomplete and compile-time argument checking — the
|
|
9
|
+
// standard ORM experience — without changing any runtime behavior.
|
|
10
|
+
//
|
|
11
|
+
// Usage:
|
|
12
|
+
// import { createAn5Adapter } from '@an5/adapters';
|
|
13
|
+
// import type { UserTableClient } from './generated/typescript/User';
|
|
14
|
+
// import type { OrderTableClient } from './generated/typescript/Order';
|
|
15
|
+
// const db = createAn5Adapter<{
|
|
16
|
+
// user: UserTableClient;
|
|
17
|
+
// order: OrderTableClient;
|
|
18
|
+
// }>({ connectionString: process.env.DATABASE_URL! });
|
|
19
|
+
// await db.user.findMany({ where: { email: { contains: '@example.com' } } });
|
|
20
|
+
|
|
21
|
+
// Public adapter surface (methods/getters) without the open-ended index
|
|
22
|
+
// signature, so explicit model props in TModels take effect instead of `any`.
|
|
23
|
+
type AdapterMethodKeys =
|
|
24
|
+
| 'dialect'
|
|
25
|
+
| 'exec'
|
|
26
|
+
| '$queryRawUnsafe'
|
|
27
|
+
| '$executeRaw'
|
|
28
|
+
| '$executeRawUnsafe'
|
|
29
|
+
| '$connect'
|
|
30
|
+
| '$disconnect'
|
|
31
|
+
| '$transaction'
|
|
32
|
+
| '$begin'
|
|
33
|
+
| 'table'
|
|
34
|
+
| '$on'
|
|
35
|
+
| '$off'
|
|
36
|
+
| '$emit'
|
|
37
|
+
| '$use'
|
|
38
|
+
| 'view'
|
|
39
|
+
| '$queryProc'
|
|
40
|
+
| '$executeProc'
|
|
41
|
+
| 'readRange'
|
|
42
|
+
| 'writeRange'
|
|
43
|
+
| 'appendRange'
|
|
44
|
+
| 'listSheets'
|
|
45
|
+
| 'deleteSheet';
|
|
46
|
+
|
|
47
|
+
export type AdapterAPI = Pick<An5Adapter, AdapterMethodKeys>;
|
|
48
|
+
|
|
49
|
+
// An5Adapter with dynamically-resolved model delegates typed as TModels.
|
|
50
|
+
// NOTE: keep AdapterMethodKeys in sync when adding public An5Adapter methods.
|
|
51
|
+
export type TypedAn5Adapter<TModels extends object = Record<string, AdapterTableClient<any>>> =
|
|
52
|
+
AdapterAPI & TModels;
|