workers-qb 1.11.0 → 1.11.1

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.
@@ -0,0 +1,602 @@
1
+ # Advanced Queries
2
+
3
+ This section delves into advanced query building features of `workers-qb`, allowing you to construct complex and efficient database interactions.
4
+
5
+ ## Joins
6
+
7
+ `workers-qb` supports various types of SQL JOIN clauses to combine data from multiple tables.
8
+
9
+ ### INNER JOIN
10
+
11
+ An INNER JOIN returns rows only when there is a match in both tables based on the join condition.
12
+
13
+ ```typescript
14
+ import { D1QB } from 'workers-qb';
15
+
16
+ // ... (D1QB initialization) ...
17
+
18
+ type UserWithRole = {
19
+ userName: string;
20
+ roleName: string;
21
+ };
22
+
23
+ const usersWithRoles = await qb.fetchAll<UserWithRole>({
24
+ tableName: 'users',
25
+ fields: ['users.name AS userName', 'roles.name AS roleName'],
26
+ join: {
27
+ type: 'INNER', // or 'INNER JOIN'
28
+ table: 'roles',
29
+ on: 'users.role_id = roles.id',
30
+ },
31
+ }).execute();
32
+
33
+ console.log('Users with roles:', usersWithRoles.results);
34
+ ```
35
+
36
+ ### LEFT JOIN
37
+
38
+ A LEFT JOIN (or LEFT OUTER JOIN) returns all rows from the left table and the matching rows from the right table. If there's no match in the right table, columns from the right table will contain `NULL` values.
39
+
40
+ ```typescript
41
+ import { D1QB } from 'workers-qb';
42
+
43
+ // ... (D1QB initialization) ...
44
+
45
+ type UserWithOptionalRole = {
46
+ userName: string;
47
+ roleName: string | null; // Role can be null if no match
48
+ };
49
+
50
+ const usersWithOptionalRoles = await qb.fetchAll<UserWithOptionalRole>({
51
+ tableName: 'users',
52
+ fields: ['users.name AS userName', 'roles.name AS roleName'],
53
+ join: {
54
+ type: 'LEFT', // or 'LEFT JOIN' or 'LEFT OUTER JOIN'
55
+ table: 'roles',
56
+ on: 'users.role_id = roles.id',
57
+ },
58
+ }).execute();
59
+
60
+ console.log('Users with optional roles:', usersWithOptionalRoles.results);
61
+ ```
62
+
63
+ ### CROSS JOIN
64
+
65
+ A CROSS JOIN returns the Cartesian product of rows from the tables in the join. It combines each row from the first table with each row from the second table. **Use CROSS JOIN with caution, as it can result in very large result sets.**
66
+
67
+ ```typescript
68
+ import { D1QB } from 'workers-qb';
69
+
70
+ // ... (D1QB initialization) ...
71
+
72
+ type UserAndProduct = {
73
+ userName: string;
74
+ productName: string;
75
+ };
76
+
77
+ const userProductCombinations = await qb.fetchAll<UserAndProduct>({
78
+ tableName: 'users',
79
+ fields: ['users.name AS userName', 'products.name AS productName'],
80
+ join: {
81
+ type: 'CROSS', // or 'CROSS JOIN'
82
+ table: 'products',
83
+ on: '1=1', // Condition is usually '1=1' for CROSS JOIN
84
+ },
85
+ }).execute();
86
+
87
+ console.log('User and product combinations:', userProductCombinations.results);
88
+ ```
89
+
90
+ ## Subqueries
91
+
92
+ `workers-qb` supports using subqueries within your `WHERE`, `HAVING` and `JOIN` clauses, allowing for more complex and powerful queries.
93
+
94
+ ### `IN` with a Subquery
95
+
96
+ Use a subquery to filter results based on a dynamic set of values.
97
+
98
+ #### Using a `SelectBuilder` instance
99
+
100
+ ```typescript
101
+ import { D1QB } from 'workers-qb';
102
+
103
+ // ... (D1QB initialization) ...
104
+
105
+ // Subquery: Get IDs of all active projects
106
+ const activeProjectsSubquery = qb
107
+ .select('projects')
108
+ .fields('id')
109
+ .where('status = ?', 'active');
110
+
111
+ // Main query: Get tasks that belong to active projects
112
+ const tasksInActiveProjects = await qb
113
+ .select('tasks')
114
+ .where('project_id IN ?', activeProjectsSubquery)
115
+ .execute();
116
+
117
+ console.log(tasksInActiveProjects.results);
118
+ // SQL: SELECT * FROM tasks WHERE project_id IN (SELECT id FROM projects WHERE status = 'active')
119
+ ```
120
+
121
+ ### `EXISTS` with a Subquery
122
+
123
+ Check for the existence of rows in a subquery. This is useful for conditional filtering.
124
+
125
+ ```typescript
126
+ import { D1QB } from 'workers-qb';
127
+
128
+ // ... (D1QB initialization) ...
129
+
130
+ // Subquery: Check if a user has the 'edit' permission for a document
131
+ const hasEditPermissionSubquery = qb
132
+ .select('permissions')
133
+ .where('user_id = ?', 100)
134
+ .where('action = ?', 'edit');
135
+
136
+ // Main query: Select documents where the user has 'edit' permission
137
+ const editableDocuments = await qb
138
+ .select('documents')
139
+ .where('EXISTS ?', hasEditPermissionSubquery)
140
+ .execute();
141
+
142
+ console.log(editableDocuments.results);
143
+ // SQL: SELECT * FROM documents WHERE EXISTS (SELECT * FROM permissions WHERE user_id = 100 AND action = 'edit')
144
+ ```
145
+
146
+ ### Scalar Subquery
147
+
148
+ Use a subquery that returns a single value (a scalar) to compare against a column.
149
+
150
+ ```typescript
151
+ import { D1QB } from 'workers-qb';
152
+
153
+ // ... (D1QB initialization) ...
154
+
155
+ // Subquery: Get the default role ID from a settings table
156
+ const defaultRoleSubquery = qb
157
+ .select('settings')
158
+ .fields('value')
159
+ .where('key = ?', 'default_role')
160
+ .limit(1);
161
+
162
+ // Main query: Find all users who have the default role
163
+ const usersWithDefaultRole = await qb
164
+ .select('users')
165
+ .where('role_id = ?', defaultRoleSubquery)
166
+ .execute();
167
+
168
+ console.log(usersWithDefaultRole.results);
169
+ // SQL: SELECT * FROM users WHERE role_id = (SELECT value FROM settings WHERE key = 'default_role' LIMIT 1)
170
+ ```
171
+
172
+ ### Subqueries in `HAVING` Clauses
173
+
174
+ You can also use subqueries within a `HAVING` clause to filter grouped results.
175
+
176
+ ```typescript
177
+ import { D1QB } from 'workers-qb';
178
+
179
+ // ... (D1QB initialization) ...
180
+
181
+ // Subquery: Get IDs of customers with total order value over 1000
182
+ const highValueCustomersSubquery = qb
183
+ .select('orders')
184
+ .fields('customer_id')
185
+ .groupBy('customer_id')
186
+ .having('SUM(total) > ?', 1000);
187
+
188
+ // Main query: Get customer details for high-value customers
189
+ const customerDetails = await qb
190
+ .select('customers')
191
+ .fields(['id', 'name'])
192
+ .having('id IN ?', highValueCustomersSubquery)
193
+ .execute();
194
+
195
+ console.log(customerDetails.results);
196
+ // SQL: SELECT id, name FROM customers HAVING id IN (SELECT customer_id FROM orders GROUP BY customer_id HAVING SUM(total) > 1000)
197
+ ```
198
+
199
+ ### Subqueries in `JOIN` Clauses
200
+
201
+ You can use a subquery as a derived table in a `JOIN` clause. This is useful for complex aggregations or when you need to join against a pre-filtered or pre-aggregated set of data.
202
+
203
+ ```typescript
204
+ import { D1QB } from 'workers-qb';
205
+
206
+ // ... (D1QB initialization) ...
207
+
208
+ // Main query: Get customer details along with their total order count
209
+ const customerOrderCounts = await qb
210
+ .select('customers')
211
+ .fields(['customers.name', 'oc.order_count'])
212
+ .join({
213
+ table: (qb) =>
214
+ qb
215
+ .select('orders')
216
+ .fields(['customer_id', 'COUNT(id) as order_count'])
217
+ .groupBy('customer_id'),
218
+ alias: 'oc',
219
+ on: 'customers.id = oc.customer_id',
220
+ })
221
+ .execute();
222
+
223
+ console.log(customerOrderCounts.results);
224
+ // SQL: SELECT customers.name, oc.order_count FROM customers JOIN (SELECT customer_id, COUNT(id) as order_count FROM orders GROUP BY customer_id) AS oc ON customers.id = oc.customer_id
225
+ ```
226
+
227
+ ## Modular Select Queries
228
+
229
+ `workers-qb` provides a modular `select()` builder for constructing SELECT queries in a chainable and readable manner.
230
+
231
+ ### Introduction to SelectBuilder
232
+
233
+ The `select()` method initiates a `SelectBuilder` instance, allowing you to progressively build your SELECT query by chaining methods.
234
+
235
+ ```typescript
236
+ import { D1QB } from 'workers-qb';
237
+
238
+ // ... (D1QB initialization) ...
239
+
240
+ const selectBuilder = qb.select('users'); // Start building a select query on 'users' table
241
+ ```
242
+
243
+ ### Chaining Methods
244
+
245
+ You can chain various methods on the `SelectBuilder` to define different parts of your query:
246
+
247
+ * `.fields()`: Specify the columns to select.
248
+ * `.where()`: Add WHERE conditions.
249
+ * `.join()`: Add JOIN clauses.
250
+ * `.groupBy()`: Add GROUP BY clause.
251
+ * `.having()`: Add HAVING clause.
252
+ * `.orderBy()`: Add ORDER BY clause.
253
+ * `.limit()`: Add LIMIT clause.
254
+ * `.offset()`: Add OFFSET clause.
255
+
256
+ ```typescript
257
+ import { D1QB } from 'workers-qb';
258
+
259
+ // ... (D1QB initialization) ...
260
+
261
+ type UserInfo = {
262
+ name: string;
263
+ email: string;
264
+ roleName: string;
265
+ };
266
+
267
+ const usersInfo = await qb.select<UserInfo>('users')
268
+ .fields(['users.name', 'users.email', 'roles.name AS roleName'])
269
+ .join({
270
+ type: 'LEFT',
271
+ table: 'roles',
272
+ on: 'users.role_id = roles.id',
273
+ })
274
+ .where('users.is_active = ?', true)
275
+ .orderBy('users.name ASC')
276
+ .limit(10)
277
+ .execute();
278
+
279
+ console.log('Users info:', usersInfo.results);
280
+ ```
281
+
282
+ ### Executing Queries
283
+
284
+ The `SelectBuilder` provides methods to execute the built query and retrieve results:
285
+
286
+ * `.execute()`: Executes the query and returns `ArrayResult` or `OneResult` based on the nature of the constructed query (e.g., if `.limit(1)` is used, it might behave like `fetchOne`).
287
+ * `.all()`: Explicitly executes as `fetchAll` and returns `ArrayResult`.
288
+ * `.one()`: Explicitly executes as `fetchOne` and returns `OneResult`.
289
+ * `.count()`: Executes a `COUNT(*)` query based on the current builder configuration (ignoring fields, limit, offset, orderBy) and returns `CountResult`.
290
+
291
+ ```typescript
292
+ import { D1QB } from 'workers-qb';
293
+
294
+ // ... (D1QB initialization) ...
295
+
296
+ const activeUserCount = await qb.select('users')
297
+ .where('is_active = ?', true)
298
+ .count(); // Executes COUNT(*) query
299
+
300
+ console.log('Active user count:', activeUserCount.results?.total);
301
+
302
+ const firstActiveUser = await qb.select<UserInfo>('users')
303
+ .where('is_active = ?', true)
304
+ .orderBy('name ASC')
305
+ .limit(1)
306
+ .one(); // Executes fetchOne query
307
+
308
+ console.log('First active user:', firstActiveUser.results);
309
+ ```
310
+
311
+ ## Where Clauses
312
+
313
+ `workers-qb` provides flexible ways to define WHERE conditions.
314
+
315
+ ### String Conditions
316
+
317
+ You can use a simple string to define your WHERE clause. Use `?` placeholders for parameterized queries to prevent SQL injection.
318
+
319
+ ```typescript
320
+ import { D1QB } from 'workers-qb';
321
+
322
+ // ... (D1QB initialization) ...
323
+
324
+ const usersByName = await qb.fetchAll({
325
+ tableName: 'users',
326
+ where: 'name LIKE ?', // String condition
327
+ params: 'J%',
328
+ }).execute();
329
+
330
+ console.log('Users starting with "J":', usersByName.results);
331
+ ```
332
+
333
+ ### Object Conditions
334
+
335
+ For more structured conditions, you can use an object with `conditions` and `params` properties. `conditions` can be a string or an array of strings (joined by `AND`).
336
+
337
+ ```typescript
338
+ import { D1QB } from 'workers-qb';
339
+
340
+ // ... (D1QB initialization) ...
341
+
342
+ const usersByRoleAndActive = await qb.fetchAll({
343
+ tableName: 'users',
344
+ where: {
345
+ conditions: [
346
+ 'role_id = ?',
347
+ 'is_active = ?',
348
+ ], // Array of conditions, joined by AND
349
+ params: [2, true], // Parameters for each condition
350
+ },
351
+ }).execute();
352
+
353
+ console.log('Active users in role 2:', usersByRoleAndActive.results);
354
+ ```
355
+
356
+ ### `whereIn` Clause
357
+
358
+ The `whereIn` method provides a convenient way to filter records based on a set of values for a specific column or columns.
359
+
360
+ ```typescript
361
+ import { D1QB } from 'workers-qb';
362
+
363
+ // ... (D1QB initialization) ...
364
+
365
+ const usersInSpecificRoles = await qb.select('users')
366
+ .whereIn('role_id', [1, 2, 3]) // Filter users with role_id in [1, 2, 3]
367
+ .execute();
368
+
369
+ console.log('Users in roles 1, 2, or 3:', usersInSpecificRoles.results);
370
+
371
+ const usersInSpecificRolesMultipleColumns = await qb.select('users')
372
+ .whereIn(['role_id', 'department_id'], [[1, 101], [2, 102]]) // Filter users with (role_id, department_id) in [(1, 101), (2, 102)]
373
+ .execute();
374
+
375
+ console.log('Users in specific role and department combinations:', usersInSpecificRolesMultipleColumns.results);
376
+ ```
377
+
378
+ ## Group By and Having
379
+
380
+ ### Group By Clause
381
+
382
+ Use the `groupBy` method to group rows with the same values in one or more columns into summary rows.
383
+
384
+ ```typescript
385
+ import { D1QB } from 'workers-qb';
386
+
387
+ // ... (D1QB initialization) ...
388
+
389
+ type RoleUserCount = {
390
+ roleName: string;
391
+ userCount: number;
392
+ };
393
+
394
+ const userCountsByRole = await qb.fetchAll<RoleUserCount>({
395
+ tableName: 'users',
396
+ fields: ['roles.name AS roleName', 'COUNT(users.id) AS userCount'],
397
+ join: {
398
+ type: 'INNER',
399
+ table: 'roles',
400
+ on: 'users.role_id = roles.id',
401
+ },
402
+ groupBy: 'roles.name', // Group by role name
403
+ }).execute();
404
+
405
+ console.log('User counts by role:', userCountsByRole.results);
406
+ ```
407
+
408
+ ### Having Clause
409
+
410
+ The `having` method filters groups based on aggregate functions, similar to WHERE but for grouped rows.
411
+
412
+ ```typescript
413
+ import { D1QB } from 'workers-qb';
414
+
415
+ // ... (D1QB initialization) ...
416
+
417
+ type RoleUserCount = {
418
+ roleName: string;
419
+ userCount: number;
420
+ };
421
+
422
+ const rolesWithMoreThan5Users = await qb.fetchAll<RoleUserCount>({
423
+ tableName: 'users',
424
+ fields: ['roles.name AS roleName', 'COUNT(users.id) AS userCount'],
425
+ join: {
426
+ type: 'INNER',
427
+ table: 'roles',
428
+ on: 'users.role_id = roles.id',
429
+ },
430
+ groupBy: 'roles.name',
431
+ having: 'COUNT(users.id) > 5', // Filter groups with user count greater than 5
432
+ }).execute();
433
+
434
+ console.log('Roles with more than 5 users:', rolesWithMoreThan5Users.results);
435
+ ```
436
+
437
+ ## Order By
438
+
439
+ ### Simple Order By
440
+
441
+ Use the `orderBy` method to sort the result set by one or more columns. By default, it sorts in ascending order (ASC).
442
+
443
+ ```typescript
444
+ import { D1QB } from 'workers-qb';
445
+
446
+ // ... (D1QB initialization) ...
447
+
448
+ type User = {
449
+ id: number;
450
+ name: string;
451
+ email: string;
452
+ };
453
+
454
+ const usersOrderedByName = await qb.fetchAll<User>({
455
+ tableName: 'users',
456
+ orderBy: 'name', // Order by name in ascending order
457
+ }).execute();
458
+
459
+ console.log('Users ordered by name:', usersOrderedByName.results);
460
+ ```
461
+
462
+ ### Order By with Direction (ASC/DESC)
463
+
464
+ Specify the sorting direction (ASC for ascending, DESC for descending) for each column.
465
+
466
+ ```typescript
467
+ import { D1QB } from 'workers-qb';
468
+
469
+ // ... (D1QB initialization) ...
470
+
471
+ const usersOrderedByNameDesc = await qb.fetchAll<User>({
472
+ tableName: 'users',
473
+ orderBy: { name: 'DESC' }, // Order by name in descending order
474
+ }).execute();
475
+
476
+ console.log('Users ordered by name (descending):', usersOrderedByNameDesc.results);
477
+ ```
478
+
479
+ ### Multiple Order By
480
+
481
+ Order by multiple columns by providing an array or an object to `orderBy`.
482
+
483
+ ```typescript
484
+ import { D1QB } from 'workers-qb';
485
+
486
+ // ... (D1QB initialization) ...
487
+
488
+ const usersOrderedByRoleNameAndName = await qb.fetchAll<User>({
489
+ tableName: 'users',
490
+ orderBy: [
491
+ { role_id: 'ASC' }, // Order by role_id ascending first
492
+ 'name DESC', // Then by name descending
493
+ ],
494
+ }).execute();
495
+
496
+ console.log('Users ordered by role and name:', usersOrderedByRoleNameAndName.results);
497
+ ```
498
+
499
+ ## Limit and Offset
500
+
501
+ ### Limit Clause
502
+
503
+ Use the `limit` method to restrict the number of rows returned by the query.
504
+
505
+ ```typescript
506
+ import { D1QB } from 'workers-qb';
507
+
508
+ // ... (D1QB initialization) ...
509
+
510
+ type User = {
511
+ id: number;
512
+ name: string;
513
+ email: string;
514
+ };
515
+
516
+ const first5Users = await qb.fetchAll<User>({
517
+ tableName: 'users',
518
+ limit: 5, // Limit results to 5 rows
519
+ }).execute();
520
+
521
+ console.log('First 5 users:', first5Users.results);
522
+ ```
523
+
524
+ ### Offset Clause
525
+
526
+ Use the `offset` method to skip a certain number of rows before starting to return the result set. Useful for pagination.
527
+
528
+ ```typescript
529
+ import { D1QB } from 'workers-qb';
530
+
531
+ // ... (D1QB initialization) ...
532
+
533
+ type User = {
534
+ id: number;
535
+ name: string;
536
+ email: string;
537
+ };
538
+
539
+ const usersPage2 = await qb.fetchAll<User>({
540
+ tableName: 'users',
541
+ limit: 10, // Page size of 10
542
+ offset: 10, // Skip first 10 rows to get page 2
543
+ }).execute();
544
+
545
+ console.log('Users page 2:', usersPage2.results);
546
+ ```
547
+
548
+ ## Raw Queries
549
+
550
+ For scenarios where you need to execute highly specific or complex SQL queries that are not easily constructed using the builder methods, `workers-qb` allows you to execute raw SQL queries.
551
+
552
+ ### Executing Raw SQL Queries
553
+
554
+ Use the `raw` method to execute a raw SQL query string. You can provide parameterized arguments as an array.
555
+
556
+ ```typescript
557
+ import { D1QB } from 'workers-qb';
558
+
559
+ // ... (D1QB initialization) ...
560
+
561
+ const tableName = 'users';
562
+ const columnName = 'email';
563
+
564
+ const rawQueryResults = await qb.raw({
565
+ query: `SELECT COUNT(*) AS userCount FROM ${tableName} WHERE LENGTH(${columnName}) > ?`,
566
+ args: [10], // Parameter for minimum email length
567
+ }).execute();
568
+
569
+ console.log('Raw query results:', rawQueryResults.results);
570
+ ```
571
+
572
+ ### Fetching Results from Raw Queries
573
+
574
+ When using `raw`, you can specify the `fetchType` to indicate whether you expect to fetch one row (`FetchTypes.ONE`) or multiple rows (`FetchTypes.ALL`). If you don't specify `fetchType`, the query will be executed without fetching results (useful for `INSERT`, `UPDATE`, `DELETE` raw queries).
575
+
576
+ ```typescript
577
+ import { D1QB } from 'workers-qb';
578
+
579
+ // ... (D1QB initialization) ...
580
+
581
+ type User = {
582
+ id: number;
583
+ name: string;
584
+ email: string;
585
+ };
586
+
587
+ const rawUsers = await qb.raw<User>({
588
+ query: 'SELECT id, name, email FROM users WHERE is_active = ?',
589
+ args: [true],
590
+ fetchType: 'ALL', // Specify FetchTypes.ALL to fetch multiple rows
591
+ }).execute();
592
+
593
+ console.log('Raw users:', rawUsers.results);
594
+
595
+ const rawSingleUser = await qb.raw<User>({
596
+ query: 'SELECT id, name, email FROM users WHERE email = ?',
597
+ args: ['john.doe@example.com'],
598
+ fetchType: 'ONE', // Specify FetchTypes.ONE to fetch a single row
599
+ }).execute();
600
+
601
+ console.log('Raw single user:', rawSingleUser.results);
602
+ ```
@@ -0,0 +1,119 @@
1
+ # Background Writes with `waitUntil`
2
+
3
+ This guide explains how to perform "fire-and-forget" database operations using Cloudflare's `waitUntil` method. This is particularly useful for tasks that don't need to block the response to the user, such as logging, analytics, or other non-critical writes.
4
+
5
+ ## How it Works
6
+
7
+ Cloudflare Workers can continue to execute code for a short period after the response has been sent to the client by using the `ctx.waitUntil()` method, where `ctx` is the `ExecutionContext` of your Worker's `fetch` handler.
8
+
9
+ By wrapping a `workers-qb` query promise in `waitUntil`, you ensure that the query is executed without making the user wait for the database operation to complete.
10
+
11
+ **Note:** This approach is suitable for operations where you don't need to return the result to the user.
12
+
13
+ ## Examples
14
+
15
+ Here are some examples of how to use `waitUntil` for different types of write operations.
16
+
17
+ ### Background `insert`
18
+
19
+ This is useful for logging requests, tracking events, or any other scenario where you need to add a record without delaying the user's response.
20
+
21
+ ```typescript
22
+ import { D1QB } from 'workers-qb';
23
+
24
+ export interface Env {
25
+ DB: D1Database;
26
+ }
27
+
28
+ export default {
29
+ async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
30
+ const qb = new D1QB(env.DB);
31
+ const url = new URL(request.url);
32
+
33
+ // Don't await this promise, let it run in the background
34
+ const insertPromise = qb.insert({
35
+ tableName: 'logs',
36
+ data: {
37
+ method: request.method,
38
+ path: url.pathname,
39
+ timestamp: new Date().toISOString(),
40
+ },
41
+ }).execute();
42
+
43
+ ctx.waitUntil(insertPromise);
44
+
45
+ return new Response('Request logged in the background!', { status: 200 });
46
+ },
47
+ };
48
+ ```
49
+
50
+ ### Background `update`
51
+
52
+ You can update records in the background, for example, to increment a counter or update a user's last-seen timestamp.
53
+
54
+ ```typescript
55
+ import { D1QB, Raw } from 'workers-qb';
56
+
57
+ export interface Env {
58
+ DB: D1Database;
59
+ }
60
+
61
+ export default {
62
+ async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
63
+ const qb = new D1QB(env.DB);
64
+ const userId = request.headers.get('X-User-ID');
65
+
66
+ if (userId) {
67
+ // Don't await this promise
68
+ const updatePromise = qb.update({
69
+ tableName: 'users',
70
+ data: {
71
+ last_seen: new Raw('CURRENT_TIMESTAMP'),
72
+ },
73
+ where: {
74
+ conditions: 'id = ?',
75
+ params: userId,
76
+ },
77
+ }).execute();
78
+
79
+ ctx.waitUntil(updatePromise);
80
+ }
81
+
82
+ return new Response('User activity updated in the background.', { status: 200 });
83
+ },
84
+ };
85
+ ```
86
+
87
+ ### Background `delete`
88
+
89
+ Perform cleanup operations, such as deleting expired sessions or old data, without affecting the response time.
90
+
91
+ ```typescript
92
+ import { D1QB } from 'workers-qb';
93
+
94
+ export interface Env {
95
+ DB: D1Database;
96
+ }
97
+
98
+ export default {
99
+ async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
100
+ const qb = new D1QB(env.DB);
101
+ const sessionId = request.headers.get('X-Session-ID');
102
+
103
+ if (sessionId) {
104
+ // Don't await this promise
105
+ const deletePromise = qb.delete({
106
+ tableName: 'sessions',
107
+ where: {
108
+ conditions: 'id = ?',
109
+ params: sessionId,
110
+ },
111
+ }).execute();
112
+
113
+ ctx.waitUntil(deletePromise);
114
+ }
115
+
116
+ return new Response('Session invalidated in the background.', { status: 200 });
117
+ },
118
+ };
119
+ ```