Skip to content

Commit

Permalink
feat(core): Add replicationMode for ctx and getRepository (#2746)
Browse files Browse the repository at this point in the history
  • Loading branch information
monrostar authored Aug 13, 2024
1 parent 5db4c66 commit 60cdae3
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 2 deletions.
40 changes: 40 additions & 0 deletions packages/core/src/api/common/request-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ID, JsonCompatible } from '@vendure/common/lib/shared-types';
import { isObject } from '@vendure/common/lib/shared-utils';
import { Request } from 'express';
import { TFunction } from 'i18next';
import { ReplicationMode } from 'typeorm';

import { idsAreEqual } from '../../common/utils';
import { CachedSession } from '../../config/session-cache/session-cache-strategy';
Expand Down Expand Up @@ -32,13 +33,27 @@ export type SerializedRequestContext = {
* the active Channel, and so on. In addition, the {@link TransactionalConnection} relies on the
* presence of the RequestContext object in order to correctly handle per-request database transactions.
*
* The RequestContext also provides mechanisms for managing the database replication mode via the
* {@link setReplicationMode} method and the {@link replicationMode} getter. This allows for finer control
* over whether database queries within the context should be executed against the master or a replica
* database, which can be particularly useful in distributed database environments.
*
* @example
* ```ts
* \@Query()
* myQuery(\@Ctx() ctx: RequestContext) {
* return this.myService.getData(ctx);
* }
* ```
*
* @example
* ```ts
* \@Query()
* myMutation(\@Ctx() ctx: RequestContext) {
* ctx.setReplicationMode('master');
* return this.myService.getData(ctx);
* }
* ```
* @docsCategory request
*/
export class RequestContext {
Expand All @@ -51,6 +66,7 @@ export class RequestContext {
private readonly _translationFn: TFunction;
private readonly _apiType: ApiType;
private readonly _req?: Request;
private _replicationMode?: ReplicationMode;

/**
* @internal
Expand Down Expand Up @@ -284,4 +300,28 @@ export class RequestContext {
}
return copySimpleFieldsToDepth(req, 1);
}

/**
* @description
* Sets the replication mode for the current RequestContext. This mode determines whether the operations
* within this context should interact with the master database or a replica. Use this method to explicitly
* define the replication mode for the context.
*
* @param mode - The replication mode to be set (e.g., 'master' or 'replica').
*/
setReplicationMode(mode: ReplicationMode): void {
this._replicationMode = mode;
}

/**
* @description
* Gets the current replication mode of the RequestContext. If no replication mode has been set,
* it returns `undefined`. This property indicates whether the context is configured to interact with
* the master database or a replica.
*
* @returns The current replication mode, or `undefined` if none is set.
*/
get replicationMode(): ReplicationMode | undefined {
return this._replicationMode;
}
}
43 changes: 41 additions & 2 deletions packages/core/src/connection/transactional-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ObjectType,
Repository,
SelectQueryBuilder,
ReplicationMode,
} from 'typeorm';

import { RequestContext } from '../api/common/request-context';
Expand Down Expand Up @@ -70,25 +71,63 @@ export class TransactionalConnection {
* Returns a TypeORM repository which is bound to any existing transactions. It is recommended to _always_ pass
* the RequestContext argument when possible, otherwise the queries will be executed outside of any
* ongoing transactions which have been started by the {@link Transaction} decorator.
*
* The `options` parameter allows specifying additional configurations, such as the `replicationMode`,
* which determines whether the repository should interact with the master or replica database.
*
* @param ctx - The RequestContext, which ensures the repository is aware of any existing transactions.
* @param target - The entity type or schema for which the repository is returned.
* @param options - Additional options for configuring the repository, such as the `replicationMode`.
*
* @returns A TypeORM repository for the specified entity type.
*/
getRepository<Entity extends ObjectLiteral>(
ctx: RequestContext | undefined,
target: ObjectType<Entity> | EntitySchema<Entity> | string,
options?: {
replicationMode?: ReplicationMode;
},
): Repository<Entity>;
/**
* @description
* Returns a TypeORM repository. Depending on the parameters passed, it will either be transaction-aware
* or not. If `RequestContext` is provided, the repository is bound to any ongoing transactions. The
* `options` parameter allows further customization, such as selecting the replication mode (e.g., 'master').
*
* @param ctxOrTarget - Either the RequestContext, which binds the repository to ongoing transactions, or the entity type/schema.
* @param maybeTarget - The entity type or schema for which the repository is returned (if `ctxOrTarget` is a RequestContext).
* @param options - Additional options for configuring the repository, such as the `replicationMode`.
*
* @returns A TypeORM repository for the specified entity type.
*/
getRepository<Entity extends ObjectLiteral>(
ctxOrTarget: RequestContext | ObjectType<Entity> | EntitySchema<Entity> | string | undefined,
maybeTarget?: ObjectType<Entity> | EntitySchema<Entity> | string,
options?: {
replicationMode?: ReplicationMode;
},
): Repository<Entity> {
if (ctxOrTarget instanceof RequestContext) {
const transactionManager = this.getTransactionManager(ctxOrTarget);
if (transactionManager) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return transactionManager.getRepository(maybeTarget!);
} else {
}

if (ctxOrTarget.replicationMode === 'master' || options?.replicationMode === 'master') {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return this.rawConnection.getRepository(maybeTarget!);
return this.dataSource.createQueryRunner('master').manager.getRepository(maybeTarget!);
}

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return this.rawConnection.getRepository(maybeTarget!);
} else {
if (options?.replicationMode === 'master') {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return this.dataSource
.createQueryRunner(options.replicationMode)
.manager.getRepository(maybeTarget!);
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return this.rawConnection.getRepository(ctxOrTarget ?? maybeTarget!);
}
Expand Down

0 comments on commit 60cdae3

Please sign in to comment.