@basicbenframework/core 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.
- package/.github/workflows/publish.yml +35 -0
- package/README.md +588 -0
- package/bin/cli.js +8 -0
- package/create-basicben-app/index.js +205 -0
- package/create-basicben-app/package.json +30 -0
- package/create-basicben-app/template/.env.example +24 -0
- package/create-basicben-app/template/README.md +59 -0
- package/create-basicben-app/template/basicben.config.js +33 -0
- package/create-basicben-app/template/index.html +54 -0
- package/create-basicben-app/template/migrations/001_create_users.js +15 -0
- package/create-basicben-app/template/migrations/002_create_posts.js +18 -0
- package/create-basicben-app/template/public/.gitkeep +0 -0
- package/create-basicben-app/template/seeds/01_users.js +29 -0
- package/create-basicben-app/template/seeds/02_posts.js +43 -0
- package/create-basicben-app/template/src/client/components/Alert.jsx +11 -0
- package/create-basicben-app/template/src/client/components/Avatar.jsx +11 -0
- package/create-basicben-app/template/src/client/components/BackLink.jsx +10 -0
- package/create-basicben-app/template/src/client/components/Button.jsx +19 -0
- package/create-basicben-app/template/src/client/components/Card.jsx +10 -0
- package/create-basicben-app/template/src/client/components/Empty.jsx +6 -0
- package/create-basicben-app/template/src/client/components/Input.jsx +12 -0
- package/create-basicben-app/template/src/client/components/Loading.jsx +6 -0
- package/create-basicben-app/template/src/client/components/Logo.jsx +40 -0
- package/create-basicben-app/template/src/client/components/Nav/DarkModeToggle.jsx +23 -0
- package/create-basicben-app/template/src/client/components/Nav/DesktopNav.jsx +32 -0
- package/create-basicben-app/template/src/client/components/Nav/MobileNav.jsx +107 -0
- package/create-basicben-app/template/src/client/components/NavLink.jsx +10 -0
- package/create-basicben-app/template/src/client/components/PageHeader.jsx +8 -0
- package/create-basicben-app/template/src/client/components/PostCard.jsx +19 -0
- package/create-basicben-app/template/src/client/components/Textarea.jsx +12 -0
- package/create-basicben-app/template/src/client/components/ThemeContext.jsx +5 -0
- package/create-basicben-app/template/src/client/contexts/ToastContext.jsx +94 -0
- package/create-basicben-app/template/src/client/layouts/AppLayout.jsx +60 -0
- package/create-basicben-app/template/src/client/layouts/AuthLayout.jsx +33 -0
- package/create-basicben-app/template/src/client/layouts/DocsLayout.jsx +60 -0
- package/create-basicben-app/template/src/client/layouts/RootLayout.jsx +25 -0
- package/create-basicben-app/template/src/client/pages/Auth.jsx +55 -0
- package/create-basicben-app/template/src/client/pages/Authentication.jsx +236 -0
- package/create-basicben-app/template/src/client/pages/Database.jsx +426 -0
- package/create-basicben-app/template/src/client/pages/Feed.jsx +34 -0
- package/create-basicben-app/template/src/client/pages/FeedPost.jsx +37 -0
- package/create-basicben-app/template/src/client/pages/GettingStarted.jsx +136 -0
- package/create-basicben-app/template/src/client/pages/Home.jsx +206 -0
- package/create-basicben-app/template/src/client/pages/PostForm.jsx +69 -0
- package/create-basicben-app/template/src/client/pages/Posts.jsx +59 -0
- package/create-basicben-app/template/src/client/pages/Profile.jsx +68 -0
- package/create-basicben-app/template/src/client/pages/Routing.jsx +207 -0
- package/create-basicben-app/template/src/client/pages/Testing.jsx +251 -0
- package/create-basicben-app/template/src/client/pages/Validation.jsx +210 -0
- package/create-basicben-app/template/src/controllers/AuthController.js +81 -0
- package/create-basicben-app/template/src/controllers/HomeController.js +17 -0
- package/create-basicben-app/template/src/controllers/PostController.js +86 -0
- package/create-basicben-app/template/src/controllers/ProfileController.js +66 -0
- package/create-basicben-app/template/src/helpers/api.js +24 -0
- package/create-basicben-app/template/src/main.jsx +9 -0
- package/create-basicben-app/template/src/middleware/auth.js +16 -0
- package/create-basicben-app/template/src/models/Post.js +63 -0
- package/create-basicben-app/template/src/models/User.js +42 -0
- package/create-basicben-app/template/src/routes/App.jsx +38 -0
- package/create-basicben-app/template/src/routes/api/auth.js +7 -0
- package/create-basicben-app/template/src/routes/api/posts.js +15 -0
- package/create-basicben-app/template/src/routes/api/profile.js +8 -0
- package/create-basicben-app/template/src/server/index.js +16 -0
- package/create-basicben-app/template/vite.config.js +18 -0
- package/database.sqlite +0 -0
- package/my-test-app/.env.example +24 -0
- package/my-test-app/README.md +59 -0
- package/my-test-app/basicben.config.js +33 -0
- package/my-test-app/database.sqlite-shm +0 -0
- package/my-test-app/database.sqlite-wal +0 -0
- package/my-test-app/index.html +54 -0
- package/my-test-app/migrations/001_create_users.js +15 -0
- package/my-test-app/migrations/002_create_posts.js +18 -0
- package/my-test-app/package-lock.json +2160 -0
- package/my-test-app/package.json +29 -0
- package/my-test-app/public/.gitkeep +0 -0
- package/my-test-app/seeds/01_users.js +29 -0
- package/my-test-app/seeds/02_posts.js +43 -0
- package/my-test-app/src/client/components/Alert.jsx +11 -0
- package/my-test-app/src/client/components/Avatar.jsx +11 -0
- package/my-test-app/src/client/components/BackLink.jsx +10 -0
- package/my-test-app/src/client/components/Button.jsx +19 -0
- package/my-test-app/src/client/components/Card.jsx +10 -0
- package/my-test-app/src/client/components/Empty.jsx +6 -0
- package/my-test-app/src/client/components/Input.jsx +12 -0
- package/my-test-app/src/client/components/Loading.jsx +6 -0
- package/my-test-app/src/client/components/Logo.jsx +40 -0
- package/my-test-app/src/client/components/Nav/DarkModeToggle.jsx +23 -0
- package/my-test-app/src/client/components/Nav/DesktopNav.jsx +32 -0
- package/my-test-app/src/client/components/Nav/MobileNav.jsx +107 -0
- package/my-test-app/src/client/components/NavLink.jsx +10 -0
- package/my-test-app/src/client/components/PageHeader.jsx +8 -0
- package/my-test-app/src/client/components/PostCard.jsx +19 -0
- package/my-test-app/src/client/components/Textarea.jsx +12 -0
- package/my-test-app/src/client/components/ThemeContext.jsx +5 -0
- package/my-test-app/src/client/contexts/AppContext.jsx +13 -0
- package/my-test-app/src/client/contexts/ToastContext.jsx +94 -0
- package/my-test-app/src/client/layouts/AppLayout.jsx +60 -0
- package/my-test-app/src/client/layouts/AuthLayout.jsx +33 -0
- package/my-test-app/src/client/layouts/DocsLayout.jsx +60 -0
- package/my-test-app/src/client/layouts/RootLayout.jsx +25 -0
- package/my-test-app/src/client/pages/Auth.jsx +55 -0
- package/my-test-app/src/client/pages/Authentication.jsx +236 -0
- package/my-test-app/src/client/pages/Database.jsx +426 -0
- package/my-test-app/src/client/pages/Feed.jsx +34 -0
- package/my-test-app/src/client/pages/FeedPost.jsx +37 -0
- package/my-test-app/src/client/pages/GettingStarted.jsx +136 -0
- package/my-test-app/src/client/pages/Home.jsx +206 -0
- package/my-test-app/src/client/pages/PostForm.jsx +69 -0
- package/my-test-app/src/client/pages/Posts.jsx +59 -0
- package/my-test-app/src/client/pages/Profile.jsx +68 -0
- package/my-test-app/src/client/pages/Routing.jsx +207 -0
- package/my-test-app/src/client/pages/Testing.jsx +251 -0
- package/my-test-app/src/client/pages/Validation.jsx +210 -0
- package/my-test-app/src/controllers/AuthController.js +81 -0
- package/my-test-app/src/controllers/HomeController.js +17 -0
- package/my-test-app/src/controllers/PostController.js +86 -0
- package/my-test-app/src/controllers/ProfileController.js +66 -0
- package/my-test-app/src/helpers/api.js +24 -0
- package/my-test-app/src/main.jsx +9 -0
- package/my-test-app/src/middleware/auth.js +16 -0
- package/my-test-app/src/models/Post.js +63 -0
- package/my-test-app/src/models/User.js +42 -0
- package/my-test-app/src/routes/App.jsx +38 -0
- package/my-test-app/src/routes/api/auth.js +7 -0
- package/my-test-app/src/routes/api/posts.js +15 -0
- package/my-test-app/src/routes/api/profile.js +8 -0
- package/my-test-app/src/server/index.js +16 -0
- package/my-test-app/vite.config.js +18 -0
- package/package.json +61 -0
- package/scripts/test-app.sh +59 -0
- package/src/auth/jwt.js +195 -0
- package/src/auth/password.js +132 -0
- package/src/cli/colors.js +31 -0
- package/src/cli/dispatcher.js +168 -0
- package/src/cli/parser.js +91 -0
- package/src/client/context.js +4 -0
- package/src/client/hooks.js +50 -0
- package/src/client/index.js +3 -0
- package/src/client/router.js +184 -0
- package/src/commands/build.js +155 -0
- package/src/commands/dev.js +206 -0
- package/src/commands/help.js +84 -0
- package/src/commands/make-controller.js +36 -0
- package/src/commands/make-middleware.js +44 -0
- package/src/commands/make-migration.js +51 -0
- package/src/commands/make-model.js +38 -0
- package/src/commands/make-route.js +36 -0
- package/src/commands/make-seed.js +38 -0
- package/src/commands/migrate-fresh.js +32 -0
- package/src/commands/migrate-rollback.js +30 -0
- package/src/commands/migrate-status.js +41 -0
- package/src/commands/migrate.js +30 -0
- package/src/commands/seed.js +47 -0
- package/src/commands/start.js +69 -0
- package/src/commands/test.js +46 -0
- package/src/db/Grammar.js +125 -0
- package/src/db/QueryBuilder.js +476 -0
- package/src/db/adapters/neon.js +170 -0
- package/src/db/adapters/planetscale.js +146 -0
- package/src/db/adapters/postgres.js +166 -0
- package/src/db/adapters/sqlite.js +125 -0
- package/src/db/adapters/turso.js +165 -0
- package/src/db/index.js +156 -0
- package/src/db/migrator.js +250 -0
- package/src/db/seeder.js +124 -0
- package/src/index.js +12 -0
- package/src/scaffolding/index.js +152 -0
- package/src/server/body-parser.js +159 -0
- package/src/server/cors.js +63 -0
- package/src/server/default-entry.js +13 -0
- package/src/server/http.js +221 -0
- package/src/server/index.js +168 -0
- package/src/server/loader.js +128 -0
- package/src/server/router.js +281 -0
- package/src/server/static.js +139 -0
- package/src/validation/index.js +436 -0
- package/src/vite/config.js +49 -0
- package/stubs/controller.stub +48 -0
- package/stubs/middleware-auth.stub +29 -0
- package/stubs/middleware.stub +9 -0
- package/stubs/migration.stub +17 -0
- package/stubs/model.stub +77 -0
- package/stubs/route.stub +13 -0
- package/stubs/seed.stub +16 -0
- package/stubs/vite.config.stub +18 -0
|
@@ -0,0 +1,146 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Postgres adapter using pg (node-postgres).
|
|
3
|
+
* Provides async interface matching the SQLite adapter.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
let pg = null
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Load pg dynamically
|
|
10
|
+
*/
|
|
11
|
+
async function loadDriver() {
|
|
12
|
+
if (pg) return pg
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
pg = await import('pg')
|
|
16
|
+
return pg
|
|
17
|
+
} catch {
|
|
18
|
+
throw new Error(
|
|
19
|
+
'pg is required for Postgres support.\n' +
|
|
20
|
+
'Install it with: npm install pg'
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create Postgres adapter
|
|
27
|
+
*
|
|
28
|
+
* @param {string} url - Postgres connection string
|
|
29
|
+
* @param {Object} options - Additional options
|
|
30
|
+
*/
|
|
31
|
+
export async function createPostgresAdapter(url, options = {}) {
|
|
32
|
+
const { Pool } = await loadDriver()
|
|
33
|
+
|
|
34
|
+
const pool = new Pool({
|
|
35
|
+
connectionString: url,
|
|
36
|
+
max: options.poolSize || 10,
|
|
37
|
+
idleTimeoutMillis: options.idleTimeout || 30000,
|
|
38
|
+
connectionTimeoutMillis: options.connectionTimeout || 2000
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// Test connection
|
|
42
|
+
try {
|
|
43
|
+
const client = await pool.connect()
|
|
44
|
+
client.release()
|
|
45
|
+
} catch (err) {
|
|
46
|
+
throw new Error(`Failed to connect to Postgres: ${err.message}`)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
/**
|
|
51
|
+
* Driver name for query builder
|
|
52
|
+
*/
|
|
53
|
+
driver: 'postgres',
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Run INSERT/UPDATE/DELETE
|
|
57
|
+
*/
|
|
58
|
+
async run(sql, params = []) {
|
|
59
|
+
const result = await pool.query(sql, normalizeParams(params))
|
|
60
|
+
|
|
61
|
+
// Try to get lastInsertRowid from RETURNING clause
|
|
62
|
+
let lastInsertRowid = null
|
|
63
|
+
if (result.rows && result.rows[0] && result.rows[0].id !== undefined) {
|
|
64
|
+
lastInsertRowid = result.rows[0].id
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
lastInsertRowid,
|
|
69
|
+
changes: result.rowCount
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get single row
|
|
75
|
+
*/
|
|
76
|
+
async get(sql, params = []) {
|
|
77
|
+
const result = await pool.query(sql, normalizeParams(params))
|
|
78
|
+
return result.rows[0]
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get all rows
|
|
83
|
+
*/
|
|
84
|
+
async all(sql, params = []) {
|
|
85
|
+
const result = await pool.query(sql, normalizeParams(params))
|
|
86
|
+
return result.rows
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Execute raw SQL
|
|
91
|
+
*/
|
|
92
|
+
async exec(sql) {
|
|
93
|
+
await pool.query(sql)
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Run function in transaction
|
|
98
|
+
*/
|
|
99
|
+
async transaction(fn) {
|
|
100
|
+
const client = await pool.connect()
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
await client.query('BEGIN')
|
|
104
|
+
|
|
105
|
+
// Create a transaction-scoped adapter
|
|
106
|
+
const txAdapter = {
|
|
107
|
+
async run(sql, params = []) {
|
|
108
|
+
const result = await client.query(sql, normalizeParams(params))
|
|
109
|
+
let lastInsertRowid = null
|
|
110
|
+
if (result.rows && result.rows[0] && result.rows[0].id !== undefined) {
|
|
111
|
+
lastInsertRowid = result.rows[0].id
|
|
112
|
+
}
|
|
113
|
+
return { lastInsertRowid, changes: result.rowCount }
|
|
114
|
+
},
|
|
115
|
+
async get(sql, params = []) {
|
|
116
|
+
const result = await client.query(sql, normalizeParams(params))
|
|
117
|
+
return result.rows[0]
|
|
118
|
+
},
|
|
119
|
+
async all(sql, params = []) {
|
|
120
|
+
const result = await client.query(sql, normalizeParams(params))
|
|
121
|
+
return result.rows
|
|
122
|
+
},
|
|
123
|
+
async exec(sql) {
|
|
124
|
+
await client.query(sql)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const result = await fn(txAdapter)
|
|
129
|
+
await client.query('COMMIT')
|
|
130
|
+
return result
|
|
131
|
+
} catch (err) {
|
|
132
|
+
await client.query('ROLLBACK')
|
|
133
|
+
throw err
|
|
134
|
+
} finally {
|
|
135
|
+
client.release()
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Close connection pool
|
|
141
|
+
*/
|
|
142
|
+
async close() {
|
|
143
|
+
await pool.end()
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get underlying pool
|
|
148
|
+
*/
|
|
149
|
+
get raw() {
|
|
150
|
+
return pool
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Normalize params to array format
|
|
157
|
+
*/
|
|
158
|
+
function normalizeParams(params) {
|
|
159
|
+
if (Array.isArray(params)) {
|
|
160
|
+
return params
|
|
161
|
+
}
|
|
162
|
+
if (params === undefined || params === null) {
|
|
163
|
+
return []
|
|
164
|
+
}
|
|
165
|
+
return [params]
|
|
166
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite adapter using better-sqlite3.
|
|
3
|
+
* Provides synchronous API wrapped for consistency with async Postgres adapter.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
let Database = null
|
|
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
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Create SQLite adapter
|
|
28
|
+
*
|
|
29
|
+
* @param {string} url - Path to SQLite database file
|
|
30
|
+
* @param {Object} options - Additional options
|
|
31
|
+
*/
|
|
32
|
+
export async function createSqliteAdapter(url, options = {}) {
|
|
33
|
+
const SqliteDatabase = await loadDriver()
|
|
34
|
+
|
|
35
|
+
const dbPath = url.replace('sqlite://', '').replace('file://', '')
|
|
36
|
+
const db = new SqliteDatabase(dbPath, {
|
|
37
|
+
verbose: options.verbose ? console.log : undefined
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
// Enable foreign keys by default
|
|
41
|
+
db.pragma('foreign_keys = ON')
|
|
42
|
+
|
|
43
|
+
// Enable WAL mode for better concurrency
|
|
44
|
+
if (options.wal !== false) {
|
|
45
|
+
db.pragma('journal_mode = WAL')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
/**
|
|
50
|
+
* Driver name for query builder
|
|
51
|
+
*/
|
|
52
|
+
driver: 'sqlite',
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Run INSERT/UPDATE/DELETE
|
|
56
|
+
*/
|
|
57
|
+
run(sql, params = []) {
|
|
58
|
+
const stmt = db.prepare(sql)
|
|
59
|
+
const result = stmt.run(...normalizeParams(params))
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
lastInsertRowid: result.lastInsertRowid,
|
|
63
|
+
changes: result.changes
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get single row
|
|
69
|
+
*/
|
|
70
|
+
get(sql, params = []) {
|
|
71
|
+
const stmt = db.prepare(sql)
|
|
72
|
+
return stmt.get(...normalizeParams(params))
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get all rows
|
|
77
|
+
*/
|
|
78
|
+
all(sql, params = []) {
|
|
79
|
+
const stmt = db.prepare(sql)
|
|
80
|
+
return stmt.all(...normalizeParams(params))
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Execute raw SQL (multiple statements)
|
|
85
|
+
*/
|
|
86
|
+
exec(sql) {
|
|
87
|
+
db.exec(sql)
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Run function in transaction
|
|
92
|
+
*/
|
|
93
|
+
transaction(fn) {
|
|
94
|
+
const transaction = db.transaction(fn)
|
|
95
|
+
return transaction()
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Close database connection
|
|
100
|
+
*/
|
|
101
|
+
close() {
|
|
102
|
+
db.close()
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get underlying better-sqlite3 instance
|
|
107
|
+
*/
|
|
108
|
+
get raw() {
|
|
109
|
+
return db
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Normalize params to array format
|
|
116
|
+
*/
|
|
117
|
+
function normalizeParams(params) {
|
|
118
|
+
if (Array.isArray(params)) {
|
|
119
|
+
return params
|
|
120
|
+
}
|
|
121
|
+
if (params === undefined || params === null) {
|
|
122
|
+
return []
|
|
123
|
+
}
|
|
124
|
+
return [params]
|
|
125
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
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
|
+
}
|