@pineliner/odb-client 1.0.0 → 1.0.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.
- package/dist/database/adapters/bun-sqlite.d.ts +14 -0
- package/dist/database/adapters/bun-sqlite.d.ts.map +1 -0
- package/dist/database/adapters/libsql.d.ts +14 -0
- package/dist/database/adapters/libsql.d.ts.map +1 -0
- package/dist/database/adapters/odblite.d.ts +15 -0
- package/dist/database/adapters/odblite.d.ts.map +1 -0
- package/dist/database/index.d.ts +8 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/manager.d.ts +92 -0
- package/dist/database/manager.d.ts.map +1 -0
- package/dist/database/sql-parser.d.ts +17 -0
- package/dist/database/sql-parser.d.ts.map +1 -0
- package/dist/database/types.d.ts +106 -0
- package/dist/database/types.d.ts.map +1 -0
- package/dist/index.cjs +740 -12
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +714 -8
- package/dist/service/service-client.d.ts +9 -2
- package/dist/service/service-client.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/database/adapters/bun-sqlite.ts +170 -0
- package/src/database/adapters/libsql.ts +156 -0
- package/src/database/adapters/odblite.ts +181 -0
- package/src/database/index.ts +32 -0
- package/src/database/manager.ts +336 -0
- package/src/database/sql-parser.ts +112 -0
- package/src/database/types.ts +140 -0
- package/src/index.ts +28 -0
- package/src/service/service-client.ts +34 -5
|
@@ -86,10 +86,10 @@ export declare class ServiceClient {
|
|
|
86
86
|
* Create a new database
|
|
87
87
|
*
|
|
88
88
|
* @param name - Database name (should be unique)
|
|
89
|
-
* @param nodeId - ID of the node to host the database
|
|
89
|
+
* @param nodeId - ID of the node to host the database (optional - server will select if null)
|
|
90
90
|
* @returns Created database object with hash
|
|
91
91
|
*/
|
|
92
|
-
createDatabase(name: string, nodeId
|
|
92
|
+
createDatabase(name: string, nodeId?: string | null): Promise<ODBLiteDatabase>;
|
|
93
93
|
/**
|
|
94
94
|
* Get database details by hash
|
|
95
95
|
*
|
|
@@ -97,6 +97,13 @@ export declare class ServiceClient {
|
|
|
97
97
|
* @returns Database object
|
|
98
98
|
*/
|
|
99
99
|
getDatabase(hash: string): Promise<ODBLiteDatabase>;
|
|
100
|
+
/**
|
|
101
|
+
* Get database details by name
|
|
102
|
+
*
|
|
103
|
+
* @param name - Database name
|
|
104
|
+
* @returns Database object or null if not found
|
|
105
|
+
*/
|
|
106
|
+
getDatabaseByName(name: string): Promise<ODBLiteDatabase | null>;
|
|
100
107
|
/**
|
|
101
108
|
* Delete a database
|
|
102
109
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service-client.d.ts","sourceRoot":"","sources":["../../src/service/service-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,mBAAmB;IAClC,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,aAAa,CAAsB;gBAE/B,MAAM,EAAE,mBAAmB;IAMvC;;;;;;;;;;;;;;;;;;OAkBG;IACG,uBAAuB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"service-client.d.ts","sourceRoot":"","sources":["../../src/service/service-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,mBAAmB;IAClC,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,aAAa,CAAsB;gBAE/B,MAAM,EAAE,mBAAmB;IAMvC;;;;;;;;;;;;;;;;;;OAkBG;IACG,uBAAuB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAiDhF;;;;;;OAMG;IACG,aAAa,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IAejD;;;;;;OAMG;IACG,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,eAAe,CAAC;IAuBpF;;;;;OAKG;IACG,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAezD;;;;;OAKG;IACG,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAmBtE;;;;OAIG;IACG,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBjD;;;;OAIG;IACG,SAAS,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAezC;;;;;;;;;;;OAWG;IACG,KAAK,CAAC,CAAC,GAAG,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,GAAG,EAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAkBvF;;;;OAIG;IACH,UAAU,IAAI,IAAI;IAIlB;;;;;;OAMG;IACH,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAKnE;;;;;;;;;OASG;IACH,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;CAIpE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pineliner/odb-client",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Isomorphic client for ODB-Lite with postgres.js-like template string SQL support",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -14,11 +14,12 @@
|
|
|
14
14
|
"deploy": "bun run build && npm publish --access public"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@libsql/client": "^0.15.15"
|
|
17
|
+
"@libsql/client": "^0.15.15",
|
|
18
|
+
"node-sql-parser": "^5.3.12"
|
|
18
19
|
},
|
|
19
20
|
"devDependencies": {
|
|
20
21
|
"@types/bun": "latest",
|
|
21
|
-
"@rslib/core": "
|
|
22
|
+
"@rslib/core": "0.15.0",
|
|
22
23
|
"typescript": "^5.9.2"
|
|
23
24
|
},
|
|
24
25
|
"exports": {
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite'
|
|
2
|
+
import type {
|
|
3
|
+
DatabaseAdapter,
|
|
4
|
+
Connection,
|
|
5
|
+
QueryResult,
|
|
6
|
+
PreparedStatement,
|
|
7
|
+
BunSQLiteConfig,
|
|
8
|
+
} from '../types'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Bun SQLite adapter for DatabaseManager
|
|
12
|
+
* Wraps bun:sqlite with Connection interface
|
|
13
|
+
*/
|
|
14
|
+
export class BunSQLiteAdapter implements DatabaseAdapter {
|
|
15
|
+
readonly type = 'bun-sqlite' as const
|
|
16
|
+
private config: BunSQLiteConfig
|
|
17
|
+
|
|
18
|
+
constructor(config: BunSQLiteConfig) {
|
|
19
|
+
this.config = config
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async connect(config: BunSQLiteConfig): Promise<Connection> {
|
|
23
|
+
const db = new Database(config.databasePath, {
|
|
24
|
+
readonly: config.readonly,
|
|
25
|
+
create: config.create ?? true,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
return new BunSQLiteConnection(db)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async disconnect(tenantId?: string): Promise<void> {
|
|
32
|
+
// bun:sqlite doesn't require explicit disconnection
|
|
33
|
+
// File handles are cleaned up by GC
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async isHealthy(): Promise<boolean> {
|
|
37
|
+
try {
|
|
38
|
+
const db = new Database(this.config.databasePath, {
|
|
39
|
+
readonly: true,
|
|
40
|
+
create: false,
|
|
41
|
+
})
|
|
42
|
+
db.query('SELECT 1').get()
|
|
43
|
+
db.close()
|
|
44
|
+
return true
|
|
45
|
+
} catch {
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Connection implementation for Bun SQLite
|
|
53
|
+
*/
|
|
54
|
+
class BunSQLiteConnection implements Connection {
|
|
55
|
+
private db: Database
|
|
56
|
+
private inTransaction = false
|
|
57
|
+
|
|
58
|
+
constructor(db: Database) {
|
|
59
|
+
this.db = db
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Template tag query (postgres.js-like)
|
|
64
|
+
*/
|
|
65
|
+
async query<T = any>(
|
|
66
|
+
strings: TemplateStringsArray,
|
|
67
|
+
...values: any[]
|
|
68
|
+
): Promise<QueryResult<T>> {
|
|
69
|
+
// Build SQL from template
|
|
70
|
+
const sql = strings.reduce((acc, str, i) => {
|
|
71
|
+
return acc + str + (i < values.length ? '?' : '')
|
|
72
|
+
}, '')
|
|
73
|
+
|
|
74
|
+
return this.execute(sql, values)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Execute SQL with parameters
|
|
79
|
+
*/
|
|
80
|
+
async execute(sql: string, params: any[] = []): Promise<QueryResult> {
|
|
81
|
+
try {
|
|
82
|
+
const stmt = this.db.query(sql)
|
|
83
|
+
const isSelect = sql.trim().toUpperCase().startsWith('SELECT')
|
|
84
|
+
|
|
85
|
+
if (isSelect) {
|
|
86
|
+
const rows = stmt.all(...params)
|
|
87
|
+
return {
|
|
88
|
+
rows: rows as any[],
|
|
89
|
+
rowsAffected: 0,
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
const result = stmt.run(...params)
|
|
93
|
+
return {
|
|
94
|
+
rows: [],
|
|
95
|
+
rowsAffected: result.changes || 0,
|
|
96
|
+
lastInsertRowid: result.lastInsertRowid,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} catch (error: any) {
|
|
100
|
+
throw new Error(`SQL execution failed: ${error.message}`)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Prepare statement for repeated execution
|
|
106
|
+
*/
|
|
107
|
+
prepare(sql: string): PreparedStatement {
|
|
108
|
+
const stmt = this.db.query(sql)
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
execute: async (params: any[] = []) => {
|
|
112
|
+
const isSelect = sql.trim().toUpperCase().startsWith('SELECT')
|
|
113
|
+
|
|
114
|
+
if (isSelect) {
|
|
115
|
+
const rows = stmt.all(...params)
|
|
116
|
+
return {
|
|
117
|
+
rows: rows as any[],
|
|
118
|
+
rowsAffected: 0,
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
const result = stmt.run(...params)
|
|
122
|
+
return {
|
|
123
|
+
rows: [],
|
|
124
|
+
rowsAffected: result.changes || 0,
|
|
125
|
+
lastInsertRowid: result.lastInsertRowid,
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
all: async (params: any[] = []) => {
|
|
131
|
+
return stmt.all(...params) as any[]
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
get: async (params: any[] = []) => {
|
|
135
|
+
return stmt.get(...params) as any
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Execute function in transaction
|
|
142
|
+
*/
|
|
143
|
+
async transaction<T>(fn: (tx: Connection) => Promise<T>): Promise<T> {
|
|
144
|
+
if (this.inTransaction) {
|
|
145
|
+
// Nested transaction - just execute the function
|
|
146
|
+
return fn(this)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this.inTransaction = true
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
await this.execute('BEGIN')
|
|
153
|
+
const result = await fn(this)
|
|
154
|
+
await this.execute('COMMIT')
|
|
155
|
+
return result
|
|
156
|
+
} catch (error) {
|
|
157
|
+
await this.execute('ROLLBACK')
|
|
158
|
+
throw error
|
|
159
|
+
} finally {
|
|
160
|
+
this.inTransaction = false
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Close connection
|
|
166
|
+
*/
|
|
167
|
+
async close(): Promise<void> {
|
|
168
|
+
this.db.close()
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { createClient, Client, Transaction as LibSQLTransaction } from '@libsql/client'
|
|
2
|
+
import type {
|
|
3
|
+
DatabaseAdapter,
|
|
4
|
+
Connection,
|
|
5
|
+
QueryResult,
|
|
6
|
+
PreparedStatement,
|
|
7
|
+
LibSQLConfig,
|
|
8
|
+
} from '../types'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* LibSQL adapter for DatabaseManager
|
|
12
|
+
* Wraps @libsql/client with Connection interface
|
|
13
|
+
*/
|
|
14
|
+
export class LibSQLAdapter implements DatabaseAdapter {
|
|
15
|
+
readonly type = 'libsql' as const
|
|
16
|
+
private config: LibSQLConfig
|
|
17
|
+
|
|
18
|
+
constructor(config: LibSQLConfig) {
|
|
19
|
+
this.config = config
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async connect(config: LibSQLConfig): Promise<Connection> {
|
|
23
|
+
const client = createClient({
|
|
24
|
+
url: config.url,
|
|
25
|
+
authToken: config.authToken,
|
|
26
|
+
encryptionKey: config.encryptionKey,
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
return new LibSQLConnection(client)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async disconnect(tenantId?: string): Promise<void> {
|
|
33
|
+
// LibSQL clients don't require explicit disconnection
|
|
34
|
+
// Connections are pooled internally
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async isHealthy(): Promise<boolean> {
|
|
38
|
+
try {
|
|
39
|
+
const client = createClient({
|
|
40
|
+
url: this.config.url,
|
|
41
|
+
authToken: this.config.authToken,
|
|
42
|
+
})
|
|
43
|
+
await client.execute('SELECT 1')
|
|
44
|
+
return true
|
|
45
|
+
} catch {
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Connection implementation for LibSQL
|
|
53
|
+
*/
|
|
54
|
+
class LibSQLConnection implements Connection {
|
|
55
|
+
private client: Client
|
|
56
|
+
private txClient?: LibSQLTransaction
|
|
57
|
+
|
|
58
|
+
constructor(client: Client) {
|
|
59
|
+
this.client = client
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Template tag query (postgres.js-like)
|
|
64
|
+
*/
|
|
65
|
+
async query<T = any>(
|
|
66
|
+
strings: TemplateStringsArray,
|
|
67
|
+
...values: any[]
|
|
68
|
+
): Promise<QueryResult<T>> {
|
|
69
|
+
// Build SQL from template
|
|
70
|
+
const sql = strings.reduce((acc, str, i) => {
|
|
71
|
+
return acc + str + (i < values.length ? '?' : '')
|
|
72
|
+
}, '')
|
|
73
|
+
|
|
74
|
+
return this.execute(sql, values)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Execute SQL with parameters
|
|
79
|
+
* Supports both formats: execute(sql, params) and execute({sql, args})
|
|
80
|
+
*/
|
|
81
|
+
async execute(sql: string | { sql: string; args?: any[] }, params: any[] = []): Promise<QueryResult> {
|
|
82
|
+
try {
|
|
83
|
+
const target = this.txClient || this.client
|
|
84
|
+
|
|
85
|
+
// Support both execute(sql, params) and execute({sql, args}) formats
|
|
86
|
+
let query: { sql: string; args: any[] }
|
|
87
|
+
if (typeof sql === 'string') {
|
|
88
|
+
query = { sql, args: params }
|
|
89
|
+
} else {
|
|
90
|
+
query = { sql: sql.sql, args: sql.args || [] }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const result = await target.execute(query)
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
rows: result.rows as any[],
|
|
97
|
+
rowsAffected: Number(result.rowsAffected),
|
|
98
|
+
lastInsertRowid: result.lastInsertRowid
|
|
99
|
+
? BigInt(result.lastInsertRowid.toString())
|
|
100
|
+
: undefined,
|
|
101
|
+
}
|
|
102
|
+
} catch (error: any) {
|
|
103
|
+
throw new Error(`SQL execution failed: ${error.message}`)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Prepare statement for repeated execution
|
|
109
|
+
*/
|
|
110
|
+
prepare(sql: string): PreparedStatement {
|
|
111
|
+
return {
|
|
112
|
+
execute: async (params: any[] = []) => {
|
|
113
|
+
return this.execute(sql, params)
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
all: async (params: any[] = []) => {
|
|
117
|
+
const result = await this.execute(sql, params)
|
|
118
|
+
return result.rows
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
get: async (params: any[] = []) => {
|
|
122
|
+
const result = await this.execute(sql, params)
|
|
123
|
+
return result.rows[0] || null
|
|
124
|
+
},
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Execute function in transaction
|
|
130
|
+
*/
|
|
131
|
+
async transaction<T>(fn: (tx: Connection) => Promise<T>): Promise<T> {
|
|
132
|
+
if (this.txClient) {
|
|
133
|
+
// Nested transaction - just execute the function
|
|
134
|
+
return fn(this)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Use manual BEGIN/COMMIT/ROLLBACK for simplicity
|
|
138
|
+
try {
|
|
139
|
+
await this.execute('BEGIN')
|
|
140
|
+
const result = await fn(this)
|
|
141
|
+
await this.execute('COMMIT')
|
|
142
|
+
return result
|
|
143
|
+
} catch (error) {
|
|
144
|
+
await this.execute('ROLLBACK')
|
|
145
|
+
throw error
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Close connection
|
|
151
|
+
*/
|
|
152
|
+
async close(): Promise<void> {
|
|
153
|
+
// LibSQL client.close() if available in future versions
|
|
154
|
+
// For now, connections are managed by the client pool
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { ServiceClient } from '../../service/service-client'
|
|
2
|
+
import { ODBLiteClient } from '../../core/client'
|
|
3
|
+
import type {
|
|
4
|
+
DatabaseAdapter,
|
|
5
|
+
Connection,
|
|
6
|
+
QueryResult,
|
|
7
|
+
PreparedStatement,
|
|
8
|
+
ODBLiteConfig,
|
|
9
|
+
} from '../types'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* ODB-Lite adapter for DatabaseManager
|
|
13
|
+
* Wraps ServiceClient and ODBLiteClient with Connection interface
|
|
14
|
+
*/
|
|
15
|
+
export class ODBLiteAdapter implements DatabaseAdapter {
|
|
16
|
+
readonly type = 'odblite' as const
|
|
17
|
+
private config: ODBLiteConfig
|
|
18
|
+
private serviceClient: ServiceClient
|
|
19
|
+
|
|
20
|
+
constructor(config: ODBLiteConfig) {
|
|
21
|
+
this.config = config
|
|
22
|
+
this.serviceClient = new ServiceClient({
|
|
23
|
+
baseUrl: config.serviceUrl,
|
|
24
|
+
apiKey: config.apiKey,
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async connect(config: any): Promise<Connection> {
|
|
29
|
+
const databaseName = config.databaseName || 'default'
|
|
30
|
+
|
|
31
|
+
// Ensure database exists
|
|
32
|
+
const dbInfo = await this.serviceClient.getDatabaseByName(databaseName)
|
|
33
|
+
|
|
34
|
+
if (!dbInfo) {
|
|
35
|
+
// Create database - pass nodeId from config if provided, otherwise server selects
|
|
36
|
+
await this.serviceClient.createDatabase(databaseName, this.config.nodeId)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Get fresh database info
|
|
40
|
+
const db = await this.serviceClient.getDatabaseByName(databaseName)
|
|
41
|
+
if (!db) {
|
|
42
|
+
throw new Error(`Database ${databaseName} not found after creation`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Create ODBLiteClient for this database
|
|
46
|
+
const client = new ODBLiteClient({
|
|
47
|
+
baseUrl: this.config.serviceUrl,
|
|
48
|
+
apiKey: this.config.apiKey,
|
|
49
|
+
databaseId: db.hash,
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
return new ODBLiteConnection(client, this.serviceClient, databaseName)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async disconnect(tenantId?: string): Promise<void> {
|
|
56
|
+
if (tenantId) {
|
|
57
|
+
const prefix = 'pipeline_tenant_' // TODO: make configurable
|
|
58
|
+
const databaseName = `${prefix}${tenantId}`
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
await this.serviceClient.deleteDatabase(databaseName)
|
|
62
|
+
} catch (error: any) {
|
|
63
|
+
console.warn(`Failed to delete database ${databaseName}:`, error.message)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async isHealthy(): Promise<boolean> {
|
|
69
|
+
try {
|
|
70
|
+
await this.serviceClient.listDatabases()
|
|
71
|
+
return true
|
|
72
|
+
} catch {
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Connection implementation for ODB-Lite
|
|
80
|
+
*/
|
|
81
|
+
class ODBLiteConnection implements Connection {
|
|
82
|
+
private client: ODBLiteClient
|
|
83
|
+
private serviceClient: ServiceClient
|
|
84
|
+
private databaseName: string
|
|
85
|
+
private inTransaction = false
|
|
86
|
+
|
|
87
|
+
constructor(
|
|
88
|
+
client: ODBLiteClient,
|
|
89
|
+
serviceClient: ServiceClient,
|
|
90
|
+
databaseName: string
|
|
91
|
+
) {
|
|
92
|
+
this.client = client
|
|
93
|
+
this.serviceClient = serviceClient
|
|
94
|
+
this.databaseName = databaseName
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Template tag query (postgres.js-like)
|
|
99
|
+
*/
|
|
100
|
+
async query<T = any>(
|
|
101
|
+
strings: TemplateStringsArray,
|
|
102
|
+
...values: any[]
|
|
103
|
+
): Promise<QueryResult<T>> {
|
|
104
|
+
// ODBLiteClient.sql is a function that returns a Promise
|
|
105
|
+
const result = await this.client.sql(strings, ...values)
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
rows: result.rows as T[],
|
|
109
|
+
rowsAffected: result.rowsAffected || 0,
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Execute SQL with parameters
|
|
115
|
+
*/
|
|
116
|
+
async execute(sql: string, params: any[] = []): Promise<QueryResult> {
|
|
117
|
+
try {
|
|
118
|
+
const result = await this.client.sql.execute(sql, params)
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
rows: result.rows,
|
|
122
|
+
rowsAffected: result.rowsAffected || 0,
|
|
123
|
+
}
|
|
124
|
+
} catch (error: any) {
|
|
125
|
+
throw new Error(`SQL execution failed: ${error.message}`)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Prepare statement for repeated execution
|
|
131
|
+
*/
|
|
132
|
+
prepare(sql: string): PreparedStatement {
|
|
133
|
+
return {
|
|
134
|
+
execute: async (params: any[] = []) => {
|
|
135
|
+
return this.execute(sql, params)
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
all: async (params: any[] = []) => {
|
|
139
|
+
const result = await this.execute(sql, params)
|
|
140
|
+
return result.rows
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
get: async (params: any[] = []) => {
|
|
144
|
+
const result = await this.execute(sql, params)
|
|
145
|
+
return result.rows[0] || null
|
|
146
|
+
},
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Execute function in transaction
|
|
152
|
+
*/
|
|
153
|
+
async transaction<T>(fn: (tx: Connection) => Promise<T>): Promise<T> {
|
|
154
|
+
if (this.inTransaction) {
|
|
155
|
+
// Nested transaction - just execute the function
|
|
156
|
+
return fn(this)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
this.inTransaction = true
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
await this.execute('BEGIN')
|
|
163
|
+
const result = await fn(this)
|
|
164
|
+
await this.execute('COMMIT')
|
|
165
|
+
return result
|
|
166
|
+
} catch (error) {
|
|
167
|
+
await this.execute('ROLLBACK')
|
|
168
|
+
throw error
|
|
169
|
+
} finally {
|
|
170
|
+
this.inTransaction = false
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Close connection
|
|
176
|
+
*/
|
|
177
|
+
async close(): Promise<void> {
|
|
178
|
+
// ODBLiteClient connections are stateless HTTP requests
|
|
179
|
+
// No explicit close needed
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Database Manager - Unified database abstraction for all systems
|
|
2
|
+
export { DatabaseManager } from './manager'
|
|
3
|
+
|
|
4
|
+
// SQL Parser utilities
|
|
5
|
+
export { parseSQL, splitSQLStatements } from './sql-parser'
|
|
6
|
+
export type { ParsedStatements, SQLParserOptions } from './sql-parser'
|
|
7
|
+
|
|
8
|
+
// Backend adapters
|
|
9
|
+
export { BunSQLiteAdapter } from './adapters/bun-sqlite'
|
|
10
|
+
export { LibSQLAdapter } from './adapters/libsql'
|
|
11
|
+
export { ODBLiteAdapter } from './adapters/odblite'
|
|
12
|
+
|
|
13
|
+
// Export all types
|
|
14
|
+
export type {
|
|
15
|
+
// Core types
|
|
16
|
+
TenancyMode,
|
|
17
|
+
BackendType,
|
|
18
|
+
QueryResult,
|
|
19
|
+
Connection,
|
|
20
|
+
DatabaseAdapter,
|
|
21
|
+
PreparedStatement,
|
|
22
|
+
|
|
23
|
+
// Configuration types
|
|
24
|
+
DatabaseManagerConfig,
|
|
25
|
+
MigrationConfig,
|
|
26
|
+
BunSQLiteConfig,
|
|
27
|
+
LibSQLConfig,
|
|
28
|
+
ODBLiteConfig,
|
|
29
|
+
|
|
30
|
+
// Tenant management
|
|
31
|
+
TenantInfo,
|
|
32
|
+
} from './types'
|