@basicbenframework/core 0.1.5 → 0.1.7
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 +1 -1
- package/create-basicben-app/package.json +2 -2
- package/package.json +5 -6
- package/src/db/adapters/sqlite.js +17 -31
- package/src/db/index.js +3 -24
- package/src/db/adapters/neon.js +0 -170
- package/src/db/adapters/planetscale.js +0 -146
- package/src/db/adapters/turso.js +0 -165
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@basicbenframework/create",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Create a new BasicBen application",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,6 +25,6 @@
|
|
|
25
25
|
"url": "https://github.com/BasicBenFramework/core"
|
|
26
26
|
},
|
|
27
27
|
"engines": {
|
|
28
|
-
"node": ">=
|
|
28
|
+
"node": ">=25.8.1"
|
|
29
29
|
}
|
|
30
30
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@basicbenframework/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "A full-stack framework for React. Minimal dependencies, maximum clarity.",
|
|
5
5
|
"author": "ctmakes",
|
|
6
6
|
"repository": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"./db": "./src/db/index.js"
|
|
25
25
|
},
|
|
26
26
|
"engines": {
|
|
27
|
-
"node": ">=
|
|
27
|
+
"node": ">=25.8.1"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
30
|
"test": "node --test src/**/*.test.js",
|
|
@@ -45,6 +45,9 @@
|
|
|
45
45
|
"minimal"
|
|
46
46
|
],
|
|
47
47
|
"license": "MIT",
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"better-sqlite3": "^12.6.2"
|
|
50
|
+
},
|
|
48
51
|
"peerDependencies": {
|
|
49
52
|
"@vitejs/plugin-react": ">=5",
|
|
50
53
|
"react": ">=18",
|
|
@@ -52,10 +55,6 @@
|
|
|
52
55
|
"vite": ">=7"
|
|
53
56
|
},
|
|
54
57
|
"optionalDependencies": {
|
|
55
|
-
"@libsql/client": ">=0.17",
|
|
56
|
-
"@neondatabase/serverless": ">=1.0",
|
|
57
|
-
"@planetscale/database": ">=1",
|
|
58
|
-
"better-sqlite3": "^12.6.2",
|
|
59
58
|
"pg": ">=8"
|
|
60
59
|
}
|
|
61
60
|
}
|
|
@@ -1,27 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SQLite adapter using
|
|
2
|
+
* SQLite adapter using Node.js built-in node:sqlite.
|
|
3
3
|
* Provides synchronous API wrapped for consistency with async Postgres adapter.
|
|
4
|
+
* Requires Node.js 25+ (release candidate).
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Load better-sqlite3 dynamically
|
|
10
|
-
*/
|
|
11
|
-
async function loadDriver() {
|
|
12
|
-
if (Database) return Database
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
const module = await import('better-sqlite3')
|
|
16
|
-
Database = module.default
|
|
17
|
-
return Database
|
|
18
|
-
} catch {
|
|
19
|
-
throw new Error(
|
|
20
|
-
'better-sqlite3 is required for SQLite support.\n' +
|
|
21
|
-
'Install it with: npm install better-sqlite3'
|
|
22
|
-
)
|
|
23
|
-
}
|
|
24
|
-
}
|
|
7
|
+
import { DatabaseSync } from 'node:sqlite'
|
|
25
8
|
|
|
26
9
|
/**
|
|
27
10
|
* Create SQLite adapter
|
|
@@ -30,19 +13,15 @@ async function loadDriver() {
|
|
|
30
13
|
* @param {Object} options - Additional options
|
|
31
14
|
*/
|
|
32
15
|
export async function createSqliteAdapter(url, options = {}) {
|
|
33
|
-
const SqliteDatabase = await loadDriver()
|
|
34
|
-
|
|
35
16
|
const dbPath = url.replace('sqlite://', '').replace('file://', '')
|
|
36
|
-
const db = new SqliteDatabase(dbPath, {
|
|
37
|
-
verbose: options.verbose ? console.log : undefined
|
|
38
|
-
})
|
|
39
17
|
|
|
40
|
-
|
|
41
|
-
|
|
18
|
+
const db = new DatabaseSync(dbPath, {
|
|
19
|
+
enableForeignKeyConstraints: true
|
|
20
|
+
})
|
|
42
21
|
|
|
43
22
|
// Enable WAL mode for better concurrency
|
|
44
23
|
if (options.wal !== false) {
|
|
45
|
-
db.
|
|
24
|
+
db.exec('PRAGMA journal_mode = WAL')
|
|
46
25
|
}
|
|
47
26
|
|
|
48
27
|
return {
|
|
@@ -91,8 +70,15 @@ export async function createSqliteAdapter(url, options = {}) {
|
|
|
91
70
|
* Run function in transaction
|
|
92
71
|
*/
|
|
93
72
|
transaction(fn) {
|
|
94
|
-
|
|
95
|
-
|
|
73
|
+
db.exec('BEGIN TRANSACTION')
|
|
74
|
+
try {
|
|
75
|
+
const result = fn()
|
|
76
|
+
db.exec('COMMIT')
|
|
77
|
+
return result
|
|
78
|
+
} catch (error) {
|
|
79
|
+
db.exec('ROLLBACK')
|
|
80
|
+
throw error
|
|
81
|
+
}
|
|
96
82
|
},
|
|
97
83
|
|
|
98
84
|
/**
|
|
@@ -103,7 +89,7 @@ export async function createSqliteAdapter(url, options = {}) {
|
|
|
103
89
|
},
|
|
104
90
|
|
|
105
91
|
/**
|
|
106
|
-
* Get underlying
|
|
92
|
+
* Get underlying DatabaseSync instance
|
|
107
93
|
*/
|
|
108
94
|
get raw() {
|
|
109
95
|
return db
|
package/src/db/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Database adapter loader.
|
|
3
|
-
* Provides a unified interface for SQLite
|
|
3
|
+
* Provides a unified interface for SQLite and Postgres.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { loadConfig } from '../server/loader.js'
|
|
@@ -34,8 +34,7 @@ export async function getDb() {
|
|
|
34
34
|
const url = dbConfig.url || process.env.DATABASE_URL || './database.sqlite'
|
|
35
35
|
|
|
36
36
|
switch (driver) {
|
|
37
|
-
case 'sqlite':
|
|
38
|
-
case 'better-sqlite3': {
|
|
37
|
+
case 'sqlite': {
|
|
39
38
|
const { createSqliteAdapter } = await import('./adapters/sqlite.js')
|
|
40
39
|
dbInstance = await createSqliteAdapter(url, dbConfig)
|
|
41
40
|
break
|
|
@@ -48,30 +47,10 @@ export async function getDb() {
|
|
|
48
47
|
break
|
|
49
48
|
}
|
|
50
49
|
|
|
51
|
-
case 'turso':
|
|
52
|
-
case 'libsql': {
|
|
53
|
-
const { createTursoAdapter } = await import('./adapters/turso.js')
|
|
54
|
-
dbInstance = await createTursoAdapter(url, dbConfig)
|
|
55
|
-
break
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
case 'planetscale':
|
|
59
|
-
case 'mysql': {
|
|
60
|
-
const { createPlanetScaleAdapter } = await import('./adapters/planetscale.js')
|
|
61
|
-
dbInstance = await createPlanetScaleAdapter(url, dbConfig)
|
|
62
|
-
break
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
case 'neon': {
|
|
66
|
-
const { createNeonAdapter } = await import('./adapters/neon.js')
|
|
67
|
-
dbInstance = await createNeonAdapter(url, dbConfig)
|
|
68
|
-
break
|
|
69
|
-
}
|
|
70
|
-
|
|
71
50
|
default:
|
|
72
51
|
throw new Error(
|
|
73
52
|
`Unknown database driver: ${driver}\n` +
|
|
74
|
-
'Supported drivers: sqlite, postgres
|
|
53
|
+
'Supported drivers: sqlite, postgres'
|
|
75
54
|
)
|
|
76
55
|
}
|
|
77
56
|
|
package/src/db/adapters/neon.js
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Neon adapter using @neondatabase/serverless.
|
|
3
|
-
* Provides serverless Postgres with WebSocket support.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
let neon = null
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Load @neondatabase/serverless dynamically
|
|
10
|
-
*/
|
|
11
|
-
async function loadDriver() {
|
|
12
|
-
if (neon) return neon
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
neon = await import('@neondatabase/serverless')
|
|
16
|
-
return neon
|
|
17
|
-
} catch {
|
|
18
|
-
throw new Error(
|
|
19
|
-
'@neondatabase/serverless is required for Neon support.\n' +
|
|
20
|
-
'Install it with: npm install @neondatabase/serverless'
|
|
21
|
-
)
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Create Neon adapter
|
|
27
|
-
*
|
|
28
|
-
* @param {string} url - Neon connection string (postgres://...)
|
|
29
|
-
* @param {Object} options - Additional options
|
|
30
|
-
*/
|
|
31
|
-
export async function createNeonAdapter(url, options = {}) {
|
|
32
|
-
const { neon: createNeon, neonConfig } = await loadDriver()
|
|
33
|
-
|
|
34
|
-
// Configure for serverless environment
|
|
35
|
-
if (options.fetchConnectionCache !== undefined) {
|
|
36
|
-
neonConfig.fetchConnectionCache = options.fetchConnectionCache
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const sql = createNeon(url)
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
/**
|
|
43
|
-
* Driver name for query builder
|
|
44
|
-
*/
|
|
45
|
-
driver: 'neon',
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Run INSERT/UPDATE/DELETE
|
|
49
|
-
*/
|
|
50
|
-
async run(sqlStr, params = []) {
|
|
51
|
-
const normalizedParams = normalizeParams(params)
|
|
52
|
-
const result = await sql(sqlStr, normalizedParams)
|
|
53
|
-
|
|
54
|
-
// Try to get lastInsertRowid from RETURNING clause
|
|
55
|
-
let lastInsertRowid = null
|
|
56
|
-
if (result[0] && result[0].id !== undefined) {
|
|
57
|
-
lastInsertRowid = result[0].id
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
lastInsertRowid,
|
|
62
|
-
changes: result.count || 0
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Get single row
|
|
68
|
-
*/
|
|
69
|
-
async get(sqlStr, params = []) {
|
|
70
|
-
const normalizedParams = normalizeParams(params)
|
|
71
|
-
const result = await sql(sqlStr, normalizedParams)
|
|
72
|
-
return result[0] || undefined
|
|
73
|
-
},
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Get all rows
|
|
77
|
-
*/
|
|
78
|
-
async all(sqlStr, params = []) {
|
|
79
|
-
const normalizedParams = normalizeParams(params)
|
|
80
|
-
return sql(sqlStr, normalizedParams)
|
|
81
|
-
},
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Execute raw SQL
|
|
85
|
-
*/
|
|
86
|
-
async exec(sqlStr) {
|
|
87
|
-
// Split by semicolon and execute each statement
|
|
88
|
-
const statements = sqlStr
|
|
89
|
-
.split(';')
|
|
90
|
-
.map(s => s.trim())
|
|
91
|
-
.filter(s => s.length > 0)
|
|
92
|
-
|
|
93
|
-
for (const statement of statements) {
|
|
94
|
-
await sql(statement)
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Run function in transaction
|
|
100
|
-
*/
|
|
101
|
-
async transaction(fn) {
|
|
102
|
-
const { Pool } = await loadDriver()
|
|
103
|
-
const pool = new Pool({ connectionString: url })
|
|
104
|
-
const client = await pool.connect()
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
await client.query('BEGIN')
|
|
108
|
-
|
|
109
|
-
const txAdapter = {
|
|
110
|
-
async run(sqlStr, params = []) {
|
|
111
|
-
const result = await client.query(sqlStr, normalizeParams(params))
|
|
112
|
-
let lastInsertRowid = null
|
|
113
|
-
if (result.rows && result.rows[0] && result.rows[0].id !== undefined) {
|
|
114
|
-
lastInsertRowid = result.rows[0].id
|
|
115
|
-
}
|
|
116
|
-
return { lastInsertRowid, changes: result.rowCount }
|
|
117
|
-
},
|
|
118
|
-
async get(sqlStr, params = []) {
|
|
119
|
-
const result = await client.query(sqlStr, normalizeParams(params))
|
|
120
|
-
return result.rows[0]
|
|
121
|
-
},
|
|
122
|
-
async all(sqlStr, params = []) {
|
|
123
|
-
const result = await client.query(sqlStr, normalizeParams(params))
|
|
124
|
-
return result.rows
|
|
125
|
-
},
|
|
126
|
-
async exec(sqlStr) {
|
|
127
|
-
await client.query(sqlStr)
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const result = await fn(txAdapter)
|
|
132
|
-
await client.query('COMMIT')
|
|
133
|
-
return result
|
|
134
|
-
} catch (err) {
|
|
135
|
-
await client.query('ROLLBACK')
|
|
136
|
-
throw err
|
|
137
|
-
} finally {
|
|
138
|
-
client.release()
|
|
139
|
-
await pool.end()
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Close connection (no-op for serverless)
|
|
145
|
-
*/
|
|
146
|
-
async close() {
|
|
147
|
-
// Serverless connections are stateless
|
|
148
|
-
},
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Get underlying sql function
|
|
152
|
-
*/
|
|
153
|
-
get raw() {
|
|
154
|
-
return sql
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Normalize params to array format
|
|
161
|
-
*/
|
|
162
|
-
function normalizeParams(params) {
|
|
163
|
-
if (Array.isArray(params)) {
|
|
164
|
-
return params
|
|
165
|
-
}
|
|
166
|
-
if (params === undefined || params === null) {
|
|
167
|
-
return []
|
|
168
|
-
}
|
|
169
|
-
return [params]
|
|
170
|
-
}
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PlanetScale adapter using @planetscale/database.
|
|
3
|
-
* Provides serverless MySQL-compatible database.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
let planetscale = null
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Load @planetscale/database dynamically
|
|
10
|
-
*/
|
|
11
|
-
async function loadDriver() {
|
|
12
|
-
if (planetscale) return planetscale
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
planetscale = await import('@planetscale/database')
|
|
16
|
-
return planetscale
|
|
17
|
-
} catch {
|
|
18
|
-
throw new Error(
|
|
19
|
-
'@planetscale/database is required for PlanetScale support.\n' +
|
|
20
|
-
'Install it with: npm install @planetscale/database'
|
|
21
|
-
)
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Create PlanetScale adapter
|
|
27
|
-
*
|
|
28
|
-
* @param {string} url - PlanetScale connection string
|
|
29
|
-
* @param {Object} options - Additional options
|
|
30
|
-
*/
|
|
31
|
-
export async function createPlanetScaleAdapter(url, options = {}) {
|
|
32
|
-
const { connect } = await loadDriver()
|
|
33
|
-
|
|
34
|
-
// Parse connection string or use options
|
|
35
|
-
const config = url.startsWith('mysql://')
|
|
36
|
-
? { url }
|
|
37
|
-
: {
|
|
38
|
-
host: options.host || process.env.PLANETSCALE_HOST,
|
|
39
|
-
username: options.username || process.env.PLANETSCALE_USERNAME,
|
|
40
|
-
password: options.password || process.env.PLANETSCALE_PASSWORD
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const conn = connect(config)
|
|
44
|
-
|
|
45
|
-
return {
|
|
46
|
-
/**
|
|
47
|
-
* Driver name for query builder
|
|
48
|
-
*/
|
|
49
|
-
driver: 'planetscale',
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Run INSERT/UPDATE/DELETE
|
|
53
|
-
*/
|
|
54
|
-
async run(sql, params = []) {
|
|
55
|
-
const result = await conn.execute(sql, normalizeParams(params))
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
lastInsertRowid: result.insertId ? Number(result.insertId) : null,
|
|
59
|
-
changes: result.rowsAffected
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Get single row
|
|
65
|
-
*/
|
|
66
|
-
async get(sql, params = []) {
|
|
67
|
-
const result = await conn.execute(sql, normalizeParams(params))
|
|
68
|
-
return result.rows[0] || undefined
|
|
69
|
-
},
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Get all rows
|
|
73
|
-
*/
|
|
74
|
-
async all(sql, params = []) {
|
|
75
|
-
const result = await conn.execute(sql, normalizeParams(params))
|
|
76
|
-
return result.rows
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Execute raw SQL
|
|
81
|
-
* Note: PlanetScale doesn't support multiple statements in one call
|
|
82
|
-
*/
|
|
83
|
-
async exec(sql) {
|
|
84
|
-
// Split by semicolon and execute each statement
|
|
85
|
-
const statements = sql
|
|
86
|
-
.split(';')
|
|
87
|
-
.map(s => s.trim())
|
|
88
|
-
.filter(s => s.length > 0)
|
|
89
|
-
|
|
90
|
-
for (const statement of statements) {
|
|
91
|
-
await conn.execute(statement)
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Run function in transaction
|
|
97
|
-
* Note: PlanetScale serverless doesn't support traditional transactions
|
|
98
|
-
* This provides a pseudo-transaction for API consistency
|
|
99
|
-
*/
|
|
100
|
-
async transaction(fn) {
|
|
101
|
-
// PlanetScale serverless driver doesn't support transactions
|
|
102
|
-
// We execute sequentially and hope for the best
|
|
103
|
-
// For true ACID, use PlanetScale's Boost or a connection pooler
|
|
104
|
-
console.warn(
|
|
105
|
-
'Warning: PlanetScale serverless does not support transactions. ' +
|
|
106
|
-
'Operations will run sequentially without ACID guarantees.'
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
const txAdapter = {
|
|
110
|
-
run: this.run.bind(this),
|
|
111
|
-
get: this.get.bind(this),
|
|
112
|
-
all: this.all.bind(this),
|
|
113
|
-
exec: this.exec.bind(this)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return fn(txAdapter)
|
|
117
|
-
},
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Close connection (no-op for serverless)
|
|
121
|
-
*/
|
|
122
|
-
async close() {
|
|
123
|
-
// Serverless connections are stateless
|
|
124
|
-
},
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Get underlying connection
|
|
128
|
-
*/
|
|
129
|
-
get raw() {
|
|
130
|
-
return conn
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Normalize params to array format
|
|
137
|
-
*/
|
|
138
|
-
function normalizeParams(params) {
|
|
139
|
-
if (Array.isArray(params)) {
|
|
140
|
-
return params
|
|
141
|
-
}
|
|
142
|
-
if (params === undefined || params === null) {
|
|
143
|
-
return []
|
|
144
|
-
}
|
|
145
|
-
return [params]
|
|
146
|
-
}
|
package/src/db/adapters/turso.js
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Turso adapter using @libsql/client.
|
|
3
|
-
* Provides edge-ready distributed SQLite.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
let libsql = null
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Load @libsql/client dynamically
|
|
10
|
-
*/
|
|
11
|
-
async function loadDriver() {
|
|
12
|
-
if (libsql) return libsql
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
libsql = await import('@libsql/client')
|
|
16
|
-
return libsql
|
|
17
|
-
} catch {
|
|
18
|
-
throw new Error(
|
|
19
|
-
'@libsql/client is required for Turso support.\n' +
|
|
20
|
-
'Install it with: npm install @libsql/client'
|
|
21
|
-
)
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Create Turso adapter
|
|
27
|
-
*
|
|
28
|
-
* @param {string} url - Turso database URL (libsql://...)
|
|
29
|
-
* @param {Object} options - Additional options
|
|
30
|
-
* @param {string} options.authToken - Turso auth token
|
|
31
|
-
*/
|
|
32
|
-
export async function createTursoAdapter(url, options = {}) {
|
|
33
|
-
const { createClient } = await loadDriver()
|
|
34
|
-
|
|
35
|
-
const client = createClient({
|
|
36
|
-
url,
|
|
37
|
-
authToken: options.authToken || process.env.TURSO_AUTH_TOKEN
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
/**
|
|
42
|
-
* Driver name for query builder
|
|
43
|
-
*/
|
|
44
|
-
driver: 'turso',
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Run INSERT/UPDATE/DELETE
|
|
48
|
-
*/
|
|
49
|
-
async run(sql, params = []) {
|
|
50
|
-
const result = await client.execute({
|
|
51
|
-
sql,
|
|
52
|
-
args: normalizeParams(params)
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
lastInsertRowid: Number(result.lastInsertRowid) || null,
|
|
57
|
-
changes: result.rowsAffected
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Get single row
|
|
63
|
-
*/
|
|
64
|
-
async get(sql, params = []) {
|
|
65
|
-
const result = await client.execute({
|
|
66
|
-
sql,
|
|
67
|
-
args: normalizeParams(params)
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
return result.rows[0] || undefined
|
|
71
|
-
},
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Get all rows
|
|
75
|
-
*/
|
|
76
|
-
async all(sql, params = []) {
|
|
77
|
-
const result = await client.execute({
|
|
78
|
-
sql,
|
|
79
|
-
args: normalizeParams(params)
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
return result.rows
|
|
83
|
-
},
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Execute raw SQL (multiple statements)
|
|
87
|
-
*/
|
|
88
|
-
async exec(sql) {
|
|
89
|
-
await client.executeMultiple(sql)
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Run function in transaction
|
|
94
|
-
*/
|
|
95
|
-
async transaction(fn) {
|
|
96
|
-
const tx = await client.transaction('write')
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
const txAdapter = {
|
|
100
|
-
async run(sql, params = []) {
|
|
101
|
-
const result = await tx.execute({
|
|
102
|
-
sql,
|
|
103
|
-
args: normalizeParams(params)
|
|
104
|
-
})
|
|
105
|
-
return {
|
|
106
|
-
lastInsertRowid: Number(result.lastInsertRowid) || null,
|
|
107
|
-
changes: result.rowsAffected
|
|
108
|
-
}
|
|
109
|
-
},
|
|
110
|
-
async get(sql, params = []) {
|
|
111
|
-
const result = await tx.execute({
|
|
112
|
-
sql,
|
|
113
|
-
args: normalizeParams(params)
|
|
114
|
-
})
|
|
115
|
-
return result.rows[0] || undefined
|
|
116
|
-
},
|
|
117
|
-
async all(sql, params = []) {
|
|
118
|
-
const result = await tx.execute({
|
|
119
|
-
sql,
|
|
120
|
-
args: normalizeParams(params)
|
|
121
|
-
})
|
|
122
|
-
return result.rows
|
|
123
|
-
},
|
|
124
|
-
async exec(sql) {
|
|
125
|
-
await tx.executeMultiple(sql)
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const result = await fn(txAdapter)
|
|
130
|
-
await tx.commit()
|
|
131
|
-
return result
|
|
132
|
-
} catch (err) {
|
|
133
|
-
await tx.rollback()
|
|
134
|
-
throw err
|
|
135
|
-
}
|
|
136
|
-
},
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Close client connection
|
|
140
|
-
*/
|
|
141
|
-
async close() {
|
|
142
|
-
client.close()
|
|
143
|
-
},
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Get underlying client
|
|
147
|
-
*/
|
|
148
|
-
get raw() {
|
|
149
|
-
return client
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Normalize params to array format
|
|
156
|
-
*/
|
|
157
|
-
function normalizeParams(params) {
|
|
158
|
-
if (Array.isArray(params)) {
|
|
159
|
-
return params
|
|
160
|
-
}
|
|
161
|
-
if (params === undefined || params === null) {
|
|
162
|
-
return []
|
|
163
|
-
}
|
|
164
|
-
return [params]
|
|
165
|
-
}
|