diff --git a/src/lib/db/access-store.ts b/src/lib/db/access-store.ts index e2c8563481..3131aa5394 100644 --- a/src/lib/db/access-store.ts +++ b/src/lib/db/access-store.ts @@ -25,6 +25,7 @@ import { NamePermissionRef, PermissionRef, } from 'lib/services/access-service'; +import { inTransaction } from './transaction'; const T = { ROLE_USER: 'role_user', @@ -574,7 +575,7 @@ export class AccessStore implements IAccessStore { }; }); - await this.db.transaction(async (tx) => { + await inTransaction(this.db, async (tx) => { if (userRows.length > 0) { await tx(T.ROLE_USER) .insert(userRows) @@ -620,7 +621,7 @@ export class AccessStore implements IAccessStore { })), ); - await this.db.transaction(async (tx) => { + await inTransaction(this.db, async (tx) => { if (groupRows.length > 0) { await tx(T.GROUP_ROLE) .insert(groupRows) @@ -656,7 +657,7 @@ export class AccessStore implements IAccessStore { role_id: role, })); - await this.db.transaction(async (tx) => { + await inTransaction(this.db, async (tx) => { await tx(T.ROLE_USER) .where('project', projectId) .andWhere('user_id', userId) @@ -707,7 +708,7 @@ export class AccessStore implements IAccessStore { created_by: createdBy, })); - await this.db.transaction(async (tx) => { + await inTransaction(this.db, async (tx) => { await tx(T.GROUP_ROLE) .where('project', projectId) .andWhere('group_id', groupId) diff --git a/src/lib/db/api-token-store.ts b/src/lib/db/api-token-store.ts index 5943c2ddc2..22af6db431 100644 --- a/src/lib/db/api-token-store.ts +++ b/src/lib/db/api-token-store.ts @@ -12,6 +12,7 @@ import { } from '../types/models/api-token'; import { ALL_PROJECTS } from '../util/constants'; import { Db } from './db'; +import { inTransaction } from './transaction'; const TABLE = 'api_tokens'; const API_LINK_TABLE = 'api_token_project'; @@ -139,7 +140,7 @@ export class ApiTokenStore implements IApiTokenStore { } async insert(newToken: IApiTokenCreate): Promise { - const response = await this.db.transaction(async (tx) => { + const response = await inTransaction(this.db, async (tx) => { const [row] = await tx(TABLE).insert( toRow(newToken), ['created_at'], diff --git a/src/lib/db/transaction.ts b/src/lib/db/transaction.ts index 701f2f1052..e26a435de8 100644 --- a/src/lib/db/transaction.ts +++ b/src/lib/db/transaction.ts @@ -26,6 +26,30 @@ export type WithTransactional = S & { transactional: (fn: (service: S) => R) => Promise; }; +/** + * @deprecated this is a temporal solution to deal with transactions at the store level. + * Ideally, we should handle transactions at the service level (each service method should be transactional). + * The controller should define the transactional scope as follows: + * https://github.com/Unleash/unleash/blob/cb034976b93abc799df774858d716a49f645d669/src/lib/features/export-import-toggles/export-import-controller.ts#L206-L208 + * + * To be able to use .transactional method, services should be instantiated like this: + * https://github.com/Unleash/unleash/blob/cb034976b93abc799df774858d716a49f645d669/src/lib/services/index.ts#L282-L284 + * + * This function makes sure that `fn` is executed in a transaction. + * If the db is already in a transaction, it will execute `fn` in that transactional scope. + * + * https://github.com/knex/knex/blob/bbbe4d4637b3838e4a297a457460cd2c76a700d5/lib/knex-builder/make-knex.js#L143C5-L144C88 + */ +export async function inTransaction( + db: Knex, + fn: (db: Knex) => R, +): Promise { + if (db.isTransaction) { + return fn(db); + } + return db.transaction(async (tx) => fn(tx)); +} + export function withTransactional( serviceFactory: (db: Knex) => S, db: Knex, @@ -33,6 +57,8 @@ export function withTransactional( const service = serviceFactory(db) as WithTransactional; service.transactional = async (fn: (service: S) => R) => + // Maybe: inTransaction(db, async (trx: Knex.Transaction) => fn(serviceFactory(trx))); + // this assumes that the caller didn't start a transaction already and opens a new one. db.transaction(async (trx: Knex.Transaction) => { const transactionalService = serviceFactory(trx); return fn(transactionalService); diff --git a/src/lib/types/option.ts b/src/lib/types/option.ts index 07cd7b79fa..11d166a3f3 100644 --- a/src/lib/types/option.ts +++ b/src/lib/types/option.ts @@ -104,7 +104,7 @@ export interface IUnleashOptions { versionCheck?: Partial; telemetry?: boolean; authentication?: Partial; - ui?: object; + ui?: IUIConfig; frontendApi?: IFrontendApi; import?: Partial; experimental?: Partial;