UNPKG

@vendure/core

Version:

A modern, headless ecommerce framework

258 lines 13.3 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TransactionalConnection = void 0; const common_1 = require("@nestjs/common"); const typeorm_decorators_1 = require("@nestjs/typeorm/dist/common/typeorm.decorators"); const typeorm_1 = require("typeorm"); const request_context_1 = require("../api/common/request-context"); const constants_1 = require("../common/constants"); const errors_1 = require("../common/error/errors"); const tree_relations_qb_joiner_1 = require("../service/helpers/utils/tree-relations-qb-joiner"); const find_options_object_to_array_1 = require("./find-options-object-to-array"); const transaction_wrapper_1 = require("./transaction-wrapper"); /** * @description * The TransactionalConnection is a wrapper around the TypeORM `Connection` object which works in conjunction * with the {@link Transaction} decorator to implement per-request transactions. All services which access the * database should use this class rather than the raw TypeORM connection, to ensure that db changes can be * easily wrapped in transactions when required. * * The service layer does not need to know about the scope of a transaction, as this is covered at the * API by the use of the `Transaction` decorator. * * @docsCategory data-access */ let TransactionalConnection = class TransactionalConnection { constructor(dataSource, transactionWrapper) { this.dataSource = dataSource; this.transactionWrapper = transactionWrapper; } /** * @description * The plain TypeORM Connection object. Should be used carefully as any operations * performed with this connection will not be performed within any outer * transactions. */ get rawConnection() { return this.dataSource; } /** * @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(ctxOrTarget, maybeTarget, options) { if (ctxOrTarget instanceof request_context_1.RequestContext) { const transactionManager = this.getTransactionManager(ctxOrTarget); if (transactionManager) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return transactionManager.getRepository(maybeTarget); } if (ctxOrTarget.replicationMode === 'master' || (options === null || options === void 0 ? void 0 : options.replicationMode) === 'master') { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 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 === null || options === void 0 ? void 0 : 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 !== null && ctxOrTarget !== void 0 ? ctxOrTarget : maybeTarget); } } async withTransaction(ctxOrWork, maybeWork) { let ctx; let work; if (ctxOrWork instanceof request_context_1.RequestContext) { ctx = ctxOrWork; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion work = maybeWork; } else { ctx = request_context_1.RequestContext.empty(); work = ctxOrWork; } return this.transactionWrapper.executeInTransaction(ctx, work, 'auto', undefined, this.rawConnection); } /** * @description * Manually start a transaction if one is not already in progress. This method should be used in * conjunction with the `'manual'` mode of the {@link Transaction} decorator. */ async startTransaction(ctx, isolationLevel) { var _a; const transactionManager = this.getTransactionManager(ctx); if (((_a = transactionManager === null || transactionManager === void 0 ? void 0 : transactionManager.queryRunner) === null || _a === void 0 ? void 0 : _a.isTransactionActive) === false) { await transactionManager.queryRunner.startTransaction(isolationLevel); } } /** * @description * Manually commits any open transaction. Should be very rarely needed, since the {@link Transaction} decorator * and the internal TransactionInterceptor take care of this automatically. Use-cases include situations * in which the worker thread needs to access changes made in the current transaction, or when using the * Transaction decorator in manual mode. */ async commitOpenTransaction(ctx) { var _a; const transactionManager = this.getTransactionManager(ctx); if ((_a = transactionManager === null || transactionManager === void 0 ? void 0 : transactionManager.queryRunner) === null || _a === void 0 ? void 0 : _a.isTransactionActive) { await transactionManager.queryRunner.commitTransaction(); } } /** * @description * Manually rolls back any open transaction. Should be very rarely needed, since the {@link Transaction} decorator * and the internal TransactionInterceptor take care of this automatically. Use-cases include when using the * Transaction decorator in manual mode. */ async rollBackTransaction(ctx) { var _a; const transactionManager = this.getTransactionManager(ctx); if ((_a = transactionManager === null || transactionManager === void 0 ? void 0 : transactionManager.queryRunner) === null || _a === void 0 ? void 0 : _a.isTransactionActive) { await transactionManager.queryRunner.rollbackTransaction(); } } /** * @description * Finds an entity of the given type by ID, or throws an `EntityNotFoundError` if none * is found. */ async getEntityOrThrow(ctx, entityType, id, options = {}) { const { retries, retryDelay } = options; if (retries == null || retries <= 0) { return this.getEntityOrThrowInternal(ctx, entityType, id, options); } else { let err; const retriesInt = Math.ceil(retries); const delay = Math.ceil(Math.max(retryDelay || 25, 1)); for (let attempt = 0; attempt < retriesInt; attempt++) { try { const result = await this.getEntityOrThrowInternal(ctx, entityType, id, options); return result; } catch (e) { err = e; if (attempt < retriesInt - 1) { await new Promise(resolve => setTimeout(resolve, delay)); } } } throw err; } } async getEntityOrThrowInternal(ctx, entityType, id, options = {}) { let entity; if (options.channelId != null) { const { channelId } = options, optionsWithoutChannelId = __rest(options, ["channelId"]); entity = await this.findOneInChannel(ctx, entityType, id, options.channelId, optionsWithoutChannelId); } else { const optionsWithId = Object.assign(Object.assign({}, options), { where: Object.assign(Object.assign({}, (options.where || {})), { id }) }); entity = await this.getRepository(ctx, entityType) .findOne(optionsWithId) .then(result => result !== null && result !== void 0 ? result : undefined); } if (!entity || (entity.hasOwnProperty('deletedAt') && entity.deletedAt !== null && options.includeSoftDeleted !== true)) { throw new errors_1.EntityNotFoundError(entityType.name, id); } return entity; } /** * @description * Like the TypeOrm `Repository.findOne()` method, but limits the results to * the given Channel. */ findOneInChannel(ctx, entity, id, channelId, options = {}) { const qb = this.getRepository(ctx, entity).createQueryBuilder('entity'); if (options.relations) { const joinedRelations = (0, tree_relations_qb_joiner_1.joinTreeRelationsDynamically)(qb, entity, options.relations); // Remove any relations which are related to the 'collection' tree, as these are handled separately // to avoid duplicate joins. options.relations = (0, find_options_object_to_array_1.findOptionsObjectToArray)(options.relations).filter(relationPath => !joinedRelations.has(relationPath)); } qb.setFindOptions(Object.assign({ relationLoadStrategy: 'query' }, options)); qb.leftJoin('entity.channels', '__channel') .andWhere('entity.id = :id', { id }) .andWhere('__channel.id = :channelId', { channelId }); return qb.getOne().then(result => { return result !== null && result !== void 0 ? result : undefined; }); } /** * @description * Like the TypeOrm `Repository.findByIds()` method, but limits the results to * the given Channel. */ findByIdsInChannel(ctx, entity, ids, channelId, options) { // the syntax described in https://github.com/typeorm/typeorm/issues/1239#issuecomment-366955628 // breaks if the array is empty if (ids.length === 0) { return Promise.resolve([]); } const qb = this.getRepository(ctx, entity).createQueryBuilder('entity'); if (Array.isArray(options.relations) && options.relations.length > 0) { const joinedRelations = (0, tree_relations_qb_joiner_1.joinTreeRelationsDynamically)(qb, entity, options.relations); // Remove any relations which are related to the 'collection' tree, as these are handled separately // to avoid duplicate joins. options.relations = options.relations.filter(relationPath => !joinedRelations.has(relationPath)); } qb.setFindOptions(Object.assign({ relationLoadStrategy: 'query' }, options)); return qb .leftJoin('entity.channels', 'channel') .andWhere('entity.id IN (:...ids)', { ids }) .andWhere('channel.id = :channelId', { channelId }) .getMany(); } getTransactionManager(ctx) { return ctx[constants_1.TRANSACTION_MANAGER_KEY]; } }; exports.TransactionalConnection = TransactionalConnection; exports.TransactionalConnection = TransactionalConnection = __decorate([ (0, common_1.Injectable)(), __param(0, (0, typeorm_decorators_1.InjectDataSource)()), __metadata("design:paramtypes", [typeorm_1.DataSource, transaction_wrapper_1.TransactionWrapper]) ], TransactionalConnection); //# sourceMappingURL=transactional-connection.js.map