@event-driven-io/dumbo 0.13.0-beta.45 → 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.cjs CHANGED
@@ -32,7 +32,6 @@ ansis = __toESM(ansis, 1);
32
32
  let sqlite3 = require("sqlite3");
33
33
  sqlite3 = __toESM(sqlite3, 1);
34
34
  let os = require("os");
35
- let node_async_hooks = require("node:async_hooks");
36
35
 
37
36
  //#region src/core/errors/index.ts
38
37
  const isNumber = (val) => typeof val === "number" && val === val;
@@ -205,6 +204,420 @@ var InvalidOperationError = class InvalidOperationError extends DumboError {
205
204
  }
206
205
  };
207
206
 
207
+ //#endregion
208
+ //#region src/core/taskProcessing/abort.ts
209
+ const never = { signal: new AbortController().signal };
210
+ const from = (options) => options?.abort ?? never;
211
+ const getSignal = (abort) => "signal" in abort ? abort.signal : abort;
212
+ const reason = (abort) => {
213
+ const signal = getSignal(abort);
214
+ return signal.reason instanceof Error ? signal.reason : new DumboError(typeof signal.reason === "string" ? signal.reason : "Operation aborted");
215
+ };
216
+ const scope = (parent, onAbort) => {
217
+ const controller = new AbortController();
218
+ return {
219
+ abort: (reason) => controller.abort(reason),
220
+ dispose: onAbortSignal(parent, (reason) => {
221
+ controller.abort(reason);
222
+ onAbort?.(reason);
223
+ }),
224
+ signal: controller.signal
225
+ };
226
+ };
227
+ const execute = (operation, options) => {
228
+ const abort = options?.abort;
229
+ if (!abort) return operation();
230
+ const signal = abort.signal;
231
+ if (signal.aborted) return Promise.reject(reason(abort));
232
+ return new Promise((resolve, reject) => {
233
+ let finished = false;
234
+ const rejectOnAbort = () => {
235
+ if (finished) return;
236
+ finished = true;
237
+ reject(reason(abort));
238
+ };
239
+ signal.addEventListener("abort", rejectOnAbort, { once: true });
240
+ let operationPromise;
241
+ try {
242
+ operationPromise = operation();
243
+ } catch (error) {
244
+ finished = true;
245
+ signal.removeEventListener("abort", rejectOnAbort);
246
+ reject(error);
247
+ return;
248
+ }
249
+ operationPromise.then((result) => {
250
+ if (finished) return;
251
+ finished = true;
252
+ signal.removeEventListener("abort", rejectOnAbort);
253
+ resolve(result);
254
+ }).catch((error) => {
255
+ if (finished) return;
256
+ finished = true;
257
+ signal.removeEventListener("abort", rejectOnAbort);
258
+ reject(error);
259
+ });
260
+ });
261
+ };
262
+ const throwIfAborted = (options) => {
263
+ const abort = options?.abort;
264
+ if (abort?.signal.aborted) throw reason(abort);
265
+ };
266
+ const rejectIfAborted = (options) => {
267
+ const abort = options?.abort;
268
+ return abort?.signal.aborted ? Promise.reject(reason(abort)) : void 0;
269
+ };
270
+ const onAbortSignal = (abort, handle) => {
271
+ if (!abort) return () => {};
272
+ const signal = abort.signal;
273
+ if (signal.aborted) {
274
+ handle(reason(abort));
275
+ return () => {};
276
+ }
277
+ const abortListener = () => handle(reason(abort));
278
+ signal.addEventListener("abort", abortListener, { once: true });
279
+ return () => signal.removeEventListener("abort", abortListener);
280
+ };
281
+ const Abort = {
282
+ execute,
283
+ from,
284
+ never,
285
+ onAbort: onAbortSignal,
286
+ reason,
287
+ rejectIfAborted,
288
+ scope,
289
+ throwIfAborted
290
+ };
291
+
292
+ //#endregion
293
+ //#region src/core/taskProcessing/taskProcessor.ts
294
+ var TaskProcessor = class {
295
+ queue = [];
296
+ isProcessing = false;
297
+ activeTasks = 0;
298
+ activeGroups = /* @__PURE__ */ new Set();
299
+ options;
300
+ stopped = false;
301
+ idleWaiters = [];
302
+ activeTaskAbortCallbacks = /* @__PURE__ */ new Set();
303
+ constructor(options) {
304
+ this.options = options;
305
+ }
306
+ enqueue(task, options) {
307
+ if (options?.abort?.signal.aborted) return Promise.reject(Abort.reason(options.abort.signal));
308
+ if (this.stopped) return Promise.reject(new DumboError("TaskProcessor has been stopped"));
309
+ if (this.queue.length >= this.options.maxQueueSize) return Promise.reject(new TransientDatabaseError("Too many pending connections. Please try again later."));
310
+ return this.schedule(task, options);
311
+ }
312
+ waitForEndOfProcessing() {
313
+ if (this.activeTasks === 0 && this.queue.length === 0) return Promise.resolve();
314
+ return new Promise((resolve) => {
315
+ this.idleWaiters.push(resolve);
316
+ });
317
+ }
318
+ async stop(options) {
319
+ if (this.stopped) return;
320
+ this.stopped = true;
321
+ const stoppedError = new DumboError("TaskProcessor has been stopped");
322
+ for (const item of this.queue.splice(0)) {
323
+ item.abort(stoppedError);
324
+ item.reject(stoppedError);
325
+ }
326
+ this.activeGroups.clear();
327
+ if (options?.force) for (const abort of this.activeTaskAbortCallbacks) abort(stoppedError);
328
+ if (options?.force) return;
329
+ if (options?.closeDeadline === void 0) {
330
+ await this.waitForEndOfProcessing();
331
+ return;
332
+ }
333
+ if (!await waitForProcessingOrDeadline(this.waitForEndOfProcessing(), options.closeDeadline)) for (const abort of this.activeTaskAbortCallbacks) abort(stoppedError);
334
+ }
335
+ schedule(task, options) {
336
+ return new Promise((resolve, reject) => {
337
+ let didQueueTimeout = false;
338
+ let didAbortBeforeStart = false;
339
+ let didStart = false;
340
+ let queueWaitTimer = noopQueueWaitTimer;
341
+ const abortScope = Abort.scope(options?.abort, (reason) => {
342
+ queueWaitTimer.cancel();
343
+ didAbortBeforeStart = !didStart;
344
+ reject(reason);
345
+ });
346
+ queueWaitTimer = createQueueWaitTimer(this.options.maxTaskIdleTime, (reason) => {
347
+ didQueueTimeout = true;
348
+ abortScope.abort(reason);
349
+ abortScope.dispose();
350
+ reject(reason);
351
+ });
352
+ const taskWithContext = () => {
353
+ return new Promise((resolveTask) => {
354
+ if (didQueueTimeout || didAbortBeforeStart) {
355
+ resolveTask();
356
+ return;
357
+ }
358
+ let taskPromise;
359
+ try {
360
+ taskPromise = task({
361
+ ack: resolveTask,
362
+ abort: abortScope
363
+ });
364
+ } catch (err) {
365
+ abortScope.dispose();
366
+ resolveTask();
367
+ reject(err);
368
+ return;
369
+ }
370
+ taskPromise.then((result) => {
371
+ abortScope.dispose();
372
+ resolve(result);
373
+ }).catch((err) => {
374
+ abortScope.dispose();
375
+ resolveTask();
376
+ reject(err);
377
+ });
378
+ });
379
+ };
380
+ this.queue.push({
381
+ task: taskWithContext,
382
+ options,
383
+ reject: (reason) => {
384
+ queueWaitTimer.cancel();
385
+ abortScope.dispose();
386
+ reject(reason);
387
+ },
388
+ markStarted: () => {
389
+ didStart = true;
390
+ queueWaitTimer.cancel();
391
+ },
392
+ abort: (reason) => {
393
+ abortScope.dispose();
394
+ abortScope.abort(reason);
395
+ }
396
+ });
397
+ if (!this.isProcessing) this.ensureProcessing();
398
+ });
399
+ }
400
+ ensureProcessing() {
401
+ if (this.isProcessing) return;
402
+ this.isProcessing = true;
403
+ this.processQueue();
404
+ }
405
+ processQueue() {
406
+ try {
407
+ while (this.activeTasks < this.options.maxActiveTasks && this.queue.length > 0) {
408
+ const item = this.takeFirstAvailableItem();
409
+ if (item === null) return;
410
+ const groupId = item.options?.taskGroupId;
411
+ if (groupId) this.activeGroups.add(groupId);
412
+ this.activeTasks++;
413
+ this.executeItem(item);
414
+ }
415
+ } catch (error) {
416
+ console.error(error);
417
+ throw error;
418
+ } finally {
419
+ this.isProcessing = false;
420
+ if (this.hasItemsToProcess() && this.activeTasks < this.options.maxActiveTasks) this.ensureProcessing();
421
+ }
422
+ }
423
+ async executeItem({ task, options, markStarted, abort }) {
424
+ markStarted();
425
+ this.activeTaskAbortCallbacks.add(abort);
426
+ try {
427
+ await task();
428
+ } finally {
429
+ this.activeTaskAbortCallbacks.delete(abort);
430
+ this.activeTasks--;
431
+ if (options && options.taskGroupId) this.activeGroups.delete(options.taskGroupId);
432
+ this.resolveIdleWaiters();
433
+ this.ensureProcessing();
434
+ }
435
+ }
436
+ takeFirstAvailableItem = () => {
437
+ const taskIndex = this.queue.findIndex((item) => !item.options?.taskGroupId || !this.activeGroups.has(item.options.taskGroupId));
438
+ if (taskIndex === -1) return null;
439
+ const [item] = this.queue.splice(taskIndex, 1);
440
+ return item ?? null;
441
+ };
442
+ hasItemsToProcess = () => this.queue.findIndex((item) => !item.options?.taskGroupId || !this.activeGroups.has(item.options.taskGroupId)) !== -1;
443
+ resolveIdleWaiters = () => {
444
+ if (this.activeTasks > 0 || this.queue.length > 0) return;
445
+ const waiters = this.idleWaiters.splice(0);
446
+ for (const resolve of waiters) resolve();
447
+ };
448
+ };
449
+ const noopQueueWaitTimer = { cancel: () => {} };
450
+ const createQueueWaitTimer = (timeoutMs, reject) => {
451
+ if (timeoutMs === void 0) return noopQueueWaitTimer;
452
+ let timeoutId = setTimeout(() => {
453
+ reject(/* @__PURE__ */ new Error("Task was not started within the maximum waiting time"));
454
+ }, timeoutMs);
455
+ timeoutId.unref();
456
+ return { cancel: () => {
457
+ if (!timeoutId) return;
458
+ clearTimeout(timeoutId);
459
+ timeoutId = null;
460
+ } };
461
+ };
462
+ const waitForProcessingOrDeadline = async (processing, closeDeadline) => {
463
+ let timeoutId = null;
464
+ try {
465
+ return await Promise.race([processing.then(() => true), new Promise((resolve) => {
466
+ timeoutId = setTimeout(() => resolve(false), closeDeadline);
467
+ timeoutId.unref();
468
+ })]);
469
+ } finally {
470
+ if (timeoutId) clearTimeout(timeoutId);
471
+ }
472
+ };
473
+
474
+ //#endregion
475
+ //#region src/core/taskProcessing/executionGuards.ts
476
+ const guardExclusiveAccess = (options) => {
477
+ const taskProcessor = new TaskProcessor({
478
+ maxActiveTasks: 1,
479
+ maxQueueSize: options?.maxQueueSize ?? 1e3,
480
+ ...options?.maxTaskIdleTime !== void 0 ? { maxTaskIdleTime: options.maxTaskIdleTime } : {}
481
+ });
482
+ return {
483
+ execute: (operation, options) => taskProcessor.enqueue(async (context) => {
484
+ try {
485
+ return await operation(context);
486
+ } finally {
487
+ context.ack();
488
+ }
489
+ }, options),
490
+ waitForIdle: () => taskProcessor.waitForEndOfProcessing(),
491
+ stop: (options) => taskProcessor.stop(options)
492
+ };
493
+ };
494
+ const guardConcurrentAccess = (options) => {
495
+ const taskProcessor = new TaskProcessor({
496
+ maxActiveTasks: options?.maxActiveTasks ?? Number.MAX_SAFE_INTEGER,
497
+ maxQueueSize: options?.maxQueueSize ?? Number.MAX_SAFE_INTEGER,
498
+ ...options?.maxTaskIdleTime !== void 0 ? { maxTaskIdleTime: options.maxTaskIdleTime } : {}
499
+ });
500
+ return {
501
+ execute: (operation, options) => taskProcessor.enqueue(async (context) => {
502
+ try {
503
+ return await operation({ abort: context.abort });
504
+ } finally {
505
+ context.ack();
506
+ }
507
+ }, options),
508
+ waitForIdle: () => taskProcessor.waitForEndOfProcessing(),
509
+ stop: (options) => taskProcessor.stop(options)
510
+ };
511
+ };
512
+ const guardBoundedAccess = (getResource, options) => {
513
+ let isStopped = false;
514
+ const taskProcessor = new TaskProcessor({
515
+ maxActiveTasks: options.maxResources,
516
+ maxQueueSize: options.maxQueueSize ?? 1e3
517
+ });
518
+ const resourcePool = [];
519
+ const allResources = /* @__PURE__ */ new Set();
520
+ const activeResourceContexts = /* @__PURE__ */ new Map();
521
+ const acquireResource = async (taskContext) => {
522
+ try {
523
+ let resource;
524
+ if (options.reuseResources) resource = resourcePool.pop();
525
+ if (!resource) {
526
+ resource = await getResource({ abort: taskContext.abort });
527
+ allResources.add(resource);
528
+ }
529
+ activeResourceContexts.set(resource, {
530
+ ack: taskContext.ack,
531
+ taskContext
532
+ });
533
+ return resource;
534
+ } catch (e) {
535
+ taskContext.ack();
536
+ throw e;
537
+ }
538
+ };
539
+ const acquire = async (operationOptions) => taskProcessor.enqueue((taskContext) => acquireResource(taskContext), operationOptions);
540
+ const getActiveResourceContext = (resource) => {
541
+ const activeResourceContext = activeResourceContexts.get(resource);
542
+ if (!activeResourceContext) throw new Error("Acquired resource is not active");
543
+ return activeResourceContext;
544
+ };
545
+ const release = (resource) => {
546
+ const activeResourceContext = activeResourceContexts.get(resource);
547
+ if (activeResourceContext) {
548
+ activeResourceContexts.delete(resource);
549
+ if (options.reuseResources) resourcePool.push(resource);
550
+ activeResourceContext.ack();
551
+ }
552
+ };
553
+ const execute = async (operation, operationOptions) => {
554
+ return taskProcessor.enqueue(async (taskContext) => {
555
+ const resource = await acquireResource(taskContext);
556
+ const activeResourceContext = getActiveResourceContext(resource);
557
+ try {
558
+ return await operation(resource, { abort: activeResourceContext.taskContext.abort });
559
+ } finally {
560
+ release(resource);
561
+ }
562
+ }, operationOptions);
563
+ };
564
+ return {
565
+ acquire,
566
+ release,
567
+ execute,
568
+ waitForIdle: () => taskProcessor.waitForEndOfProcessing(),
569
+ stop: async (stopOptions) => {
570
+ if (isStopped) return;
571
+ isStopped = true;
572
+ await taskProcessor.stop(stopOptions);
573
+ if (options?.closeResource) {
574
+ const resources = [...allResources];
575
+ allResources.clear();
576
+ resourcePool.length = 0;
577
+ await Promise.all(resources.map(async (resource) => await options.closeResource(resource)));
578
+ }
579
+ }
580
+ };
581
+ };
582
+ const guardInitializedOnce = (initialize, options) => {
583
+ let initPromise = null;
584
+ const taskProcessor = new TaskProcessor({
585
+ maxActiveTasks: 1,
586
+ maxQueueSize: options?.maxQueueSize ?? 1e3
587
+ });
588
+ const ensureInitialized = async (operationOptions, retryCount = 0) => {
589
+ if (initPromise !== null) return initPromise;
590
+ return taskProcessor.enqueue(async ({ abort, ack }) => {
591
+ if (initPromise !== null) {
592
+ ack();
593
+ return initPromise;
594
+ }
595
+ try {
596
+ const promise = initialize({ abort });
597
+ initPromise = promise;
598
+ const result = await promise;
599
+ ack();
600
+ return result;
601
+ } catch (error) {
602
+ initPromise = null;
603
+ ack();
604
+ if (retryCount < (options?.maxRetries ?? 3)) return ensureInitialized(operationOptions, retryCount + 1);
605
+ throw error;
606
+ }
607
+ }, {
608
+ ...operationOptions,
609
+ taskGroupId: (0, uuid.v7)()
610
+ });
611
+ };
612
+ return {
613
+ ensureInitialized,
614
+ reset: () => {
615
+ initPromise = null;
616
+ },
617
+ stop: (options) => taskProcessor.stop(options)
618
+ };
619
+ };
620
+
208
621
  //#endregion
209
622
  //#region src/core/execute/execute.ts
210
623
  const mapSQLQueryResult = (result, mapping) => {
@@ -229,26 +642,63 @@ var BatchCommandNoChangesError = class BatchCommandNoChangesError extends DumboE
229
642
  }
230
643
  };
231
644
  const sqlExecutor = (sqlExecutor, options) => ({
232
- query: (sql, queryOptions) => executeInNewDbClient((client) => sqlExecutor.query(client, sql, queryOptions), options),
233
- batchQuery: (sqls, queryOptions) => executeInNewDbClient((client) => sqlExecutor.batchQuery(client, sqls, queryOptions), options),
234
- command: (sql, commandOptions) => executeInNewDbClient((client) => sqlExecutor.command(client, sql, commandOptions), options),
235
- batchCommand: (sqls, commandOptions) => executeInNewDbClient((client) => sqlExecutor.batchCommand(client, sqls, commandOptions), options)
645
+ query: (sql, queryOptions) => executeInNewDbClient((client) => sqlExecutor.query(client, sql, queryOptions), {
646
+ ...options,
647
+ ...queryOptions
648
+ }),
649
+ batchQuery: (sqls, queryOptions) => executeInNewDbClient((client) => sqlExecutor.batchQuery(client, sqls, queryOptions), {
650
+ ...options,
651
+ ...queryOptions
652
+ }),
653
+ command: (sql, commandOptions) => executeInNewDbClient((client) => sqlExecutor.command(client, sql, commandOptions), {
654
+ ...options,
655
+ ...commandOptions
656
+ }),
657
+ batchCommand: (sqls, commandOptions) => executeInNewDbClient((client) => sqlExecutor.batchCommand(client, sqls, commandOptions), {
658
+ ...options,
659
+ ...commandOptions
660
+ })
236
661
  });
237
662
  const sqlExecutorInNewConnection = (options) => ({
238
- query: (sql, queryOptions) => executeInNewConnection((connection) => connection.execute.query(sql, queryOptions), options),
239
- batchQuery: (sqls, queryOptions) => executeInNewConnection((connection) => connection.execute.batchQuery(sqls, queryOptions), options),
240
- command: (sql, commandOptions) => executeInNewConnection((connection) => connection.execute.command(sql, commandOptions), options),
241
- batchCommand: (sqls, commandOptions) => executeInNewConnection((connection) => connection.execute.batchCommand(sqls, commandOptions), options)
663
+ query: (sql, queryOptions) => executeInNewConnection((connection) => connection.execute.query(sql, queryOptions), {
664
+ ...options,
665
+ ...queryOptions
666
+ }),
667
+ batchQuery: (sqls, queryOptions) => executeInNewConnection((connection) => connection.execute.batchQuery(sqls, queryOptions), {
668
+ ...options,
669
+ ...queryOptions
670
+ }),
671
+ command: (sql, commandOptions) => executeInNewConnection((connection) => connection.execute.command(sql, commandOptions), {
672
+ ...options,
673
+ ...commandOptions
674
+ }),
675
+ batchCommand: (sqls, commandOptions) => executeInNewConnection((connection) => connection.execute.batchCommand(sqls, commandOptions), {
676
+ ...options,
677
+ ...commandOptions
678
+ })
242
679
  });
243
680
  const sqlExecutorInAmbientConnection = (options) => ({
244
- query: (sql, queryOptions) => executeInAmbientConnection((connection) => connection.execute.query(sql, queryOptions), options),
245
- batchQuery: (sqls, queryOptions) => executeInAmbientConnection((connection) => connection.execute.batchQuery(sqls, queryOptions), options),
246
- command: (sql, commandOptions) => executeInAmbientConnection((connection) => connection.execute.command(sql, commandOptions), options),
247
- batchCommand: (sqls, commandOptions) => executeInAmbientConnection((connection) => connection.execute.batchCommand(sqls, commandOptions), options)
681
+ query: (sql, queryOptions) => executeInAmbientConnection((connection) => connection.execute.query(sql, queryOptions), {
682
+ ...options,
683
+ ...queryOptions
684
+ }),
685
+ batchQuery: (sqls, queryOptions) => executeInAmbientConnection((connection) => connection.execute.batchQuery(sqls, queryOptions), {
686
+ ...options,
687
+ ...queryOptions
688
+ }),
689
+ command: (sql, commandOptions) => executeInAmbientConnection((connection) => connection.execute.command(sql, commandOptions), {
690
+ ...options,
691
+ ...commandOptions
692
+ }),
693
+ batchCommand: (sqls, commandOptions) => executeInAmbientConnection((connection) => connection.execute.batchCommand(sqls, commandOptions), {
694
+ ...options,
695
+ ...commandOptions
696
+ })
248
697
  });
249
698
  const executeInNewDbClient = async (handle, options) => {
699
+ Abort.throwIfAborted(options);
250
700
  const { connect, close } = options;
251
- const client = await connect();
701
+ const client = await connect({ abort: Abort.from(options) });
252
702
  try {
253
703
  return await handle(client);
254
704
  } catch (error) {
@@ -257,7 +707,8 @@ const executeInNewDbClient = async (handle, options) => {
257
707
  }
258
708
  };
259
709
  const executeInNewConnection = async (handle, options) => {
260
- const connection = await options.connection();
710
+ Abort.throwIfAborted(options);
711
+ const connection = await options.connection({ abort: Abort.from(options) });
261
712
  try {
262
713
  return await handle(connection);
263
714
  } finally {
@@ -265,10 +716,8 @@ const executeInNewConnection = async (handle, options) => {
265
716
  }
266
717
  };
267
718
  const executeInAmbientConnection = async (handle, options) => {
268
- const connection = await options.connection();
269
- try {
270
- return await handle(connection);
271
- } finally {}
719
+ Abort.throwIfAborted(options);
720
+ return handle(await options.connection({ abort: Abort.from(options) }));
272
721
  };
273
722
 
274
723
  //#endregion
@@ -336,10 +785,10 @@ const toTransactionResult = (transactionResult) => transactionResult !== void 0
336
785
  success: true,
337
786
  result: transactionResult
338
787
  };
339
- const executeInTransaction = async (transaction, handle) => {
788
+ const executeInTransaction = async (transaction, handle, context = { abort: Abort.never }) => {
340
789
  await transaction.begin();
341
790
  try {
342
- const { success, result } = toTransactionResult(await handle(transaction));
791
+ const { success, result } = toTransactionResult(await handle(transaction, context));
343
792
  if (success) await transaction.commit();
344
793
  else await transaction.rollback();
345
794
  return result;
@@ -348,18 +797,32 @@ const executeInTransaction = async (transaction, handle) => {
348
797
  throw e;
349
798
  }
350
799
  };
800
+ const executeInNestedTransaction = async (transaction, handle, options, context) => {
801
+ Abort.throwIfAborted(options);
802
+ 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.");
803
+ return executeInTransaction(transaction, handle, context);
804
+ };
351
805
  const transactionFactoryWithDbClient = (connect, initTransaction) => {
352
806
  let currentTransaction = void 0;
353
- const getOrInitCurrentTransaction = (options) => currentTransaction ?? (currentTransaction = initTransaction(connect(), {
354
- close: () => {
355
- currentTransaction = void 0;
356
- return Promise.resolve();
357
- },
358
- ...options ?? {}
359
- }));
807
+ const getOrInitCurrentTransaction = (options) => {
808
+ Abort.throwIfAborted(options);
809
+ if (currentTransaction) return currentTransaction;
810
+ currentTransaction = initTransaction(connect({ abort: Abort.from(options) }), {
811
+ close: () => {
812
+ currentTransaction = void 0;
813
+ return Promise.resolve();
814
+ },
815
+ ...options ?? {}
816
+ });
817
+ return currentTransaction;
818
+ };
360
819
  return {
361
820
  transaction: getOrInitCurrentTransaction,
362
- withTransaction: (handle, options) => executeInTransaction(getOrInitCurrentTransaction(options), handle)
821
+ withTransaction: (handle, options) => {
822
+ const abortRejection = Abort.rejectIfAborted(options);
823
+ if (abortRejection) return abortRejection;
824
+ return executeInTransaction(getOrInitCurrentTransaction(options), handle, { abort: Abort.from(options) });
825
+ }
363
826
  };
364
827
  };
365
828
  const wrapInConnectionClosure = async (connection, handle) => {
@@ -371,7 +834,8 @@ const wrapInConnectionClosure = async (connection, handle) => {
371
834
  };
372
835
  const transactionFactoryWithNewConnection = (connect) => ({
373
836
  transaction: (options) => {
374
- const connection = connect();
837
+ Abort.throwIfAborted(options);
838
+ const connection = connect({ abort: Abort.from(options) });
375
839
  const transaction = connection.transaction(options);
376
840
  return {
377
841
  ...transaction,
@@ -380,7 +844,9 @@ const transactionFactoryWithNewConnection = (connect) => ({
380
844
  };
381
845
  },
382
846
  withTransaction: (handle, options) => {
383
- const connection = connect();
847
+ const abortRejection = Abort.rejectIfAborted(options);
848
+ if (abortRejection) return abortRejection;
849
+ const connection = connect({ abort: Abort.from(options) });
384
850
  const withTx = connection.withTransaction;
385
851
  return wrapInConnectionClosure(connection, () => withTx(handle, options));
386
852
  }
@@ -389,19 +855,21 @@ const transactionFactoryWithAsyncAmbientConnection = (driverType, connect, close
389
855
  close ??= () => Promise.resolve();
390
856
  return {
391
857
  transaction: (options) => {
858
+ Abort.throwIfAborted(options);
392
859
  let conn = null;
393
860
  let innerTx = null;
394
861
  let connectingPromise = null;
395
862
  const ensureConnection = async () => {
396
863
  if (conn) return innerTx;
397
864
  if (!connectingPromise) connectingPromise = (async () => {
398
- conn = await connect();
865
+ Abort.throwIfAborted(options);
866
+ conn = await connect({ abort: Abort.from(options) });
399
867
  innerTx = conn.transaction(options);
400
868
  })();
401
869
  await connectingPromise;
402
870
  return innerTx;
403
871
  };
404
- return {
872
+ const tx = {
405
873
  driverType,
406
874
  get connection() {
407
875
  if (!conn) throw new Error("Transaction not started - call begin() first");
@@ -443,11 +911,14 @@ const transactionFactoryWithAsyncAmbientConnection = (driverType, connect, close
443
911
  if (conn) await close(conn);
444
912
  }
445
913
  },
446
- _transactionOptions: void 0
914
+ withTransaction: (handle, options) => executeInNestedTransaction(tx, handle, options, { abort: Abort.from(options) }),
915
+ _transactionOptions: options ?? {}
447
916
  };
917
+ return tx;
448
918
  },
449
919
  withTransaction: async (handle, options) => {
450
- const conn = await connect();
920
+ Abort.throwIfAborted(options);
921
+ const conn = await connect({ abort: Abort.from(options) });
451
922
  try {
452
923
  const withTx = conn.withTransaction;
453
924
  return await withTx(handle, options);
@@ -464,7 +935,10 @@ const createAmbientConnection = (options) => {
464
935
  const { driverType, client, executor, initTransaction, serializer } = options;
465
936
  const clientPromise = Promise.resolve(client);
466
937
  const closePromise = Promise.resolve();
467
- const open = () => clientPromise;
938
+ const open = (context) => {
939
+ Abort.throwIfAborted(context);
940
+ return clientPromise;
941
+ };
468
942
  const close = () => closePromise;
469
943
  const typedConnection = {
470
944
  driverType,
@@ -480,9 +954,10 @@ const createConnection = (options) => {
480
954
  const { driverType, connect, close, initTransaction, executor, serializer } = options;
481
955
  let client = null;
482
956
  let connectPromise = null;
483
- const getClient = async () => {
957
+ const getClient = async (context) => {
958
+ Abort.throwIfAborted(context);
484
959
  if (client) return client;
485
- if (!connectPromise) connectPromise = connect().then((c) => {
960
+ if (!connectPromise) connectPromise = connect(context ?? { abort: Abort.never }).then((c) => {
486
961
  client = c;
487
962
  return c;
488
963
  });
@@ -499,209 +974,6 @@ const createConnection = (options) => {
499
974
  return typedConnection;
500
975
  };
501
976
 
502
- //#endregion
503
- //#region src/core/taskProcessing/taskProcessor.ts
504
- var TaskProcessor = class {
505
- queue = [];
506
- isProcessing = false;
507
- activeTasks = 0;
508
- activeGroups = /* @__PURE__ */ new Set();
509
- options;
510
- stopped = false;
511
- constructor(options) {
512
- this.options = options;
513
- }
514
- enqueue(task, options) {
515
- if (this.stopped) return Promise.reject(new DumboError("TaskProcessor has been stopped"));
516
- if (this.queue.length >= this.options.maxQueueSize) return Promise.reject(new TransientDatabaseError("Too many pending connections. Please try again later."));
517
- return this.schedule(task, options);
518
- }
519
- waitForEndOfProcessing() {
520
- return this.schedule(({ ack }) => Promise.resolve(ack()));
521
- }
522
- async stop(options) {
523
- if (this.stopped) return;
524
- this.stopped = true;
525
- this.queue.length = 0;
526
- this.activeGroups.clear();
527
- if (!options?.force) await this.waitForEndOfProcessing();
528
- }
529
- schedule(task, options) {
530
- return promiseWithDeadline((resolve, reject) => {
531
- const taskWithContext = () => {
532
- return new Promise((resolveTask, failTask) => {
533
- task({ ack: resolveTask }).then(resolve).catch((err) => {
534
- failTask(err);
535
- reject(err);
536
- });
537
- });
538
- };
539
- this.queue.push({
540
- task: taskWithContext,
541
- options
542
- });
543
- if (!this.isProcessing) this.ensureProcessing();
544
- }, { deadline: this.options.maxTaskIdleTime });
545
- }
546
- ensureProcessing() {
547
- if (this.isProcessing) return;
548
- this.isProcessing = true;
549
- this.processQueue();
550
- }
551
- processQueue() {
552
- try {
553
- while (this.activeTasks < this.options.maxActiveTasks && this.queue.length > 0) {
554
- const item = this.takeFirstAvailableItem();
555
- if (item === null) return;
556
- const groupId = item.options?.taskGroupId;
557
- if (groupId) this.activeGroups.add(groupId);
558
- this.activeTasks++;
559
- this.executeItem(item);
560
- }
561
- } catch (error) {
562
- console.error(error);
563
- throw error;
564
- } finally {
565
- this.isProcessing = false;
566
- if (this.hasItemsToProcess() && this.activeTasks < this.options.maxActiveTasks) this.ensureProcessing();
567
- }
568
- }
569
- async executeItem({ task, options }) {
570
- try {
571
- await task();
572
- } finally {
573
- this.activeTasks--;
574
- if (options && options.taskGroupId) this.activeGroups.delete(options.taskGroupId);
575
- this.ensureProcessing();
576
- }
577
- }
578
- takeFirstAvailableItem = () => {
579
- const taskIndex = this.queue.findIndex((item) => !item.options?.taskGroupId || !this.activeGroups.has(item.options.taskGroupId));
580
- if (taskIndex === -1) return null;
581
- const [item] = this.queue.splice(taskIndex, 1);
582
- return item ?? null;
583
- };
584
- hasItemsToProcess = () => this.queue.findIndex((item) => !item.options?.taskGroupId || !this.activeGroups.has(item.options.taskGroupId)) !== -1;
585
- };
586
- const DEFAULT_PROMISE_DEADLINE = 2147483647;
587
- const promiseWithDeadline = (executor, options) => {
588
- return new Promise((resolve, reject) => {
589
- let taskStarted = false;
590
- let timeoutId = null;
591
- const deadline = options.deadline ?? DEFAULT_PROMISE_DEADLINE;
592
- timeoutId = setTimeout(() => {
593
- if (!taskStarted) reject(/* @__PURE__ */ new Error("Task was not started within the maximum waiting time"));
594
- }, deadline);
595
- timeoutId.unref();
596
- executor((value) => {
597
- taskStarted = true;
598
- if (timeoutId) clearTimeout(timeoutId);
599
- timeoutId = null;
600
- resolve(value);
601
- }, (reason) => {
602
- if (timeoutId) clearTimeout(timeoutId);
603
- timeoutId = null;
604
- reject(reason);
605
- });
606
- });
607
- };
608
-
609
- //#endregion
610
- //#region src/core/taskProcessing/executionGuards.ts
611
- const guardBoundedAccess = (getResource, options) => {
612
- let isStopped = false;
613
- const taskProcessor = new TaskProcessor({
614
- maxActiveTasks: options.maxResources,
615
- maxQueueSize: options.maxQueueSize ?? 1e3
616
- });
617
- const resourcePool = [];
618
- const allResources = /* @__PURE__ */ new Set();
619
- const ackCallbacks = /* @__PURE__ */ new Map();
620
- const acquire = async () => taskProcessor.enqueue(async ({ ack }) => {
621
- try {
622
- let resource;
623
- if (options.reuseResources) resource = resourcePool.pop();
624
- if (!resource) {
625
- resource = await getResource();
626
- allResources.add(resource);
627
- }
628
- ackCallbacks.set(resource, ack);
629
- return resource;
630
- } catch (e) {
631
- ack();
632
- throw e;
633
- }
634
- });
635
- const release = (resource) => {
636
- const ack = ackCallbacks.get(resource);
637
- if (ack) {
638
- ackCallbacks.delete(resource);
639
- if (options.reuseResources) resourcePool.push(resource);
640
- ack();
641
- }
642
- };
643
- const execute = async (operation) => {
644
- const resource = await acquire();
645
- try {
646
- return await operation(resource);
647
- } finally {
648
- release(resource);
649
- }
650
- };
651
- return {
652
- acquire,
653
- release,
654
- execute,
655
- waitForIdle: () => taskProcessor.waitForEndOfProcessing(),
656
- stop: async (stopOptions) => {
657
- if (isStopped) return;
658
- isStopped = true;
659
- if (options?.closeResource) {
660
- const resources = [...allResources];
661
- allResources.clear();
662
- resourcePool.length = 0;
663
- await Promise.all(resources.map(async (resource) => await options.closeResource(resource)));
664
- }
665
- await taskProcessor.stop(stopOptions);
666
- }
667
- };
668
- };
669
- const guardInitializedOnce = (initialize, options) => {
670
- let initPromise = null;
671
- const taskProcessor = new TaskProcessor({
672
- maxActiveTasks: 1,
673
- maxQueueSize: options?.maxQueueSize ?? 1e3
674
- });
675
- const ensureInitialized = async (retryCount = 0) => {
676
- if (initPromise !== null) return initPromise;
677
- return taskProcessor.enqueue(async ({ ack }) => {
678
- if (initPromise !== null) {
679
- ack();
680
- return initPromise;
681
- }
682
- try {
683
- const promise = initialize();
684
- initPromise = promise;
685
- const result = await promise;
686
- ack();
687
- return result;
688
- } catch (error) {
689
- initPromise = null;
690
- ack();
691
- if (retryCount < (options?.maxRetries ?? 3)) return ensureInitialized(retryCount + 1);
692
- throw error;
693
- }
694
- }, { taskGroupId: (0, uuid.v7)() });
695
- };
696
- return {
697
- ensureInitialized,
698
- reset: () => {
699
- initPromise = null;
700
- },
701
- stop: (options) => taskProcessor.stop(options)
702
- };
703
- };
704
-
705
977
  //#endregion
706
978
  //#region src/core/connections/pool.ts
707
979
  const wrapPooledConnection = (conn, onClose) => ({
@@ -715,7 +987,10 @@ const createAmbientConnectionPool = (options) => {
715
987
  getConnection: () => connection,
716
988
  execute: connection.execute,
717
989
  transaction: (options) => connection.transaction(options),
718
- withConnection: (handle, _options) => handle(connection),
990
+ withConnection: (handle, options) => {
991
+ Abort.throwIfAborted(options);
992
+ return handle(connection, { abort: Abort.from(options) });
993
+ },
719
994
  withTransaction: (handle, options) => {
720
995
  const withTx = connection.withTransaction;
721
996
  return withTx(handle, options);
@@ -726,48 +1001,45 @@ const createSingletonConnectionPool = (options) => {
726
1001
  const { driverType, getConnection } = options;
727
1002
  let connectionPromise = null;
728
1003
  let closed = false;
729
- const activeOperations = /* @__PURE__ */ new Set();
1004
+ const operationGuard = guardConcurrentAccess();
730
1005
  const closedError = () => /* @__PURE__ */ new Error("Singleton connection pool has been closed");
731
- const run = async (operation) => {
1006
+ const executeIfOpen = async (operation, operationOptions) => {
732
1007
  if (closed) throw closedError();
733
- const activeOperation = operation();
734
- activeOperations.add(activeOperation);
735
- try {
736
- return await activeOperation;
737
- } finally {
738
- activeOperations.delete(activeOperation);
739
- }
1008
+ return operationGuard.execute(operation, operationOptions);
740
1009
  };
741
- const getExistingOrNewConnection = () => {
742
- if (!connectionPromise) connectionPromise ??= Promise.resolve(getConnection());
1010
+ const getExistingOrNewConnection = (context) => {
1011
+ if (!connectionPromise) connectionPromise ??= Promise.resolve(getConnection(context));
743
1012
  return connectionPromise;
744
1013
  };
745
1014
  const innerTransactionFactory = transactionFactoryWithAsyncAmbientConnection(options.driverType, getExistingOrNewConnection, options.closeConnection);
746
1015
  return {
747
1016
  driverType,
748
- connection: () => run(() => getExistingOrNewConnection().then((conn) => wrapPooledConnection(conn, () => Promise.resolve()))),
1017
+ connection: (connectionOptions) => executeIfOpen((context) => getExistingOrNewConnection(context).then((conn) => wrapPooledConnection(conn, () => Promise.resolve())), connectionOptions),
749
1018
  execute: (() => {
750
1019
  const ambientExecutor = sqlExecutorInAmbientConnection({
751
1020
  driverType,
752
1021
  connection: getExistingOrNewConnection
753
1022
  });
754
1023
  return {
755
- query: (sql, opts) => run(() => ambientExecutor.query(sql, opts)),
756
- batchQuery: (sqls, opts) => run(() => ambientExecutor.batchQuery(sqls, opts)),
757
- command: (sql, opts) => run(() => ambientExecutor.command(sql, opts)),
758
- batchCommand: (sqls, opts) => run(() => ambientExecutor.batchCommand(sqls, opts))
1024
+ query: (sql, opts) => executeIfOpen(() => ambientExecutor.query(sql, opts), opts),
1025
+ batchQuery: (sqls, opts) => executeIfOpen(() => ambientExecutor.batchQuery(sqls, opts), opts),
1026
+ command: (sql, opts) => executeIfOpen(() => ambientExecutor.command(sql, opts), opts),
1027
+ batchCommand: (sqls, opts) => executeIfOpen(() => ambientExecutor.batchCommand(sqls, opts), opts)
759
1028
  };
760
1029
  })(),
761
- withConnection: (handle, _options) => run(() => executeInAmbientConnection(handle, { connection: getExistingOrNewConnection })),
1030
+ withConnection: (handle, options) => executeIfOpen((context) => executeInAmbientConnection((connection) => handle(connection, context), {
1031
+ connection: getExistingOrNewConnection,
1032
+ ...options
1033
+ }), options),
762
1034
  transaction: (transactionOptions) => {
763
1035
  if (closed) throw closedError();
764
1036
  return innerTransactionFactory.transaction(transactionOptions);
765
1037
  },
766
- withTransaction: (handle, transactionOptions) => run(() => innerTransactionFactory.withTransaction(handle, transactionOptions)),
1038
+ withTransaction: (handle, transactionOptions) => executeIfOpen((context) => innerTransactionFactory.withTransaction((tx) => handle(tx, context), transactionOptions), transactionOptions),
767
1039
  close: async (closeOptions) => {
768
1040
  if (closed) return;
769
1041
  closed = true;
770
- if (!closeOptions?.force) await Promise.allSettled([...activeOperations]);
1042
+ await operationGuard.stop(closeOptions);
771
1043
  if (!connectionPromise) return;
772
1044
  const connection = await connectionPromise;
773
1045
  connectionPromise = null;
@@ -777,63 +1049,49 @@ const createSingletonConnectionPool = (options) => {
777
1049
  };
778
1050
  const createBoundedConnectionPool = (options) => {
779
1051
  const { driverType, maxConnections } = options;
780
- const allConnections = /* @__PURE__ */ new Set();
781
- const getTrackedConnection = async () => {
782
- const connection = await options.getConnection();
783
- allConnections.add(connection);
784
- return connection;
785
- };
786
- const guardMaxConnections = guardBoundedAccess(getTrackedConnection, {
1052
+ const guardMaxConnections = guardBoundedAccess(options.getConnection, {
787
1053
  maxResources: maxConnections,
788
- reuseResources: true
1054
+ reuseResources: true,
1055
+ closeResource: (connection) => connection.close()
789
1056
  });
790
1057
  let closed = false;
791
1058
  const closedError = () => /* @__PURE__ */ new Error("Bounded connection pool has been closed");
792
1059
  const ensureOpen = () => {
793
1060
  if (closed) throw closedError();
794
1061
  };
795
- const closeAllConnections = async () => {
796
- const connections = [...allConnections];
797
- allConnections.clear();
798
- await Promise.all(connections.map((conn) => conn.close()));
799
- };
800
- const executeWithPooling = async (operation) => {
1062
+ const executeWithPooledConnection = async (operation, operationOptions) => {
801
1063
  ensureOpen();
802
- const conn = await guardMaxConnections.acquire();
803
- try {
804
- return await operation(conn);
805
- } finally {
806
- guardMaxConnections.release(conn);
807
- }
1064
+ return guardMaxConnections.execute(operation, operationOptions);
808
1065
  };
809
1066
  return {
810
1067
  driverType,
811
- connection: async () => {
1068
+ connection: async (connectionOptions) => {
812
1069
  ensureOpen();
813
- const conn = await guardMaxConnections.acquire();
1070
+ const conn = await guardMaxConnections.acquire(connectionOptions);
814
1071
  return wrapPooledConnection(conn, () => Promise.resolve(guardMaxConnections.release(conn)));
815
1072
  },
816
1073
  execute: {
817
- query: (sql, opts) => executeWithPooling((c) => c.execute.query(sql, opts)),
818
- batchQuery: (sqls, opts) => executeWithPooling((c) => c.execute.batchQuery(sqls, opts)),
819
- command: (sql, opts) => executeWithPooling((c) => c.execute.command(sql, opts)),
820
- batchCommand: (sqls, opts) => executeWithPooling((c) => c.execute.batchCommand(sqls, opts))
1074
+ query: (sql, opts) => executeWithPooledConnection((c) => c.execute.query(sql, opts), opts),
1075
+ batchQuery: (sqls, opts) => executeWithPooledConnection((c) => c.execute.batchQuery(sqls, opts), opts),
1076
+ command: (sql, opts) => executeWithPooledConnection((c) => c.execute.command(sql, opts), opts),
1077
+ batchCommand: (sqls, opts) => executeWithPooledConnection((c) => c.execute.batchCommand(sqls, opts), opts)
821
1078
  },
822
- withConnection: executeWithPooling,
1079
+ withConnection: executeWithPooledConnection,
823
1080
  transaction: (transactionOptions) => {
824
1081
  ensureOpen();
825
- return transactionFactoryWithAsyncAmbientConnection(driverType, guardMaxConnections.acquire, guardMaxConnections.release).transaction(transactionOptions);
1082
+ return transactionFactoryWithAsyncAmbientConnection(driverType, (context) => guardMaxConnections.acquire({
1083
+ ...transactionOptions,
1084
+ abort: context.abort
1085
+ }), guardMaxConnections.release).transaction(transactionOptions);
826
1086
  },
827
- withTransaction: (handle, transactionOptions) => executeWithPooling((conn) => {
1087
+ withTransaction: (handle, transactionOptions) => executeWithPooledConnection((conn, context) => {
828
1088
  const withTx = conn.withTransaction;
829
- return withTx(handle, transactionOptions);
830
- }),
1089
+ return withTx((tx) => handle(tx, context), transactionOptions);
1090
+ }, transactionOptions),
831
1091
  close: async (closeOptions) => {
832
1092
  if (closed) return;
833
1093
  closed = true;
834
- if (!closeOptions?.force) await guardMaxConnections.waitForIdle();
835
- await guardMaxConnections.stop({ force: true });
836
- await closeAllConnections();
1094
+ await guardMaxConnections.stop(closeOptions);
837
1095
  }
838
1096
  };
839
1097
  };
@@ -841,16 +1099,22 @@ const createAlwaysNewConnectionPool = (options) => {
841
1099
  const { driverType, getConnection, connectionOptions } = options;
842
1100
  return createConnectionPool({
843
1101
  driverType,
844
- getConnection: () => connectionOptions ? getConnection(connectionOptions) : getConnection()
1102
+ getConnection: (context) => connectionOptions ? getConnection(connectionOptions, context) : getConnection(context)
845
1103
  });
846
1104
  };
847
1105
  const createConnectionPool = (pool) => {
848
1106
  const { driverType, getConnection } = pool;
849
- const connection = "connection" in pool ? pool.connection : () => Promise.resolve(getConnection());
1107
+ const connection = "connection" in pool ? pool.connection : (options) => {
1108
+ Abort.throwIfAborted(options);
1109
+ return Promise.resolve(getConnection({ abort: Abort.from(options) }));
1110
+ };
850
1111
  return {
851
1112
  driverType,
852
1113
  connection,
853
- withConnection: "withConnection" in pool ? pool.withConnection : (handle, _options) => executeInNewConnection(handle, { connection }),
1114
+ withConnection: "withConnection" in pool ? pool.withConnection : (handle, options) => executeInNewConnection((connection) => handle(connection, { abort: Abort.from(options) }), {
1115
+ connection,
1116
+ ...options
1117
+ }),
854
1118
  close: "close" in pool ? pool.close : () => Promise.resolve(),
855
1119
  execute: "execute" in pool ? pool.execute : sqlExecutorInNewConnection({
856
1120
  driverType,
@@ -2190,9 +2454,9 @@ const sqliteSQLExecutor = (driverType, serializer, formatter, errorMapper) => ({
2190
2454
 
2191
2455
  //#endregion
2192
2456
  //#region src/storage/sqlite/core/transactions/index.ts
2193
- const sqliteTransaction = (driverType, connection, allowNestedTransactions, serializer, defaultTransactionMode) => (getClient, options) => {
2194
- allowNestedTransactions = options?.allowNestedTransactions ?? allowNestedTransactions;
2195
- const useSavepoints = options?.useSavepoints ?? false;
2457
+ const sqliteTransaction = (driverType, connection, defaultOptions, serializer, defaultTransactionMode) => (getClient, options) => {
2458
+ const defaultTransactionOptions = typeof defaultOptions === "boolean" ? { allowNestedTransactions: defaultOptions } : defaultOptions;
2459
+ const allowNestedTransactions = options?.allowNestedTransactions ?? defaultTransactionOptions.allowNestedTransactions ?? false;
2196
2460
  const tx = databaseTransaction({
2197
2461
  begin: async () => {
2198
2462
  const client = await getClient;
@@ -2220,23 +2484,28 @@ const sqliteTransaction = (driverType, connection, allowNestedTransactions, seri
2220
2484
  },
2221
2485
  releaseSavepoint: async (level) => {
2222
2486
  await (await getClient).command(SQL`RELEASE transaction${SQL.plain(level.toString())}`);
2487
+ },
2488
+ rollbackToSavepoint: async (level) => {
2489
+ await (await getClient).command(SQL`ROLLBACK TO transaction${SQL.plain(level.toString())}`);
2223
2490
  }
2224
2491
  }, {
2225
2492
  allowNestedTransactions,
2226
- useSavepoints
2493
+ useSavepoints: options?.useSavepoints ?? defaultTransactionOptions.useSavepoints ?? false
2227
2494
  });
2228
- return {
2495
+ const transaction = {
2229
2496
  connection: connection(),
2230
2497
  driverType,
2231
2498
  begin: tx.begin,
2232
2499
  commit: tx.commit,
2233
2500
  rollback: tx.rollback,
2234
2501
  execute: sqlExecutor(sqliteSQLExecutor(driverType, serializer), { connect: () => getClient }),
2502
+ withTransaction: (handle, options) => executeInNestedTransaction(transaction, handle, options),
2235
2503
  _transactionOptions: {
2236
2504
  ...options,
2237
2505
  allowNestedTransactions
2238
2506
  }
2239
2507
  };
2508
+ return transaction;
2240
2509
  };
2241
2510
 
2242
2511
  //#endregion
@@ -2281,7 +2550,7 @@ const sqliteAmbientClientConnection = (options) => {
2281
2550
  return createAmbientConnection({
2282
2551
  driverType,
2283
2552
  client,
2284
- initTransaction: initTransaction ?? ((connection) => sqliteTransaction(driverType, connection, allowNestedTransactions ?? false, serializer, defaultTransactionMode)),
2553
+ initTransaction: initTransaction ?? ((connection) => sqliteTransaction(driverType, connection, { allowNestedTransactions: allowNestedTransactions ?? false }, serializer, defaultTransactionMode)),
2285
2554
  executor: ({ serializer }) => sqliteSQLExecutor(driverType, serializer, void 0, errorMapper),
2286
2555
  serializer
2287
2556
  });
@@ -2306,7 +2575,7 @@ const sqliteClientConnection = (options) => {
2306
2575
  if (client && "close" in client && typeof client.close === "function") await client.close();
2307
2576
  else if (client && "release" in client && typeof client.release === "function") client.release();
2308
2577
  },
2309
- initTransaction: (connection) => sqliteTransaction(options.driverType, connection, connectionOptions.transactionOptions?.allowNestedTransactions ?? false, serializer, connectionOptions.defaultTransactionMode),
2578
+ initTransaction: (connection) => sqliteTransaction(options.driverType, connection, connectionOptions.transactionOptions ?? {}, serializer, connectionOptions.defaultTransactionMode),
2310
2579
  executor: ({ serializer }) => sqliteSQLExecutor(options.driverType, serializer),
2311
2580
  serializer
2312
2581
  });
@@ -2328,7 +2597,7 @@ const sqlitePoolClientConnection = (options) => {
2328
2597
  driverType: options.driverType,
2329
2598
  connect,
2330
2599
  close: () => client !== null ? Promise.resolve(client.release()) : Promise.resolve(),
2331
- initTransaction: (connection) => sqliteTransaction(options.driverType, connection, connectionOptions.transactionOptions?.allowNestedTransactions ?? false, serializer, connectionOptions.defaultTransactionMode),
2600
+ initTransaction: (connection) => sqliteTransaction(options.driverType, connection, connectionOptions.transactionOptions ?? {}, serializer, connectionOptions.defaultTransactionMode),
2332
2601
  executor: ({ serializer }) => sqliteSQLExecutor(options.driverType, serializer),
2333
2602
  serializer
2334
2603
  });
@@ -2702,43 +2971,67 @@ const sqlite3Connection = (options) => sqliteConnection({
2702
2971
  //#endregion
2703
2972
  //#region src/storage/sqlite/sqlite3/pool/singletonPool.ts
2704
2973
  const DEFAULT_SQLITE_MAX_TASK_IDLE_TIME_MS = 3e4;
2974
+ const createSQLiteTransactionContext = () => {
2975
+ if (typeof process === "undefined") return void 0;
2976
+ const asyncHooks = process.getBuiltinModule?.("node:async_hooks");
2977
+ if (!asyncHooks) return void 0;
2978
+ const context = new asyncHooks.AsyncLocalStorage();
2979
+ return {
2980
+ current: () => context.getStore(),
2981
+ run: (active, handle) => context.run(active, handle)
2982
+ };
2983
+ };
2705
2984
  const sqlite3SingletonPool = (options) => {
2706
- const maxTaskIdleTime = options.maxTaskIdleTime ?? 3e4;
2707
2985
  const inner = createSingletonConnectionPool({
2708
2986
  driverType: options.driverType,
2709
2987
  getConnection: options.getConnection,
2710
2988
  ...options.closeConnection ? { closeConnection: options.closeConnection } : {}
2711
2989
  });
2712
- const taskProcessor = new TaskProcessor({
2713
- maxActiveTasks: 1,
2990
+ const writerGuard = guardExclusiveAccess({
2714
2991
  maxQueueSize: options.maxQueueSize ?? 1e3,
2715
- maxTaskIdleTime
2992
+ maxTaskIdleTime: options.maxTaskIdleTime ?? 3e4
2716
2993
  });
2717
- const insideWriterTask = new node_async_hooks.AsyncLocalStorage();
2718
- const enqueue = (op) => {
2719
- if (insideWriterTask.getStore() === true) return op();
2720
- return taskProcessor.enqueue(({ ack }) => insideWriterTask.run(true, async () => {
2721
- try {
2722
- return await op();
2723
- } finally {
2724
- ack();
2725
- }
2726
- }));
2994
+ const transactionContext = options.transactionContext ?? createSQLiteTransactionContext();
2995
+ const activeConnection = () => transactionContext?.current()?.connection;
2996
+ const runInTransactionContext = (connection, operation) => transactionContext ? transactionContext.run({ connection }, operation) : operation();
2997
+ const transactionAwareConnection = (connection) => transactionContext ? {
2998
+ ...connection,
2999
+ withTransaction: (handle, transactionOptions) => runInTransactionContext(connection, () => connection.withTransaction(handle, transactionOptions))
3000
+ } : connection;
3001
+ const runConnectionTransaction = (connection, handle, context, transactionOptions) => {
3002
+ const withTransaction = connection.withTransaction;
3003
+ return withTransaction((transaction) => handle(transaction, context), transactionOptions);
3004
+ };
3005
+ const runOnWriterConnection = (handle, options) => {
3006
+ const connection = activeConnection();
3007
+ if (connection) return inner.withConnection((_connection, context) => handle(connection, context), options);
3008
+ return writerGuard.execute((context) => inner.withConnection((connection) => handle(connection, context), options), options);
2727
3009
  };
3010
+ const withWriterConnection = (handle, options) => runOnWriterConnection((connection, context) => handle(transactionAwareConnection(connection), context), options);
2728
3011
  return {
2729
3012
  driverType: inner.driverType,
2730
3013
  connection: inner.connection.bind(inner),
2731
3014
  transaction: inner.transaction.bind(inner),
2732
- withConnection: (handle, connectionOptions) => enqueue(() => inner.withConnection(handle, connectionOptions)),
2733
- withTransaction: (handle, transactionOptions) => enqueue(() => inner.withTransaction(handle, transactionOptions)),
3015
+ withConnection: (handle, connectionOptions) => activeConnection() || !connectionOptions?.readonly ? withWriterConnection(handle, connectionOptions) : inner.withConnection(handle, connectionOptions),
3016
+ withTransaction: (handle, transactionOptions) => {
3017
+ const connection = activeConnection();
3018
+ if (connection) return inner.withConnection((_connection, context) => runConnectionTransaction(connection, handle, context, transactionOptions), transactionOptions);
3019
+ return runOnWriterConnection((connection, context) => runInTransactionContext(connection, () => runConnectionTransaction(connection, handle, context, transactionOptions)), transactionOptions);
3020
+ },
2734
3021
  execute: {
2735
- query: (sql, queryOptions) => enqueue(() => inner.execute.query(sql, queryOptions)),
2736
- batchQuery: (sqls, queryOptions) => enqueue(() => inner.execute.batchQuery(sqls, queryOptions)),
2737
- command: (sql, commandOptions) => enqueue(() => inner.execute.command(sql, commandOptions)),
2738
- batchCommand: (sqls, commandOptions) => enqueue(() => inner.execute.batchCommand(sqls, commandOptions))
3022
+ query: (sql, queryOptions) => {
3023
+ const connection = activeConnection();
3024
+ return connection ? connection.execute.query(sql, queryOptions) : inner.execute.query(sql, queryOptions);
3025
+ },
3026
+ batchQuery: (sqls, queryOptions) => {
3027
+ const connection = activeConnection();
3028
+ return connection ? connection.execute.batchQuery(sqls, queryOptions) : inner.execute.batchQuery(sqls, queryOptions);
3029
+ },
3030
+ command: (sql, commandOptions) => runOnWriterConnection((connection) => connection.execute.command(sql, commandOptions), commandOptions),
3031
+ batchCommand: (sqls, commandOptions) => runOnWriterConnection((connection) => connection.execute.batchCommand(sqls, commandOptions), commandOptions)
2739
3032
  },
2740
3033
  close: async (closeOptions) => {
2741
- await taskProcessor.stop({ ...closeOptions?.force ? { force: true } : {} });
3034
+ await writerGuard.stop(closeOptions);
2742
3035
  await inner.close(closeOptions);
2743
3036
  }
2744
3037
  };
@@ -2749,15 +3042,16 @@ const sqlite3SingletonPool = (options) => {
2749
3042
  const sqliteDualConnectionPool = (options) => {
2750
3043
  const { sqliteConnectionFactory, connectionOptions } = options;
2751
3044
  const readerPoolSize = options.readerPoolSize ?? Math.max(4, (0, os.cpus)().length);
3045
+ const transactionContext = createSQLiteTransactionContext();
2752
3046
  let databaseInitPromise = null;
2753
- const guardSingleConnection = guardInitializedOnce(async () => {
3047
+ const guardSingleConnection = guardInitializedOnce(async (context) => {
2754
3048
  if (databaseInitPromise !== null) return databaseInitPromise;
2755
3049
  const initConnection = sqliteConnectionFactory({
2756
3050
  ...connectionOptions,
2757
3051
  skipDatabasePragmas: false,
2758
3052
  readonly: false
2759
3053
  });
2760
- const initPromise = initConnection.open();
3054
+ const initPromise = initConnection.open(context);
2761
3055
  databaseInitPromise = initPromise;
2762
3056
  try {
2763
3057
  await initPromise;
@@ -2768,45 +3062,47 @@ const sqliteDualConnectionPool = (options) => {
2768
3062
  throw mapSqliteError(error);
2769
3063
  }
2770
3064
  });
2771
- const ensureDatabaseInitialized = async () => {
3065
+ const ensureDatabaseInitialized = async (context) => {
2772
3066
  if (databaseInitPromise !== null) return databaseInitPromise;
2773
- return guardSingleConnection.ensureInitialized();
3067
+ return guardSingleConnection.ensureInitialized({ abort: context.abort });
2774
3068
  };
2775
- const wrappedConnectionFactory = async (readonly, connectionOptions) => {
2776
- await ensureDatabaseInitialized();
3069
+ const wrappedConnectionFactory = async (readonly, connectionOptions, context) => {
3070
+ await ensureDatabaseInitialized(context);
2777
3071
  const connection = sqliteConnectionFactory({
2778
3072
  ...connectionOptions,
2779
3073
  skipDatabasePragmas: true,
2780
3074
  readonly
2781
3075
  });
2782
- await connection.open();
3076
+ await connection.open(context);
2783
3077
  return connection;
2784
3078
  };
2785
3079
  const writerPool = sqlite3SingletonPool({
2786
3080
  driverType: options.driverType,
2787
- getConnection: () => wrappedConnectionFactory(false, connectionOptions),
3081
+ getConnection: (context) => wrappedConnectionFactory(false, connectionOptions, context),
3082
+ ...transactionContext ? { transactionContext } : {},
2788
3083
  ...options.maxTaskIdleTime !== void 0 ? { maxTaskIdleTime: options.maxTaskIdleTime } : {}
2789
3084
  });
2790
3085
  const readerPool = createBoundedConnectionPool({
2791
3086
  driverType: options.driverType,
2792
- getConnection: () => wrappedConnectionFactory(true, connectionOptions),
3087
+ getConnection: (context) => wrappedConnectionFactory(true, connectionOptions, context),
2793
3088
  maxConnections: readerPoolSize
2794
3089
  });
3090
+ const hasActiveTransaction = () => !!transactionContext?.current();
2795
3091
  return {
2796
3092
  driverType: options.driverType,
2797
- connection: (connectionOptions) => connectionOptions?.readonly ? readerPool.connection(connectionOptions) : writerPool.connection(connectionOptions),
3093
+ connection: (connectionOptions) => connectionOptions?.readonly && !hasActiveTransaction() ? readerPool.connection(connectionOptions) : writerPool.connection(connectionOptions),
2798
3094
  execute: {
2799
- query: (...args) => readerPool.execute.query(...args),
2800
- batchQuery: (...args) => readerPool.execute.batchQuery(...args),
3095
+ query: (...args) => hasActiveTransaction() ? writerPool.execute.query(...args) : readerPool.execute.query(...args),
3096
+ batchQuery: (...args) => hasActiveTransaction() ? writerPool.execute.batchQuery(...args) : readerPool.execute.batchQuery(...args),
2801
3097
  command: (...args) => writerPool.execute.command(...args),
2802
3098
  batchCommand: (...args) => writerPool.execute.batchCommand(...args)
2803
3099
  },
2804
- withConnection: (handle, connectionOptions) => connectionOptions?.readonly ? readerPool.withConnection(handle, connectionOptions) : writerPool.withConnection(handle, connectionOptions),
3100
+ withConnection: (handle, connectionOptions) => connectionOptions?.readonly && !hasActiveTransaction() ? readerPool.withConnection(handle, connectionOptions) : writerPool.withConnection(handle, connectionOptions),
2805
3101
  transaction: writerPool.transaction,
2806
3102
  withTransaction: writerPool.withTransaction,
2807
- close: async () => {
2808
- await guardSingleConnection.stop();
2809
- await Promise.all([writerPool.close(), readerPool.close()]);
3103
+ close: async (closeOptions) => {
3104
+ await guardSingleConnection.stop(closeOptions);
3105
+ await Promise.all([writerPool.close(closeOptions), readerPool.close(closeOptions)]);
2810
3106
  }
2811
3107
  };
2812
3108
  };