@getstrata/core 2.2.36 → 2.2.38

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.
@@ -568,6 +568,17 @@ var init_repositoryConnection = __esm(() => {
568
568
 
569
569
  // ../../src/core/database/transaction.ts
570
570
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
571
+ function scopedConnection(source) {
572
+ const connection = createDatabaseConnection(source);
573
+ transactionAliases.set(connection, source);
574
+ return connection;
575
+ }
576
+ function isActiveTransactionConnection(connection) {
577
+ if (!hasActiveTransaction() || !hasActiveDatabaseConnection())
578
+ return false;
579
+ const active = getActiveDatabaseConnection(connection);
580
+ return connection === active || transactionAliases.get(connection) === active;
581
+ }
571
582
  async function runInSavepoint(connection, operation) {
572
583
  const inherited = transactionFrame.getStore();
573
584
  let parent = inherited?.connection === connection ? inherited : outerFrames.get(connection);
@@ -584,7 +595,7 @@ async function runInSavepoint(connection, operation) {
584
595
  return await settleTransaction(() => runWithDeferredModelEvents(async () => {
585
596
  await connection.unsafe(`SAVEPOINT ${name}`);
586
597
  try {
587
- const result = await transactionFrame.run({ connection, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await operation(createDatabaseConnection(connection)))));
598
+ const result = await transactionFrame.run({ connection, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await operation(scopedConnection(connection)))));
588
599
  await connection.unsafe(`RELEASE SAVEPOINT ${name}`);
589
600
  return result;
590
601
  } catch (error) {
@@ -607,9 +618,12 @@ async function runInTransaction(operation, options = {}) {
607
618
  signal?.throwIfAborted();
608
619
  return operation(connection);
609
620
  };
610
- const pool = resolveRepositoryConnection();
621
+ const pool = options.connection ?? resolveRepositoryConnection();
611
622
  if (hasActiveDatabaseConnection()) {
612
- return await runInSavepoint(getActiveDatabaseConnection(pool), admittedOperation);
623
+ const active = getActiveDatabaseConnection(pool);
624
+ if (options.connection && options.connection !== active && options.connection !== transactionRoots.get(active) && transactionAliases.get(options.connection) !== active)
625
+ throw new Error("Cannot switch database connections within an active transaction.");
626
+ return await runInSavepoint(active, admittedOperation);
613
627
  }
614
628
  if (!supportsTransactions(pool)) {
615
629
  throw new Error("Active database connection does not support transactions. Bind a client with begin() via bindDatabaseConnection.");
@@ -628,14 +642,15 @@ async function runInTransaction(operation, options = {}) {
628
642
  if (!supportsTransactions(connection))
629
643
  throw new Error("Reserved database connection does not support transactions.");
630
644
  return await connection.begin(AsyncLocalStorage2.bind(async (transaction) => {
631
- return await runWithDatabaseConnection(transaction, () => transactionFrame.run({ connection: transaction, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await admittedOperation(createDatabaseConnection(transaction))))));
645
+ transactionRoots.set(transaction, pool);
646
+ return await runWithDatabaseConnection(transaction, () => transactionFrame.run({ connection: transaction, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await admittedOperation(scopedConnection(transaction))))));
632
647
  }));
633
648
  } finally {
634
649
  await reserved?.release();
635
650
  }
636
651
  }));
637
652
  }
638
- var transactionFrame, outerFrames;
653
+ var transactionFrame, outerFrames, transactionRoots, transactionAliases;
639
654
  var init_transaction = __esm(() => {
640
655
  init_deferredModelEvents();
641
656
  init_asyncContextStore();
@@ -647,6 +662,8 @@ var init_transaction = __esm(() => {
647
662
  init_acquisitionMetrics();
648
663
  transactionFrame = createAsyncContextStore("@getstrata/transactionFrame");
649
664
  outerFrames = new WeakMap;
665
+ transactionRoots = new WeakMap;
666
+ transactionAliases = new WeakMap;
650
667
  });
651
668
 
652
669
  // ../../src/core/tenant/tenancyConfig.ts
@@ -968,7 +985,6 @@ function buildPaginationMeta(input) {
968
985
  }
969
986
 
970
987
  // ../../src/core/database/baseRepository.ts
971
- init_connectionContext();
972
988
  init_dialect();
973
989
 
974
990
  // ../../src/core/database/errors.ts
@@ -2515,7 +2531,7 @@ class BaseRepository {
2515
2531
  assertTransactionalEvent(action) {
2516
2532
  if (!eventBus.hasTransactionalListeners(modelEventName(this.table.name, action)))
2517
2533
  return;
2518
- if (!hasActiveTransaction() || this.connection !== repositoryConnection && this.connection !== getActiveDatabaseConnection(repositoryConnection))
2534
+ if (!hasActiveTransaction() || this.connection !== repositoryConnection && !isActiveTransactionConnection(this.connection))
2519
2535
  throw new Error("Transactional model listeners require runInTransaction and the framework-bound repository connection before writing.");
2520
2536
  }
2521
2537
  async runModelMutation(action, operation) {
@@ -2523,6 +2539,32 @@ class BaseRepository {
2523
2539
  return operation();
2524
2540
  return runInTransaction(operation);
2525
2541
  }
2542
+ mysqlMutation(operation) {
2543
+ return runInTransaction((db) => operation(this.withConnection(db)), this.connection === repositoryConnection ? {} : { connection: this.connection });
2544
+ }
2545
+ mysqlRow(id, onlyTrashed = false) {
2546
+ if (id === undefined || id === null)
2547
+ throw new Error(`MySQL write to ${this.table.name} did not identify its persisted primary key.`);
2548
+ return this.firstOrNull({ [this.table.primaryKey]: id }, {
2549
+ withTrashed: true,
2550
+ ...onlyTrashed ? { onlyTrashed: true } : {}
2551
+ });
2552
+ }
2553
+ mysqlAffected(row) {
2554
+ const count = row?.affectedRows;
2555
+ if (count === undefined || !Number.isSafeInteger(count) || count < 0)
2556
+ throw new Error("MySQL mutations require affectedRows metadata from the framework adapter.");
2557
+ return count;
2558
+ }
2559
+ async mysqlWriteById(query, id, onlyTrashed = false, allowUnchanged = false) {
2560
+ const [row] = await this.connection.unsafe(query.text, query.params);
2561
+ if (!this.mysqlAffected(row)) {
2562
+ if (!allowUnchanged)
2563
+ return null;
2564
+ return this.findById(id);
2565
+ }
2566
+ return this.mysqlRow(id, onlyTrashed);
2567
+ }
2526
2568
  async count(options = {}) {
2527
2569
  if (options.lock)
2528
2570
  throw new Error("Row locks require a row selection, not count().");
@@ -2531,7 +2573,7 @@ class BaseRepository {
2531
2573
  }
2532
2574
  async findAll(options = {}) {
2533
2575
  return await withDatabaseErrorHandling(async () => {
2534
- if (options.lock && (!hasActiveTransaction() || this.connection !== repositoryConnection && this.connection !== getActiveDatabaseConnection(repositoryConnection))) {
2576
+ if (options.lock && (!hasActiveTransaction() || this.connection !== repositoryConnection && !isActiveTransactionConnection(this.connection))) {
2535
2577
  throw new Error("Row locks require the framework active transaction connection.");
2536
2578
  }
2537
2579
  const { whereNodes, ...queryOptions } = options;
@@ -2743,6 +2785,32 @@ class BaseRepository {
2743
2785
  return record ?? null;
2744
2786
  }
2745
2787
  async upsert(values, conflictColumns, updateColumns) {
2788
+ if (currentSqlDialect().driver === "mysql")
2789
+ return this.mysqlMutation((repository) => withDatabaseErrorHandling(async () => {
2790
+ const where = {};
2791
+ for (const column of conflictColumns) {
2792
+ if (!this.table.columns.includes(column) || values[column] === undefined || values[column] === null)
2793
+ throw new Error("MySQL upsert requires non-null supplied conflict-column values.");
2794
+ where[column] = values[column];
2795
+ }
2796
+ const query = buildUpsertQuery(this.table, values, conflictColumns, updateColumns);
2797
+ const supplied = this.table.columns.filter((column) => values[column] !== undefined);
2798
+ const updates = updateColumns ?? supplied.filter((column) => !conflictColumns.includes(column));
2799
+ const doNothing = !updates.some((column) => supplied.includes(column));
2800
+ const existing = doNothing ? await repository.firstOrNull(where, {
2801
+ withTrashed: true,
2802
+ lock: { mode: "update" }
2803
+ }) : null;
2804
+ const [result] = await repository.connection.unsafe(query.text, query.params);
2805
+ repository.mysqlAffected(result);
2806
+ if (doNothing && existing)
2807
+ return null;
2808
+ const generated = result?.insertId;
2809
+ const row = generated !== undefined && String(generated) !== "0" ? await repository.mysqlRow(generated) : await repository.firstOrNull(where, { withTrashed: true });
2810
+ if (!row)
2811
+ throw new Error("MySQL upsert could not resolve the persisted row; transaction rolled back.");
2812
+ return row;
2813
+ }));
2746
2814
  return await withDatabaseErrorHandling(async () => {
2747
2815
  const { text, params } = buildUpsertQuery(this.table, values, conflictColumns, updateColumns);
2748
2816
  const [record] = await this.connection.unsafe(text, params);
@@ -2750,6 +2818,8 @@ class BaseRepository {
2750
2818
  });
2751
2819
  }
2752
2820
  async incrementById(id, column, amount = 1, extra = {}) {
2821
+ if (currentSqlDialect().driver === "mysql")
2822
+ return this.mysqlMutation((repository) => withDatabaseErrorHandling(() => repository.mysqlWriteById(buildIncrementQuery(this.table, id, column, amount, extra), id, false, true)));
2753
2823
  return await withDatabaseErrorHandling(async () => {
2754
2824
  const { text, params } = buildIncrementQuery(this.table, id, column, amount, extra);
2755
2825
  const [record] = await this.connection.unsafe(text, params);
@@ -2761,6 +2831,18 @@ class BaseRepository {
2761
2831
  }
2762
2832
  async create(values) {
2763
2833
  this.assertTransactionalEvent("created");
2834
+ if (currentSqlDialect().driver === "mysql")
2835
+ return this.mysqlMutation((repository) => withDatabaseErrorHandling(async () => {
2836
+ const query = buildInsertQuery(this.table, values);
2837
+ const [result] = await repository.connection.unsafe(query.text, query.params);
2838
+ repository.mysqlAffected(result);
2839
+ const key = String(result?.insertId ?? "0") !== "0" ? result?.insertId : values[this.table.primaryKey];
2840
+ const row = await repository.mysqlRow(key);
2841
+ if (!row)
2842
+ throw new Error(`Insert into ${this.table.name} did not return a record.`);
2843
+ await dispatchModelEvent(modelEventName(this.table.name, "created"), row);
2844
+ return row;
2845
+ }));
2764
2846
  return await this.runModelMutation("created", () => withDatabaseErrorHandling(async () => {
2765
2847
  const { text, params } = buildInsertQuery(this.table, values);
2766
2848
  const [record] = await this.connection.unsafe(text, params);
@@ -2796,6 +2878,13 @@ class BaseRepository {
2796
2878
  }
2797
2879
  async updateById(id, changes) {
2798
2880
  this.assertTransactionalEvent("updated");
2881
+ if (currentSqlDialect().driver === "mysql")
2882
+ return this.mysqlMutation((repository) => withDatabaseErrorHandling(async () => {
2883
+ const row = await repository.mysqlWriteById(buildUpdateQuery(this.table, id, changes), id, false, true);
2884
+ if (row)
2885
+ await dispatchModelEvent(modelEventName(this.table.name, "updated"), row);
2886
+ return row;
2887
+ }));
2799
2888
  return await this.runModelMutation("updated", () => withDatabaseErrorHandling(async () => {
2800
2889
  const { text, params } = buildUpdateQuery(this.table, id, changes);
2801
2890
  const [record] = await this.connection.unsafe(text, params);
@@ -2821,6 +2910,14 @@ class BaseRepository {
2821
2910
  }
2822
2911
  async softDeleteById(id) {
2823
2912
  this.assertTransactionalEvent("deleted");
2913
+ if (currentSqlDialect().driver === "mysql")
2914
+ return this.mysqlMutation((repository) => withDatabaseErrorHandling(async () => {
2915
+ const row = await repository.mysqlWriteById(buildSoftDeleteByIdQuery(this.table, id, new Date), id, true);
2916
+ if (!row)
2917
+ return false;
2918
+ await dispatchModelEvent(modelEventName(this.table.name, "deleted"), row);
2919
+ return true;
2920
+ }));
2824
2921
  return await this.runModelMutation("deleted", () => withDatabaseErrorHandling(async () => {
2825
2922
  const { text, params } = buildSoftDeleteByIdQuery(this.table, id, new Date);
2826
2923
  const [record] = await this.connection.unsafe(text, params);
@@ -2833,6 +2930,15 @@ class BaseRepository {
2833
2930
  }
2834
2931
  async forceDeleteById(id) {
2835
2932
  this.assertTransactionalEvent("force-deleted");
2933
+ if (currentSqlDialect().driver === "mysql")
2934
+ return this.mysqlMutation((repository) => withDatabaseErrorHandling(async () => {
2935
+ const query = buildDeleteByIdQuery(this.table, id);
2936
+ const [result] = await repository.connection.unsafe(query.text, query.params);
2937
+ if (!repository.mysqlAffected(result))
2938
+ return false;
2939
+ await dispatchModelEvent(modelEventName(this.table.name, "force-deleted"), { id });
2940
+ return true;
2941
+ }));
2836
2942
  return await this.runModelMutation("force-deleted", () => withDatabaseErrorHandling(async () => {
2837
2943
  const { text, params } = buildDeleteByIdQuery(this.table, id);
2838
2944
  const [row] = await this.connection.unsafe(text, params);
@@ -2847,6 +2953,13 @@ class BaseRepository {
2847
2953
  }
2848
2954
  async restoreById(id) {
2849
2955
  this.assertTransactionalEvent("restored");
2956
+ if (currentSqlDialect().driver === "mysql")
2957
+ return this.mysqlMutation((repository) => withDatabaseErrorHandling(async () => {
2958
+ const row = await repository.mysqlWriteById(buildRestoreByIdQuery(this.table, id), id);
2959
+ if (row)
2960
+ await dispatchModelEvent(modelEventName(this.table.name, "restored"), row);
2961
+ return row;
2962
+ }));
2850
2963
  return await this.runModelMutation("restored", () => withDatabaseErrorHandling(async () => {
2851
2964
  const { text, params } = buildRestoreByIdQuery(this.table, id);
2852
2965
  const [record] = await this.connection.unsafe(text, params);
@@ -733,6 +733,17 @@ var init_repositoryConnection = __esm(() => {
733
733
 
734
734
  // ../../src/core/database/transaction.ts
735
735
  import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
736
+ function scopedConnection(source) {
737
+ const connection = createDatabaseConnection(source);
738
+ transactionAliases.set(connection, source);
739
+ return connection;
740
+ }
741
+ function isActiveTransactionConnection(connection) {
742
+ if (!hasActiveTransaction() || !hasActiveDatabaseConnection())
743
+ return false;
744
+ const active = getActiveDatabaseConnection(connection);
745
+ return connection === active || transactionAliases.get(connection) === active;
746
+ }
736
747
  async function runInSavepoint(connection, operation) {
737
748
  const inherited = transactionFrame.getStore();
738
749
  let parent = inherited?.connection === connection ? inherited : outerFrames.get(connection);
@@ -749,7 +760,7 @@ async function runInSavepoint(connection, operation) {
749
760
  return await settleTransaction(() => runWithDeferredModelEvents(async () => {
750
761
  await connection.unsafe(`SAVEPOINT ${name}`);
751
762
  try {
752
- const result = await transactionFrame.run({ connection, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await operation(createDatabaseConnection(connection)))));
763
+ const result = await transactionFrame.run({ connection, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await operation(scopedConnection(connection)))));
753
764
  await connection.unsafe(`RELEASE SAVEPOINT ${name}`);
754
765
  return result;
755
766
  } catch (error) {
@@ -772,9 +783,12 @@ async function runInTransaction(operation, options = {}) {
772
783
  signal?.throwIfAborted();
773
784
  return operation(connection);
774
785
  };
775
- const pool = resolveRepositoryConnection();
786
+ const pool = options.connection ?? resolveRepositoryConnection();
776
787
  if (hasActiveDatabaseConnection()) {
777
- return await runInSavepoint(getActiveDatabaseConnection(pool), admittedOperation);
788
+ const active = getActiveDatabaseConnection(pool);
789
+ if (options.connection && options.connection !== active && options.connection !== transactionRoots.get(active) && transactionAliases.get(options.connection) !== active)
790
+ throw new Error("Cannot switch database connections within an active transaction.");
791
+ return await runInSavepoint(active, admittedOperation);
778
792
  }
779
793
  if (!supportsTransactions(pool)) {
780
794
  throw new Error("Active database connection does not support transactions. Bind a client with begin() via bindDatabaseConnection.");
@@ -793,14 +807,15 @@ async function runInTransaction(operation, options = {}) {
793
807
  if (!supportsTransactions(connection))
794
808
  throw new Error("Reserved database connection does not support transactions.");
795
809
  return await connection.begin(AsyncLocalStorage3.bind(async (transaction) => {
796
- return await runWithDatabaseConnection(transaction, () => transactionFrame.run({ connection: transaction, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await admittedOperation(createDatabaseConnection(transaction))))));
810
+ transactionRoots.set(transaction, pool);
811
+ return await runWithDatabaseConnection(transaction, () => transactionFrame.run({ connection: transaction, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await admittedOperation(scopedConnection(transaction))))));
797
812
  }));
798
813
  } finally {
799
814
  await reserved?.release();
800
815
  }
801
816
  }));
802
817
  }
803
- var transactionFrame, outerFrames;
818
+ var transactionFrame, outerFrames, transactionRoots, transactionAliases;
804
819
  var init_transaction = __esm(() => {
805
820
  init_deferredModelEvents();
806
821
  init_asyncContextStore();
@@ -812,6 +827,8 @@ var init_transaction = __esm(() => {
812
827
  init_acquisitionMetrics();
813
828
  transactionFrame = createAsyncContextStore("@getstrata/transactionFrame");
814
829
  outerFrames = new WeakMap;
830
+ transactionRoots = new WeakMap;
831
+ transactionAliases = new WeakMap;
815
832
  });
816
833
 
817
834
  // ../../src/core/tenant/databaseTenantContext.ts
@@ -808,6 +808,17 @@ var init_connection = () => {};
808
808
 
809
809
  // ../../src/core/database/transaction.ts
810
810
  import { AsyncLocalStorage as AsyncLocalStorage4 } from "async_hooks";
811
+ function scopedConnection(source) {
812
+ const connection = createDatabaseConnection(source);
813
+ transactionAliases.set(connection, source);
814
+ return connection;
815
+ }
816
+ function isActiveTransactionConnection(connection) {
817
+ if (!hasActiveTransaction() || !hasActiveDatabaseConnection())
818
+ return false;
819
+ const active = getActiveDatabaseConnection(connection);
820
+ return connection === active || transactionAliases.get(connection) === active;
821
+ }
811
822
  async function runInSavepoint(connection, operation) {
812
823
  const inherited = transactionFrame.getStore();
813
824
  let parent = inherited?.connection === connection ? inherited : outerFrames.get(connection);
@@ -824,7 +835,7 @@ async function runInSavepoint(connection, operation) {
824
835
  return await settleTransaction(() => runWithDeferredModelEvents(async () => {
825
836
  await connection.unsafe(`SAVEPOINT ${name}`);
826
837
  try {
827
- const result = await transactionFrame.run({ connection, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await operation(createDatabaseConnection(connection)))));
838
+ const result = await transactionFrame.run({ connection, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await operation(scopedConnection(connection)))));
828
839
  await connection.unsafe(`RELEASE SAVEPOINT ${name}`);
829
840
  return result;
830
841
  } catch (error) {
@@ -847,9 +858,12 @@ async function runInTransaction(operation, options = {}) {
847
858
  signal?.throwIfAborted();
848
859
  return operation(connection);
849
860
  };
850
- const pool = resolveRepositoryConnection();
861
+ const pool = options.connection ?? resolveRepositoryConnection();
851
862
  if (hasActiveDatabaseConnection()) {
852
- return await runInSavepoint(getActiveDatabaseConnection(pool), admittedOperation);
863
+ const active = getActiveDatabaseConnection(pool);
864
+ if (options.connection && options.connection !== active && options.connection !== transactionRoots.get(active) && transactionAliases.get(options.connection) !== active)
865
+ throw new Error("Cannot switch database connections within an active transaction.");
866
+ return await runInSavepoint(active, admittedOperation);
853
867
  }
854
868
  if (!supportsTransactions(pool)) {
855
869
  throw new Error("Active database connection does not support transactions. Bind a client with begin() via bindDatabaseConnection.");
@@ -868,14 +882,15 @@ async function runInTransaction(operation, options = {}) {
868
882
  if (!supportsTransactions(connection))
869
883
  throw new Error("Reserved database connection does not support transactions.");
870
884
  return await connection.begin(AsyncLocalStorage4.bind(async (transaction) => {
871
- return await runWithDatabaseConnection(transaction, () => transactionFrame.run({ connection: transaction, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await admittedOperation(createDatabaseConnection(transaction))))));
885
+ transactionRoots.set(transaction, pool);
886
+ return await runWithDatabaseConnection(transaction, () => transactionFrame.run({ connection: transaction, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await admittedOperation(scopedConnection(transaction))))));
872
887
  }));
873
888
  } finally {
874
889
  await reserved?.release();
875
890
  }
876
891
  }));
877
892
  }
878
- var transactionFrame, outerFrames;
893
+ var transactionFrame, outerFrames, transactionRoots, transactionAliases;
879
894
  var init_transaction = __esm(() => {
880
895
  init_deferredModelEvents();
881
896
  init_asyncContextStore();
@@ -887,6 +902,8 @@ var init_transaction = __esm(() => {
887
902
  init_acquisitionMetrics();
888
903
  transactionFrame = createAsyncContextStore("@getstrata/transactionFrame");
889
904
  outerFrames = new WeakMap;
905
+ transactionRoots = new WeakMap;
906
+ transactionAliases = new WeakMap;
890
907
  });
891
908
 
892
909
  // ../../src/core/queue/queueMetrics.ts
@@ -733,6 +733,17 @@ var init_repositoryConnection = __esm(() => {
733
733
 
734
734
  // ../../src/core/database/transaction.ts
735
735
  import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
736
+ function scopedConnection(source) {
737
+ const connection = createDatabaseConnection(source);
738
+ transactionAliases.set(connection, source);
739
+ return connection;
740
+ }
741
+ function isActiveTransactionConnection(connection) {
742
+ if (!hasActiveTransaction() || !hasActiveDatabaseConnection())
743
+ return false;
744
+ const active = getActiveDatabaseConnection(connection);
745
+ return connection === active || transactionAliases.get(connection) === active;
746
+ }
736
747
  async function runInSavepoint(connection, operation) {
737
748
  const inherited = transactionFrame.getStore();
738
749
  let parent = inherited?.connection === connection ? inherited : outerFrames.get(connection);
@@ -749,7 +760,7 @@ async function runInSavepoint(connection, operation) {
749
760
  return await settleTransaction(() => runWithDeferredModelEvents(async () => {
750
761
  await connection.unsafe(`SAVEPOINT ${name}`);
751
762
  try {
752
- const result = await transactionFrame.run({ connection, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await operation(createDatabaseConnection(connection)))));
763
+ const result = await transactionFrame.run({ connection, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await operation(scopedConnection(connection)))));
753
764
  await connection.unsafe(`RELEASE SAVEPOINT ${name}`);
754
765
  return result;
755
766
  } catch (error) {
@@ -772,9 +783,12 @@ async function runInTransaction(operation, options = {}) {
772
783
  signal?.throwIfAborted();
773
784
  return operation(connection);
774
785
  };
775
- const pool = resolveRepositoryConnection();
786
+ const pool = options.connection ?? resolveRepositoryConnection();
776
787
  if (hasActiveDatabaseConnection()) {
777
- return await runInSavepoint(getActiveDatabaseConnection(pool), admittedOperation);
788
+ const active = getActiveDatabaseConnection(pool);
789
+ if (options.connection && options.connection !== active && options.connection !== transactionRoots.get(active) && transactionAliases.get(options.connection) !== active)
790
+ throw new Error("Cannot switch database connections within an active transaction.");
791
+ return await runInSavepoint(active, admittedOperation);
778
792
  }
779
793
  if (!supportsTransactions(pool)) {
780
794
  throw new Error("Active database connection does not support transactions. Bind a client with begin() via bindDatabaseConnection.");
@@ -793,14 +807,15 @@ async function runInTransaction(operation, options = {}) {
793
807
  if (!supportsTransactions(connection))
794
808
  throw new Error("Reserved database connection does not support transactions.");
795
809
  return await connection.begin(AsyncLocalStorage3.bind(async (transaction) => {
796
- return await runWithDatabaseConnection(transaction, () => transactionFrame.run({ connection: transaction, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await admittedOperation(createDatabaseConnection(transaction))))));
810
+ transactionRoots.set(transaction, pool);
811
+ return await runWithDatabaseConnection(transaction, () => transactionFrame.run({ connection: transaction, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await admittedOperation(scopedConnection(transaction))))));
797
812
  }));
798
813
  } finally {
799
814
  await reserved?.release();
800
815
  }
801
816
  }));
802
817
  }
803
- var transactionFrame, outerFrames;
818
+ var transactionFrame, outerFrames, transactionRoots, transactionAliases;
804
819
  var init_transaction = __esm(() => {
805
820
  init_deferredModelEvents();
806
821
  init_asyncContextStore();
@@ -812,6 +827,8 @@ var init_transaction = __esm(() => {
812
827
  init_acquisitionMetrics();
813
828
  transactionFrame = createAsyncContextStore("@getstrata/transactionFrame");
814
829
  outerFrames = new WeakMap;
830
+ transactionRoots = new WeakMap;
831
+ transactionAliases = new WeakMap;
815
832
  });
816
833
 
817
834
  // ../../src/core/tenant/databaseTenantContext.ts
@@ -666,6 +666,17 @@ var init_repositoryConnection = __esm(() => {
666
666
 
667
667
  // ../../src/core/database/transaction.ts
668
668
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
669
+ function scopedConnection(source) {
670
+ const connection = createDatabaseConnection(source);
671
+ transactionAliases.set(connection, source);
672
+ return connection;
673
+ }
674
+ function isActiveTransactionConnection(connection) {
675
+ if (!hasActiveTransaction() || !hasActiveDatabaseConnection())
676
+ return false;
677
+ const active = getActiveDatabaseConnection(connection);
678
+ return connection === active || transactionAliases.get(connection) === active;
679
+ }
669
680
  async function runInSavepoint(connection, operation) {
670
681
  const inherited = transactionFrame.getStore();
671
682
  let parent = inherited?.connection === connection ? inherited : outerFrames.get(connection);
@@ -682,7 +693,7 @@ async function runInSavepoint(connection, operation) {
682
693
  return await settleTransaction(() => runWithDeferredModelEvents(async () => {
683
694
  await connection.unsafe(`SAVEPOINT ${name}`);
684
695
  try {
685
- const result = await transactionFrame.run({ connection, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await operation(createDatabaseConnection(connection)))));
696
+ const result = await transactionFrame.run({ connection, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await operation(scopedConnection(connection)))));
686
697
  await connection.unsafe(`RELEASE SAVEPOINT ${name}`);
687
698
  return result;
688
699
  } catch (error) {
@@ -705,9 +716,12 @@ async function runInTransaction(operation, options = {}) {
705
716
  signal?.throwIfAborted();
706
717
  return operation(connection);
707
718
  };
708
- const pool = resolveRepositoryConnection();
719
+ const pool = options.connection ?? resolveRepositoryConnection();
709
720
  if (hasActiveDatabaseConnection()) {
710
- return await runInSavepoint(getActiveDatabaseConnection(pool), admittedOperation);
721
+ const active = getActiveDatabaseConnection(pool);
722
+ if (options.connection && options.connection !== active && options.connection !== transactionRoots.get(active) && transactionAliases.get(options.connection) !== active)
723
+ throw new Error("Cannot switch database connections within an active transaction.");
724
+ return await runInSavepoint(active, admittedOperation);
711
725
  }
712
726
  if (!supportsTransactions(pool)) {
713
727
  throw new Error("Active database connection does not support transactions. Bind a client with begin() via bindDatabaseConnection.");
@@ -726,14 +740,15 @@ async function runInTransaction(operation, options = {}) {
726
740
  if (!supportsTransactions(connection))
727
741
  throw new Error("Reserved database connection does not support transactions.");
728
742
  return await connection.begin(AsyncLocalStorage2.bind(async (transaction) => {
729
- return await runWithDatabaseConnection(transaction, () => transactionFrame.run({ connection: transaction, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await admittedOperation(createDatabaseConnection(transaction))))));
743
+ transactionRoots.set(transaction, pool);
744
+ return await runWithDatabaseConnection(transaction, () => transactionFrame.run({ connection: transaction, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await admittedOperation(scopedConnection(transaction))))));
730
745
  }));
731
746
  } finally {
732
747
  await reserved?.release();
733
748
  }
734
749
  }));
735
750
  }
736
- var transactionFrame, outerFrames;
751
+ var transactionFrame, outerFrames, transactionRoots, transactionAliases;
737
752
  var init_transaction = __esm(() => {
738
753
  init_deferredModelEvents();
739
754
  init_asyncContextStore();
@@ -745,6 +760,8 @@ var init_transaction = __esm(() => {
745
760
  init_acquisitionMetrics();
746
761
  transactionFrame = createAsyncContextStore("@getstrata/transactionFrame");
747
762
  outerFrames = new WeakMap;
763
+ transactionRoots = new WeakMap;
764
+ transactionAliases = new WeakMap;
748
765
  });
749
766
 
750
767
  // ../../src/core/tenant/tenancyConfig.ts
@@ -69,7 +69,7 @@ export type { Seeder, SeederDatabase } from "../core/database/seeders/types.ts";
69
69
  export { createSqliteConnection } from "../core/database/sqliteConnection.ts";
70
70
  export { defineTable } from "../core/database/table.ts";
71
71
  export type { TransactionOptions } from "../core/database/transaction.ts";
72
- export { hasActiveTransaction, requestTransactionRollback, runInTransaction, } from "../core/database/transaction.ts";
72
+ export { hasActiveTransaction, isActiveTransactionConnection, requestTransactionRollback, runInTransaction, } from "../core/database/transaction.ts";
73
73
  export type { ModelWhere, ModelWriteValues, QueryJoin, QueryJoinOn, QueryOptions, QueryOrder, QuerySelectItem, QueryWhere, } from "../core/database/types.ts";
74
74
  export type { WhereNode } from "../core/database/whereBuilder.ts";
75
75
  export { WhereBuilder } from "../core/database/whereBuilder.ts";