|
|
@@ -4,8 +4,13 @@ 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 { EntityManager } from 'typeorm';
|
|
|
|
|
|
-import { REQUEST_CONTEXT_KEY, REQUEST_CONTEXT_MAP_KEY } from '../../common/constants';
|
|
|
+import {
|
|
|
+ REQUEST_CONTEXT_KEY,
|
|
|
+ REQUEST_CONTEXT_MAP_KEY,
|
|
|
+ TRANSACTION_MANAGER_KEY,
|
|
|
+} from '../../common/constants';
|
|
|
import { idsAreEqual } from '../../common/utils';
|
|
|
import { CachedSession } from '../../config/session-cache/session-cache-strategy';
|
|
|
import { Channel } from '../../entity/channel/channel.entity';
|
|
|
@@ -22,6 +27,32 @@ export type SerializedRequestContext = {
|
|
|
_authorizedAsOwnerOnly: boolean;
|
|
|
};
|
|
|
|
|
|
+/**
|
|
|
+ * This object is used to store the RequestContext on the Express Request object.
|
|
|
+ */
|
|
|
+interface RequestContextStore {
|
|
|
+ /**
|
|
|
+ * This is the default RequestContext for the handler.
|
|
|
+ */
|
|
|
+ default: RequestContext;
|
|
|
+ /**
|
|
|
+ * If a transaction is started, the resulting RequestContext is stored here.
|
|
|
+ * This RequestContext will have a transaction manager attached via the
|
|
|
+ * TRANSACTION_MANAGER_KEY symbol.
|
|
|
+ *
|
|
|
+ * When a transaction is started, the TRANSACTION_MANAGER_KEY symbol is added to the RequestContext
|
|
|
+ * object. This is then detected inside the {@link internal_setRequestContext} function and the
|
|
|
+ * RequestContext object is stored in the RequestContextStore under the withTransactionManager key.
|
|
|
+ */
|
|
|
+ withTransactionManager?: RequestContext;
|
|
|
+}
|
|
|
+
|
|
|
+interface RequestWithStores extends Request {
|
|
|
+ // eslint-disable-next-line @typescript-eslint/ban-types
|
|
|
+ [REQUEST_CONTEXT_MAP_KEY]?: Map<Function, RequestContextStore>;
|
|
|
+ [REQUEST_CONTEXT_KEY]?: RequestContextStore;
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* @description
|
|
|
* This function is used to set the {@link RequestContext} on the `req` object. This is the underlying
|
|
|
@@ -42,23 +73,39 @@ export type SerializedRequestContext = {
|
|
|
* We named it this way to discourage usage outside the framework internals.
|
|
|
*/
|
|
|
export function internal_setRequestContext(
|
|
|
- req: Request,
|
|
|
+ req: RequestWithStores,
|
|
|
ctx: RequestContext,
|
|
|
executionContext?: ExecutionContext,
|
|
|
) {
|
|
|
// If we have access to the `ExecutionContext`, it means we are able to bind
|
|
|
// the `ctx` object to the specific "handler", i.e. the resolver function (for GraphQL)
|
|
|
// or controller (for REST).
|
|
|
+ let item: RequestContextStore | undefined;
|
|
|
if (executionContext && typeof executionContext.getHandler === 'function') {
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
|
- const map: Map<Function, RequestContext> = (req as any)[REQUEST_CONTEXT_MAP_KEY] || new Map();
|
|
|
- map.set(executionContext.getHandler(), ctx);
|
|
|
+ const map = req[REQUEST_CONTEXT_MAP_KEY] || new Map();
|
|
|
+ item = map.get(executionContext.getHandler());
|
|
|
+ const ctxHasTransaction = Object.getOwnPropertySymbols(ctx).includes(TRANSACTION_MANAGER_KEY);
|
|
|
+ if (item) {
|
|
|
+ item.default = item.default ?? ctx;
|
|
|
+ if (ctxHasTransaction) {
|
|
|
+ item.withTransactionManager = ctx;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ item = {
|
|
|
+ default: ctx,
|
|
|
+ withTransactionManager: ctxHasTransaction ? ctx : undefined,
|
|
|
+ };
|
|
|
+ }
|
|
|
+ map.set(executionContext.getHandler(), item);
|
|
|
|
|
|
- (req as any)[REQUEST_CONTEXT_MAP_KEY] = map;
|
|
|
+ req[REQUEST_CONTEXT_MAP_KEY] = map;
|
|
|
}
|
|
|
// We also bind to a shared key so that we can access the `ctx` object
|
|
|
// later even if we don't have a reference to the `ExecutionContext`
|
|
|
- (req as any)[REQUEST_CONTEXT_KEY] = ctx;
|
|
|
+ req[REQUEST_CONTEXT_KEY] = item ?? {
|
|
|
+ default: ctx,
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -67,20 +114,31 @@ export function internal_setRequestContext(
|
|
|
* for more details on this mechanism.
|
|
|
*/
|
|
|
export function internal_getRequestContext(
|
|
|
- req: Request,
|
|
|
+ req: RequestWithStores,
|
|
|
executionContext?: ExecutionContext,
|
|
|
): RequestContext {
|
|
|
+ let item: RequestContextStore | undefined;
|
|
|
if (executionContext && typeof executionContext.getHandler === 'function') {
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
|
- const map: Map<Function, RequestContext> | undefined = (req as any)[REQUEST_CONTEXT_MAP_KEY];
|
|
|
- const ctx = map?.get(executionContext.getHandler());
|
|
|
+ const map = req[REQUEST_CONTEXT_MAP_KEY];
|
|
|
+ item = map?.get(executionContext.getHandler());
|
|
|
// If we have a ctx associated with the current handler (resolver function), we
|
|
|
// return it. Otherwise, we fall back to the shared key which will be there.
|
|
|
- if (ctx) {
|
|
|
- return ctx;
|
|
|
+ if (item) {
|
|
|
+ return item.withTransactionManager || item.default;
|
|
|
}
|
|
|
}
|
|
|
- return (req as any)[REQUEST_CONTEXT_KEY];
|
|
|
+ if (!item) {
|
|
|
+ item = req[REQUEST_CONTEXT_KEY] as RequestContextStore;
|
|
|
+ }
|
|
|
+ const transactionalCtx =
|
|
|
+ item?.withTransactionManager &&
|
|
|
+ ((item.withTransactionManager as any)[TRANSACTION_MANAGER_KEY] as EntityManager | undefined)
|
|
|
+ ?.queryRunner?.isReleased === false
|
|
|
+ ? item.withTransactionManager
|
|
|
+ : undefined;
|
|
|
+
|
|
|
+ return transactionalCtx || item.default;
|
|
|
}
|
|
|
|
|
|
/**
|