bunigniter 0.2.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 (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +229 -0
  3. package/dist/LICENSE +21 -0
  4. package/dist/README.md +229 -0
  5. package/dist/base/controller.ts +324 -0
  6. package/dist/base/index.ts +5 -0
  7. package/dist/base/service.ts +21 -0
  8. package/dist/cli/index.ts +318 -0
  9. package/dist/cli/list-routes.ts +72 -0
  10. package/dist/cli/repl.ts +461 -0
  11. package/dist/cli/templates.ts +283 -0
  12. package/dist/client/index.ts +159 -0
  13. package/dist/db/drizzle.ts +550 -0
  14. package/dist/db/validators.ts +229 -0
  15. package/dist/edge-builder.ts +120 -0
  16. package/dist/edge.ts +69 -0
  17. package/dist/helpers/cache.ts +173 -0
  18. package/dist/helpers/cors.ts +103 -0
  19. package/dist/helpers/csrf.ts +155 -0
  20. package/dist/helpers/debug.ts +158 -0
  21. package/dist/helpers/env.ts +147 -0
  22. package/dist/helpers/handler.ts +158 -0
  23. package/dist/helpers/http.ts +194 -0
  24. package/dist/helpers/image.ts +217 -0
  25. package/dist/helpers/jwt.ts +147 -0
  26. package/dist/helpers/logger.ts +96 -0
  27. package/dist/helpers/mail.ts +272 -0
  28. package/dist/helpers/middleware-loader.ts +116 -0
  29. package/dist/helpers/middleware.ts +57 -0
  30. package/dist/helpers/modules.ts +115 -0
  31. package/dist/helpers/openapi.ts +140 -0
  32. package/dist/helpers/pagination.ts +159 -0
  33. package/dist/helpers/queue.ts +186 -0
  34. package/dist/helpers/request-context.ts +13 -0
  35. package/dist/helpers/request.ts +376 -0
  36. package/dist/helpers/schedule.ts +173 -0
  37. package/dist/helpers/session-middleware.ts +89 -0
  38. package/dist/helpers/session.ts +286 -0
  39. package/dist/helpers/sse.ts +90 -0
  40. package/dist/helpers/throttle.ts +156 -0
  41. package/dist/helpers/upload.ts +417 -0
  42. package/dist/helpers/validator.ts +287 -0
  43. package/dist/helpers/ws.ts +123 -0
  44. package/dist/index.ts +221 -0
  45. package/dist/package.json +70 -0
  46. package/dist/router/file-router.ts +541 -0
  47. package/dist/router/server-router.ts +103 -0
  48. package/dist/view/page.ts +96 -0
  49. package/dist/view/renderer.tsx +390 -0
  50. package/dist/view/view-response.ts +10 -0
  51. package/package.json +70 -0
@@ -0,0 +1,550 @@
1
+ /**
2
+ * Drizzle wrapper — CodeIgniter-style database interface.
3
+ *
4
+ * Provides `db.query('SQL', [params])` for raw SQL with parameter binding,
5
+ * plus full Drizzle ORM access for type-safe queries.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * // Raw SQL (CodeIgniter style)
10
+ * const users = await db.query('SELECT * FROM users WHERE id = ?', [1])
11
+ *
12
+ * // Drizzle ORM (type-safe)
13
+ * const rows = await db.select().from(users).all()
14
+ * ```
15
+ */
16
+ import { type ExtractTablesWithRelations } from 'drizzle-orm'
17
+ import type { PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core'
18
+ import type { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite'
19
+
20
+ /** Dialect types supported. */
21
+ export type Dialect = 'postgres' | 'mysql' | 'sqlite' | 'bun-sqlite' | 'd1'
22
+
23
+ /** Database configuration. */
24
+ export interface DbConfig {
25
+ dialect: Dialect
26
+ connection: Record<string, any>
27
+ logging?: boolean
28
+ autoMigrate?: boolean
29
+ migrationsFolder?: string
30
+ }
31
+
32
+ /** Query result from raw SQL. */
33
+ export interface QueryResult<T = any> {
34
+ rows: T[]
35
+ affectedRows: number
36
+ insertId?: number | string
37
+ }
38
+
39
+ /**
40
+ * Thin wrapper around Drizzle ORM.
41
+ *
42
+ * Wraps a Drizzle database client and exposes:
43
+ * - `query(sql, params)` — CodeIgniter-style raw SQL
44
+ * - `select()` / `insert(table)` / `update(table)` / `delete(table)` — Drizzle builders
45
+ * - `transaction(fn)` — ACID transactions
46
+ */
47
+ export class DbClient {
48
+ private client: any
49
+ private dialect: Dialect
50
+ private rawExecutor: RawExecutor | null = null
51
+ private opened = false
52
+ private config: DbConfig
53
+
54
+ constructor(config: DbConfig) {
55
+ this.config = config
56
+ this.dialect = config.dialect
57
+ }
58
+
59
+ /** Initialize the database connection. */
60
+ async open(): Promise<void> {
61
+ if (this.opened) return
62
+ const drv = await resolveDriver(this.dialect, this.config)
63
+ this.client = drv.db
64
+ this.rawExecutor = drv.rawExecutor ?? null
65
+ this.opened = true
66
+ }
67
+
68
+ /** The raw Drizzle client for type-safe queries. */
69
+ get drizzle(): any {
70
+ this.assertOpen()
71
+ return this.client
72
+ }
73
+
74
+ /** Database dialect name. */
75
+ get dialectName(): Dialect {
76
+ return this.dialect
77
+ }
78
+
79
+ // ─── Raw SQL (CodeIgniter style) ─────────────────────────────
80
+
81
+ /**
82
+ * Execute a parameterized SQL query.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * const users = await db.query('SELECT * FROM users WHERE id = ?', [1])
87
+ * const result = await db.query('INSERT INTO users (name) VALUES (?)', ['Alice'])
88
+ * ```
89
+ */
90
+ /**
91
+ * Tagged template SQL — Drizzle-style `sql\`...\``.
92
+ * Inline parameters, no need for separate params array.
93
+ *
94
+ * @example
95
+ * ```ts
96
+ * await db.sql\`SELECT * FROM users WHERE id = ${id}\`
97
+ * await db.sql\`UPDATE posts SET title = ${title} WHERE id = ${id}\`
98
+ * ```
99
+ */
100
+ async sql(strings: TemplateStringsArray, ...values: unknown[]): Promise<QueryResult<any>> {
101
+ let s = strings[0] ?? ''
102
+ for (let i = 1; i < strings.length; i++) s += '?' + strings[i]
103
+ return this.query(s, values)
104
+ }
105
+
106
+ // ─── CodeIgniter-style Active Record ─────────────────────────
107
+
108
+ /**
109
+ * Insert a record.
110
+ *
111
+ * @example await db.insert('users', { name: 'Alice', email: 'a@b.com' })
112
+ */
113
+ async insert(table: string, data: Record<string, any>): Promise<QueryResult> {
114
+ const keys = Object.keys(data)
115
+ const vals = Object.values(data)
116
+ return this.query(
117
+ `INSERT INTO ${table} (${keys.join(', ')}) VALUES (${keys.map(() => '?').join(', ')})`,
118
+ vals
119
+ )
120
+ }
121
+
122
+ /**
123
+ * Update records.
124
+ *
125
+ * @example
126
+ * await db.update('users', { name: 'Bob' }, { id: 1 })
127
+ * await db.update('posts', { views: 0 }, { views: ['<', 0] }) // views < 0
128
+ */
129
+ async update(table: string, data: Record<string, any>, where: Record<string, any>): Promise<QueryResult> {
130
+ const setCols = Object.keys(data).map(k => `${k} = ?`)
131
+ const { clause, vals } = buildWhere(where)
132
+ return this.query(`UPDATE ${table} SET ${setCols.join(', ')} WHERE ${clause}`, [...Object.values(data), ...vals])
133
+ }
134
+
135
+ /**
136
+ * Delete records.
137
+ *
138
+ * @example await db.delete('users', { id: 1 })
139
+ * @example await db.delete('posts', { createdAt: ['<', '2024-01-01'] })
140
+ */
141
+ async delete(table: string, where: Record<string, any>): Promise<QueryResult> {
142
+ const { clause, vals } = buildWhere(where)
143
+ return this.query(`DELETE FROM ${table} WHERE ${clause}`, vals)
144
+ }
145
+
146
+ /**
147
+ * Select records with ordering and limits.
148
+ *
149
+ * @example
150
+ * const all = await db.get('users')
151
+ * const user = await db.get('users', { id: 1 })
152
+ * const recent = await db.get('posts', { status: 'published' }, { orderBy: 'created_at DESC', limit: 10 })
153
+ * const admins = await db.get('users', { role: 'admin', age: ['>=', 18] })
154
+ */
155
+ async get<T = any>(table: string, where?: Record<string, any> | null, options?: {
156
+ select?: string
157
+ orderBy?: string
158
+ limit?: number
159
+ offset?: number
160
+ groupBy?: string
161
+ having?: Record<string, any>
162
+ }): Promise<T[]> {
163
+ let sql = `SELECT ${options?.select ?? '*'} FROM ${table}`
164
+ const params: unknown[] = []
165
+
166
+ if (where && Object.keys(where).length > 0) {
167
+ const r = buildWhere(where)
168
+ sql += ` WHERE ${r.clause}`
169
+ params.push(...r.vals)
170
+ }
171
+ if (options?.groupBy) sql += ` GROUP BY ${options.groupBy}`
172
+ if (options?.having && Object.keys(options.having).length > 0) {
173
+ const h = buildWhere(options.having)
174
+ sql += ` HAVING ${h.clause}`
175
+ params.push(...h.vals)
176
+ }
177
+ if (options?.orderBy) sql += ` ORDER BY ${options.orderBy}`
178
+ if (options?.limit) { sql += ' LIMIT ?'; params.push(options.limit) }
179
+ if (options?.offset) { sql += ' OFFSET ?'; params.push(options.offset) }
180
+
181
+ const result = await this.query<T>(sql, params)
182
+ return result.rows
183
+ }
184
+
185
+ /**
186
+ * SELECT with JOIN — CodeIgniter-style.
187
+ *
188
+ * @example
189
+ * await db.getJoin('posts p', [['users u', 'u.id = p.user_id']], { where: { 'p.status': 'published' } })
190
+ * await db.getJoin('orders o', [
191
+ * ['users u', 'u.id = o.user_id'],
192
+ * ['order_items oi', 'oi.order_id = o.id', 'left'],
193
+ * ], { orderBy: 'o.created_at DESC', limit: 10 })
194
+ */
195
+ async getJoin<T = any>(
196
+ from: string,
197
+ joins: Array<[string, string] | [string, string, string]>,
198
+ options?: {
199
+ where?: Record<string, any>
200
+ orderBy?: string
201
+ limit?: number
202
+ offset?: number
203
+ select?: string
204
+ groupBy?: string
205
+ having?: Record<string, any>
206
+ }
207
+ ): Promise<T[]> {
208
+ let sql = `SELECT ${options?.select ?? '*'} FROM ${from}`
209
+ const params: unknown[] = []
210
+
211
+ for (const join of joins) {
212
+ const [table, on, type] = join
213
+ const joinType = (type?.toUpperCase() === 'LEFT' || type?.toUpperCase() === 'RIGHT' || type?.toUpperCase() === 'INNER')
214
+ ? type.toUpperCase()
215
+ : type?.toUpperCase() === 'OUTER' ? 'LEFT' : ''
216
+ sql += joinType ? ` ${joinType} JOIN ${table} ON ${on}` : ` JOIN ${table} ON ${on}`
217
+ }
218
+
219
+ if (options?.where && Object.keys(options.where).length > 0) {
220
+ const r = buildWhere(options.where)
221
+ sql += ` WHERE ${r.clause}`
222
+ params.push(...r.vals)
223
+ }
224
+ if (options?.groupBy) sql += ` GROUP BY ${options.groupBy}`
225
+ if (options?.having && Object.keys(options.having).length > 0) {
226
+ const h = buildWhere(options.having)
227
+ sql += ` HAVING ${h.clause}`
228
+ params.push(...h.vals)
229
+ }
230
+ if (options?.orderBy) sql += ` ORDER BY ${options.orderBy}`
231
+ if (options?.limit) { sql += ' LIMIT ?'; params.push(options.limit) }
232
+ if (options?.offset) { sql += ' OFFSET ?'; params.push(options.offset) }
233
+
234
+ const result = await this.query<T>(sql, params)
235
+ return result.rows
236
+ }
237
+
238
+ /**
239
+ * Count records in a table, optionally with WHERE.
240
+ * @example await db.count('users') // → 42
241
+ * @example await db.count('users', { role: 'admin' }) // → 5
242
+ */
243
+ async count(table: string, where?: Record<string, any>): Promise<number> {
244
+ let sql = `SELECT count(*) as c FROM ${table}`
245
+ const params: unknown[] = []
246
+ if (where && Object.keys(where).length > 0) {
247
+ const r = buildWhere(where)
248
+ sql += ` WHERE ${r.clause}`
249
+ params.push(...r.vals)
250
+ }
251
+ const result = await this.query<{ c: number }>(sql, params)
252
+ return Number(result.rows[0]?.c ?? 0)
253
+ }
254
+
255
+ /**
256
+ * Batch insert multiple rows at once.
257
+ * @example await db.insertBatch('users', [{name:'A'},{name:'B'},{name:'C'}])
258
+ */
259
+ async insertBatch(table: string, data: Record<string, any>[]): Promise<QueryResult> {
260
+ if (data.length === 0) return { rows: [], affectedRows: 0 }
261
+ const keys = Object.keys(data[0])
262
+ const placeholders = data.map(() => `(${keys.map(() => '?').join(',')})`).join(',')
263
+ const vals = data.flatMap(d => keys.map(k => d[k]))
264
+ return this.query(`INSERT INTO ${table} (${keys.join(',')}) VALUES ${placeholders}`, vals)
265
+ }
266
+
267
+ /**
268
+ * Delete all rows from a table (Drizzle truncate).
269
+ * @example await db.truncate('logs')
270
+ */
271
+ async truncate(table: string): Promise<QueryResult> {
272
+ return this.query(`DELETE FROM ${table}`)
273
+ }
274
+
275
+ async query<T = any>(sql: string, params: unknown[] = []): Promise<QueryResult<T>> {
276
+ this.assertOpen()
277
+ if (!this.rawExecutor) {
278
+ throw new Error(`[db] dialect "${this.dialect}" does not support raw queries`)
279
+ }
280
+ const start = performance.now()
281
+ const result = await this.rawExecutor.query(sql, params)
282
+ const duration = performance.now() - start
283
+
284
+ // Log to debug toolbar if active
285
+ try {
286
+ const ctx = getRequestContext()
287
+ if (ctx) {
288
+ const { getStore } = await import('../helpers/debug')
289
+ const data = getStore(ctx)
290
+ data.queries.push({
291
+ id: data.queries.length + 1,
292
+ sql,
293
+ duration: Math.round(duration * 100) / 100,
294
+ rows: result.rows.length,
295
+ params,
296
+ time: new Date().toLocaleTimeString(),
297
+ })
298
+ }
299
+ } catch {}
300
+
301
+ return result
302
+ }
303
+
304
+ /**
305
+ * Execute a query and return the first row.
306
+ */
307
+ async first<T = any>(sql: string, params: unknown[] = []): Promise<T | null> {
308
+ const result = await this.query<T>(sql, params)
309
+ return result.rows[0] ?? null
310
+ }
311
+
312
+ /**
313
+ * Execute a query and return all rows.
314
+ */
315
+ async all<T = any>(sql: string, params: unknown[] = []): Promise<T[]> {
316
+ const result = await this.query<T>(sql, params)
317
+ return result.rows
318
+ }
319
+
320
+ // ─── Pagination ─────────────────────────────────────────────
321
+
322
+ /**
323
+ * Paginated query with automatic count.
324
+ *
325
+ * @example
326
+ * const result = await db.paginate('SELECT * FROM users', { page: 2, perPage: 20 })
327
+ * const result = await db.paginate('SELECT * FROM posts WHERE status = ?', ['published'], { page: 1, perPage: 10 })
328
+ */
329
+ async paginate<T = any>(
330
+ sql: string,
331
+ params: unknown[] = [],
332
+ options: { page?: number; perPage?: number } = {}
333
+ ): Promise<{ data: T[]; total: number; page: number; perPage: number; pages: number }> {
334
+ const page = Math.max(1, options.page ?? 1)
335
+ const perPage = Math.max(1, options.perPage ?? 20)
336
+ const offset = (page - 1) * perPage
337
+
338
+ const countResult = await this.query<{ count: number }>(`SELECT count(*) as count FROM (${sql})`, params)
339
+ const total = Number(countResult.rows[0]?.count ?? 0)
340
+
341
+ const data = await this.all<T>(`${sql} LIMIT ? OFFSET ?`, [...params, perPage, offset])
342
+
343
+ return { data, total, page, perPage, pages: Math.ceil(total / perPage) }
344
+ }
345
+
346
+ // ─── Transactions ────────────────────────────────────────────
347
+
348
+ /**
349
+ * Execute a callback within an ACID transaction.
350
+ *
351
+ * @example
352
+ * ```ts
353
+ * const user = await db.transaction(async (tx) => {
354
+ * const [u] = await tx.query("INSERT INTO users (name) VALUES (?) RETURNING *", ['Bob'])
355
+ * await tx.query("INSERT INTO logs (action) VALUES (?)", ['created_user'])
356
+ * return u
357
+ * })
358
+ * ```
359
+ */
360
+ async transaction<T>(fn: (tx: TxClient) => Promise<T>): Promise<T> {
361
+ this.assertOpen()
362
+ return this.client.transaction(async (tx: any) => {
363
+ const txClient = Object.create(this) as TxClient
364
+ Object.defineProperty(txClient, 'client', { value: tx, writable: false })
365
+ // Build a temporary raw executor from the tx client if needed
366
+ Object.defineProperty(txClient, 'rawExecutor', {
367
+ value: this.rawExecutor,
368
+ writable: false
369
+ })
370
+ return fn(txClient)
371
+ })
372
+ }
373
+
374
+ // ─── Lifecycle ───────────────────────────────────────────────
375
+
376
+ async close(): Promise<void> {
377
+ if (!this.opened) return
378
+ await this.client?.close?.()
379
+ this.opened = false
380
+ }
381
+
382
+ // ─── Internal ────────────────────────────────────────────────
383
+
384
+ private assertOpen(): void {
385
+ if (!this.opened) {
386
+ throw new Error('[db] not opened. Call await db.open() first.')
387
+ }
388
+ }
389
+ }
390
+
391
+ /** Transaction client — same interface as DbClient. */
392
+ export type TxClient = DbClient
393
+
394
+ // ─── Driver resolution ──────────────────────────────────────────
395
+
396
+ interface RawExecutor {
397
+ query<T>(sql: string, params?: unknown[]): Promise<QueryResult<T>>
398
+ }
399
+
400
+ interface DriverResult {
401
+ db: any
402
+ rawExecutor?: RawExecutor
403
+ }
404
+
405
+ /** Build WHERE clause with operator support. */
406
+ function buildWhere(where: Record<string, any>): { clause: string; vals: unknown[] } {
407
+ const parts: string[] = []
408
+ const vals: unknown[] = []
409
+
410
+ for (const [key, val] of Object.entries(where)) {
411
+ if (Array.isArray(val)) {
412
+ const op = val[0] ?? '='
413
+ const v = val[1] ?? val[0]
414
+ if (op.toUpperCase() === 'IN') {
415
+ const items = Array.isArray(v) ? v : [v]
416
+ parts.push(`${key} IN (${items.map(() => '?').join(', ')})`)
417
+ vals.push(...items)
418
+ } else {
419
+ parts.push(`${key} ${op} ?`)
420
+ vals.push(v)
421
+ }
422
+ } else {
423
+ parts.push(`${key} = ?`)
424
+ vals.push(val)
425
+ }
426
+ }
427
+
428
+ return { clause: parts.join(' AND '), vals }
429
+ }
430
+
431
+ async function resolveDriver(dialect: Dialect, config: DbConfig): Promise<DriverResult> {
432
+ const conn = config.connection
433
+
434
+ switch (dialect) {
435
+ case 'postgres': {
436
+ const drizzleMod = await import('drizzle-orm/postgres-js')
437
+ const postgres = await import('postgres')
438
+ const sql = postgres.default({
439
+ host: conn.host ?? 'localhost',
440
+ port: conn.port ?? 5432,
441
+ user: conn.user,
442
+ password: conn.password,
443
+ database: conn.database,
444
+ ...(conn as any)
445
+ })
446
+ const db = drizzleMod.drizzle(sql, { logger: config.logging as any })
447
+ const rawExecutor: RawExecutor = {
448
+ async query<T>(querySql: string, params: unknown[] = []) {
449
+ const rows = await sql.unsafe(querySql, params as any[])
450
+ return {
451
+ rows: rows as T[],
452
+ affectedRows: rows.length
453
+ }
454
+ }
455
+ }
456
+ return { db, rawExecutor }
457
+ }
458
+
459
+ case 'bun-sqlite': {
460
+ const drizzleMod = await import('drizzle-orm/bun-sqlite')
461
+ const { Database } = await import('bun:sqlite')
462
+ const filename = (conn as any).filename ?? 'app.db'
463
+ const sqlite = new Database(filename)
464
+ const db = drizzleMod.drizzle(sqlite, { logger: config.logging as any })
465
+ const rawExecutor: RawExecutor = {
466
+ async query<T>(querySql: string, params: unknown[] = []) {
467
+ const stmt = sqlite.prepare(querySql)
468
+ const isSelect = /^\s*(select|pragma|with)\b/i.test(querySql)
469
+ if (isSelect) {
470
+ const rows = stmt.all(...params)
471
+ return { rows: rows as T[], affectedRows: 0 }
472
+ }
473
+ const r = stmt.run(...params)
474
+ return {
475
+ rows: [],
476
+ affectedRows: Number(r.changes ?? 0),
477
+ insertId: r.lastInsertRowid as number | string
478
+ }
479
+ }
480
+ }
481
+ return { db, rawExecutor }
482
+ }
483
+
484
+ case 'sqlite': {
485
+ const drizzleMod = await import('drizzle-orm/better-sqlite3')
486
+ const sqliteMod = await import('better-sqlite3')
487
+ const Database = (sqliteMod as any).default ?? sqliteMod
488
+ const filename = (conn as any).filename ?? 'app.db'
489
+ const sqlite = new Database(filename)
490
+ const db = drizzleMod.drizzle(sqlite, { logger: config.logging as any })
491
+ const rawExecutor: RawExecutor = {
492
+ async query<T>(querySql: string, params: unknown[] = []) {
493
+ const stmt = sqlite.prepare(querySql)
494
+ const isSelect = /^\s*(select|pragma|with)\b/i.test(querySql)
495
+ if (isSelect) {
496
+ const rows = stmt.all(...params)
497
+ return { rows: rows as T[], affectedRows: 0 }
498
+ }
499
+ const r = stmt.run(...params)
500
+ return {
501
+ rows: [],
502
+ affectedRows: Number(r.changes ?? 0),
503
+ insertId: r.lastInsertRowid as number | string
504
+ }
505
+ }
506
+ }
507
+ return { db, rawExecutor }
508
+ }
509
+
510
+ case 'mysql': {
511
+ const drizzleMod = await import('drizzle-orm/mysql2')
512
+ const mysqlMod = await import('mysql2/promise')
513
+ const pool = (mysqlMod as any).createPool({
514
+ host: conn.host ?? 'localhost',
515
+ port: conn.port ?? 3306,
516
+ user: conn.user,
517
+ password: conn.password,
518
+ database: conn.database,
519
+ ...(conn as any)
520
+ })
521
+ const db = drizzleMod.drizzle(pool, { logger: config.logging as any })
522
+ const rawExecutor: RawExecutor = {
523
+ async query<T>(querySql: string, params: unknown[] = []) {
524
+ const [rows] = await pool.query(querySql, params as any[])
525
+ return { rows: rows as T[], affectedRows: (rows as any[])?.length ?? 0 }
526
+ }
527
+ }
528
+ return { db, rawExecutor }
529
+ }
530
+
531
+ case 'd1': {
532
+ const drizzleMod = await import('drizzle-orm/d1')
533
+ const binding = conn.binding as any
534
+ if (!binding) throw new Error('D1 driver requires connection.binding')
535
+ const db = drizzleMod.drizzle(binding, { logger: config.logging as any })
536
+ const rawExecutor: RawExecutor = {
537
+ async query<T>(querySql: string, params: unknown[] = []) {
538
+ const stmt = binding.prepare(querySql)
539
+ if (params.length > 0) stmt.bind(...params)
540
+ const result = await stmt.run()
541
+ return { rows: (result as any)?.results ?? [], affectedRows: result.meta?.changes ?? 0 }
542
+ }
543
+ }
544
+ return { db, rawExecutor }
545
+ }
546
+
547
+ default:
548
+ throw new Error(`[db] unsupported dialect: ${dialect}`)
549
+ }
550
+ }