@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.
Files changed (186) hide show
  1. package/.github/workflows/publish.yml +35 -0
  2. package/README.md +588 -0
  3. package/bin/cli.js +8 -0
  4. package/create-basicben-app/index.js +205 -0
  5. package/create-basicben-app/package.json +30 -0
  6. package/create-basicben-app/template/.env.example +24 -0
  7. package/create-basicben-app/template/README.md +59 -0
  8. package/create-basicben-app/template/basicben.config.js +33 -0
  9. package/create-basicben-app/template/index.html +54 -0
  10. package/create-basicben-app/template/migrations/001_create_users.js +15 -0
  11. package/create-basicben-app/template/migrations/002_create_posts.js +18 -0
  12. package/create-basicben-app/template/public/.gitkeep +0 -0
  13. package/create-basicben-app/template/seeds/01_users.js +29 -0
  14. package/create-basicben-app/template/seeds/02_posts.js +43 -0
  15. package/create-basicben-app/template/src/client/components/Alert.jsx +11 -0
  16. package/create-basicben-app/template/src/client/components/Avatar.jsx +11 -0
  17. package/create-basicben-app/template/src/client/components/BackLink.jsx +10 -0
  18. package/create-basicben-app/template/src/client/components/Button.jsx +19 -0
  19. package/create-basicben-app/template/src/client/components/Card.jsx +10 -0
  20. package/create-basicben-app/template/src/client/components/Empty.jsx +6 -0
  21. package/create-basicben-app/template/src/client/components/Input.jsx +12 -0
  22. package/create-basicben-app/template/src/client/components/Loading.jsx +6 -0
  23. package/create-basicben-app/template/src/client/components/Logo.jsx +40 -0
  24. package/create-basicben-app/template/src/client/components/Nav/DarkModeToggle.jsx +23 -0
  25. package/create-basicben-app/template/src/client/components/Nav/DesktopNav.jsx +32 -0
  26. package/create-basicben-app/template/src/client/components/Nav/MobileNav.jsx +107 -0
  27. package/create-basicben-app/template/src/client/components/NavLink.jsx +10 -0
  28. package/create-basicben-app/template/src/client/components/PageHeader.jsx +8 -0
  29. package/create-basicben-app/template/src/client/components/PostCard.jsx +19 -0
  30. package/create-basicben-app/template/src/client/components/Textarea.jsx +12 -0
  31. package/create-basicben-app/template/src/client/components/ThemeContext.jsx +5 -0
  32. package/create-basicben-app/template/src/client/contexts/ToastContext.jsx +94 -0
  33. package/create-basicben-app/template/src/client/layouts/AppLayout.jsx +60 -0
  34. package/create-basicben-app/template/src/client/layouts/AuthLayout.jsx +33 -0
  35. package/create-basicben-app/template/src/client/layouts/DocsLayout.jsx +60 -0
  36. package/create-basicben-app/template/src/client/layouts/RootLayout.jsx +25 -0
  37. package/create-basicben-app/template/src/client/pages/Auth.jsx +55 -0
  38. package/create-basicben-app/template/src/client/pages/Authentication.jsx +236 -0
  39. package/create-basicben-app/template/src/client/pages/Database.jsx +426 -0
  40. package/create-basicben-app/template/src/client/pages/Feed.jsx +34 -0
  41. package/create-basicben-app/template/src/client/pages/FeedPost.jsx +37 -0
  42. package/create-basicben-app/template/src/client/pages/GettingStarted.jsx +136 -0
  43. package/create-basicben-app/template/src/client/pages/Home.jsx +206 -0
  44. package/create-basicben-app/template/src/client/pages/PostForm.jsx +69 -0
  45. package/create-basicben-app/template/src/client/pages/Posts.jsx +59 -0
  46. package/create-basicben-app/template/src/client/pages/Profile.jsx +68 -0
  47. package/create-basicben-app/template/src/client/pages/Routing.jsx +207 -0
  48. package/create-basicben-app/template/src/client/pages/Testing.jsx +251 -0
  49. package/create-basicben-app/template/src/client/pages/Validation.jsx +210 -0
  50. package/create-basicben-app/template/src/controllers/AuthController.js +81 -0
  51. package/create-basicben-app/template/src/controllers/HomeController.js +17 -0
  52. package/create-basicben-app/template/src/controllers/PostController.js +86 -0
  53. package/create-basicben-app/template/src/controllers/ProfileController.js +66 -0
  54. package/create-basicben-app/template/src/helpers/api.js +24 -0
  55. package/create-basicben-app/template/src/main.jsx +9 -0
  56. package/create-basicben-app/template/src/middleware/auth.js +16 -0
  57. package/create-basicben-app/template/src/models/Post.js +63 -0
  58. package/create-basicben-app/template/src/models/User.js +42 -0
  59. package/create-basicben-app/template/src/routes/App.jsx +38 -0
  60. package/create-basicben-app/template/src/routes/api/auth.js +7 -0
  61. package/create-basicben-app/template/src/routes/api/posts.js +15 -0
  62. package/create-basicben-app/template/src/routes/api/profile.js +8 -0
  63. package/create-basicben-app/template/src/server/index.js +16 -0
  64. package/create-basicben-app/template/vite.config.js +18 -0
  65. package/database.sqlite +0 -0
  66. package/my-test-app/.env.example +24 -0
  67. package/my-test-app/README.md +59 -0
  68. package/my-test-app/basicben.config.js +33 -0
  69. package/my-test-app/database.sqlite-shm +0 -0
  70. package/my-test-app/database.sqlite-wal +0 -0
  71. package/my-test-app/index.html +54 -0
  72. package/my-test-app/migrations/001_create_users.js +15 -0
  73. package/my-test-app/migrations/002_create_posts.js +18 -0
  74. package/my-test-app/package-lock.json +2160 -0
  75. package/my-test-app/package.json +29 -0
  76. package/my-test-app/public/.gitkeep +0 -0
  77. package/my-test-app/seeds/01_users.js +29 -0
  78. package/my-test-app/seeds/02_posts.js +43 -0
  79. package/my-test-app/src/client/components/Alert.jsx +11 -0
  80. package/my-test-app/src/client/components/Avatar.jsx +11 -0
  81. package/my-test-app/src/client/components/BackLink.jsx +10 -0
  82. package/my-test-app/src/client/components/Button.jsx +19 -0
  83. package/my-test-app/src/client/components/Card.jsx +10 -0
  84. package/my-test-app/src/client/components/Empty.jsx +6 -0
  85. package/my-test-app/src/client/components/Input.jsx +12 -0
  86. package/my-test-app/src/client/components/Loading.jsx +6 -0
  87. package/my-test-app/src/client/components/Logo.jsx +40 -0
  88. package/my-test-app/src/client/components/Nav/DarkModeToggle.jsx +23 -0
  89. package/my-test-app/src/client/components/Nav/DesktopNav.jsx +32 -0
  90. package/my-test-app/src/client/components/Nav/MobileNav.jsx +107 -0
  91. package/my-test-app/src/client/components/NavLink.jsx +10 -0
  92. package/my-test-app/src/client/components/PageHeader.jsx +8 -0
  93. package/my-test-app/src/client/components/PostCard.jsx +19 -0
  94. package/my-test-app/src/client/components/Textarea.jsx +12 -0
  95. package/my-test-app/src/client/components/ThemeContext.jsx +5 -0
  96. package/my-test-app/src/client/contexts/AppContext.jsx +13 -0
  97. package/my-test-app/src/client/contexts/ToastContext.jsx +94 -0
  98. package/my-test-app/src/client/layouts/AppLayout.jsx +60 -0
  99. package/my-test-app/src/client/layouts/AuthLayout.jsx +33 -0
  100. package/my-test-app/src/client/layouts/DocsLayout.jsx +60 -0
  101. package/my-test-app/src/client/layouts/RootLayout.jsx +25 -0
  102. package/my-test-app/src/client/pages/Auth.jsx +55 -0
  103. package/my-test-app/src/client/pages/Authentication.jsx +236 -0
  104. package/my-test-app/src/client/pages/Database.jsx +426 -0
  105. package/my-test-app/src/client/pages/Feed.jsx +34 -0
  106. package/my-test-app/src/client/pages/FeedPost.jsx +37 -0
  107. package/my-test-app/src/client/pages/GettingStarted.jsx +136 -0
  108. package/my-test-app/src/client/pages/Home.jsx +206 -0
  109. package/my-test-app/src/client/pages/PostForm.jsx +69 -0
  110. package/my-test-app/src/client/pages/Posts.jsx +59 -0
  111. package/my-test-app/src/client/pages/Profile.jsx +68 -0
  112. package/my-test-app/src/client/pages/Routing.jsx +207 -0
  113. package/my-test-app/src/client/pages/Testing.jsx +251 -0
  114. package/my-test-app/src/client/pages/Validation.jsx +210 -0
  115. package/my-test-app/src/controllers/AuthController.js +81 -0
  116. package/my-test-app/src/controllers/HomeController.js +17 -0
  117. package/my-test-app/src/controllers/PostController.js +86 -0
  118. package/my-test-app/src/controllers/ProfileController.js +66 -0
  119. package/my-test-app/src/helpers/api.js +24 -0
  120. package/my-test-app/src/main.jsx +9 -0
  121. package/my-test-app/src/middleware/auth.js +16 -0
  122. package/my-test-app/src/models/Post.js +63 -0
  123. package/my-test-app/src/models/User.js +42 -0
  124. package/my-test-app/src/routes/App.jsx +38 -0
  125. package/my-test-app/src/routes/api/auth.js +7 -0
  126. package/my-test-app/src/routes/api/posts.js +15 -0
  127. package/my-test-app/src/routes/api/profile.js +8 -0
  128. package/my-test-app/src/server/index.js +16 -0
  129. package/my-test-app/vite.config.js +18 -0
  130. package/package.json +61 -0
  131. package/scripts/test-app.sh +59 -0
  132. package/src/auth/jwt.js +195 -0
  133. package/src/auth/password.js +132 -0
  134. package/src/cli/colors.js +31 -0
  135. package/src/cli/dispatcher.js +168 -0
  136. package/src/cli/parser.js +91 -0
  137. package/src/client/context.js +4 -0
  138. package/src/client/hooks.js +50 -0
  139. package/src/client/index.js +3 -0
  140. package/src/client/router.js +184 -0
  141. package/src/commands/build.js +155 -0
  142. package/src/commands/dev.js +206 -0
  143. package/src/commands/help.js +84 -0
  144. package/src/commands/make-controller.js +36 -0
  145. package/src/commands/make-middleware.js +44 -0
  146. package/src/commands/make-migration.js +51 -0
  147. package/src/commands/make-model.js +38 -0
  148. package/src/commands/make-route.js +36 -0
  149. package/src/commands/make-seed.js +38 -0
  150. package/src/commands/migrate-fresh.js +32 -0
  151. package/src/commands/migrate-rollback.js +30 -0
  152. package/src/commands/migrate-status.js +41 -0
  153. package/src/commands/migrate.js +30 -0
  154. package/src/commands/seed.js +47 -0
  155. package/src/commands/start.js +69 -0
  156. package/src/commands/test.js +46 -0
  157. package/src/db/Grammar.js +125 -0
  158. package/src/db/QueryBuilder.js +476 -0
  159. package/src/db/adapters/neon.js +170 -0
  160. package/src/db/adapters/planetscale.js +146 -0
  161. package/src/db/adapters/postgres.js +166 -0
  162. package/src/db/adapters/sqlite.js +125 -0
  163. package/src/db/adapters/turso.js +165 -0
  164. package/src/db/index.js +156 -0
  165. package/src/db/migrator.js +250 -0
  166. package/src/db/seeder.js +124 -0
  167. package/src/index.js +12 -0
  168. package/src/scaffolding/index.js +152 -0
  169. package/src/server/body-parser.js +159 -0
  170. package/src/server/cors.js +63 -0
  171. package/src/server/default-entry.js +13 -0
  172. package/src/server/http.js +221 -0
  173. package/src/server/index.js +168 -0
  174. package/src/server/loader.js +128 -0
  175. package/src/server/router.js +281 -0
  176. package/src/server/static.js +139 -0
  177. package/src/validation/index.js +436 -0
  178. package/src/vite/config.js +49 -0
  179. package/stubs/controller.stub +48 -0
  180. package/stubs/middleware-auth.stub +29 -0
  181. package/stubs/middleware.stub +9 -0
  182. package/stubs/migration.stub +17 -0
  183. package/stubs/model.stub +77 -0
  184. package/stubs/route.stub +13 -0
  185. package/stubs/seed.stub +16 -0
  186. 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
+ }