@getstrata/core 2.2.35 → 2.2.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @getstrata/core changelog
2
2
 
3
+ ## 2.2.37
4
+
5
+ Fix MySQL repository entity-returning mutations with transaction-pinned readback, persisted keys/defaults, honest null and deletion outcomes, complete model event payloads and injected connection ownership. Preserve native Postgres/SQLite RETURNING and conditional affected-row metadata. Close owned generated OpenAPI and failed-job command applications on success and failure before exit; retain both command and cleanup errors. Add mandatory packaged MySQL production column-tenancy CLI and HTTP auth/SCIM acceptance with an owned DML-only account, unchanged route/spec/SDK contracts, native ORM regressions and cleanup tests. Records dogfood issues #232 and #233 without application SQL workarounds.
6
+
7
+ ## 2.2.36
8
+
9
+ Add a mandatory installed-package SQLite column-tenancy CLI/OpenAPI/HTTP profile, including cookie/token/JWT/Basic and repeated SCIM isolation, while preserving existing Postgres security, route inventories, compiler combinations and Docker gates. Reconcile observability/rotation trackers against shipped capabilities and remaining operational qualification.
10
+
11
+ Fix declared default exports missing from nine published core subpaths. Preserve defaults in generated non-shared entries and alias the shared BaseRepository default to its canonical root constructor, retaining named exports and singleton/class identity. Extend declaration/runtime verification with value-default and type-only-default regressions; compile all nine imports across the existing 18 generated compiler combinations and execute them in every installed candidate app. Records the published 2.2.35 reproduction from Q01 dogfooding; no shop adapter or node_modules patch.
12
+
3
13
  ## 2.2.35
4
14
 
5
15
  Retire checked-in HiroApp applications after preserving security, CLI/OpenAPI/HTTP, source, migration and Docker acceptance in generated candidate-package fixtures. Keep all 23 security cases, 40 runtime operations, 22 API/protocol operations, three-engine migration cases and 18 compiler combinations. Make the private root CLI fixture-only, isolate post-commit HTTP databases, and validate the generated production image with restricted runtime credentials, Redis recovery and graceful shutdown. Remove obsolete app regeneration/selectors and update contributor, generator and deployment documentation; retain compatible example preset IDs and explicit application-owned deployment boundaries. Regenerate the sample SDK from the API-only snapshot and verify it against the installed candidate package. Core coverage retains 136 enforced files at 100%; remove only the two deleted private-entrypoint exemptions (272 remaining).
@@ -35,6 +35,10 @@ declare class BaseRepository<TEntity extends object, PrimaryKey extends keyof TE
35
35
  constructor(table: TableDefinition<TEntity, PrimaryKey>, connection?: DatabaseConnection);
36
36
  private assertTransactionalEvent;
37
37
  private runModelMutation;
38
+ private mysqlMutation;
39
+ private mysqlRow;
40
+ private mysqlAffected;
41
+ private mysqlWriteById;
38
42
  count(options?: ExtendedQueryOptions<TEntity>): Promise<number>;
39
43
  findAll(options?: ExtendedQueryOptions<TEntity>): Promise<TEntity[]>;
40
44
  paginate(options: {
@@ -24,7 +24,7 @@ export { Blueprint, ColumnDefinition, compileBlueprint, createSchemaBuilder, For
24
24
  export { createSqliteConnection } from "./sqliteConnection.ts";
25
25
  export type { TableDefinition } from "./table.ts";
26
26
  export { defineTable } from "./table.ts";
27
- export { runInTransaction } from "./transaction.ts";
27
+ export { isActiveTransactionConnection, runInTransaction } from "./transaction.ts";
28
28
  export type { DatabaseComparable, DatabaseScalar, MutationValues, QueryFilterValue, QueryJoin, QueryJoinOn, QueryOperator, QueryOptions, QueryOrder, QuerySelectItem, QueryWhere, UpdateValues, } from "./types.ts";
29
29
  export type { WhereNode } from "./whereBuilder.ts";
30
30
  export { WhereBuilder } from "./whereBuilder.ts";
@@ -1,7 +1,11 @@
1
1
  import { type TransactionAcquisitionMetrics } from "./acquisitionMetrics.ts";
2
2
  import type { DatabaseConnection } from "./baseRepository.ts";
3
3
  import { hasActiveTransaction, requestTransactionRollback } from "./transactionControl.ts";
4
+ /** Recognizes the callback handle without admitting an unrelated pool. */
5
+ declare function isActiveTransactionConnection(connection: DatabaseConnection): boolean;
4
6
  interface TransactionOptions {
7
+ /** Explicit root pool for injected repositories; cannot switch an active transaction's owner. */
8
+ connection?: DatabaseConnection;
5
9
  /** Cancel admission/pool checkout only. Does not cancel SQL or an admitted business callback. */
6
10
  acquisitionSignal?: AbortSignal;
7
11
  /** Observe native checkout only; requires a reserve-capable adapter. */
@@ -11,4 +15,4 @@ declare function runInTransaction<TValue>(operation: (connection: DatabaseConnec
11
15
  export type { TransactionAcquisitionMetrics, TransactionAcquisitionSnapshot, } from "./acquisitionMetrics.ts";
12
16
  export { createTransactionAcquisitionMetrics, renderTransactionAcquisitionMetrics, } from "./acquisitionMetrics.ts";
13
17
  export type { TransactionOptions };
14
- export { hasActiveTransaction, requestTransactionRollback, runInTransaction };
18
+ export { hasActiveTransaction, isActiveTransactionConnection, requestTransactionRollback, runInTransaction, };
@@ -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
@@ -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
@@ -182,5 +182,6 @@ class MembershipService {
182
182
  }
183
183
  var membershipService_default = MembershipService;
184
184
  export {
185
+ membershipService_default as default,
185
186
  resolveMembershipService
186
187
  };
@@ -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);
@@ -60,6 +60,8 @@ class CacheRepository {
60
60
  return this.store.size();
61
61
  }
62
62
  }
63
+ var repository_default = CacheRepository;
63
64
  export {
64
- CacheRepository
65
+ CacheRepository,
66
+ repository_default as default
65
67
  };
@@ -174,5 +174,6 @@ class SimpleCache {
174
174
  }
175
175
  var simpleCache_default = SimpleCache;
176
176
  export {
177
- SimpleCache
177
+ SimpleCache,
178
+ simpleCache_default as default
178
179
  };
@@ -38,5 +38,6 @@ class SimpleCacheStore {
38
38
  }
39
39
  var simpleCacheStore_default = SimpleCacheStore;
40
40
  export {
41
- SimpleCacheStore
41
+ SimpleCacheStore,
42
+ simpleCacheStore_default as default
42
43
  };
@@ -1 +1,2 @@
1
1
  export * from "../../index.js";
2
+ export { BaseRepository as default } from "../../index.js";
@@ -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
@@ -1602,6 +1619,8 @@ class ExportAuditLogsJob extends Job {
1602
1619
  }
1603
1620
  }
1604
1621
  }
1622
+ var exportAuditLogsJob_default = ExportAuditLogsJob;
1605
1623
  export {
1606
- ExportAuditLogsJob
1624
+ ExportAuditLogsJob,
1625
+ exportAuditLogsJob_default as default
1607
1626
  };
@@ -105,6 +105,8 @@ class InvalidateCacheTagsJob extends Job {
105
105
  await this.cache.tags(...payload.tags).flush();
106
106
  }
107
107
  }
108
+ var invalidateCacheTagsJob_default = InvalidateCacheTagsJob;
108
109
  export {
109
- InvalidateCacheTagsJob
110
+ InvalidateCacheTagsJob,
111
+ invalidateCacheTagsJob_default as default
110
112
  };
@@ -19,5 +19,6 @@ class FailedJobRepository extends BaseRepository {
19
19
  }
20
20
  var failedJobRepository_default = FailedJobRepository;
21
21
  export {
22
- FailedJobRepository
22
+ FailedJobRepository,
23
+ failedJobRepository_default as default
23
24
  };
@@ -47,5 +47,6 @@ class FailedJobService {
47
47
  }
48
48
  var failedJobService_default = FailedJobService;
49
49
  export {
50
- FailedJobService
50
+ FailedJobService,
51
+ failedJobService_default as default
51
52
  };
@@ -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";
package/dist/index.js CHANGED
@@ -853,6 +853,17 @@ var init_repositoryConnection = __esm(() => {
853
853
 
854
854
  // ../../src/core/database/transaction.ts
855
855
  import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
856
+ function scopedConnection(source) {
857
+ const connection = createDatabaseConnection(source);
858
+ transactionAliases.set(connection, source);
859
+ return connection;
860
+ }
861
+ function isActiveTransactionConnection(connection) {
862
+ if (!hasActiveTransaction() || !hasActiveDatabaseConnection())
863
+ return false;
864
+ const active = getActiveDatabaseConnection(connection);
865
+ return connection === active || transactionAliases.get(connection) === active;
866
+ }
856
867
  async function runInSavepoint(connection, operation) {
857
868
  const inherited = transactionFrame.getStore();
858
869
  let parent = inherited?.connection === connection ? inherited : outerFrames.get(connection);
@@ -869,7 +880,7 @@ async function runInSavepoint(connection, operation) {
869
880
  return await settleTransaction(() => runWithDeferredModelEvents(async () => {
870
881
  await connection.unsafe(`SAVEPOINT ${name}`);
871
882
  try {
872
- const result = await transactionFrame.run({ connection, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await operation(createDatabaseConnection(connection)))));
883
+ const result = await transactionFrame.run({ connection, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await operation(scopedConnection(connection)))));
873
884
  await connection.unsafe(`RELEASE SAVEPOINT ${name}`);
874
885
  return result;
875
886
  } catch (error) {
@@ -892,9 +903,12 @@ async function runInTransaction(operation, options = {}) {
892
903
  signal?.throwIfAborted();
893
904
  return operation(connection);
894
905
  };
895
- const pool = resolveRepositoryConnection();
906
+ const pool = options.connection ?? resolveRepositoryConnection();
896
907
  if (hasActiveDatabaseConnection()) {
897
- return await runInSavepoint(getActiveDatabaseConnection(pool), admittedOperation);
908
+ const active = getActiveDatabaseConnection(pool);
909
+ if (options.connection && options.connection !== active && options.connection !== transactionRoots.get(active) && transactionAliases.get(options.connection) !== active)
910
+ throw new Error("Cannot switch database connections within an active transaction.");
911
+ return await runInSavepoint(active, admittedOperation);
898
912
  }
899
913
  if (!supportsTransactions(pool)) {
900
914
  throw new Error("Active database connection does not support transactions. Bind a client with begin() via bindDatabaseConnection.");
@@ -913,14 +927,15 @@ async function runInTransaction(operation, options = {}) {
913
927
  if (!supportsTransactions(connection))
914
928
  throw new Error("Reserved database connection does not support transactions.");
915
929
  return await connection.begin(AsyncLocalStorage3.bind(async (transaction) => {
916
- return await runWithDatabaseConnection(transaction, () => transactionFrame.run({ connection: transaction, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await admittedOperation(createDatabaseConnection(transaction))))));
930
+ transactionRoots.set(transaction, pool);
931
+ return await runWithDatabaseConnection(transaction, () => transactionFrame.run({ connection: transaction, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await admittedOperation(scopedConnection(transaction))))));
917
932
  }));
918
933
  } finally {
919
934
  await reserved?.release();
920
935
  }
921
936
  }));
922
937
  }
923
- var transactionFrame, outerFrames;
938
+ var transactionFrame, outerFrames, transactionRoots, transactionAliases;
924
939
  var init_transaction = __esm(() => {
925
940
  init_deferredModelEvents();
926
941
  init_asyncContextStore();
@@ -932,6 +947,8 @@ var init_transaction = __esm(() => {
932
947
  init_acquisitionMetrics();
933
948
  transactionFrame = createAsyncContextStore("@getstrata/transactionFrame");
934
949
  outerFrames = new WeakMap;
950
+ transactionRoots = new WeakMap;
951
+ transactionAliases = new WeakMap;
935
952
  });
936
953
 
937
954
  // ../../src/core/tenant/databaseTenantContext.ts
@@ -3743,7 +3760,6 @@ function buildPaginationMeta(input) {
3743
3760
  }
3744
3761
 
3745
3762
  // ../../src/core/database/baseRepository.ts
3746
- init_connectionContext();
3747
3763
  init_dialect();
3748
3764
 
3749
3765
  // ../../src/core/database/keyset.ts
@@ -5171,7 +5187,7 @@ class BaseRepository {
5171
5187
  assertTransactionalEvent(action) {
5172
5188
  if (!eventBus.hasTransactionalListeners(modelEventName(this.table.name, action)))
5173
5189
  return;
5174
- if (!hasActiveTransaction() || this.connection !== repositoryConnection && this.connection !== getActiveDatabaseConnection(repositoryConnection))
5190
+ if (!hasActiveTransaction() || this.connection !== repositoryConnection && !isActiveTransactionConnection(this.connection))
5175
5191
  throw new Error("Transactional model listeners require runInTransaction and the framework-bound repository connection before writing.");
5176
5192
  }
5177
5193
  async runModelMutation(action, operation) {
@@ -5179,6 +5195,32 @@ class BaseRepository {
5179
5195
  return operation();
5180
5196
  return runInTransaction(operation);
5181
5197
  }
5198
+ mysqlMutation(operation) {
5199
+ return runInTransaction((db) => operation(this.withConnection(db)), this.connection === repositoryConnection ? {} : { connection: this.connection });
5200
+ }
5201
+ mysqlRow(id, onlyTrashed = false) {
5202
+ if (id === undefined || id === null)
5203
+ throw new Error(`MySQL write to ${this.table.name} did not identify its persisted primary key.`);
5204
+ return this.firstOrNull({ [this.table.primaryKey]: id }, {
5205
+ withTrashed: true,
5206
+ ...onlyTrashed ? { onlyTrashed: true } : {}
5207
+ });
5208
+ }
5209
+ mysqlAffected(row) {
5210
+ const count = row?.affectedRows;
5211
+ if (count === undefined || !Number.isSafeInteger(count) || count < 0)
5212
+ throw new Error("MySQL mutations require affectedRows metadata from the framework adapter.");
5213
+ return count;
5214
+ }
5215
+ async mysqlWriteById(query, id, onlyTrashed = false, allowUnchanged = false) {
5216
+ const [row] = await this.connection.unsafe(query.text, query.params);
5217
+ if (!this.mysqlAffected(row)) {
5218
+ if (!allowUnchanged)
5219
+ return null;
5220
+ return this.findById(id);
5221
+ }
5222
+ return this.mysqlRow(id, onlyTrashed);
5223
+ }
5182
5224
  async count(options = {}) {
5183
5225
  if (options.lock)
5184
5226
  throw new Error("Row locks require a row selection, not count().");
@@ -5187,7 +5229,7 @@ class BaseRepository {
5187
5229
  }
5188
5230
  async findAll(options = {}) {
5189
5231
  return await withDatabaseErrorHandling(async () => {
5190
- if (options.lock && (!hasActiveTransaction() || this.connection !== repositoryConnection && this.connection !== getActiveDatabaseConnection(repositoryConnection))) {
5232
+ if (options.lock && (!hasActiveTransaction() || this.connection !== repositoryConnection && !isActiveTransactionConnection(this.connection))) {
5191
5233
  throw new Error("Row locks require the framework active transaction connection.");
5192
5234
  }
5193
5235
  const { whereNodes, ...queryOptions } = options;
@@ -5399,6 +5441,32 @@ class BaseRepository {
5399
5441
  return record ?? null;
5400
5442
  }
5401
5443
  async upsert(values, conflictColumns, updateColumns) {
5444
+ if (currentSqlDialect().driver === "mysql")
5445
+ return this.mysqlMutation((repository) => withDatabaseErrorHandling(async () => {
5446
+ const where = {};
5447
+ for (const column of conflictColumns) {
5448
+ if (!this.table.columns.includes(column) || values[column] === undefined || values[column] === null)
5449
+ throw new Error("MySQL upsert requires non-null supplied conflict-column values.");
5450
+ where[column] = values[column];
5451
+ }
5452
+ const query = buildUpsertQuery(this.table, values, conflictColumns, updateColumns);
5453
+ const supplied = this.table.columns.filter((column) => values[column] !== undefined);
5454
+ const updates = updateColumns ?? supplied.filter((column) => !conflictColumns.includes(column));
5455
+ const doNothing = !updates.some((column) => supplied.includes(column));
5456
+ const existing = doNothing ? await repository.firstOrNull(where, {
5457
+ withTrashed: true,
5458
+ lock: { mode: "update" }
5459
+ }) : null;
5460
+ const [result] = await repository.connection.unsafe(query.text, query.params);
5461
+ repository.mysqlAffected(result);
5462
+ if (doNothing && existing)
5463
+ return null;
5464
+ const generated = result?.insertId;
5465
+ const row = generated !== undefined && String(generated) !== "0" ? await repository.mysqlRow(generated) : await repository.firstOrNull(where, { withTrashed: true });
5466
+ if (!row)
5467
+ throw new Error("MySQL upsert could not resolve the persisted row; transaction rolled back.");
5468
+ return row;
5469
+ }));
5402
5470
  return await withDatabaseErrorHandling(async () => {
5403
5471
  const { text, params } = buildUpsertQuery(this.table, values, conflictColumns, updateColumns);
5404
5472
  const [record] = await this.connection.unsafe(text, params);
@@ -5406,6 +5474,8 @@ class BaseRepository {
5406
5474
  });
5407
5475
  }
5408
5476
  async incrementById(id, column, amount = 1, extra = {}) {
5477
+ if (currentSqlDialect().driver === "mysql")
5478
+ return this.mysqlMutation((repository) => withDatabaseErrorHandling(() => repository.mysqlWriteById(buildIncrementQuery(this.table, id, column, amount, extra), id, false, true)));
5409
5479
  return await withDatabaseErrorHandling(async () => {
5410
5480
  const { text, params } = buildIncrementQuery(this.table, id, column, amount, extra);
5411
5481
  const [record] = await this.connection.unsafe(text, params);
@@ -5417,6 +5487,18 @@ class BaseRepository {
5417
5487
  }
5418
5488
  async create(values) {
5419
5489
  this.assertTransactionalEvent("created");
5490
+ if (currentSqlDialect().driver === "mysql")
5491
+ return this.mysqlMutation((repository) => withDatabaseErrorHandling(async () => {
5492
+ const query = buildInsertQuery(this.table, values);
5493
+ const [result] = await repository.connection.unsafe(query.text, query.params);
5494
+ repository.mysqlAffected(result);
5495
+ const key = String(result?.insertId ?? "0") !== "0" ? result?.insertId : values[this.table.primaryKey];
5496
+ const row = await repository.mysqlRow(key);
5497
+ if (!row)
5498
+ throw new Error(`Insert into ${this.table.name} did not return a record.`);
5499
+ await dispatchModelEvent(modelEventName(this.table.name, "created"), row);
5500
+ return row;
5501
+ }));
5420
5502
  return await this.runModelMutation("created", () => withDatabaseErrorHandling(async () => {
5421
5503
  const { text, params } = buildInsertQuery(this.table, values);
5422
5504
  const [record] = await this.connection.unsafe(text, params);
@@ -5452,6 +5534,13 @@ class BaseRepository {
5452
5534
  }
5453
5535
  async updateById(id, changes) {
5454
5536
  this.assertTransactionalEvent("updated");
5537
+ if (currentSqlDialect().driver === "mysql")
5538
+ return this.mysqlMutation((repository) => withDatabaseErrorHandling(async () => {
5539
+ const row = await repository.mysqlWriteById(buildUpdateQuery(this.table, id, changes), id, false, true);
5540
+ if (row)
5541
+ await dispatchModelEvent(modelEventName(this.table.name, "updated"), row);
5542
+ return row;
5543
+ }));
5455
5544
  return await this.runModelMutation("updated", () => withDatabaseErrorHandling(async () => {
5456
5545
  const { text, params } = buildUpdateQuery(this.table, id, changes);
5457
5546
  const [record] = await this.connection.unsafe(text, params);
@@ -5477,6 +5566,14 @@ class BaseRepository {
5477
5566
  }
5478
5567
  async softDeleteById(id) {
5479
5568
  this.assertTransactionalEvent("deleted");
5569
+ if (currentSqlDialect().driver === "mysql")
5570
+ return this.mysqlMutation((repository) => withDatabaseErrorHandling(async () => {
5571
+ const row = await repository.mysqlWriteById(buildSoftDeleteByIdQuery(this.table, id, new Date), id, true);
5572
+ if (!row)
5573
+ return false;
5574
+ await dispatchModelEvent(modelEventName(this.table.name, "deleted"), row);
5575
+ return true;
5576
+ }));
5480
5577
  return await this.runModelMutation("deleted", () => withDatabaseErrorHandling(async () => {
5481
5578
  const { text, params } = buildSoftDeleteByIdQuery(this.table, id, new Date);
5482
5579
  const [record] = await this.connection.unsafe(text, params);
@@ -5489,6 +5586,15 @@ class BaseRepository {
5489
5586
  }
5490
5587
  async forceDeleteById(id) {
5491
5588
  this.assertTransactionalEvent("force-deleted");
5589
+ if (currentSqlDialect().driver === "mysql")
5590
+ return this.mysqlMutation((repository) => withDatabaseErrorHandling(async () => {
5591
+ const query = buildDeleteByIdQuery(this.table, id);
5592
+ const [result] = await repository.connection.unsafe(query.text, query.params);
5593
+ if (!repository.mysqlAffected(result))
5594
+ return false;
5595
+ await dispatchModelEvent(modelEventName(this.table.name, "force-deleted"), { id });
5596
+ return true;
5597
+ }));
5492
5598
  return await this.runModelMutation("force-deleted", () => withDatabaseErrorHandling(async () => {
5493
5599
  const { text, params } = buildDeleteByIdQuery(this.table, id);
5494
5600
  const [row] = await this.connection.unsafe(text, params);
@@ -5503,6 +5609,13 @@ class BaseRepository {
5503
5609
  }
5504
5610
  async restoreById(id) {
5505
5611
  this.assertTransactionalEvent("restored");
5612
+ if (currentSqlDialect().driver === "mysql")
5613
+ return this.mysqlMutation((repository) => withDatabaseErrorHandling(async () => {
5614
+ const row = await repository.mysqlWriteById(buildRestoreByIdQuery(this.table, id), id);
5615
+ if (row)
5616
+ await dispatchModelEvent(modelEventName(this.table.name, "restored"), row);
5617
+ return row;
5618
+ }));
5506
5619
  return await this.runModelMutation("restored", () => withDatabaseErrorHandling(async () => {
5507
5620
  const { text, params } = buildRestoreByIdQuery(this.table, id);
5508
5621
  const [record] = await this.connection.unsafe(text, params);
@@ -17204,6 +17317,7 @@ export {
17204
17317
  indexMorphToRelation,
17205
17318
  inferReferencedTable,
17206
17319
  installGracefulShutdownSignals,
17320
+ isActiveTransactionConnection,
17207
17321
  isBlockedHostname,
17208
17322
  isBlockedIpAddress,
17209
17323
  isDefaultDatabaseConnection,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/core",
3
- "version": "2.2.35",
3
+ "version": "2.2.37",
4
4
  "description": "Strata Bun framework public API",
5
5
  "type": "module",
6
6
  "license": "MIT",