@event-driven-io/dumbo 0.13.0-beta.46 → 0.13.0-beta.47

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 (43) hide show
  1. package/dist/cloudflare.cjs +432 -69
  2. package/dist/cloudflare.cjs.map +1 -1
  3. package/dist/cloudflare.d.cts +2 -2
  4. package/dist/cloudflare.d.ts +2 -2
  5. package/dist/cloudflare.js +432 -69
  6. package/dist/cloudflare.js.map +1 -1
  7. package/dist/{index-B5krjjrh.d.ts → index-4hcCG4SA.d.ts} +66 -30
  8. package/dist/{index-BaLXbc2l.d.ts → index-B7366zWR.d.ts} +1 -1
  9. package/dist/{index-_dj3upBo.d.cts → index-BJ-dm7dG.d.cts} +66 -30
  10. package/dist/{index-kYttgYkC.d.cts → index-BkJ1pipM.d.cts} +1 -1
  11. package/dist/{index-C9B46a1u.d.ts → index-CQokD3za.d.cts} +2 -2
  12. package/dist/{index-BoLWBnxd.d.cts → index-DIJv-L6M.d.ts} +2 -2
  13. package/dist/index.cjs +523 -269
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +2 -2
  16. package/dist/index.d.ts +2 -2
  17. package/dist/index.js +522 -270
  18. package/dist/index.js.map +1 -1
  19. package/dist/pg.cjs +168 -28
  20. package/dist/pg.cjs.map +1 -1
  21. package/dist/pg.d.cts +2 -2
  22. package/dist/pg.d.ts +2 -2
  23. package/dist/pg.js +168 -28
  24. package/dist/pg.js.map +1 -1
  25. package/dist/postgresql.cjs +4 -0
  26. package/dist/postgresql.cjs.map +1 -1
  27. package/dist/postgresql.d.cts +2 -2
  28. package/dist/postgresql.d.ts +2 -2
  29. package/dist/postgresql.js +4 -0
  30. package/dist/postgresql.js.map +1 -1
  31. package/dist/sqlite.cjs +429 -68
  32. package/dist/sqlite.cjs.map +1 -1
  33. package/dist/sqlite.d.cts +2 -2
  34. package/dist/sqlite.d.ts +2 -2
  35. package/dist/sqlite.js +429 -68
  36. package/dist/sqlite.js.map +1 -1
  37. package/dist/sqlite3.cjs +639 -343
  38. package/dist/sqlite3.cjs.map +1 -1
  39. package/dist/sqlite3.d.cts +10 -10
  40. package/dist/sqlite3.d.ts +10 -10
  41. package/dist/sqlite3.js +639 -343
  42. package/dist/sqlite3.js.map +1 -1
  43. package/package.json +1 -1
package/dist/sqlite3.js CHANGED
@@ -2,7 +2,6 @@ import { v7 } from "uuid";
2
2
  import ansis from "ansis";
3
3
  import sqlite3 from "sqlite3";
4
4
  import { cpus } from "os";
5
- import { AsyncLocalStorage } from "node:async_hooks";
6
5
 
7
6
  //#region src/core/errors/index.ts
8
7
  const isNumber = (val) => typeof val === "number" && val === val;
@@ -175,6 +174,420 @@ var InvalidOperationError = class InvalidOperationError extends DumboError {
175
174
  }
176
175
  };
177
176
 
177
+ //#endregion
178
+ //#region src/core/taskProcessing/abort.ts
179
+ const never = { signal: new AbortController().signal };
180
+ const from = (options) => options?.abort ?? never;
181
+ const getSignal = (abort) => "signal" in abort ? abort.signal : abort;
182
+ const reason = (abort) => {
183
+ const signal = getSignal(abort);
184
+ return signal.reason instanceof Error ? signal.reason : new DumboError(typeof signal.reason === "string" ? signal.reason : "Operation aborted");
185
+ };
186
+ const scope = (parent, onAbort) => {
187
+ const controller = new AbortController();
188
+ return {
189
+ abort: (reason) => controller.abort(reason),
190
+ dispose: onAbortSignal(parent, (reason) => {
191
+ controller.abort(reason);
192
+ onAbort?.(reason);
193
+ }),
194
+ signal: controller.signal
195
+ };
196
+ };
197
+ const execute = (operation, options) => {
198
+ const abort = options?.abort;
199
+ if (!abort) return operation();
200
+ const signal = abort.signal;
201
+ if (signal.aborted) return Promise.reject(reason(abort));
202
+ return new Promise((resolve, reject) => {
203
+ let finished = false;
204
+ const rejectOnAbort = () => {
205
+ if (finished) return;
206
+ finished = true;
207
+ reject(reason(abort));
208
+ };
209
+ signal.addEventListener("abort", rejectOnAbort, { once: true });
210
+ let operationPromise;
211
+ try {
212
+ operationPromise = operation();
213
+ } catch (error) {
214
+ finished = true;
215
+ signal.removeEventListener("abort", rejectOnAbort);
216
+ reject(error);
217
+ return;
218
+ }
219
+ operationPromise.then((result) => {
220
+ if (finished) return;
221
+ finished = true;
222
+ signal.removeEventListener("abort", rejectOnAbort);
223
+ resolve(result);
224
+ }).catch((error) => {
225
+ if (finished) return;
226
+ finished = true;
227
+ signal.removeEventListener("abort", rejectOnAbort);
228
+ reject(error);
229
+ });
230
+ });
231
+ };
232
+ const throwIfAborted = (options) => {
233
+ const abort = options?.abort;
234
+ if (abort?.signal.aborted) throw reason(abort);
235
+ };
236
+ const rejectIfAborted = (options) => {
237
+ const abort = options?.abort;
238
+ return abort?.signal.aborted ? Promise.reject(reason(abort)) : void 0;
239
+ };
240
+ const onAbortSignal = (abort, handle) => {
241
+ if (!abort) return () => {};
242
+ const signal = abort.signal;
243
+ if (signal.aborted) {
244
+ handle(reason(abort));
245
+ return () => {};
246
+ }
247
+ const abortListener = () => handle(reason(abort));
248
+ signal.addEventListener("abort", abortListener, { once: true });
249
+ return () => signal.removeEventListener("abort", abortListener);
250
+ };
251
+ const Abort = {
252
+ execute,
253
+ from,
254
+ never,
255
+ onAbort: onAbortSignal,
256
+ reason,
257
+ rejectIfAborted,
258
+ scope,
259
+ throwIfAborted
260
+ };
261
+
262
+ //#endregion
263
+ //#region src/core/taskProcessing/taskProcessor.ts
264
+ var TaskProcessor = class {
265
+ queue = [];
266
+ isProcessing = false;
267
+ activeTasks = 0;
268
+ activeGroups = /* @__PURE__ */ new Set();
269
+ options;
270
+ stopped = false;
271
+ idleWaiters = [];
272
+ activeTaskAbortCallbacks = /* @__PURE__ */ new Set();
273
+ constructor(options) {
274
+ this.options = options;
275
+ }
276
+ enqueue(task, options) {
277
+ if (options?.abort?.signal.aborted) return Promise.reject(Abort.reason(options.abort.signal));
278
+ if (this.stopped) return Promise.reject(new DumboError("TaskProcessor has been stopped"));
279
+ if (this.queue.length >= this.options.maxQueueSize) return Promise.reject(new TransientDatabaseError("Too many pending connections. Please try again later."));
280
+ return this.schedule(task, options);
281
+ }
282
+ waitForEndOfProcessing() {
283
+ if (this.activeTasks === 0 && this.queue.length === 0) return Promise.resolve();
284
+ return new Promise((resolve) => {
285
+ this.idleWaiters.push(resolve);
286
+ });
287
+ }
288
+ async stop(options) {
289
+ if (this.stopped) return;
290
+ this.stopped = true;
291
+ const stoppedError = new DumboError("TaskProcessor has been stopped");
292
+ for (const item of this.queue.splice(0)) {
293
+ item.abort(stoppedError);
294
+ item.reject(stoppedError);
295
+ }
296
+ this.activeGroups.clear();
297
+ if (options?.force) for (const abort of this.activeTaskAbortCallbacks) abort(stoppedError);
298
+ if (options?.force) return;
299
+ if (options?.closeDeadline === void 0) {
300
+ await this.waitForEndOfProcessing();
301
+ return;
302
+ }
303
+ if (!await waitForProcessingOrDeadline(this.waitForEndOfProcessing(), options.closeDeadline)) for (const abort of this.activeTaskAbortCallbacks) abort(stoppedError);
304
+ }
305
+ schedule(task, options) {
306
+ return new Promise((resolve, reject) => {
307
+ let didQueueTimeout = false;
308
+ let didAbortBeforeStart = false;
309
+ let didStart = false;
310
+ let queueWaitTimer = noopQueueWaitTimer;
311
+ const abortScope = Abort.scope(options?.abort, (reason) => {
312
+ queueWaitTimer.cancel();
313
+ didAbortBeforeStart = !didStart;
314
+ reject(reason);
315
+ });
316
+ queueWaitTimer = createQueueWaitTimer(this.options.maxTaskIdleTime, (reason) => {
317
+ didQueueTimeout = true;
318
+ abortScope.abort(reason);
319
+ abortScope.dispose();
320
+ reject(reason);
321
+ });
322
+ const taskWithContext = () => {
323
+ return new Promise((resolveTask) => {
324
+ if (didQueueTimeout || didAbortBeforeStart) {
325
+ resolveTask();
326
+ return;
327
+ }
328
+ let taskPromise;
329
+ try {
330
+ taskPromise = task({
331
+ ack: resolveTask,
332
+ abort: abortScope
333
+ });
334
+ } catch (err) {
335
+ abortScope.dispose();
336
+ resolveTask();
337
+ reject(err);
338
+ return;
339
+ }
340
+ taskPromise.then((result) => {
341
+ abortScope.dispose();
342
+ resolve(result);
343
+ }).catch((err) => {
344
+ abortScope.dispose();
345
+ resolveTask();
346
+ reject(err);
347
+ });
348
+ });
349
+ };
350
+ this.queue.push({
351
+ task: taskWithContext,
352
+ options,
353
+ reject: (reason) => {
354
+ queueWaitTimer.cancel();
355
+ abortScope.dispose();
356
+ reject(reason);
357
+ },
358
+ markStarted: () => {
359
+ didStart = true;
360
+ queueWaitTimer.cancel();
361
+ },
362
+ abort: (reason) => {
363
+ abortScope.dispose();
364
+ abortScope.abort(reason);
365
+ }
366
+ });
367
+ if (!this.isProcessing) this.ensureProcessing();
368
+ });
369
+ }
370
+ ensureProcessing() {
371
+ if (this.isProcessing) return;
372
+ this.isProcessing = true;
373
+ this.processQueue();
374
+ }
375
+ processQueue() {
376
+ try {
377
+ while (this.activeTasks < this.options.maxActiveTasks && this.queue.length > 0) {
378
+ const item = this.takeFirstAvailableItem();
379
+ if (item === null) return;
380
+ const groupId = item.options?.taskGroupId;
381
+ if (groupId) this.activeGroups.add(groupId);
382
+ this.activeTasks++;
383
+ this.executeItem(item);
384
+ }
385
+ } catch (error) {
386
+ console.error(error);
387
+ throw error;
388
+ } finally {
389
+ this.isProcessing = false;
390
+ if (this.hasItemsToProcess() && this.activeTasks < this.options.maxActiveTasks) this.ensureProcessing();
391
+ }
392
+ }
393
+ async executeItem({ task, options, markStarted, abort }) {
394
+ markStarted();
395
+ this.activeTaskAbortCallbacks.add(abort);
396
+ try {
397
+ await task();
398
+ } finally {
399
+ this.activeTaskAbortCallbacks.delete(abort);
400
+ this.activeTasks--;
401
+ if (options && options.taskGroupId) this.activeGroups.delete(options.taskGroupId);
402
+ this.resolveIdleWaiters();
403
+ this.ensureProcessing();
404
+ }
405
+ }
406
+ takeFirstAvailableItem = () => {
407
+ const taskIndex = this.queue.findIndex((item) => !item.options?.taskGroupId || !this.activeGroups.has(item.options.taskGroupId));
408
+ if (taskIndex === -1) return null;
409
+ const [item] = this.queue.splice(taskIndex, 1);
410
+ return item ?? null;
411
+ };
412
+ hasItemsToProcess = () => this.queue.findIndex((item) => !item.options?.taskGroupId || !this.activeGroups.has(item.options.taskGroupId)) !== -1;
413
+ resolveIdleWaiters = () => {
414
+ if (this.activeTasks > 0 || this.queue.length > 0) return;
415
+ const waiters = this.idleWaiters.splice(0);
416
+ for (const resolve of waiters) resolve();
417
+ };
418
+ };
419
+ const noopQueueWaitTimer = { cancel: () => {} };
420
+ const createQueueWaitTimer = (timeoutMs, reject) => {
421
+ if (timeoutMs === void 0) return noopQueueWaitTimer;
422
+ let timeoutId = setTimeout(() => {
423
+ reject(/* @__PURE__ */ new Error("Task was not started within the maximum waiting time"));
424
+ }, timeoutMs);
425
+ timeoutId.unref();
426
+ return { cancel: () => {
427
+ if (!timeoutId) return;
428
+ clearTimeout(timeoutId);
429
+ timeoutId = null;
430
+ } };
431
+ };
432
+ const waitForProcessingOrDeadline = async (processing, closeDeadline) => {
433
+ let timeoutId = null;
434
+ try {
435
+ return await Promise.race([processing.then(() => true), new Promise((resolve) => {
436
+ timeoutId = setTimeout(() => resolve(false), closeDeadline);
437
+ timeoutId.unref();
438
+ })]);
439
+ } finally {
440
+ if (timeoutId) clearTimeout(timeoutId);
441
+ }
442
+ };
443
+
444
+ //#endregion
445
+ //#region src/core/taskProcessing/executionGuards.ts
446
+ const guardExclusiveAccess = (options) => {
447
+ const taskProcessor = new TaskProcessor({
448
+ maxActiveTasks: 1,
449
+ maxQueueSize: options?.maxQueueSize ?? 1e3,
450
+ ...options?.maxTaskIdleTime !== void 0 ? { maxTaskIdleTime: options.maxTaskIdleTime } : {}
451
+ });
452
+ return {
453
+ execute: (operation, options) => taskProcessor.enqueue(async (context) => {
454
+ try {
455
+ return await operation(context);
456
+ } finally {
457
+ context.ack();
458
+ }
459
+ }, options),
460
+ waitForIdle: () => taskProcessor.waitForEndOfProcessing(),
461
+ stop: (options) => taskProcessor.stop(options)
462
+ };
463
+ };
464
+ const guardConcurrentAccess = (options) => {
465
+ const taskProcessor = new TaskProcessor({
466
+ maxActiveTasks: options?.maxActiveTasks ?? Number.MAX_SAFE_INTEGER,
467
+ maxQueueSize: options?.maxQueueSize ?? Number.MAX_SAFE_INTEGER,
468
+ ...options?.maxTaskIdleTime !== void 0 ? { maxTaskIdleTime: options.maxTaskIdleTime } : {}
469
+ });
470
+ return {
471
+ execute: (operation, options) => taskProcessor.enqueue(async (context) => {
472
+ try {
473
+ return await operation({ abort: context.abort });
474
+ } finally {
475
+ context.ack();
476
+ }
477
+ }, options),
478
+ waitForIdle: () => taskProcessor.waitForEndOfProcessing(),
479
+ stop: (options) => taskProcessor.stop(options)
480
+ };
481
+ };
482
+ const guardBoundedAccess = (getResource, options) => {
483
+ let isStopped = false;
484
+ const taskProcessor = new TaskProcessor({
485
+ maxActiveTasks: options.maxResources,
486
+ maxQueueSize: options.maxQueueSize ?? 1e3
487
+ });
488
+ const resourcePool = [];
489
+ const allResources = /* @__PURE__ */ new Set();
490
+ const activeResourceContexts = /* @__PURE__ */ new Map();
491
+ const acquireResource = async (taskContext) => {
492
+ try {
493
+ let resource;
494
+ if (options.reuseResources) resource = resourcePool.pop();
495
+ if (!resource) {
496
+ resource = await getResource({ abort: taskContext.abort });
497
+ allResources.add(resource);
498
+ }
499
+ activeResourceContexts.set(resource, {
500
+ ack: taskContext.ack,
501
+ taskContext
502
+ });
503
+ return resource;
504
+ } catch (e) {
505
+ taskContext.ack();
506
+ throw e;
507
+ }
508
+ };
509
+ const acquire = async (operationOptions) => taskProcessor.enqueue((taskContext) => acquireResource(taskContext), operationOptions);
510
+ const getActiveResourceContext = (resource) => {
511
+ const activeResourceContext = activeResourceContexts.get(resource);
512
+ if (!activeResourceContext) throw new Error("Acquired resource is not active");
513
+ return activeResourceContext;
514
+ };
515
+ const release = (resource) => {
516
+ const activeResourceContext = activeResourceContexts.get(resource);
517
+ if (activeResourceContext) {
518
+ activeResourceContexts.delete(resource);
519
+ if (options.reuseResources) resourcePool.push(resource);
520
+ activeResourceContext.ack();
521
+ }
522
+ };
523
+ const execute = async (operation, operationOptions) => {
524
+ return taskProcessor.enqueue(async (taskContext) => {
525
+ const resource = await acquireResource(taskContext);
526
+ const activeResourceContext = getActiveResourceContext(resource);
527
+ try {
528
+ return await operation(resource, { abort: activeResourceContext.taskContext.abort });
529
+ } finally {
530
+ release(resource);
531
+ }
532
+ }, operationOptions);
533
+ };
534
+ return {
535
+ acquire,
536
+ release,
537
+ execute,
538
+ waitForIdle: () => taskProcessor.waitForEndOfProcessing(),
539
+ stop: async (stopOptions) => {
540
+ if (isStopped) return;
541
+ isStopped = true;
542
+ await taskProcessor.stop(stopOptions);
543
+ if (options?.closeResource) {
544
+ const resources = [...allResources];
545
+ allResources.clear();
546
+ resourcePool.length = 0;
547
+ await Promise.all(resources.map(async (resource) => await options.closeResource(resource)));
548
+ }
549
+ }
550
+ };
551
+ };
552
+ const guardInitializedOnce = (initialize, options) => {
553
+ let initPromise = null;
554
+ const taskProcessor = new TaskProcessor({
555
+ maxActiveTasks: 1,
556
+ maxQueueSize: options?.maxQueueSize ?? 1e3
557
+ });
558
+ const ensureInitialized = async (operationOptions, retryCount = 0) => {
559
+ if (initPromise !== null) return initPromise;
560
+ return taskProcessor.enqueue(async ({ abort, ack }) => {
561
+ if (initPromise !== null) {
562
+ ack();
563
+ return initPromise;
564
+ }
565
+ try {
566
+ const promise = initialize({ abort });
567
+ initPromise = promise;
568
+ const result = await promise;
569
+ ack();
570
+ return result;
571
+ } catch (error) {
572
+ initPromise = null;
573
+ ack();
574
+ if (retryCount < (options?.maxRetries ?? 3)) return ensureInitialized(operationOptions, retryCount + 1);
575
+ throw error;
576
+ }
577
+ }, {
578
+ ...operationOptions,
579
+ taskGroupId: v7()
580
+ });
581
+ };
582
+ return {
583
+ ensureInitialized,
584
+ reset: () => {
585
+ initPromise = null;
586
+ },
587
+ stop: (options) => taskProcessor.stop(options)
588
+ };
589
+ };
590
+
178
591
  //#endregion
179
592
  //#region src/core/execute/execute.ts
180
593
  const mapSQLQueryResult = (result, mapping) => {
@@ -199,26 +612,63 @@ var BatchCommandNoChangesError = class BatchCommandNoChangesError extends DumboE
199
612
  }
200
613
  };
201
614
  const sqlExecutor = (sqlExecutor, options) => ({
202
- query: (sql, queryOptions) => executeInNewDbClient((client) => sqlExecutor.query(client, sql, queryOptions), options),
203
- batchQuery: (sqls, queryOptions) => executeInNewDbClient((client) => sqlExecutor.batchQuery(client, sqls, queryOptions), options),
204
- command: (sql, commandOptions) => executeInNewDbClient((client) => sqlExecutor.command(client, sql, commandOptions), options),
205
- batchCommand: (sqls, commandOptions) => executeInNewDbClient((client) => sqlExecutor.batchCommand(client, sqls, commandOptions), options)
615
+ query: (sql, queryOptions) => executeInNewDbClient((client) => sqlExecutor.query(client, sql, queryOptions), {
616
+ ...options,
617
+ ...queryOptions
618
+ }),
619
+ batchQuery: (sqls, queryOptions) => executeInNewDbClient((client) => sqlExecutor.batchQuery(client, sqls, queryOptions), {
620
+ ...options,
621
+ ...queryOptions
622
+ }),
623
+ command: (sql, commandOptions) => executeInNewDbClient((client) => sqlExecutor.command(client, sql, commandOptions), {
624
+ ...options,
625
+ ...commandOptions
626
+ }),
627
+ batchCommand: (sqls, commandOptions) => executeInNewDbClient((client) => sqlExecutor.batchCommand(client, sqls, commandOptions), {
628
+ ...options,
629
+ ...commandOptions
630
+ })
206
631
  });
207
632
  const sqlExecutorInNewConnection = (options) => ({
208
- query: (sql, queryOptions) => executeInNewConnection((connection) => connection.execute.query(sql, queryOptions), options),
209
- batchQuery: (sqls, queryOptions) => executeInNewConnection((connection) => connection.execute.batchQuery(sqls, queryOptions), options),
210
- command: (sql, commandOptions) => executeInNewConnection((connection) => connection.execute.command(sql, commandOptions), options),
211
- batchCommand: (sqls, commandOptions) => executeInNewConnection((connection) => connection.execute.batchCommand(sqls, commandOptions), options)
633
+ query: (sql, queryOptions) => executeInNewConnection((connection) => connection.execute.query(sql, queryOptions), {
634
+ ...options,
635
+ ...queryOptions
636
+ }),
637
+ batchQuery: (sqls, queryOptions) => executeInNewConnection((connection) => connection.execute.batchQuery(sqls, queryOptions), {
638
+ ...options,
639
+ ...queryOptions
640
+ }),
641
+ command: (sql, commandOptions) => executeInNewConnection((connection) => connection.execute.command(sql, commandOptions), {
642
+ ...options,
643
+ ...commandOptions
644
+ }),
645
+ batchCommand: (sqls, commandOptions) => executeInNewConnection((connection) => connection.execute.batchCommand(sqls, commandOptions), {
646
+ ...options,
647
+ ...commandOptions
648
+ })
212
649
  });
213
650
  const sqlExecutorInAmbientConnection = (options) => ({
214
- query: (sql, queryOptions) => executeInAmbientConnection((connection) => connection.execute.query(sql, queryOptions), options),
215
- batchQuery: (sqls, queryOptions) => executeInAmbientConnection((connection) => connection.execute.batchQuery(sqls, queryOptions), options),
216
- command: (sql, commandOptions) => executeInAmbientConnection((connection) => connection.execute.command(sql, commandOptions), options),
217
- batchCommand: (sqls, commandOptions) => executeInAmbientConnection((connection) => connection.execute.batchCommand(sqls, commandOptions), options)
651
+ query: (sql, queryOptions) => executeInAmbientConnection((connection) => connection.execute.query(sql, queryOptions), {
652
+ ...options,
653
+ ...queryOptions
654
+ }),
655
+ batchQuery: (sqls, queryOptions) => executeInAmbientConnection((connection) => connection.execute.batchQuery(sqls, queryOptions), {
656
+ ...options,
657
+ ...queryOptions
658
+ }),
659
+ command: (sql, commandOptions) => executeInAmbientConnection((connection) => connection.execute.command(sql, commandOptions), {
660
+ ...options,
661
+ ...commandOptions
662
+ }),
663
+ batchCommand: (sqls, commandOptions) => executeInAmbientConnection((connection) => connection.execute.batchCommand(sqls, commandOptions), {
664
+ ...options,
665
+ ...commandOptions
666
+ })
218
667
  });
219
668
  const executeInNewDbClient = async (handle, options) => {
669
+ Abort.throwIfAborted(options);
220
670
  const { connect, close } = options;
221
- const client = await connect();
671
+ const client = await connect({ abort: Abort.from(options) });
222
672
  try {
223
673
  return await handle(client);
224
674
  } catch (error) {
@@ -227,7 +677,8 @@ const executeInNewDbClient = async (handle, options) => {
227
677
  }
228
678
  };
229
679
  const executeInNewConnection = async (handle, options) => {
230
- const connection = await options.connection();
680
+ Abort.throwIfAborted(options);
681
+ const connection = await options.connection({ abort: Abort.from(options) });
231
682
  try {
232
683
  return await handle(connection);
233
684
  } finally {
@@ -235,10 +686,8 @@ const executeInNewConnection = async (handle, options) => {
235
686
  }
236
687
  };
237
688
  const executeInAmbientConnection = async (handle, options) => {
238
- const connection = await options.connection();
239
- try {
240
- return await handle(connection);
241
- } finally {}
689
+ Abort.throwIfAborted(options);
690
+ return handle(await options.connection({ abort: Abort.from(options) }));
242
691
  };
243
692
 
244
693
  //#endregion
@@ -306,10 +755,10 @@ const toTransactionResult = (transactionResult) => transactionResult !== void 0
306
755
  success: true,
307
756
  result: transactionResult
308
757
  };
309
- const executeInTransaction = async (transaction, handle) => {
758
+ const executeInTransaction = async (transaction, handle, context = { abort: Abort.never }) => {
310
759
  await transaction.begin();
311
760
  try {
312
- const { success, result } = toTransactionResult(await handle(transaction));
761
+ const { success, result } = toTransactionResult(await handle(transaction, context));
313
762
  if (success) await transaction.commit();
314
763
  else await transaction.rollback();
315
764
  return result;
@@ -318,18 +767,32 @@ const executeInTransaction = async (transaction, handle) => {
318
767
  throw e;
319
768
  }
320
769
  };
770
+ const executeInNestedTransaction = async (transaction, handle, options, context) => {
771
+ Abort.throwIfAborted(options);
772
+ if (!(options?.allowNestedTransactions ?? transaction._transactionOptions.allowNestedTransactions ?? false)) throw new InvalidOperationError("Cannot start a nested transaction: allowNestedTransactions is false. Set transactionOptions: { allowNestedTransactions: true } on your pool or connection.");
773
+ return executeInTransaction(transaction, handle, context);
774
+ };
321
775
  const transactionFactoryWithDbClient = (connect, initTransaction) => {
322
776
  let currentTransaction = void 0;
323
- const getOrInitCurrentTransaction = (options) => currentTransaction ?? (currentTransaction = initTransaction(connect(), {
324
- close: () => {
325
- currentTransaction = void 0;
326
- return Promise.resolve();
327
- },
328
- ...options ?? {}
329
- }));
777
+ const getOrInitCurrentTransaction = (options) => {
778
+ Abort.throwIfAborted(options);
779
+ if (currentTransaction) return currentTransaction;
780
+ currentTransaction = initTransaction(connect({ abort: Abort.from(options) }), {
781
+ close: () => {
782
+ currentTransaction = void 0;
783
+ return Promise.resolve();
784
+ },
785
+ ...options ?? {}
786
+ });
787
+ return currentTransaction;
788
+ };
330
789
  return {
331
790
  transaction: getOrInitCurrentTransaction,
332
- withTransaction: (handle, options) => executeInTransaction(getOrInitCurrentTransaction(options), handle)
791
+ withTransaction: (handle, options) => {
792
+ const abortRejection = Abort.rejectIfAborted(options);
793
+ if (abortRejection) return abortRejection;
794
+ return executeInTransaction(getOrInitCurrentTransaction(options), handle, { abort: Abort.from(options) });
795
+ }
333
796
  };
334
797
  };
335
798
  const wrapInConnectionClosure = async (connection, handle) => {
@@ -341,7 +804,8 @@ const wrapInConnectionClosure = async (connection, handle) => {
341
804
  };
342
805
  const transactionFactoryWithNewConnection = (connect) => ({
343
806
  transaction: (options) => {
344
- const connection = connect();
807
+ Abort.throwIfAborted(options);
808
+ const connection = connect({ abort: Abort.from(options) });
345
809
  const transaction = connection.transaction(options);
346
810
  return {
347
811
  ...transaction,
@@ -350,7 +814,9 @@ const transactionFactoryWithNewConnection = (connect) => ({
350
814
  };
351
815
  },
352
816
  withTransaction: (handle, options) => {
353
- const connection = connect();
817
+ const abortRejection = Abort.rejectIfAborted(options);
818
+ if (abortRejection) return abortRejection;
819
+ const connection = connect({ abort: Abort.from(options) });
354
820
  const withTx = connection.withTransaction;
355
821
  return wrapInConnectionClosure(connection, () => withTx(handle, options));
356
822
  }
@@ -359,19 +825,21 @@ const transactionFactoryWithAsyncAmbientConnection = (driverType, connect, close
359
825
  close ??= () => Promise.resolve();
360
826
  return {
361
827
  transaction: (options) => {
828
+ Abort.throwIfAborted(options);
362
829
  let conn = null;
363
830
  let innerTx = null;
364
831
  let connectingPromise = null;
365
832
  const ensureConnection = async () => {
366
833
  if (conn) return innerTx;
367
834
  if (!connectingPromise) connectingPromise = (async () => {
368
- conn = await connect();
835
+ Abort.throwIfAborted(options);
836
+ conn = await connect({ abort: Abort.from(options) });
369
837
  innerTx = conn.transaction(options);
370
838
  })();
371
839
  await connectingPromise;
372
840
  return innerTx;
373
841
  };
374
- return {
842
+ const tx = {
375
843
  driverType,
376
844
  get connection() {
377
845
  if (!conn) throw new Error("Transaction not started - call begin() first");
@@ -413,11 +881,14 @@ const transactionFactoryWithAsyncAmbientConnection = (driverType, connect, close
413
881
  if (conn) await close(conn);
414
882
  }
415
883
  },
416
- _transactionOptions: void 0
884
+ withTransaction: (handle, options) => executeInNestedTransaction(tx, handle, options, { abort: Abort.from(options) }),
885
+ _transactionOptions: options ?? {}
417
886
  };
887
+ return tx;
418
888
  },
419
889
  withTransaction: async (handle, options) => {
420
- const conn = await connect();
890
+ Abort.throwIfAborted(options);
891
+ const conn = await connect({ abort: Abort.from(options) });
421
892
  try {
422
893
  const withTx = conn.withTransaction;
423
894
  return await withTx(handle, options);
@@ -434,7 +905,10 @@ const createAmbientConnection = (options) => {
434
905
  const { driverType, client, executor, initTransaction, serializer } = options;
435
906
  const clientPromise = Promise.resolve(client);
436
907
  const closePromise = Promise.resolve();
437
- const open = () => clientPromise;
908
+ const open = (context) => {
909
+ Abort.throwIfAborted(context);
910
+ return clientPromise;
911
+ };
438
912
  const close = () => closePromise;
439
913
  const typedConnection = {
440
914
  driverType,
@@ -450,9 +924,10 @@ const createConnection = (options) => {
450
924
  const { driverType, connect, close, initTransaction, executor, serializer } = options;
451
925
  let client = null;
452
926
  let connectPromise = null;
453
- const getClient = async () => {
927
+ const getClient = async (context) => {
928
+ Abort.throwIfAborted(context);
454
929
  if (client) return client;
455
- if (!connectPromise) connectPromise = connect().then((c) => {
930
+ if (!connectPromise) connectPromise = connect(context ?? { abort: Abort.never }).then((c) => {
456
931
  client = c;
457
932
  return c;
458
933
  });
@@ -469,209 +944,6 @@ const createConnection = (options) => {
469
944
  return typedConnection;
470
945
  };
471
946
 
472
- //#endregion
473
- //#region src/core/taskProcessing/taskProcessor.ts
474
- var TaskProcessor = class {
475
- queue = [];
476
- isProcessing = false;
477
- activeTasks = 0;
478
- activeGroups = /* @__PURE__ */ new Set();
479
- options;
480
- stopped = false;
481
- constructor(options) {
482
- this.options = options;
483
- }
484
- enqueue(task, options) {
485
- if (this.stopped) return Promise.reject(new DumboError("TaskProcessor has been stopped"));
486
- if (this.queue.length >= this.options.maxQueueSize) return Promise.reject(new TransientDatabaseError("Too many pending connections. Please try again later."));
487
- return this.schedule(task, options);
488
- }
489
- waitForEndOfProcessing() {
490
- return this.schedule(({ ack }) => Promise.resolve(ack()));
491
- }
492
- async stop(options) {
493
- if (this.stopped) return;
494
- this.stopped = true;
495
- this.queue.length = 0;
496
- this.activeGroups.clear();
497
- if (!options?.force) await this.waitForEndOfProcessing();
498
- }
499
- schedule(task, options) {
500
- return promiseWithDeadline((resolve, reject) => {
501
- const taskWithContext = () => {
502
- return new Promise((resolveTask, failTask) => {
503
- task({ ack: resolveTask }).then(resolve).catch((err) => {
504
- failTask(err);
505
- reject(err);
506
- });
507
- });
508
- };
509
- this.queue.push({
510
- task: taskWithContext,
511
- options
512
- });
513
- if (!this.isProcessing) this.ensureProcessing();
514
- }, { deadline: this.options.maxTaskIdleTime });
515
- }
516
- ensureProcessing() {
517
- if (this.isProcessing) return;
518
- this.isProcessing = true;
519
- this.processQueue();
520
- }
521
- processQueue() {
522
- try {
523
- while (this.activeTasks < this.options.maxActiveTasks && this.queue.length > 0) {
524
- const item = this.takeFirstAvailableItem();
525
- if (item === null) return;
526
- const groupId = item.options?.taskGroupId;
527
- if (groupId) this.activeGroups.add(groupId);
528
- this.activeTasks++;
529
- this.executeItem(item);
530
- }
531
- } catch (error) {
532
- console.error(error);
533
- throw error;
534
- } finally {
535
- this.isProcessing = false;
536
- if (this.hasItemsToProcess() && this.activeTasks < this.options.maxActiveTasks) this.ensureProcessing();
537
- }
538
- }
539
- async executeItem({ task, options }) {
540
- try {
541
- await task();
542
- } finally {
543
- this.activeTasks--;
544
- if (options && options.taskGroupId) this.activeGroups.delete(options.taskGroupId);
545
- this.ensureProcessing();
546
- }
547
- }
548
- takeFirstAvailableItem = () => {
549
- const taskIndex = this.queue.findIndex((item) => !item.options?.taskGroupId || !this.activeGroups.has(item.options.taskGroupId));
550
- if (taskIndex === -1) return null;
551
- const [item] = this.queue.splice(taskIndex, 1);
552
- return item ?? null;
553
- };
554
- hasItemsToProcess = () => this.queue.findIndex((item) => !item.options?.taskGroupId || !this.activeGroups.has(item.options.taskGroupId)) !== -1;
555
- };
556
- const DEFAULT_PROMISE_DEADLINE = 2147483647;
557
- const promiseWithDeadline = (executor, options) => {
558
- return new Promise((resolve, reject) => {
559
- let taskStarted = false;
560
- let timeoutId = null;
561
- const deadline = options.deadline ?? DEFAULT_PROMISE_DEADLINE;
562
- timeoutId = setTimeout(() => {
563
- if (!taskStarted) reject(/* @__PURE__ */ new Error("Task was not started within the maximum waiting time"));
564
- }, deadline);
565
- timeoutId.unref();
566
- executor((value) => {
567
- taskStarted = true;
568
- if (timeoutId) clearTimeout(timeoutId);
569
- timeoutId = null;
570
- resolve(value);
571
- }, (reason) => {
572
- if (timeoutId) clearTimeout(timeoutId);
573
- timeoutId = null;
574
- reject(reason);
575
- });
576
- });
577
- };
578
-
579
- //#endregion
580
- //#region src/core/taskProcessing/executionGuards.ts
581
- const guardBoundedAccess = (getResource, options) => {
582
- let isStopped = false;
583
- const taskProcessor = new TaskProcessor({
584
- maxActiveTasks: options.maxResources,
585
- maxQueueSize: options.maxQueueSize ?? 1e3
586
- });
587
- const resourcePool = [];
588
- const allResources = /* @__PURE__ */ new Set();
589
- const ackCallbacks = /* @__PURE__ */ new Map();
590
- const acquire = async () => taskProcessor.enqueue(async ({ ack }) => {
591
- try {
592
- let resource;
593
- if (options.reuseResources) resource = resourcePool.pop();
594
- if (!resource) {
595
- resource = await getResource();
596
- allResources.add(resource);
597
- }
598
- ackCallbacks.set(resource, ack);
599
- return resource;
600
- } catch (e) {
601
- ack();
602
- throw e;
603
- }
604
- });
605
- const release = (resource) => {
606
- const ack = ackCallbacks.get(resource);
607
- if (ack) {
608
- ackCallbacks.delete(resource);
609
- if (options.reuseResources) resourcePool.push(resource);
610
- ack();
611
- }
612
- };
613
- const execute = async (operation) => {
614
- const resource = await acquire();
615
- try {
616
- return await operation(resource);
617
- } finally {
618
- release(resource);
619
- }
620
- };
621
- return {
622
- acquire,
623
- release,
624
- execute,
625
- waitForIdle: () => taskProcessor.waitForEndOfProcessing(),
626
- stop: async (stopOptions) => {
627
- if (isStopped) return;
628
- isStopped = true;
629
- if (options?.closeResource) {
630
- const resources = [...allResources];
631
- allResources.clear();
632
- resourcePool.length = 0;
633
- await Promise.all(resources.map(async (resource) => await options.closeResource(resource)));
634
- }
635
- await taskProcessor.stop(stopOptions);
636
- }
637
- };
638
- };
639
- const guardInitializedOnce = (initialize, options) => {
640
- let initPromise = null;
641
- const taskProcessor = new TaskProcessor({
642
- maxActiveTasks: 1,
643
- maxQueueSize: options?.maxQueueSize ?? 1e3
644
- });
645
- const ensureInitialized = async (retryCount = 0) => {
646
- if (initPromise !== null) return initPromise;
647
- return taskProcessor.enqueue(async ({ ack }) => {
648
- if (initPromise !== null) {
649
- ack();
650
- return initPromise;
651
- }
652
- try {
653
- const promise = initialize();
654
- initPromise = promise;
655
- const result = await promise;
656
- ack();
657
- return result;
658
- } catch (error) {
659
- initPromise = null;
660
- ack();
661
- if (retryCount < (options?.maxRetries ?? 3)) return ensureInitialized(retryCount + 1);
662
- throw error;
663
- }
664
- }, { taskGroupId: v7() });
665
- };
666
- return {
667
- ensureInitialized,
668
- reset: () => {
669
- initPromise = null;
670
- },
671
- stop: (options) => taskProcessor.stop(options)
672
- };
673
- };
674
-
675
947
  //#endregion
676
948
  //#region src/core/connections/pool.ts
677
949
  const wrapPooledConnection = (conn, onClose) => ({
@@ -685,7 +957,10 @@ const createAmbientConnectionPool = (options) => {
685
957
  getConnection: () => connection,
686
958
  execute: connection.execute,
687
959
  transaction: (options) => connection.transaction(options),
688
- withConnection: (handle, _options) => handle(connection),
960
+ withConnection: (handle, options) => {
961
+ Abort.throwIfAborted(options);
962
+ return handle(connection, { abort: Abort.from(options) });
963
+ },
689
964
  withTransaction: (handle, options) => {
690
965
  const withTx = connection.withTransaction;
691
966
  return withTx(handle, options);
@@ -696,48 +971,45 @@ const createSingletonConnectionPool = (options) => {
696
971
  const { driverType, getConnection } = options;
697
972
  let connectionPromise = null;
698
973
  let closed = false;
699
- const activeOperations = /* @__PURE__ */ new Set();
974
+ const operationGuard = guardConcurrentAccess();
700
975
  const closedError = () => /* @__PURE__ */ new Error("Singleton connection pool has been closed");
701
- const run = async (operation) => {
976
+ const executeIfOpen = async (operation, operationOptions) => {
702
977
  if (closed) throw closedError();
703
- const activeOperation = operation();
704
- activeOperations.add(activeOperation);
705
- try {
706
- return await activeOperation;
707
- } finally {
708
- activeOperations.delete(activeOperation);
709
- }
978
+ return operationGuard.execute(operation, operationOptions);
710
979
  };
711
- const getExistingOrNewConnection = () => {
712
- if (!connectionPromise) connectionPromise ??= Promise.resolve(getConnection());
980
+ const getExistingOrNewConnection = (context) => {
981
+ if (!connectionPromise) connectionPromise ??= Promise.resolve(getConnection(context));
713
982
  return connectionPromise;
714
983
  };
715
984
  const innerTransactionFactory = transactionFactoryWithAsyncAmbientConnection(options.driverType, getExistingOrNewConnection, options.closeConnection);
716
985
  return {
717
986
  driverType,
718
- connection: () => run(() => getExistingOrNewConnection().then((conn) => wrapPooledConnection(conn, () => Promise.resolve()))),
987
+ connection: (connectionOptions) => executeIfOpen((context) => getExistingOrNewConnection(context).then((conn) => wrapPooledConnection(conn, () => Promise.resolve())), connectionOptions),
719
988
  execute: (() => {
720
989
  const ambientExecutor = sqlExecutorInAmbientConnection({
721
990
  driverType,
722
991
  connection: getExistingOrNewConnection
723
992
  });
724
993
  return {
725
- query: (sql, opts) => run(() => ambientExecutor.query(sql, opts)),
726
- batchQuery: (sqls, opts) => run(() => ambientExecutor.batchQuery(sqls, opts)),
727
- command: (sql, opts) => run(() => ambientExecutor.command(sql, opts)),
728
- batchCommand: (sqls, opts) => run(() => ambientExecutor.batchCommand(sqls, opts))
994
+ query: (sql, opts) => executeIfOpen(() => ambientExecutor.query(sql, opts), opts),
995
+ batchQuery: (sqls, opts) => executeIfOpen(() => ambientExecutor.batchQuery(sqls, opts), opts),
996
+ command: (sql, opts) => executeIfOpen(() => ambientExecutor.command(sql, opts), opts),
997
+ batchCommand: (sqls, opts) => executeIfOpen(() => ambientExecutor.batchCommand(sqls, opts), opts)
729
998
  };
730
999
  })(),
731
- withConnection: (handle, _options) => run(() => executeInAmbientConnection(handle, { connection: getExistingOrNewConnection })),
1000
+ withConnection: (handle, options) => executeIfOpen((context) => executeInAmbientConnection((connection) => handle(connection, context), {
1001
+ connection: getExistingOrNewConnection,
1002
+ ...options
1003
+ }), options),
732
1004
  transaction: (transactionOptions) => {
733
1005
  if (closed) throw closedError();
734
1006
  return innerTransactionFactory.transaction(transactionOptions);
735
1007
  },
736
- withTransaction: (handle, transactionOptions) => run(() => innerTransactionFactory.withTransaction(handle, transactionOptions)),
1008
+ withTransaction: (handle, transactionOptions) => executeIfOpen((context) => innerTransactionFactory.withTransaction((tx) => handle(tx, context), transactionOptions), transactionOptions),
737
1009
  close: async (closeOptions) => {
738
1010
  if (closed) return;
739
1011
  closed = true;
740
- if (!closeOptions?.force) await Promise.allSettled([...activeOperations]);
1012
+ await operationGuard.stop(closeOptions);
741
1013
  if (!connectionPromise) return;
742
1014
  const connection = await connectionPromise;
743
1015
  connectionPromise = null;
@@ -747,63 +1019,49 @@ const createSingletonConnectionPool = (options) => {
747
1019
  };
748
1020
  const createBoundedConnectionPool = (options) => {
749
1021
  const { driverType, maxConnections } = options;
750
- const allConnections = /* @__PURE__ */ new Set();
751
- const getTrackedConnection = async () => {
752
- const connection = await options.getConnection();
753
- allConnections.add(connection);
754
- return connection;
755
- };
756
- const guardMaxConnections = guardBoundedAccess(getTrackedConnection, {
1022
+ const guardMaxConnections = guardBoundedAccess(options.getConnection, {
757
1023
  maxResources: maxConnections,
758
- reuseResources: true
1024
+ reuseResources: true,
1025
+ closeResource: (connection) => connection.close()
759
1026
  });
760
1027
  let closed = false;
761
1028
  const closedError = () => /* @__PURE__ */ new Error("Bounded connection pool has been closed");
762
1029
  const ensureOpen = () => {
763
1030
  if (closed) throw closedError();
764
1031
  };
765
- const closeAllConnections = async () => {
766
- const connections = [...allConnections];
767
- allConnections.clear();
768
- await Promise.all(connections.map((conn) => conn.close()));
769
- };
770
- const executeWithPooling = async (operation) => {
1032
+ const executeWithPooledConnection = async (operation, operationOptions) => {
771
1033
  ensureOpen();
772
- const conn = await guardMaxConnections.acquire();
773
- try {
774
- return await operation(conn);
775
- } finally {
776
- guardMaxConnections.release(conn);
777
- }
1034
+ return guardMaxConnections.execute(operation, operationOptions);
778
1035
  };
779
1036
  return {
780
1037
  driverType,
781
- connection: async () => {
1038
+ connection: async (connectionOptions) => {
782
1039
  ensureOpen();
783
- const conn = await guardMaxConnections.acquire();
1040
+ const conn = await guardMaxConnections.acquire(connectionOptions);
784
1041
  return wrapPooledConnection(conn, () => Promise.resolve(guardMaxConnections.release(conn)));
785
1042
  },
786
1043
  execute: {
787
- query: (sql, opts) => executeWithPooling((c) => c.execute.query(sql, opts)),
788
- batchQuery: (sqls, opts) => executeWithPooling((c) => c.execute.batchQuery(sqls, opts)),
789
- command: (sql, opts) => executeWithPooling((c) => c.execute.command(sql, opts)),
790
- batchCommand: (sqls, opts) => executeWithPooling((c) => c.execute.batchCommand(sqls, opts))
1044
+ query: (sql, opts) => executeWithPooledConnection((c) => c.execute.query(sql, opts), opts),
1045
+ batchQuery: (sqls, opts) => executeWithPooledConnection((c) => c.execute.batchQuery(sqls, opts), opts),
1046
+ command: (sql, opts) => executeWithPooledConnection((c) => c.execute.command(sql, opts), opts),
1047
+ batchCommand: (sqls, opts) => executeWithPooledConnection((c) => c.execute.batchCommand(sqls, opts), opts)
791
1048
  },
792
- withConnection: executeWithPooling,
1049
+ withConnection: executeWithPooledConnection,
793
1050
  transaction: (transactionOptions) => {
794
1051
  ensureOpen();
795
- return transactionFactoryWithAsyncAmbientConnection(driverType, guardMaxConnections.acquire, guardMaxConnections.release).transaction(transactionOptions);
1052
+ return transactionFactoryWithAsyncAmbientConnection(driverType, (context) => guardMaxConnections.acquire({
1053
+ ...transactionOptions,
1054
+ abort: context.abort
1055
+ }), guardMaxConnections.release).transaction(transactionOptions);
796
1056
  },
797
- withTransaction: (handle, transactionOptions) => executeWithPooling((conn) => {
1057
+ withTransaction: (handle, transactionOptions) => executeWithPooledConnection((conn, context) => {
798
1058
  const withTx = conn.withTransaction;
799
- return withTx(handle, transactionOptions);
800
- }),
1059
+ return withTx((tx) => handle(tx, context), transactionOptions);
1060
+ }, transactionOptions),
801
1061
  close: async (closeOptions) => {
802
1062
  if (closed) return;
803
1063
  closed = true;
804
- if (!closeOptions?.force) await guardMaxConnections.waitForIdle();
805
- await guardMaxConnections.stop({ force: true });
806
- await closeAllConnections();
1064
+ await guardMaxConnections.stop(closeOptions);
807
1065
  }
808
1066
  };
809
1067
  };
@@ -811,16 +1069,22 @@ const createAlwaysNewConnectionPool = (options) => {
811
1069
  const { driverType, getConnection, connectionOptions } = options;
812
1070
  return createConnectionPool({
813
1071
  driverType,
814
- getConnection: () => connectionOptions ? getConnection(connectionOptions) : getConnection()
1072
+ getConnection: (context) => connectionOptions ? getConnection(connectionOptions, context) : getConnection(context)
815
1073
  });
816
1074
  };
817
1075
  const createConnectionPool = (pool) => {
818
1076
  const { driverType, getConnection } = pool;
819
- const connection = "connection" in pool ? pool.connection : () => Promise.resolve(getConnection());
1077
+ const connection = "connection" in pool ? pool.connection : (options) => {
1078
+ Abort.throwIfAborted(options);
1079
+ return Promise.resolve(getConnection({ abort: Abort.from(options) }));
1080
+ };
820
1081
  return {
821
1082
  driverType,
822
1083
  connection,
823
- withConnection: "withConnection" in pool ? pool.withConnection : (handle, _options) => executeInNewConnection(handle, { connection }),
1084
+ withConnection: "withConnection" in pool ? pool.withConnection : (handle, options) => executeInNewConnection((connection) => handle(connection, { abort: Abort.from(options) }), {
1085
+ connection,
1086
+ ...options
1087
+ }),
824
1088
  close: "close" in pool ? pool.close : () => Promise.resolve(),
825
1089
  execute: "execute" in pool ? pool.execute : sqlExecutorInNewConnection({
826
1090
  driverType,
@@ -2160,9 +2424,9 @@ const sqliteSQLExecutor = (driverType, serializer, formatter, errorMapper) => ({
2160
2424
 
2161
2425
  //#endregion
2162
2426
  //#region src/storage/sqlite/core/transactions/index.ts
2163
- const sqliteTransaction = (driverType, connection, allowNestedTransactions, serializer, defaultTransactionMode) => (getClient, options) => {
2164
- allowNestedTransactions = options?.allowNestedTransactions ?? allowNestedTransactions;
2165
- const useSavepoints = options?.useSavepoints ?? false;
2427
+ const sqliteTransaction = (driverType, connection, defaultOptions, serializer, defaultTransactionMode) => (getClient, options) => {
2428
+ const defaultTransactionOptions = typeof defaultOptions === "boolean" ? { allowNestedTransactions: defaultOptions } : defaultOptions;
2429
+ const allowNestedTransactions = options?.allowNestedTransactions ?? defaultTransactionOptions.allowNestedTransactions ?? false;
2166
2430
  const tx = databaseTransaction({
2167
2431
  begin: async () => {
2168
2432
  const client = await getClient;
@@ -2190,23 +2454,28 @@ const sqliteTransaction = (driverType, connection, allowNestedTransactions, seri
2190
2454
  },
2191
2455
  releaseSavepoint: async (level) => {
2192
2456
  await (await getClient).command(SQL`RELEASE transaction${SQL.plain(level.toString())}`);
2457
+ },
2458
+ rollbackToSavepoint: async (level) => {
2459
+ await (await getClient).command(SQL`ROLLBACK TO transaction${SQL.plain(level.toString())}`);
2193
2460
  }
2194
2461
  }, {
2195
2462
  allowNestedTransactions,
2196
- useSavepoints
2463
+ useSavepoints: options?.useSavepoints ?? defaultTransactionOptions.useSavepoints ?? false
2197
2464
  });
2198
- return {
2465
+ const transaction = {
2199
2466
  connection: connection(),
2200
2467
  driverType,
2201
2468
  begin: tx.begin,
2202
2469
  commit: tx.commit,
2203
2470
  rollback: tx.rollback,
2204
2471
  execute: sqlExecutor(sqliteSQLExecutor(driverType, serializer), { connect: () => getClient }),
2472
+ withTransaction: (handle, options) => executeInNestedTransaction(transaction, handle, options),
2205
2473
  _transactionOptions: {
2206
2474
  ...options,
2207
2475
  allowNestedTransactions
2208
2476
  }
2209
2477
  };
2478
+ return transaction;
2210
2479
  };
2211
2480
 
2212
2481
  //#endregion
@@ -2251,7 +2520,7 @@ const sqliteAmbientClientConnection = (options) => {
2251
2520
  return createAmbientConnection({
2252
2521
  driverType,
2253
2522
  client,
2254
- initTransaction: initTransaction ?? ((connection) => sqliteTransaction(driverType, connection, allowNestedTransactions ?? false, serializer, defaultTransactionMode)),
2523
+ initTransaction: initTransaction ?? ((connection) => sqliteTransaction(driverType, connection, { allowNestedTransactions: allowNestedTransactions ?? false }, serializer, defaultTransactionMode)),
2255
2524
  executor: ({ serializer }) => sqliteSQLExecutor(driverType, serializer, void 0, errorMapper),
2256
2525
  serializer
2257
2526
  });
@@ -2276,7 +2545,7 @@ const sqliteClientConnection = (options) => {
2276
2545
  if (client && "close" in client && typeof client.close === "function") await client.close();
2277
2546
  else if (client && "release" in client && typeof client.release === "function") client.release();
2278
2547
  },
2279
- initTransaction: (connection) => sqliteTransaction(options.driverType, connection, connectionOptions.transactionOptions?.allowNestedTransactions ?? false, serializer, connectionOptions.defaultTransactionMode),
2548
+ initTransaction: (connection) => sqliteTransaction(options.driverType, connection, connectionOptions.transactionOptions ?? {}, serializer, connectionOptions.defaultTransactionMode),
2280
2549
  executor: ({ serializer }) => sqliteSQLExecutor(options.driverType, serializer),
2281
2550
  serializer
2282
2551
  });
@@ -2298,7 +2567,7 @@ const sqlitePoolClientConnection = (options) => {
2298
2567
  driverType: options.driverType,
2299
2568
  connect,
2300
2569
  close: () => client !== null ? Promise.resolve(client.release()) : Promise.resolve(),
2301
- initTransaction: (connection) => sqliteTransaction(options.driverType, connection, connectionOptions.transactionOptions?.allowNestedTransactions ?? false, serializer, connectionOptions.defaultTransactionMode),
2570
+ initTransaction: (connection) => sqliteTransaction(options.driverType, connection, connectionOptions.transactionOptions ?? {}, serializer, connectionOptions.defaultTransactionMode),
2302
2571
  executor: ({ serializer }) => sqliteSQLExecutor(options.driverType, serializer),
2303
2572
  serializer
2304
2573
  });
@@ -2672,43 +2941,67 @@ const sqlite3Connection = (options) => sqliteConnection({
2672
2941
  //#endregion
2673
2942
  //#region src/storage/sqlite/sqlite3/pool/singletonPool.ts
2674
2943
  const DEFAULT_SQLITE_MAX_TASK_IDLE_TIME_MS = 3e4;
2944
+ const createSQLiteTransactionContext = () => {
2945
+ if (typeof process === "undefined") return void 0;
2946
+ const asyncHooks = process.getBuiltinModule?.("node:async_hooks");
2947
+ if (!asyncHooks) return void 0;
2948
+ const context = new asyncHooks.AsyncLocalStorage();
2949
+ return {
2950
+ current: () => context.getStore(),
2951
+ run: (active, handle) => context.run(active, handle)
2952
+ };
2953
+ };
2675
2954
  const sqlite3SingletonPool = (options) => {
2676
- const maxTaskIdleTime = options.maxTaskIdleTime ?? 3e4;
2677
2955
  const inner = createSingletonConnectionPool({
2678
2956
  driverType: options.driverType,
2679
2957
  getConnection: options.getConnection,
2680
2958
  ...options.closeConnection ? { closeConnection: options.closeConnection } : {}
2681
2959
  });
2682
- const taskProcessor = new TaskProcessor({
2683
- maxActiveTasks: 1,
2960
+ const writerGuard = guardExclusiveAccess({
2684
2961
  maxQueueSize: options.maxQueueSize ?? 1e3,
2685
- maxTaskIdleTime
2962
+ maxTaskIdleTime: options.maxTaskIdleTime ?? 3e4
2686
2963
  });
2687
- const insideWriterTask = new AsyncLocalStorage();
2688
- const enqueue = (op) => {
2689
- if (insideWriterTask.getStore() === true) return op();
2690
- return taskProcessor.enqueue(({ ack }) => insideWriterTask.run(true, async () => {
2691
- try {
2692
- return await op();
2693
- } finally {
2694
- ack();
2695
- }
2696
- }));
2964
+ const transactionContext = options.transactionContext ?? createSQLiteTransactionContext();
2965
+ const activeConnection = () => transactionContext?.current()?.connection;
2966
+ const runInTransactionContext = (connection, operation) => transactionContext ? transactionContext.run({ connection }, operation) : operation();
2967
+ const transactionAwareConnection = (connection) => transactionContext ? {
2968
+ ...connection,
2969
+ withTransaction: (handle, transactionOptions) => runInTransactionContext(connection, () => connection.withTransaction(handle, transactionOptions))
2970
+ } : connection;
2971
+ const runConnectionTransaction = (connection, handle, context, transactionOptions) => {
2972
+ const withTransaction = connection.withTransaction;
2973
+ return withTransaction((transaction) => handle(transaction, context), transactionOptions);
2974
+ };
2975
+ const runOnWriterConnection = (handle, options) => {
2976
+ const connection = activeConnection();
2977
+ if (connection) return inner.withConnection((_connection, context) => handle(connection, context), options);
2978
+ return writerGuard.execute((context) => inner.withConnection((connection) => handle(connection, context), options), options);
2697
2979
  };
2980
+ const withWriterConnection = (handle, options) => runOnWriterConnection((connection, context) => handle(transactionAwareConnection(connection), context), options);
2698
2981
  return {
2699
2982
  driverType: inner.driverType,
2700
2983
  connection: inner.connection.bind(inner),
2701
2984
  transaction: inner.transaction.bind(inner),
2702
- withConnection: (handle, connectionOptions) => enqueue(() => inner.withConnection(handle, connectionOptions)),
2703
- withTransaction: (handle, transactionOptions) => enqueue(() => inner.withTransaction(handle, transactionOptions)),
2985
+ withConnection: (handle, connectionOptions) => activeConnection() || !connectionOptions?.readonly ? withWriterConnection(handle, connectionOptions) : inner.withConnection(handle, connectionOptions),
2986
+ withTransaction: (handle, transactionOptions) => {
2987
+ const connection = activeConnection();
2988
+ if (connection) return inner.withConnection((_connection, context) => runConnectionTransaction(connection, handle, context, transactionOptions), transactionOptions);
2989
+ return runOnWriterConnection((connection, context) => runInTransactionContext(connection, () => runConnectionTransaction(connection, handle, context, transactionOptions)), transactionOptions);
2990
+ },
2704
2991
  execute: {
2705
- query: (sql, queryOptions) => enqueue(() => inner.execute.query(sql, queryOptions)),
2706
- batchQuery: (sqls, queryOptions) => enqueue(() => inner.execute.batchQuery(sqls, queryOptions)),
2707
- command: (sql, commandOptions) => enqueue(() => inner.execute.command(sql, commandOptions)),
2708
- batchCommand: (sqls, commandOptions) => enqueue(() => inner.execute.batchCommand(sqls, commandOptions))
2992
+ query: (sql, queryOptions) => {
2993
+ const connection = activeConnection();
2994
+ return connection ? connection.execute.query(sql, queryOptions) : inner.execute.query(sql, queryOptions);
2995
+ },
2996
+ batchQuery: (sqls, queryOptions) => {
2997
+ const connection = activeConnection();
2998
+ return connection ? connection.execute.batchQuery(sqls, queryOptions) : inner.execute.batchQuery(sqls, queryOptions);
2999
+ },
3000
+ command: (sql, commandOptions) => runOnWriterConnection((connection) => connection.execute.command(sql, commandOptions), commandOptions),
3001
+ batchCommand: (sqls, commandOptions) => runOnWriterConnection((connection) => connection.execute.batchCommand(sqls, commandOptions), commandOptions)
2709
3002
  },
2710
3003
  close: async (closeOptions) => {
2711
- await taskProcessor.stop({ ...closeOptions?.force ? { force: true } : {} });
3004
+ await writerGuard.stop(closeOptions);
2712
3005
  await inner.close(closeOptions);
2713
3006
  }
2714
3007
  };
@@ -2719,15 +3012,16 @@ const sqlite3SingletonPool = (options) => {
2719
3012
  const sqliteDualConnectionPool = (options) => {
2720
3013
  const { sqliteConnectionFactory, connectionOptions } = options;
2721
3014
  const readerPoolSize = options.readerPoolSize ?? Math.max(4, cpus().length);
3015
+ const transactionContext = createSQLiteTransactionContext();
2722
3016
  let databaseInitPromise = null;
2723
- const guardSingleConnection = guardInitializedOnce(async () => {
3017
+ const guardSingleConnection = guardInitializedOnce(async (context) => {
2724
3018
  if (databaseInitPromise !== null) return databaseInitPromise;
2725
3019
  const initConnection = sqliteConnectionFactory({
2726
3020
  ...connectionOptions,
2727
3021
  skipDatabasePragmas: false,
2728
3022
  readonly: false
2729
3023
  });
2730
- const initPromise = initConnection.open();
3024
+ const initPromise = initConnection.open(context);
2731
3025
  databaseInitPromise = initPromise;
2732
3026
  try {
2733
3027
  await initPromise;
@@ -2738,45 +3032,47 @@ const sqliteDualConnectionPool = (options) => {
2738
3032
  throw mapSqliteError(error);
2739
3033
  }
2740
3034
  });
2741
- const ensureDatabaseInitialized = async () => {
3035
+ const ensureDatabaseInitialized = async (context) => {
2742
3036
  if (databaseInitPromise !== null) return databaseInitPromise;
2743
- return guardSingleConnection.ensureInitialized();
3037
+ return guardSingleConnection.ensureInitialized({ abort: context.abort });
2744
3038
  };
2745
- const wrappedConnectionFactory = async (readonly, connectionOptions) => {
2746
- await ensureDatabaseInitialized();
3039
+ const wrappedConnectionFactory = async (readonly, connectionOptions, context) => {
3040
+ await ensureDatabaseInitialized(context);
2747
3041
  const connection = sqliteConnectionFactory({
2748
3042
  ...connectionOptions,
2749
3043
  skipDatabasePragmas: true,
2750
3044
  readonly
2751
3045
  });
2752
- await connection.open();
3046
+ await connection.open(context);
2753
3047
  return connection;
2754
3048
  };
2755
3049
  const writerPool = sqlite3SingletonPool({
2756
3050
  driverType: options.driverType,
2757
- getConnection: () => wrappedConnectionFactory(false, connectionOptions),
3051
+ getConnection: (context) => wrappedConnectionFactory(false, connectionOptions, context),
3052
+ ...transactionContext ? { transactionContext } : {},
2758
3053
  ...options.maxTaskIdleTime !== void 0 ? { maxTaskIdleTime: options.maxTaskIdleTime } : {}
2759
3054
  });
2760
3055
  const readerPool = createBoundedConnectionPool({
2761
3056
  driverType: options.driverType,
2762
- getConnection: () => wrappedConnectionFactory(true, connectionOptions),
3057
+ getConnection: (context) => wrappedConnectionFactory(true, connectionOptions, context),
2763
3058
  maxConnections: readerPoolSize
2764
3059
  });
3060
+ const hasActiveTransaction = () => !!transactionContext?.current();
2765
3061
  return {
2766
3062
  driverType: options.driverType,
2767
- connection: (connectionOptions) => connectionOptions?.readonly ? readerPool.connection(connectionOptions) : writerPool.connection(connectionOptions),
3063
+ connection: (connectionOptions) => connectionOptions?.readonly && !hasActiveTransaction() ? readerPool.connection(connectionOptions) : writerPool.connection(connectionOptions),
2768
3064
  execute: {
2769
- query: (...args) => readerPool.execute.query(...args),
2770
- batchQuery: (...args) => readerPool.execute.batchQuery(...args),
3065
+ query: (...args) => hasActiveTransaction() ? writerPool.execute.query(...args) : readerPool.execute.query(...args),
3066
+ batchQuery: (...args) => hasActiveTransaction() ? writerPool.execute.batchQuery(...args) : readerPool.execute.batchQuery(...args),
2771
3067
  command: (...args) => writerPool.execute.command(...args),
2772
3068
  batchCommand: (...args) => writerPool.execute.batchCommand(...args)
2773
3069
  },
2774
- withConnection: (handle, connectionOptions) => connectionOptions?.readonly ? readerPool.withConnection(handle, connectionOptions) : writerPool.withConnection(handle, connectionOptions),
3070
+ withConnection: (handle, connectionOptions) => connectionOptions?.readonly && !hasActiveTransaction() ? readerPool.withConnection(handle, connectionOptions) : writerPool.withConnection(handle, connectionOptions),
2775
3071
  transaction: writerPool.transaction,
2776
3072
  withTransaction: writerPool.withTransaction,
2777
- close: async () => {
2778
- await guardSingleConnection.stop();
2779
- await Promise.all([writerPool.close(), readerPool.close()]);
3073
+ close: async (closeOptions) => {
3074
+ await guardSingleConnection.stop(closeOptions);
3075
+ await Promise.all([writerPool.close(closeOptions), readerPool.close(closeOptions)]);
2780
3076
  }
2781
3077
  };
2782
3078
  };