Skip to content

Commit 60cdae3

Browse files
authored
feat(core): Add replicationMode for ctx and getRepository (#2746)
1 parent 5db4c66 commit 60cdae3

File tree

2 files changed

+81
-2
lines changed

2 files changed

+81
-2
lines changed

packages/core/src/api/common/request-context.ts

+40
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ID, JsonCompatible } from '@vendure/common/lib/shared-types';
33
import { isObject } from '@vendure/common/lib/shared-utils';
44
import { Request } from 'express';
55
import { TFunction } from 'i18next';
6+
import { ReplicationMode } from 'typeorm';
67

78
import { idsAreEqual } from '../../common/utils';
89
import { CachedSession } from '../../config/session-cache/session-cache-strategy';
@@ -32,13 +33,27 @@ export type SerializedRequestContext = {
3233
* the active Channel, and so on. In addition, the {@link TransactionalConnection} relies on the
3334
* presence of the RequestContext object in order to correctly handle per-request database transactions.
3435
*
36+
* The RequestContext also provides mechanisms for managing the database replication mode via the
37+
* {@link setReplicationMode} method and the {@link replicationMode} getter. This allows for finer control
38+
* over whether database queries within the context should be executed against the master or a replica
39+
* database, which can be particularly useful in distributed database environments.
40+
*
3541
* @example
3642
* ```ts
3743
* \@Query()
3844
* myQuery(\@Ctx() ctx: RequestContext) {
3945
* return this.myService.getData(ctx);
4046
* }
4147
* ```
48+
*
49+
* @example
50+
* ```ts
51+
* \@Query()
52+
* myMutation(\@Ctx() ctx: RequestContext) {
53+
* ctx.setReplicationMode('master');
54+
* return this.myService.getData(ctx);
55+
* }
56+
* ```
4257
* @docsCategory request
4358
*/
4459
export class RequestContext {
@@ -51,6 +66,7 @@ export class RequestContext {
5166
private readonly _translationFn: TFunction;
5267
private readonly _apiType: ApiType;
5368
private readonly _req?: Request;
69+
private _replicationMode?: ReplicationMode;
5470

5571
/**
5672
* @internal
@@ -284,4 +300,28 @@ export class RequestContext {
284300
}
285301
return copySimpleFieldsToDepth(req, 1);
286302
}
303+
304+
/**
305+
* @description
306+
* Sets the replication mode for the current RequestContext. This mode determines whether the operations
307+
* within this context should interact with the master database or a replica. Use this method to explicitly
308+
* define the replication mode for the context.
309+
*
310+
* @param mode - The replication mode to be set (e.g., 'master' or 'replica').
311+
*/
312+
setReplicationMode(mode: ReplicationMode): void {
313+
this._replicationMode = mode;
314+
}
315+
316+
/**
317+
* @description
318+
* Gets the current replication mode of the RequestContext. If no replication mode has been set,
319+
* it returns `undefined`. This property indicates whether the context is configured to interact with
320+
* the master database or a replica.
321+
*
322+
* @returns The current replication mode, or `undefined` if none is set.
323+
*/
324+
get replicationMode(): ReplicationMode | undefined {
325+
return this._replicationMode;
326+
}
287327
}

packages/core/src/connection/transactional-connection.ts

+41-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
ObjectType,
1212
Repository,
1313
SelectQueryBuilder,
14+
ReplicationMode,
1415
} from 'typeorm';
1516

1617
import { RequestContext } from '../api/common/request-context';
@@ -70,25 +71,63 @@ export class TransactionalConnection {
7071
* Returns a TypeORM repository which is bound to any existing transactions. It is recommended to _always_ pass
7172
* the RequestContext argument when possible, otherwise the queries will be executed outside of any
7273
* ongoing transactions which have been started by the {@link Transaction} decorator.
74+
*
75+
* The `options` parameter allows specifying additional configurations, such as the `replicationMode`,
76+
* which determines whether the repository should interact with the master or replica database.
77+
*
78+
* @param ctx - The RequestContext, which ensures the repository is aware of any existing transactions.
79+
* @param target - The entity type or schema for which the repository is returned.
80+
* @param options - Additional options for configuring the repository, such as the `replicationMode`.
81+
*
82+
* @returns A TypeORM repository for the specified entity type.
7383
*/
7484
getRepository<Entity extends ObjectLiteral>(
7585
ctx: RequestContext | undefined,
7686
target: ObjectType<Entity> | EntitySchema<Entity> | string,
87+
options?: {
88+
replicationMode?: ReplicationMode;
89+
},
7790
): Repository<Entity>;
91+
/**
92+
* @description
93+
* Returns a TypeORM repository. Depending on the parameters passed, it will either be transaction-aware
94+
* or not. If `RequestContext` is provided, the repository is bound to any ongoing transactions. The
95+
* `options` parameter allows further customization, such as selecting the replication mode (e.g., 'master').
96+
*
97+
* @param ctxOrTarget - Either the RequestContext, which binds the repository to ongoing transactions, or the entity type/schema.
98+
* @param maybeTarget - The entity type or schema for which the repository is returned (if `ctxOrTarget` is a RequestContext).
99+
* @param options - Additional options for configuring the repository, such as the `replicationMode`.
100+
*
101+
* @returns A TypeORM repository for the specified entity type.
102+
*/
78103
getRepository<Entity extends ObjectLiteral>(
79104
ctxOrTarget: RequestContext | ObjectType<Entity> | EntitySchema<Entity> | string | undefined,
80105
maybeTarget?: ObjectType<Entity> | EntitySchema<Entity> | string,
106+
options?: {
107+
replicationMode?: ReplicationMode;
108+
},
81109
): Repository<Entity> {
82110
if (ctxOrTarget instanceof RequestContext) {
83111
const transactionManager = this.getTransactionManager(ctxOrTarget);
84112
if (transactionManager) {
85113
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
86114
return transactionManager.getRepository(maybeTarget!);
87-
} else {
115+
}
116+
117+
if (ctxOrTarget.replicationMode === 'master' || options?.replicationMode === 'master') {
88118
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
89-
return this.rawConnection.getRepository(maybeTarget!);
119+
return this.dataSource.createQueryRunner('master').manager.getRepository(maybeTarget!);
90120
}
121+
122+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
123+
return this.rawConnection.getRepository(maybeTarget!);
91124
} else {
125+
if (options?.replicationMode === 'master') {
126+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
127+
return this.dataSource
128+
.createQueryRunner(options.replicationMode)
129+
.manager.getRepository(maybeTarget!);
130+
}
92131
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
93132
return this.rawConnection.getRepository(ctxOrTarget ?? maybeTarget!);
94133
}

0 commit comments

Comments
 (0)