@dangao/bun-server 3.1.0 → 3.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.
@@ -4,6 +4,12 @@ import type {
4
4
  DatabaseType,
5
5
  } from './types';
6
6
  import { getRuntime } from '../platform/runtime';
7
+ import {
8
+ closeViaDriver,
9
+ createMysqlConnection,
10
+ createPostgresConnection,
11
+ resolveDriver,
12
+ } from './driver';
7
13
 
8
14
  /**
9
15
  * 连接池中的连接项
@@ -215,7 +221,7 @@ export class ConnectionPool {
215
221
  }
216
222
 
217
223
  /**
218
- * 创建 PostgreSQL 连接(自动感知运行时)
224
+ * 创建 PostgreSQL 连接(按所选 driver 分流,与运行时平台解耦)
219
225
  */
220
226
  private async createPostgresConnection(
221
227
  config: {
@@ -227,18 +233,16 @@ export class ConnectionPool {
227
233
  ssl?: boolean;
228
234
  },
229
235
  ): Promise<unknown> {
230
- const url = `postgres://${config.user}:${config.password}@${config.host}:${config.port}/${config.database}`;
231
- if (getRuntime().engine === 'bun') {
232
- const { SQL } = await import('bun');
233
- return new SQL(url, { max: 1, tls: config.ssl ?? false });
234
- }
235
- // Node.js:使用 postgres
236
- const postgres = require('postgres') as typeof import('postgres');
237
- return postgres(url, { max: 1, ssl: config.ssl ? 'require' : false });
236
+ const driver = resolveDriver(
237
+ 'postgres',
238
+ this.config.type === 'postgres' ? this.config.driver : undefined,
239
+ getRuntime().engine,
240
+ );
241
+ return await createPostgresConnection(config, driver);
238
242
  }
239
243
 
240
244
  /**
241
- * 创建 MySQL 连接(自动感知运行时)
245
+ * 创建 MySQL 连接(按所选 driver 分流,与运行时平台解耦)
242
246
  */
243
247
  private async createMysqlConnection(
244
248
  config: {
@@ -249,21 +253,12 @@ export class ConnectionPool {
249
253
  password: string;
250
254
  },
251
255
  ): Promise<unknown> {
252
- if (getRuntime().engine === 'bun') {
253
- const url = `mysql://${config.user}:${config.password}@${config.host}:${config.port}/${config.database}`;
254
- const { SQL } = await import('bun');
255
- return new SQL(url, { max: 1 });
256
- }
257
- // Node.js:使用 mysql2
258
- const mysql2 = require('mysql2/promise') as typeof import('mysql2/promise');
259
- const conn = await mysql2.createConnection({
260
- host: config.host,
261
- port: config.port,
262
- database: config.database,
263
- user: config.user,
264
- password: config.password,
265
- });
266
- return conn;
256
+ const driver = resolveDriver(
257
+ 'mysql',
258
+ this.config.type === 'mysql' ? this.config.driver : undefined,
259
+ getRuntime().engine,
260
+ );
261
+ return await createMysqlConnection(config, driver);
267
262
  }
268
263
 
269
264
  /**
@@ -295,35 +290,17 @@ export class ConnectionPool {
295
290
  }
296
291
 
297
292
  /**
298
- * 关闭 PostgreSQL 连接(Bun.SQL 会自动管理连接)
293
+ * 关闭 PostgreSQL 连接(按连接 driver tag 分流:bun-sql → close(),postgres → end())
299
294
  */
300
- private async closePostgresConnection(_connection: unknown): Promise<void> {
301
- // Bun.SQL 会自动管理连接池,这里不需要手动关闭
302
- // 如果需要强制关闭,可以调用 connection.close()
303
- if (
304
- _connection &&
305
- typeof _connection === 'object' &&
306
- 'close' in _connection &&
307
- typeof (_connection as { close: () => void }).close === 'function'
308
- ) {
309
- (_connection as { close: () => void }).close();
310
- }
295
+ private async closePostgresConnection(connection: unknown): Promise<void> {
296
+ await closeViaDriver(connection);
311
297
  }
312
298
 
313
299
  /**
314
- * 关闭 MySQL 连接(Bun.SQL 会自动管理连接)
300
+ * 关闭 MySQL 连接(按连接 driver tag 分流:bun-sql → close(),mysql2 → end())
315
301
  */
316
- private async closeMysqlConnection(_connection: unknown): Promise<void> {
317
- // Bun.SQL 会自动管理连接池,这里不需要手动关闭
318
- // 如果需要强制关闭,可以调用 connection.close()
319
- if (
320
- _connection &&
321
- typeof _connection === 'object' &&
322
- 'close' in _connection &&
323
- typeof (_connection as { close: () => void }).close === 'function'
324
- ) {
325
- (_connection as { close: () => void }).close();
326
- }
302
+ private async closeMysqlConnection(connection: unknown): Promise<void> {
303
+ await closeViaDriver(connection);
327
304
  }
328
305
 
329
306
  /**
@@ -76,6 +76,7 @@ export class DatabaseModule {
76
76
  type: db.type,
77
77
  url,
78
78
  pool: options.bunSqlPool,
79
+ driver: db.driver ?? options.driver,
79
80
  },
80
81
  },
81
82
  ];
@@ -103,6 +104,7 @@ export class DatabaseModule {
103
104
  type: options.type,
104
105
  url: options.url,
105
106
  pool: options.bunSqlPool,
107
+ driver: options.driver,
106
108
  },
107
109
  },
108
110
  ];
@@ -119,6 +121,7 @@ export class DatabaseModule {
119
121
  type: options.type,
120
122
  url,
121
123
  pool: options.bunSqlPool,
124
+ driver: options.driver,
122
125
  },
123
126
  },
124
127
  ];
@@ -245,6 +248,9 @@ export class DatabaseModule {
245
248
  user: options.username ?? 'root',
246
249
  password: options.password ?? '',
247
250
  },
251
+ driver:
252
+ (normalized[0]?.config as BunSQLConfig | undefined)?.driver ??
253
+ options.driver,
248
254
  },
249
255
  };
250
256
 
@@ -1,5 +1,6 @@
1
1
  import type { BunSQLManager } from './sql-manager';
2
2
  import { getCurrentSession } from './database-context';
3
+ import { templateQueryViaDriver } from './driver';
3
4
  import type { TransactionManager } from './orm/transaction-manager';
4
5
 
5
6
  type DbResult = Promise<unknown>;
@@ -49,10 +50,10 @@ const baseDb = async (
49
50
  if (tenantId) {
50
51
  const tenantSql = sqlManager.get(tenantId);
51
52
  if (tenantSql) {
52
- return await (tenantSql as any)(strings, ...values);
53
+ return await templateQueryViaDriver(tenantSql, strings, values);
53
54
  }
54
55
  }
55
- return await (sqlManager.getDefault() as any)(strings, ...values);
56
+ return await templateQueryViaDriver(sqlManager.getDefault(), strings, values);
56
57
  };
57
58
 
58
59
  function createDb(tenantId?: string): DbProxy {
@@ -0,0 +1,368 @@
1
+ import type { DatabaseDriver, MysqlConfig, PostgresConfig } from './types';
2
+
3
+ /**
4
+ * 已解析的具体驱动类型(连接级别 tag)
5
+ * - `'bun-sql'`:Bun 内建 `Bun.SQL`
6
+ * - `'mysql2'`:纯 JS `mysql2` 驱动
7
+ * - `'postgres'`:纯 JS `postgres` 驱动
8
+ */
9
+ export type ResolvedDriver = 'bun-sql' | 'mysql2' | 'postgres';
10
+
11
+ /**
12
+ * 运行时引擎(来自 platform/runtime)
13
+ */
14
+ export type RuntimeEngine = 'bun' | 'node';
15
+
16
+ /**
17
+ * 连接级别的驱动 tag,附加在连接对象/函数上,
18
+ * 让查询执行 / 健康检查 / 关闭逻辑统一按所选 driver 分流,
19
+ * 而不是按 `getRuntime().engine` 分流。
20
+ */
21
+ const DRIVER_TAG: unique symbol = Symbol.for('@dangao/bun-server:database:driver');
22
+
23
+ /**
24
+ * 将驱动选项(含 'auto')解析为具体驱动。
25
+ *
26
+ * - `'auto'`:Bun → `bun-sql`,Node → `mysql2`(MySQL)/ `postgres`(PostgreSQL),保持历史行为。
27
+ * - `'bun-sql'`:强制 `Bun.SQL`,仅 Bun 合法,Node 抛清晰错误。
28
+ * - `'mysql2'`:强制 `mysql2`,仅 `type: 'mysql'` 合法。
29
+ * - `'postgres'`:强制 `postgres`,仅 `type: 'postgres'` 合法。
30
+ *
31
+ * @param dbType 数据库类型(postgres / mysql)
32
+ * @param option 用户配置的 driver(默认 'auto')
33
+ * @param engine 当前运行时引擎
34
+ */
35
+ export function resolveDriver(
36
+ dbType: 'postgres' | 'mysql',
37
+ option: DatabaseDriver | undefined,
38
+ engine: RuntimeEngine,
39
+ ): ResolvedDriver {
40
+ const driver = option ?? 'auto';
41
+
42
+ switch (driver) {
43
+ case 'auto':
44
+ if (engine === 'bun') {
45
+ return 'bun-sql';
46
+ }
47
+ return dbType === 'mysql' ? 'mysql2' : 'postgres';
48
+
49
+ case 'bun-sql':
50
+ if (engine !== 'bun') {
51
+ throw new Error(
52
+ `[bun-server] driver 'bun-sql' requires the Bun runtime, but the current platform engine is '${engine}'. ` +
53
+ `Use driver 'auto' / 'mysql2' / 'postgres', or run on Bun.`,
54
+ );
55
+ }
56
+ return 'bun-sql';
57
+
58
+ case 'mysql2':
59
+ if (dbType !== 'mysql') {
60
+ throw new Error(
61
+ `[bun-server] driver 'mysql2' is only valid for type 'mysql', but got type '${dbType}'.`,
62
+ );
63
+ }
64
+ return 'mysql2';
65
+
66
+ case 'postgres':
67
+ if (dbType !== 'postgres') {
68
+ throw new Error(
69
+ `[bun-server] driver 'postgres' is only valid for type 'postgres', but got type '${dbType}'.`,
70
+ );
71
+ }
72
+ return 'postgres';
73
+
74
+ default:
75
+ throw new Error(`[bun-server] unknown driver '${String(driver)}'.`);
76
+ }
77
+ }
78
+
79
+ /**
80
+ * 给连接对象/函数打上驱动 tag(返回同一引用,保证池内 identity 比较不被破坏)。
81
+ */
82
+ export function tagConnection<T>(connection: T, driver: ResolvedDriver): T {
83
+ if (connection && (typeof connection === 'object' || typeof connection === 'function')) {
84
+ try {
85
+ Object.defineProperty(connection as object, DRIVER_TAG, {
86
+ value: driver,
87
+ enumerable: false,
88
+ configurable: true,
89
+ writable: true,
90
+ });
91
+ } catch {
92
+ // 某些 frozen 连接无法附加 tag,回退到 heuristic 推断
93
+ }
94
+ }
95
+ return connection;
96
+ }
97
+
98
+ /**
99
+ * 读取连接 tag;若无显式 tag 则按结构启发式推断(向后兼容未打 tag 的连接,
100
+ * 例如 `sql.reserve()` 返回的 reserved 连接)。
101
+ */
102
+ export function getConnectionDriver(connection: unknown): ResolvedDriver | undefined {
103
+ if (!connection) {
104
+ return undefined;
105
+ }
106
+ if (typeof connection === 'object' || typeof connection === 'function') {
107
+ const tagged = (connection as Record<symbol, unknown>)[DRIVER_TAG];
108
+ if (tagged === 'bun-sql' || tagged === 'mysql2' || tagged === 'postgres') {
109
+ return tagged;
110
+ }
111
+ }
112
+ // heuristic 回退:可作为模板字符串调用的(Bun.SQL / postgres-js / reserved)按模板路径处理
113
+ if (typeof connection === 'function') {
114
+ return 'bun-sql';
115
+ }
116
+ return undefined;
117
+ }
118
+
119
+ /**
120
+ * 创建 PostgreSQL 连接(按所选 driver 分流,并打 tag)。
121
+ */
122
+ export async function createPostgresConnection(
123
+ config: PostgresConfig,
124
+ driver: ResolvedDriver,
125
+ ): Promise<unknown> {
126
+ const url = `postgres://${config.user}:${config.password}@${config.host}:${config.port}/${config.database}`;
127
+
128
+ if (driver === 'bun-sql') {
129
+ const { SQL } = await import('bun');
130
+ return tagConnection(
131
+ new SQL({
132
+ adapter: 'postgres',
133
+ hostname: config.host,
134
+ port: config.port,
135
+ username: config.user,
136
+ password: config.password,
137
+ database: config.database,
138
+ max: 1,
139
+ tls: config.ssl ?? false,
140
+ } as unknown as string),
141
+ 'bun-sql',
142
+ );
143
+ }
144
+
145
+ if (driver === 'postgres') {
146
+ const postgres = loadPostgres();
147
+ return tagConnection(
148
+ postgres(url, { max: 1, ssl: config.ssl ? 'require' : false }),
149
+ 'postgres',
150
+ );
151
+ }
152
+
153
+ throw new Error(`[bun-server] driver '${driver}' cannot create a postgres connection.`);
154
+ }
155
+
156
+ /**
157
+ * 创建 MySQL 连接(按所选 driver 分流,并打 tag)。
158
+ *
159
+ * Bun.SQL 走 options-object 形式而非连接字符串,绕开 oven-sh/bun#26648
160
+ * (MySQL 连接串被误判为 postgres)。
161
+ */
162
+ export async function createMysqlConnection(
163
+ config: MysqlConfig & { ssl?: boolean },
164
+ driver: ResolvedDriver,
165
+ ): Promise<unknown> {
166
+ if (driver === 'bun-sql') {
167
+ const { SQL } = await import('bun');
168
+ return tagConnection(
169
+ new SQL({
170
+ adapter: 'mysql',
171
+ hostname: config.host,
172
+ port: config.port,
173
+ username: config.user,
174
+ password: config.password,
175
+ database: config.database,
176
+ max: 1,
177
+ ssl: config.ssl ?? false,
178
+ } as unknown as string),
179
+ 'bun-sql',
180
+ );
181
+ }
182
+
183
+ if (driver === 'mysql2') {
184
+ const mysql2 = loadMysql2();
185
+ const conn = await mysql2.createConnection({
186
+ host: config.host,
187
+ port: config.port,
188
+ database: config.database,
189
+ user: config.user,
190
+ password: config.password,
191
+ });
192
+ return tagConnection(conn, 'mysql2');
193
+ }
194
+
195
+ throw new Error(`[bun-server] driver '${driver}' cannot create a mysql connection.`);
196
+ }
197
+
198
+ /**
199
+ * 执行参数化查询(按连接 tag 分流),统一返回行数组。
200
+ *
201
+ * - `bun-sql`:通过模板字符串调用,参数走 Bun.SQL 的 values 通道。
202
+ * - `mysql2`:`conn.query(sql, params)`,返回 `[rows, fields]`,取 rows。
203
+ * - `postgres`:`sql.unsafe(sql, params)`,直接返回行数组。
204
+ */
205
+ export async function queryViaDriver<T = unknown>(
206
+ connection: unknown,
207
+ sql: string,
208
+ params?: unknown[],
209
+ ): Promise<T[]> {
210
+ const driver = getConnectionDriver(connection);
211
+
212
+ if (driver === 'mysql2') {
213
+ const conn = connection as {
214
+ query: (sql: string, params?: unknown[]) => Promise<[unknown, unknown]>;
215
+ };
216
+ const [rows] = await conn.query(sql, params ?? []);
217
+ return (rows ?? []) as T[];
218
+ }
219
+
220
+ if (driver === 'postgres') {
221
+ const conn = connection as {
222
+ unsafe: (sql: string, params?: unknown[]) => Promise<unknown[]>;
223
+ };
224
+ const rows = await conn.unsafe(sql, params ?? []);
225
+ return (rows ?? []) as T[];
226
+ }
227
+
228
+ // bun-sql 及 heuristic 回退:模板字符串路径
229
+ if (typeof connection === 'function') {
230
+ const { strings, values } = buildTemplateFromSql(sql, params);
231
+ const template = Object.assign(strings.slice(), {
232
+ raw: strings.slice(),
233
+ }) as unknown as TemplateStringsArray;
234
+ const result = await (
235
+ connection as (
236
+ template: TemplateStringsArray,
237
+ ...values: unknown[]
238
+ ) => Promise<Array<Record<string, unknown>>>
239
+ )(template, ...values);
240
+ return result as T[];
241
+ }
242
+
243
+ // 对象但带 query 方法(兜底)
244
+ if (
245
+ connection &&
246
+ typeof connection === 'object' &&
247
+ 'query' in connection &&
248
+ typeof (connection as { query: unknown }).query === 'function'
249
+ ) {
250
+ const result = await (
251
+ connection as {
252
+ query: (sql: string, params?: unknown[]) => Promise<unknown>;
253
+ }
254
+ ).query(sql, params ?? []);
255
+ return (Array.isArray(result) ? result[0] : result) as T[];
256
+ }
257
+
258
+ throw new Error('[bun-server] invalid SQL connection for query.');
259
+ }
260
+
261
+ /**
262
+ * 通过 tagged template 执行查询(供 db proxy 使用),按连接 tag 分流。
263
+ *
264
+ * - `mysql2`:把模板片段拼成 `?` 占位 SQL,走 `conn.query(sql, values)`。
265
+ * - 其余(bun-sql / postgres / reserved):连接本身即可作为 tagged template 调用。
266
+ */
267
+ export async function templateQueryViaDriver(
268
+ connection: unknown,
269
+ strings: TemplateStringsArray,
270
+ values: unknown[],
271
+ ): Promise<unknown> {
272
+ const driver = getConnectionDriver(connection);
273
+
274
+ if (driver === 'mysql2') {
275
+ const sql = strings.join('?');
276
+ const conn = connection as {
277
+ query: (sql: string, params?: unknown[]) => Promise<[unknown, unknown]>;
278
+ };
279
+ const [rows] = await conn.query(sql, values);
280
+ return rows;
281
+ }
282
+
283
+ return await (
284
+ connection as (
285
+ strings: TemplateStringsArray,
286
+ ...values: unknown[]
287
+ ) => Promise<unknown>
288
+ )(strings, ...values);
289
+ }
290
+
291
+ /**
292
+ * 健康检查(按连接 tag 分流)。
293
+ */
294
+ export async function healthCheckViaDriver(connection: unknown): Promise<boolean> {
295
+ try {
296
+ const rows = await queryViaDriver(connection, 'SELECT 1');
297
+ return Array.isArray(rows);
298
+ } catch {
299
+ return false;
300
+ }
301
+ }
302
+
303
+ /**
304
+ * 关闭连接(按连接 tag 分流)。
305
+ *
306
+ * - `bun-sql`:`.close()`
307
+ * - `mysql2` / `postgres`:`.end()`
308
+ * - 兜底:可用的 `.close()` 或 `.end()`
309
+ */
310
+ export async function closeViaDriver(connection: unknown): Promise<void> {
311
+ if (!connection || (typeof connection !== 'object' && typeof connection !== 'function')) {
312
+ return;
313
+ }
314
+
315
+ const driver = getConnectionDriver(connection);
316
+ const conn = connection as { close?: () => unknown; end?: () => unknown };
317
+
318
+ if (driver === 'mysql2' || driver === 'postgres') {
319
+ if (typeof conn.end === 'function') {
320
+ await conn.end();
321
+ return;
322
+ }
323
+ }
324
+
325
+ if (driver === 'bun-sql') {
326
+ if (typeof conn.close === 'function') {
327
+ await conn.close();
328
+ return;
329
+ }
330
+ }
331
+
332
+ // 兜底
333
+ if (typeof conn.close === 'function') {
334
+ await conn.close();
335
+ } else if (typeof conn.end === 'function') {
336
+ await conn.end();
337
+ }
338
+ }
339
+
340
+ /**
341
+ * 将 `SELECT ... ?` 占位符 SQL + 参数转换为模板字符串片段,
342
+ * 让参数通过 Bun.SQL 的 values 通道注入。
343
+ */
344
+ export function buildTemplateFromSql(
345
+ sql: string,
346
+ params?: unknown[],
347
+ ): { strings: string[]; values: unknown[] } {
348
+ if (!params || params.length === 0) {
349
+ return { strings: [sql], values: [] };
350
+ }
351
+
352
+ const strings = sql.split('?');
353
+ if (strings.length !== params.length + 1) {
354
+ throw new Error('SQL placeholders count does not match parameters count');
355
+ }
356
+
357
+ return { strings, values: params };
358
+ }
359
+
360
+ function loadMysql2(): typeof import('mysql2/promise') {
361
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
362
+ return require('mysql2/promise') as typeof import('mysql2/promise');
363
+ }
364
+
365
+ function loadPostgres(): typeof import('postgres') {
366
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
367
+ return require('postgres') as typeof import('postgres');
368
+ }
@@ -5,6 +5,13 @@ export { ConnectionPool } from './connection-pool';
5
5
  export { DatabaseHealthIndicator } from './health-indicator';
6
6
  export { DatabaseExtension } from './database-extension';
7
7
  export { BunSQLManager } from './sql-manager';
8
+ export {
9
+ resolveDriver,
10
+ tagConnection,
11
+ getConnectionDriver,
12
+ type ResolvedDriver,
13
+ type RuntimeEngine,
14
+ } from './driver';
8
15
  export { SqliteAdapter, SqliteManager, Semaphore } from './sqlite-adapter';
9
16
  export {
10
17
  db,
@@ -37,6 +44,7 @@ export {
37
44
  type ConnectionInfo,
38
45
  type ConnectionPoolOptions,
39
46
  type DatabaseConfig,
47
+ type DatabaseDriver,
40
48
  type DatabaseModuleOptions,
41
49
  type DatabaseType,
42
50
  type MysqlConfig,
@@ -2,6 +2,7 @@ import { Injectable } from '../di/decorators';
2
2
 
3
3
  import { DatabaseConnectionManager } from './connection-manager';
4
4
  import { getCurrentSession } from './database-context';
5
+ import { queryViaDriver } from './driver';
5
6
  import type {
6
7
  ConnectionInfo,
7
8
  DatabaseConfig,
@@ -131,7 +132,8 @@ export class DatabaseService {
131
132
  if (dbType === 'sqlite') {
132
133
  return this.querySqlite(connection, sql, params);
133
134
  } else if (dbType === 'postgres' || dbType === 'mysql') {
134
- return this.queryBunSQL(connection, sql, params);
135
+ // 按连接 driver tag 分流(bun-sql 模板 / mysql2 / postgres)
136
+ return queryViaDriver(connection, sql, params);
135
137
  }
136
138
 
137
139
  throw new Error(`Query not supported for database type: ${dbType}`);
@@ -184,77 +186,4 @@ export class DatabaseService {
184
186
 
185
187
  throw new Error('Invalid SQLite connection');
186
188
  }
187
-
188
- /**
189
- * Bun.SQL 查询实现(PostgreSQL/MySQL)
190
- * 通过模板字符串调用 Bun.SQL,确保参数走 Bun.SQL 转义逻辑
191
- */
192
- private async queryBunSQL<T = unknown>(
193
- connection: unknown,
194
- sql: string,
195
- params?: unknown[],
196
- ): Promise<T[]> {
197
- // Bun.SQL 对象可以作为函数调用(模板字符串)
198
- if (connection && typeof connection === 'function') {
199
- try {
200
- const { strings, values } = this.buildTemplateFromSql(sql, params);
201
- const template = Object.assign(strings, {
202
- raw: strings,
203
- }) as unknown as TemplateStringsArray;
204
- const result = await (connection as (
205
- template: TemplateStringsArray,
206
- ...values: unknown[]
207
- ) => Promise<Array<Record<string, unknown>>>)(template, ...values);
208
- return result as T[];
209
- } catch (error) {
210
- const errorMessage =
211
- error instanceof Error ? error.message : String(error);
212
- // 如果模板字符串方式失败,保留原始错误,便于排查参数/SQL 构造问题
213
- throw new Error(
214
- `Bun.SQL parameterized queries are not fully supported. Consider using template string queries. Original error: ${errorMessage}`,
215
- );
216
- }
217
- }
218
-
219
- // 尝试使用 query 方法(如果存在)
220
- if (
221
- connection &&
222
- typeof connection === 'object' &&
223
- 'query' in connection &&
224
- typeof connection.query === 'function'
225
- ) {
226
- const db = connection as {
227
- query: (
228
- sql: string,
229
- ...params: unknown[]
230
- ) => Promise<Array<Record<string, unknown>>>;
231
- };
232
- const result = await db.query(sql, ...(params ?? []));
233
- return result as T[];
234
- }
235
-
236
- throw new Error('Invalid Bun.SQL connection');
237
- }
238
-
239
- /**
240
- * 将 SQL 与 ? 占位符参数转换为模板字符串片段
241
- * 让参数通过 Bun.SQL 的 values 通道注入,避免手工拼接 SQL
242
- */
243
- private buildTemplateFromSql(
244
- sql: string,
245
- params?: unknown[],
246
- ): { strings: string[]; values: unknown[] } {
247
- if (!params || params.length === 0) {
248
- return { strings: [sql], values: [] };
249
- }
250
-
251
- const strings = sql.split('?');
252
- if (strings.length !== params.length + 1) {
253
- throw new Error(
254
- 'SQL placeholders count does not match parameters count',
255
- );
256
- }
257
-
258
- return { strings, values: params };
259
- }
260
189
  }