bunql 1.0.0 → 1.0.1-dev.2

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/README.md CHANGED
@@ -49,6 +49,35 @@ const users = await db.select('*').from('users').execute();
49
49
 
50
50
  This makes the API more concise and intuitive while maintaining backward compatibility.
51
51
 
52
+ ## Count Functionality
53
+
54
+ BunQL provides `.count()` methods on all query types to get the number of records:
55
+
56
+ ```typescript
57
+ // Count all records in a table
58
+ const totalUsers = await db.select('*').from('users').count();
59
+
60
+ // Count with WHERE conditions
61
+ const activeUsers = await db.select('*')
62
+ .from('users')
63
+ .where('active', '=', true)
64
+ .count();
65
+
66
+ // Count records that would be affected by an update
67
+ const usersToUpdate = await db.update('users')
68
+ .set({ active: false })
69
+ .where('last_login', '<', new Date('2023-01-01'))
70
+ .count();
71
+
72
+ // Count records that would be deleted
73
+ const usersToDelete = await db.delete('users')
74
+ .where('active', '=', false)
75
+ .count();
76
+
77
+ // Count total records in table (for insert queries)
78
+ const totalRecords = await db.insert('users').values({ name: 'Test' }).count();
79
+ ```
80
+
52
81
  ## Usage Examples
53
82
 
54
83
  ### Select Queries
@@ -165,6 +194,37 @@ const user = await db.get('SELECT * FROM users WHERE id = ?', [1]);
165
194
 
166
195
  ### Transactions
167
196
 
197
+ BunQL provides two ways to handle transactions:
198
+
199
+ #### Method 1: Using `db.transaction()` (Recommended)
200
+
201
+ ```typescript
202
+ // Clean transaction API with automatic rollback on error
203
+ const result = await db.transaction(async (trx) => {
204
+ // Insert user
205
+ const userResult = await trx.insert('users')
206
+ .values({ name: 'John', email: 'john@example.com' });
207
+
208
+ // Insert user profile
209
+ await trx.insert('user_profiles')
210
+ .values({
211
+ user_id: userResult.lastInsertRowid,
212
+ bio: 'Software developer'
213
+ });
214
+
215
+ // Update user status
216
+ await trx.update('users')
217
+ .set({ active: true })
218
+ .where('id', '=', userResult.lastInsertRowid);
219
+
220
+ return userResult.lastInsertRowid;
221
+ });
222
+
223
+ console.log('Transaction completed, user ID:', result);
224
+ ```
225
+
226
+ #### Method 2: Using `db.begin()` (Legacy)
227
+
168
228
  ```typescript
169
229
  // Transaction with automatic rollback on error
170
230
  const result = await db.begin(async (tx) => {
@@ -392,7 +452,8 @@ Main class for building and executing queries.
392
452
  - `run(query, params?)`: Execute raw SQL
393
453
  - `all(query, params?)`: Execute raw SQL and return all results
394
454
  - `get(query, params?)`: Execute raw SQL and return first result
395
- - `begin(callback)`: Execute queries in a transaction
455
+ - `begin(callback)`: Execute queries in a transaction (legacy)
456
+ - `transaction(callback)`: Execute queries in a transaction (recommended)
396
457
 
397
458
  ### SelectQuery
398
459
 
@@ -410,6 +471,7 @@ Methods for building SELECT queries.
410
471
  - `execute()`: Execute the query and return results
411
472
  - `first()`: Execute the query and return first result
412
473
  - `all()`: Alias for `execute()`
474
+ - `count()`: Execute and return count of matching records
413
475
 
414
476
  ### InsertQuery
415
477
 
@@ -419,6 +481,7 @@ Methods for building INSERT queries.
419
481
 
420
482
  - `values(data)`: Specify values to insert (object or array)
421
483
  - `execute()`: Execute the insert query
484
+ - `count()`: Return count of total records in table
422
485
 
423
486
  ### UpdateQuery
424
487
 
@@ -429,6 +492,7 @@ Methods for building UPDATE queries.
429
492
  - `set(data)`: Specify values to update
430
493
  - `where(column, operator, value)`: Add WHERE condition
431
494
  - `execute()`: Execute the update query
495
+ - `count()`: Return count of records that would be affected
432
496
 
433
497
  ### DeleteQuery
434
498
 
@@ -438,6 +502,7 @@ Methods for building DELETE queries.
438
502
 
439
503
  - `where(column, operator, value)`: Add WHERE condition
440
504
  - `execute()`: Execute the delete query
505
+ - `count()`: Return count of records that would be deleted
441
506
 
442
507
  ## Error Handling
443
508
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunql",
3
- "version": "1.0.0",
3
+ "version": "1.0.1-dev.2",
4
4
  "description": "A fluent SQL query builder for Bun with transaction support",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
package/src/index.test.ts CHANGED
@@ -243,6 +243,33 @@ describe('BunQL', () => {
243
243
  });
244
244
  });
245
245
 
246
+ describe('Count Functionality', () => {
247
+ it('should have count method on select queries', () => {
248
+ const query = db.select('*').from('users');
249
+ expect(typeof query.count).toBe('function');
250
+ });
251
+
252
+ it('should have count method on update queries', () => {
253
+ const query = db.update('users').set({ name: 'Test' });
254
+ expect(typeof query.count).toBe('function');
255
+ });
256
+
257
+ it('should have count method on insert queries', () => {
258
+ const query = db.insert('users').values({ name: 'Test' });
259
+ expect(typeof query.count).toBe('function');
260
+ });
261
+
262
+ it('should have count method on delete queries', () => {
263
+ const query = db.delete('users').where('id', '=', 1);
264
+ expect(typeof query.count).toBe('function');
265
+ });
266
+
267
+ it('should throw error when calling count on select without table', async () => {
268
+ const query = db.select('*');
269
+ await expect(query.count()).rejects.toThrow('Table name is required. Use .from() method.');
270
+ });
271
+ });
272
+
246
273
  describe('Method Availability', () => {
247
274
  it('should have run method', () => {
248
275
  expect(typeof db.run).toBe('function');
@@ -260,6 +287,10 @@ describe('BunQL', () => {
260
287
  expect(typeof db.begin).toBe('function');
261
288
  });
262
289
 
290
+ it('should have transaction method', () => {
291
+ expect(typeof db.transaction).toBe('function');
292
+ });
293
+
263
294
  it('should have commit method', () => {
264
295
  expect(typeof db.commit).toBe('function');
265
296
  });
package/src/index.ts CHANGED
@@ -42,35 +42,35 @@ async function executeSql(executor: any, query: string, params?: any[]): Promise
42
42
 
43
43
  export class BunQL {
44
44
  private sql: SQL;
45
- private transaction?: any;
45
+ private _transaction?: any;
46
46
 
47
47
  constructor(connectionString?: string) {
48
48
  this.sql = connectionString ? new SQL(connectionString) : sql;
49
49
  }
50
50
 
51
51
  select<T = any>(columns?: string | string[]): SelectQuery<T> {
52
- return new SelectQuery<T>(this.sql, this.transaction, columns);
52
+ return new SelectQuery<T>(this.sql, this._transaction, columns);
53
53
  }
54
54
 
55
55
  update(table: string): UpdateQuery {
56
- return new UpdateQuery(this.sql, table, this.transaction);
56
+ return new UpdateQuery(this.sql, table, this._transaction);
57
57
  }
58
58
 
59
59
  insert(table: string): InsertQuery {
60
- return new InsertQuery(this.sql, table, this.transaction);
60
+ return new InsertQuery(this.sql, table, this._transaction);
61
61
  }
62
62
 
63
63
  delete(table: string): DeleteQuery {
64
- return new DeleteQuery(this.sql, table, this.transaction);
64
+ return new DeleteQuery(this.sql, table, this._transaction);
65
65
  }
66
66
 
67
67
  async run(query: string, params?: any[]): Promise<any> {
68
- const executor = this.transaction || this.sql;
68
+ const executor = this._transaction || this.sql;
69
69
  return await executeSql(executor, query, params);
70
70
  }
71
71
 
72
72
  async all<T = any>(query: string, params?: any[]): Promise<T[]> {
73
- const executor = this.transaction || this.sql;
73
+ const executor = this._transaction || this.sql;
74
74
  const result = await executeSql(executor, query, params);
75
75
  return Array.isArray(result) ? result : [];
76
76
  }
@@ -84,7 +84,16 @@ export class BunQL {
84
84
  return await this.sql.begin(async (tx) => {
85
85
  const transactionBuilder = new BunQL();
86
86
  transactionBuilder.sql = this.sql;
87
- transactionBuilder.transaction = tx;
87
+ transactionBuilder._transaction = tx;
88
+ return await callback(transactionBuilder);
89
+ });
90
+ }
91
+
92
+ async transaction<T>(callback: (trx: BunQL) => Promise<T>): Promise<T> {
93
+ return await this.sql.begin(async (tx) => {
94
+ const transactionBuilder = new BunQL();
95
+ transactionBuilder.sql = this.sql;
96
+ transactionBuilder._transaction = tx;
88
97
  return await callback(transactionBuilder);
89
98
  });
90
99
  }
@@ -198,6 +207,26 @@ export class SelectQuery<T = any> {
198
207
  return this.execute();
199
208
  }
200
209
 
210
+ async count(): Promise<number> {
211
+ if (!this.table) {
212
+ throw new Error('Table name is required. Use .from() method.');
213
+ }
214
+
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
+ }
220
+
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;
228
+ }
229
+
201
230
  // Make the query awaitable by implementing then method
202
231
  then<TResult1 = T[], TResult2 = never>(
203
232
  onfulfilled?: ((value: T[]) => TResult1 | PromiseLike<TResult1>) | undefined | null,
@@ -252,6 +281,22 @@ export class UpdateQuery {
252
281
  return await executeSql(executor, query, allParams);
253
282
  }
254
283
 
284
+ 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
+ }
290
+
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;
298
+ }
299
+
255
300
  // Make the query awaitable by implementing then method
256
301
  then<TResult1 = any, TResult2 = never>(
257
302
  onfulfilled?: ((value: any) => TResult1 | PromiseLike<TResult1>) | undefined | null,
@@ -315,6 +360,17 @@ export class InsertQuery {
315
360
  return await executeSql(executor, query, this.valuesArray);
316
361
  }
317
362
 
363
+ 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;
372
+ }
373
+
318
374
  // Make the query awaitable by implementing then method
319
375
  then<TResult1 = any, TResult2 = never>(
320
376
  onfulfilled?: ((value: any) => TResult1 | PromiseLike<TResult1>) | undefined | null,
@@ -354,6 +410,22 @@ export class DeleteQuery {
354
410
  return await executeSql(executor, query, this.whereParams);
355
411
  }
356
412
 
413
+ 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
+ }
419
+
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;
427
+ }
428
+
357
429
  // Make the query awaitable by implementing then method
358
430
  then<TResult1 = any, TResult2 = never>(
359
431
  onfulfilled?: ((value: any) => TResult1 | PromiseLike<TResult1>) | undefined | null,