bunql 1.0.1-dev.4 → 1.0.1-dev.5

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/src/index.ts CHANGED
@@ -26,75 +26,235 @@ async function executeSql(executor: any, query: string, params?: any[]): Promise
26
26
  return await executor(strings);
27
27
  }
28
28
 
29
- // Build the template literal with parameters
30
- const parts = query.split('?');
31
- if (parts.length - 1 !== params.length) {
32
- throw new Error(`Parameter count mismatch: query has ${parts.length - 1} placeholders but ${params.length} parameters provided`);
33
- }
34
-
35
- // Create a proper template strings array
36
- const strings: any = [...parts];
37
- strings.raw = [...parts];
29
+ // Check if query uses PostgreSQL-style parameters ($1, $2, etc.)
30
+ const postgresPattern = /\$\d+/g;
31
+ const postgresMatches = query.match(postgresPattern);
38
32
 
39
- // Call the executor as a tagged template
40
- return await executor(strings, ...params);
33
+ if (postgresMatches && postgresMatches.length > 0) {
34
+ // Handle PostgreSQL-style parameters
35
+ if (postgresMatches.length !== params.length) {
36
+ throw new Error(`Parameter count mismatch: query has ${postgresMatches.length} placeholders but ${params.length} parameters provided`);
37
+ }
38
+
39
+ // Replace PostgreSQL parameters with ? placeholders
40
+ let processedQuery = query;
41
+ for (let i = 0; i < postgresMatches.length; i++) {
42
+ processedQuery = processedQuery.replace(`$${i + 1}`, '?');
43
+ }
44
+
45
+ // Build the template literal with parameters
46
+ const parts = processedQuery.split('?');
47
+ const strings: any = [...parts];
48
+ strings.raw = [...parts];
49
+
50
+ // Call the executor as a tagged template
51
+ return await executor(strings, ...params);
52
+ } else {
53
+ // Handle SQLite-style parameters (?)
54
+ const parts = query.split('?');
55
+ if (parts.length - 1 !== params.length) {
56
+ throw new Error(`Parameter count mismatch: query has ${parts.length - 1} placeholders but ${params.length} parameters provided`);
57
+ }
58
+
59
+ // Create a proper template strings array
60
+ const strings: any = [...parts];
61
+ strings.raw = [...parts];
62
+
63
+ // Call the executor as a tagged template
64
+ return await executor(strings, ...params);
65
+ }
66
+ }
67
+
68
+ // Connection pool for managing database connections
69
+ class ConnectionPool {
70
+ private static instance: ConnectionPool;
71
+ private connections: Map<string, SQL> = new Map();
72
+ private maxConnections = 10;
73
+
74
+ static getInstance(): ConnectionPool {
75
+ if (!ConnectionPool.instance) {
76
+ ConnectionPool.instance = new ConnectionPool();
77
+ }
78
+ return ConnectionPool.instance;
79
+ }
80
+
81
+ getConnection(connectionString: string): SQL {
82
+ if (!this.connections.has(connectionString)) {
83
+ if (this.connections.size >= this.maxConnections) {
84
+ // If pool is full, return a new connection (will be closed after use)
85
+ return new SQL(connectionString);
86
+ }
87
+ this.connections.set(connectionString, new SQL(connectionString));
88
+ }
89
+ return this.connections.get(connectionString)!;
90
+ }
91
+
92
+ releaseConnection(connectionString: string, sql: SQL): void {
93
+ // Keep the connection in the pool for reuse
94
+ if (this.connections.size < this.maxConnections) {
95
+ this.connections.set(connectionString, sql);
96
+ }
97
+ }
98
+
99
+ closeAll(): void {
100
+ for (const [key, sql] of this.connections) {
101
+ if (sql && typeof sql.close === 'function') {
102
+ sql.close();
103
+ }
104
+ }
105
+ this.connections.clear();
106
+ }
107
+ }
108
+
109
+ // Base class for query builders with auto-close support
110
+ abstract class BaseQuery {
111
+ protected sql: SQL;
112
+ protected transaction?: any;
113
+ protected bunql?: BunQL; // Reference to parent BunQL instance
114
+
115
+ constructor(sql: SQL, transaction?: any, bunql?: BunQL) {
116
+ this.sql = sql;
117
+ this.transaction = transaction;
118
+ this.bunql = bunql;
119
+ }
120
+
121
+ protected async executeWithAutoClose<T>(operation: () => Promise<T>): Promise<T> {
122
+ if (this.bunql) {
123
+ await this.bunql['ensureConnection']();
124
+ }
125
+
126
+ try {
127
+ const result = await operation();
128
+
129
+ // Only auto-close if not in transaction AND not using connection pool
130
+ if (!this.transaction && this.bunql && !this.bunql['connectionString']) {
131
+ await this.bunql.close();
132
+ }
133
+
134
+ return result;
135
+ } catch (error) {
136
+ // Only auto-close on error if not in transaction AND not using connection pool
137
+ if (!this.transaction && this.bunql && !this.bunql['connectionString']) {
138
+ await this.bunql.close();
139
+ }
140
+ throw error;
141
+ }
142
+ }
41
143
  }
42
144
 
43
145
  export class BunQL {
44
146
  private sql: SQL;
45
147
  private _transaction?: any;
148
+ private connectionString?: string;
149
+ private isClosed: boolean = false;
150
+ private pool: ConnectionPool;
46
151
 
47
152
  constructor(connectionString?: string) {
48
- this.sql = connectionString ? new SQL(connectionString) : sql;
153
+ this.connectionString = connectionString;
154
+ this.pool = ConnectionPool.getInstance();
155
+
156
+ if (connectionString) {
157
+ this.sql = this.pool.getConnection(connectionString);
158
+ } else {
159
+ this.sql = sql;
160
+ }
161
+ }
162
+
163
+ private async ensureConnection(): Promise<void> {
164
+ if (this.isClosed && this.connectionString) {
165
+ this.sql = this.pool.getConnection(this.connectionString);
166
+ this.isClosed = false;
167
+ }
168
+ }
169
+
170
+ private async executeWithAutoClose<T>(operation: () => Promise<T>): Promise<T> {
171
+ await this.ensureConnection();
172
+
173
+ try {
174
+ const result = await operation();
175
+
176
+ // Only auto-close if not in transaction AND not using connection pool
177
+ if (!this._transaction && !this.connectionString) {
178
+ await this.close();
179
+ }
180
+
181
+ return result;
182
+ } catch (error) {
183
+ // Only auto-close on error if not in transaction AND not using connection pool
184
+ if (!this._transaction && !this.connectionString) {
185
+ await this.close();
186
+ }
187
+ throw error;
188
+ }
49
189
  }
50
190
 
51
191
  select<T = any>(columns?: string | string[]): SelectQuery<T> {
52
- return new SelectQuery<T>(this.sql, this._transaction, columns);
192
+ return new SelectQuery<T>(this.sql, this._transaction, columns, this);
53
193
  }
54
194
 
55
195
  update(table: string): UpdateQuery {
56
- return new UpdateQuery(this.sql, table, this._transaction);
196
+ return new UpdateQuery(this.sql, table, this._transaction, this);
57
197
  }
58
198
 
59
199
  insert(table: string): InsertQuery {
60
- return new InsertQuery(this.sql, table, this._transaction);
200
+ return new InsertQuery(this.sql, table, this._transaction, this);
61
201
  }
62
202
 
63
203
  delete(table: string): DeleteQuery {
64
- return new DeleteQuery(this.sql, table, this._transaction);
204
+ return new DeleteQuery(this.sql, table, this._transaction, this);
65
205
  }
66
206
 
67
207
  async run(query: string, params?: any[]): Promise<any> {
68
- const executor = this._transaction || this.sql;
69
- return await executeSql(executor, query, params);
208
+ return await this.executeWithAutoClose(async () => {
209
+ const executor = this._transaction || this.sql;
210
+ return await executeSql(executor, query, params);
211
+ });
70
212
  }
71
213
 
72
214
  async all<T = any>(query: string, params?: any[]): Promise<T[]> {
73
- const executor = this._transaction || this.sql;
74
- const result = await executeSql(executor, query, params);
75
- return Array.isArray(result) ? result : [];
215
+ return await this.executeWithAutoClose(async () => {
216
+ const executor = this._transaction || this.sql;
217
+ const result = await executeSql(executor, query, params);
218
+ return Array.isArray(result) ? result : [];
219
+ });
76
220
  }
77
221
 
78
222
  async get<T = any>(query: string, params?: any[]): Promise<T | null> {
79
- const results = await this.all<T>(query, params);
80
- return results.length > 0 ? results[0]! : null;
223
+ return await this.executeWithAutoClose(async () => {
224
+ const results = await this.all<T>(query, params);
225
+ return results.length > 0 ? results[0]! : null;
226
+ });
81
227
  }
82
228
 
83
- async begin<T>(callback: (tx: Transaction) => Promise<T>): Promise<T> {
229
+ async begin<T>(callback: (tx: BunQL) => Promise<T>): Promise<T> {
84
230
  return await this.sql.begin(async (tx) => {
85
- const transactionBuilder = new BunQL();
86
- transactionBuilder.sql = this.sql;
87
- transactionBuilder._transaction = tx;
88
- return await callback(transactionBuilder);
231
+ // Create a transaction builder that reuses the existing connection
232
+ // Don't create a new BunQL instance, just modify the current one temporarily
233
+ const originalTransaction = this._transaction;
234
+ this._transaction = tx;
235
+
236
+ try {
237
+ return await callback(this);
238
+ } finally {
239
+ // Restore original transaction state
240
+ this._transaction = originalTransaction;
241
+ }
89
242
  });
90
243
  }
91
244
 
92
245
  async transaction<T>(callback: (trx: BunQL) => Promise<T>): Promise<T> {
93
246
  return await this.sql.begin(async (tx) => {
94
- const transactionBuilder = new BunQL();
95
- transactionBuilder.sql = this.sql;
96
- transactionBuilder._transaction = tx;
97
- return await callback(transactionBuilder);
247
+ // Create a transaction builder that reuses the existing connection
248
+ // Don't create a new BunQL instance, just modify the current one temporarily
249
+ const originalTransaction = this._transaction;
250
+ this._transaction = tx;
251
+
252
+ try {
253
+ return await callback(this);
254
+ } finally {
255
+ // Restore original transaction state
256
+ this._transaction = originalTransaction;
257
+ }
98
258
  });
99
259
  }
100
260
 
@@ -105,11 +265,19 @@ export class BunQL {
105
265
  async rollback(): Promise<void> {
106
266
  // Transactions are handled by Bun's SQL implementation
107
267
  }
268
+
269
+ async close(): Promise<void> {
270
+ if (this.connectionString) {
271
+ // Release connection back to pool instead of closing it
272
+ this.pool.releaseConnection(this.connectionString, this.sql);
273
+ } else if (this.sql && typeof this.sql.close === 'function') {
274
+ await this.sql.close();
275
+ }
276
+ this.isClosed = true;
277
+ }
108
278
  }
109
279
 
110
- export class SelectQuery<T = any> {
111
- private sql: SQL;
112
- private transaction?: any;
280
+ export class SelectQuery<T = any> extends BaseQuery {
113
281
  private columns: string;
114
282
  private table?: string;
115
283
  private whereClauses: string[] = [];
@@ -118,9 +286,8 @@ export class SelectQuery<T = any> {
118
286
  private limitCount?: number;
119
287
  private offsetCount?: number;
120
288
 
121
- constructor(sql: SQL, transaction?: any, columns?: string | string[]) {
122
- this.sql = sql;
123
- this.transaction = transaction;
289
+ constructor(sql: SQL, transaction?: any, columns?: string | string[], bunql?: BunQL) {
290
+ super(sql, transaction, bunql);
124
291
  this.columns = this.formatColumns(columns);
125
292
  }
126
293
 
@@ -136,27 +303,37 @@ export class SelectQuery<T = any> {
136
303
  }
137
304
 
138
305
  where(column: string, operator: string, value: any): this {
139
- this.whereClauses.push(`${column} ${operator} ?`);
140
- this.whereParams.push(value);
306
+ if (operator.toUpperCase() === 'IN' && Array.isArray(value)) {
307
+ const placeholders = value.map(() => '?').join(', ');
308
+ this.whereClauses.push(`"${column}" IN (${placeholders})`);
309
+ this.whereParams.push(...value);
310
+ } else if (operator.toUpperCase() === 'NOT IN' && Array.isArray(value)) {
311
+ const placeholders = value.map(() => '?').join(', ');
312
+ this.whereClauses.push(`"${column}" NOT IN (${placeholders})`);
313
+ this.whereParams.push(...value);
314
+ } else {
315
+ this.whereClauses.push(`"${column}" ${operator} ?`);
316
+ this.whereParams.push(value);
317
+ }
141
318
  return this;
142
319
  }
143
320
 
144
321
  whereIn(column: string, values: any[]): this {
145
322
  const placeholders = values.map(() => '?').join(', ');
146
- this.whereClauses.push(`${column} IN (${placeholders})`);
323
+ this.whereClauses.push(`"${column}" IN (${placeholders})`);
147
324
  this.whereParams.push(...values);
148
325
  return this;
149
326
  }
150
327
 
151
328
  whereNotIn(column: string, values: any[]): this {
152
329
  const placeholders = values.map(() => '?').join(', ');
153
- this.whereClauses.push(`${column} NOT IN (${placeholders})`);
330
+ this.whereClauses.push(`"${column}" NOT IN (${placeholders})`);
154
331
  this.whereParams.push(...values);
155
332
  return this;
156
333
  }
157
334
 
158
335
  orderBy(column: string, direction: 'ASC' | 'DESC' = 'ASC'): this {
159
- this.orderByClause = `${column} ${direction}`;
336
+ this.orderByClause = `"${column}" ${direction}`;
160
337
  return this;
161
338
  }
162
339
 
@@ -171,31 +348,33 @@ export class SelectQuery<T = any> {
171
348
  }
172
349
 
173
350
  async execute(): Promise<T[]> {
174
- if (!this.table) {
175
- throw new Error('Table name is required. Use .from() method.');
176
- }
351
+ return await this.executeWithAutoClose(async () => {
352
+ if (!this.table) {
353
+ throw new Error('Table name is required. Use .from() method.');
354
+ }
177
355
 
178
- let query = `SELECT ${this.columns} FROM "${this.table}"`;
179
-
180
- if (this.whereClauses.length > 0) {
181
- query += ` WHERE ${this.whereClauses.join(' AND ')}`;
182
- }
356
+ let query = `SELECT ${this.columns} FROM "${this.table}"`;
357
+
358
+ if (this.whereClauses.length > 0) {
359
+ query += ` WHERE ${this.whereClauses.join(' AND ')}`;
360
+ }
183
361
 
184
- if (this.orderByClause) {
185
- query += ` ORDER BY ${this.orderByClause}`;
186
- }
362
+ if (this.orderByClause) {
363
+ query += ` ORDER BY ${this.orderByClause}`;
364
+ }
187
365
 
188
- if (this.limitCount !== undefined) {
189
- query += ` LIMIT ${this.limitCount}`;
190
- }
366
+ if (this.limitCount !== undefined) {
367
+ query += ` LIMIT ${this.limitCount}`;
368
+ }
191
369
 
192
- if (this.offsetCount !== undefined) {
193
- query += ` OFFSET ${this.offsetCount}`;
194
- }
370
+ if (this.offsetCount !== undefined) {
371
+ query += ` OFFSET ${this.offsetCount}`;
372
+ }
195
373
 
196
- const executor = this.transaction || this.sql;
197
- const result = await executeSql(executor, query, this.whereParams);
198
- return Array.isArray(result) ? result : [];
374
+ const executor = this.transaction || this.sql;
375
+ const result = await executeSql(executor, query, this.whereParams);
376
+ return Array.isArray(result) ? result : [];
377
+ });
199
378
  }
200
379
 
201
380
  async first(): Promise<T | null> {
@@ -203,28 +382,34 @@ export class SelectQuery<T = any> {
203
382
  return results.length > 0 ? results[0]! : null;
204
383
  }
205
384
 
385
+ async get(): Promise<T | null> {
386
+ return this.first();
387
+ }
388
+
206
389
  async all(): Promise<T[]> {
207
390
  return this.execute();
208
391
  }
209
392
 
210
393
  async count(): Promise<number> {
211
- if (!this.table) {
212
- throw new Error('Table name is required. Use .from() method.');
213
- }
394
+ return await this.executeWithAutoClose(async () => {
395
+ if (!this.table) {
396
+ throw new Error('Table name is required. Use .from() method.');
397
+ }
214
398
 
215
- let query = `SELECT COUNT(*) as count FROM "${this.table}"`;
216
-
217
- if (this.whereClauses.length > 0) {
218
- query += ` WHERE ${this.whereClauses.join(' AND ')}`;
219
- }
399
+ let query = `SELECT COUNT(*) as count FROM "${this.table}"`;
400
+
401
+ if (this.whereClauses.length > 0) {
402
+ query += ` WHERE ${this.whereClauses.join(' AND ')}`;
403
+ }
220
404
 
221
- const executor = this.transaction || this.sql;
222
- const result = await executeSql(executor, query, this.whereParams);
223
- // Handle different result formats
224
- if (Array.isArray(result)) {
225
- return result[0]?.count || 0;
226
- }
227
- return result.count || 0;
405
+ const executor = this.transaction || this.sql;
406
+ const result = await executeSql(executor, query, this.whereParams);
407
+ // Handle different result formats
408
+ if (Array.isArray(result)) {
409
+ return result[0]?.count || 0;
410
+ }
411
+ return result.count || 0;
412
+ });
228
413
  }
229
414
 
230
415
  // Make the query awaitable by implementing then method
@@ -236,65 +421,76 @@ export class SelectQuery<T = any> {
236
421
  }
237
422
  }
238
423
 
239
- export class UpdateQuery {
240
- private sql: SQL;
241
- private transaction?: any;
424
+ export class UpdateQuery extends BaseQuery {
242
425
  private table: string;
243
426
  private setClauses: string[] = [];
244
427
  private setParams: any[] = [];
245
428
  private whereClauses: string[] = [];
246
429
  private whereParams: any[] = [];
247
430
 
248
- constructor(sql: SQL, table: string, transaction?: any) {
249
- this.sql = sql;
250
- this.transaction = transaction;
431
+ constructor(sql: SQL, table: string, transaction?: any, bunql?: BunQL) {
432
+ super(sql, transaction, bunql);
251
433
  this.table = table;
252
434
  }
253
435
 
254
436
  set(data: Record<string, any>): this {
255
437
  Object.entries(data).forEach(([key, value]) => {
256
- this.setClauses.push(`${key} = ?`);
438
+ this.setClauses.push(`"${key}" = ?`);
257
439
  this.setParams.push(value);
258
440
  });
259
441
  return this;
260
442
  }
261
443
 
262
444
  where(column: string, operator: string, value: any): this {
263
- this.whereClauses.push(`${column} ${operator} ?`);
264
- this.whereParams.push(value);
445
+ if (operator.toUpperCase() === 'IN' && Array.isArray(value)) {
446
+ const placeholders = value.map(() => '?').join(', ');
447
+ this.whereClauses.push(`"${column}" IN (${placeholders})`);
448
+ this.whereParams.push(...value);
449
+ } else if (operator.toUpperCase() === 'NOT IN' && Array.isArray(value)) {
450
+ const placeholders = value.map(() => '?').join(', ');
451
+ this.whereClauses.push(`"${column}" NOT IN (${placeholders})`);
452
+ this.whereParams.push(...value);
453
+ } else {
454
+ this.whereClauses.push(`"${column}" ${operator} ?`);
455
+ this.whereParams.push(value);
456
+ }
265
457
  return this;
266
458
  }
267
459
 
268
460
  async execute(): Promise<any> {
269
- if (this.setClauses.length === 0) {
270
- throw new Error('No values to update. Use .set() method.');
271
- }
461
+ return await this.executeWithAutoClose(async () => {
462
+ if (this.setClauses.length === 0) {
463
+ throw new Error('No values to update. Use .set() method.');
464
+ }
272
465
 
273
- let query = `UPDATE "${this.table}" SET ${this.setClauses.join(', ')}`;
274
-
275
- if (this.whereClauses.length > 0) {
276
- query += ` WHERE ${this.whereClauses.join(' AND ')}`;
277
- }
466
+ let query = `UPDATE "${this.table}" SET ${this.setClauses.join(', ')}`;
467
+
468
+ if (this.whereClauses.length > 0) {
469
+ query += ` WHERE ${this.whereClauses.join(' AND ')}`;
470
+ }
278
471
 
279
- const executor = this.transaction || this.sql;
280
- const allParams = [...this.setParams, ...this.whereParams];
281
- return await executeSql(executor, query, allParams);
472
+ const executor = this.transaction || this.sql;
473
+ const allParams = [...this.setParams, ...this.whereParams];
474
+ return await executeSql(executor, query, allParams);
475
+ });
282
476
  }
283
477
 
284
478
  async count(): Promise<number> {
285
- let query = `SELECT COUNT(*) as count FROM "${this.table}"`;
286
-
287
- if (this.whereClauses.length > 0) {
288
- query += ` WHERE ${this.whereClauses.join(' AND ')}`;
289
- }
479
+ return await this.executeWithAutoClose(async () => {
480
+ let query = `SELECT COUNT(*) as count FROM "${this.table}"`;
481
+
482
+ if (this.whereClauses.length > 0) {
483
+ query += ` WHERE ${this.whereClauses.join(' AND ')}`;
484
+ }
290
485
 
291
- const executor = this.transaction || this.sql;
292
- const result = await executeSql(executor, query, this.whereParams);
293
- // Handle different result formats
294
- if (Array.isArray(result)) {
295
- return result[0]?.count || 0;
296
- }
297
- return result.count || 0;
486
+ const executor = this.transaction || this.sql;
487
+ const result = await executeSql(executor, query, this.whereParams);
488
+ // Handle different result formats
489
+ if (Array.isArray(result)) {
490
+ return result[0]?.count || 0;
491
+ }
492
+ return result.count || 0;
493
+ });
298
494
  }
299
495
 
300
496
  // Make the query awaitable by implementing then method
@@ -306,17 +502,16 @@ export class UpdateQuery {
306
502
  }
307
503
  }
308
504
 
309
- export class InsertQuery {
310
- private sql: SQL;
311
- private transaction?: any;
505
+ export class InsertQuery extends BaseQuery {
312
506
  private table: string;
313
507
  private columns: string[] = [];
314
508
  private valuesArray: any[] = [];
315
509
  private isEmptyArray: boolean = false;
510
+ private isBulkInsert: boolean = false;
511
+ private bulkData: Record<string, any>[] = [];
316
512
 
317
- constructor(sql: SQL, table: string, transaction?: any) {
318
- this.sql = sql;
319
- this.transaction = transaction;
513
+ constructor(sql: SQL, table: string, transaction?: any, bunql?: BunQL) {
514
+ super(sql, transaction, bunql);
320
515
  this.table = table;
321
516
  }
322
517
 
@@ -330,11 +525,9 @@ export class InsertQuery {
330
525
  const firstRow = data[0];
331
526
  this.columns = Object.keys(firstRow);
332
527
 
333
- data.forEach(row => {
334
- this.columns.forEach(col => {
335
- this.valuesArray.push(row[col]);
336
- });
337
- });
528
+ // For bulk insert, store the data separately
529
+ this.isBulkInsert = true;
530
+ this.bulkData = data;
338
531
  } else {
339
532
  this.columns = Object.keys(data);
340
533
  this.valuesArray = Object.values(data);
@@ -343,32 +536,65 @@ export class InsertQuery {
343
536
  }
344
537
 
345
538
  async execute(): Promise<any> {
346
- if (this.isEmptyArray) {
347
- throw new Error('Cannot insert empty array');
348
- }
349
-
350
- if (this.columns.length === 0 || this.valuesArray.length === 0) {
351
- throw new Error('No values to insert. Use .values() method.');
352
- }
353
-
354
- const columnsStr = this.columns.map(col => `"${col}"`).join(', ');
355
- const placeholders = this.columns.map(() => '?').join(', ');
356
-
357
- let query = `INSERT INTO "${this.table}" (${columnsStr}) VALUES (${placeholders})`;
358
-
359
- const executor = this.transaction || this.sql;
360
- return await executeSql(executor, query, this.valuesArray);
539
+ return await this.executeWithAutoClose(async () => {
540
+ if (this.isEmptyArray) {
541
+ throw new Error('Cannot insert empty array');
542
+ }
543
+
544
+ if (this.isBulkInsert) {
545
+ if (this.columns.length === 0 || this.bulkData.length === 0) {
546
+ throw new Error('No values to insert. Use .values() method.');
547
+ }
548
+
549
+ const columnsStr = this.columns.map(col => `"${col}"`).join(', ');
550
+ const placeholders = this.columns.map(() => '?').join(', ');
551
+
552
+ let query = `INSERT INTO "${this.table}" (${columnsStr}) VALUES (${placeholders})`;
553
+
554
+ const executor = this.transaction || this.sql;
555
+
556
+ // Execute each row separately for bulk insert
557
+ let totalAffectedRows = 0;
558
+ let lastInsertRowid: any = null;
559
+
560
+ for (const row of this.bulkData) {
561
+ const values = this.columns.map(col => row[col]);
562
+ const result = await executeSql(executor, query, values);
563
+ totalAffectedRows += result.affectedRows || 1;
564
+ lastInsertRowid = result.lastInsertRowid;
565
+ }
566
+
567
+ return {
568
+ affectedRows: totalAffectedRows,
569
+ lastInsertRowid: lastInsertRowid
570
+ };
571
+ } else {
572
+ if (this.columns.length === 0 || this.valuesArray.length === 0) {
573
+ throw new Error('No values to insert. Use .values() method.');
574
+ }
575
+
576
+ const columnsStr = this.columns.map(col => `"${col}"`).join(', ');
577
+ const placeholders = this.columns.map(() => '?').join(', ');
578
+
579
+ let query = `INSERT INTO "${this.table}" (${columnsStr}) VALUES (${placeholders})`;
580
+
581
+ const executor = this.transaction || this.sql;
582
+ return await executeSql(executor, query, this.valuesArray);
583
+ }
584
+ });
361
585
  }
362
586
 
363
587
  async count(): Promise<number> {
364
- let query = `SELECT COUNT(*) as count FROM "${this.table}"`;
365
- const executor = this.transaction || this.sql;
366
- const result = await executeSql(executor, query, []);
367
- // Handle different result formats
368
- if (Array.isArray(result)) {
369
- return result[0]?.count || 0;
370
- }
371
- return result.count || 0;
588
+ return await this.executeWithAutoClose(async () => {
589
+ let query = `SELECT COUNT(*) as count FROM "${this.table}"`;
590
+ const executor = this.transaction || this.sql;
591
+ const result = await executeSql(executor, query, []);
592
+ // Handle different result formats
593
+ if (Array.isArray(result)) {
594
+ return result[0]?.count || 0;
595
+ }
596
+ return result.count || 0;
597
+ });
372
598
  }
373
599
 
374
600
  // Make the query awaitable by implementing then method
@@ -380,50 +606,61 @@ export class InsertQuery {
380
606
  }
381
607
  }
382
608
 
383
- export class DeleteQuery {
384
- private sql: SQL;
385
- private transaction?: any;
609
+ export class DeleteQuery extends BaseQuery {
386
610
  private table: string;
387
611
  private whereClauses: string[] = [];
388
612
  private whereParams: any[] = [];
389
613
 
390
- constructor(sql: SQL, table: string, transaction?: any) {
391
- this.sql = sql;
392
- this.transaction = transaction;
614
+ constructor(sql: SQL, table: string, transaction?: any, bunql?: BunQL) {
615
+ super(sql, transaction, bunql);
393
616
  this.table = table;
394
617
  }
395
618
 
396
619
  where(column: string, operator: string, value: any): this {
397
- this.whereClauses.push(`${column} ${operator} ?`);
398
- this.whereParams.push(value);
620
+ if (operator.toUpperCase() === 'IN' && Array.isArray(value)) {
621
+ const placeholders = value.map(() => '?').join(', ');
622
+ this.whereClauses.push(`"${column}" IN (${placeholders})`);
623
+ this.whereParams.push(...value);
624
+ } else if (operator.toUpperCase() === 'NOT IN' && Array.isArray(value)) {
625
+ const placeholders = value.map(() => '?').join(', ');
626
+ this.whereClauses.push(`"${column}" NOT IN (${placeholders})`);
627
+ this.whereParams.push(...value);
628
+ } else {
629
+ this.whereClauses.push(`"${column}" ${operator} ?`);
630
+ this.whereParams.push(value);
631
+ }
399
632
  return this;
400
633
  }
401
634
 
402
635
  async execute(): Promise<any> {
403
- let query = `DELETE FROM "${this.table}"`;
404
-
405
- if (this.whereClauses.length > 0) {
406
- query += ` WHERE ${this.whereClauses.join(' AND ')}`;
407
- }
636
+ return await this.executeWithAutoClose(async () => {
637
+ let query = `DELETE FROM "${this.table}"`;
638
+
639
+ if (this.whereClauses.length > 0) {
640
+ query += ` WHERE ${this.whereClauses.join(' AND ')}`;
641
+ }
408
642
 
409
- const executor = this.transaction || this.sql;
410
- return await executeSql(executor, query, this.whereParams);
643
+ const executor = this.transaction || this.sql;
644
+ return await executeSql(executor, query, this.whereParams);
645
+ });
411
646
  }
412
647
 
413
648
  async count(): Promise<number> {
414
- let query = `SELECT COUNT(*) as count FROM "${this.table}"`;
415
-
416
- if (this.whereClauses.length > 0) {
417
- query += ` WHERE ${this.whereClauses.join(' AND ')}`;
418
- }
649
+ return await this.executeWithAutoClose(async () => {
650
+ let query = `SELECT COUNT(*) as count FROM "${this.table}"`;
651
+
652
+ if (this.whereClauses.length > 0) {
653
+ query += ` WHERE ${this.whereClauses.join(' AND ')}`;
654
+ }
419
655
 
420
- const executor = this.transaction || this.sql;
421
- const result = await executeSql(executor, query, this.whereParams);
422
- // Handle different result formats
423
- if (Array.isArray(result)) {
424
- return result[0]?.count || 0;
425
- }
426
- return result.count || 0;
656
+ const executor = this.transaction || this.sql;
657
+ const result = await executeSql(executor, query, this.whereParams);
658
+ // Handle different result formats
659
+ if (Array.isArray(result)) {
660
+ return result[0]?.count || 0;
661
+ }
662
+ return result.count || 0;
663
+ });
427
664
  }
428
665
 
429
666
  // Make the query awaitable by implementing then method