diff --git a/packages/core/src/service/transaction/transactional-connection.ts b/packages/core/src/service/transaction/transactional-connection.ts index 4f9e56958b..fa80ec6710 100644 --- a/packages/core/src/service/transaction/transactional-connection.ts +++ b/packages/core/src/service/transaction/transactional-connection.ts @@ -19,10 +19,35 @@ import { ChannelAware, SoftDeletable } from '../../common/types/common-types'; import { VendureEntity } from '../../entity/base/base.entity'; /** + * @description + * Options used by the {@link TransactionalConnection} `getEntityOrThrow` method. + * * @docsCategory data-access */ -export interface FindEntityOptions extends FindOneOptions { +export interface GetEntityOrThrowOptions extends FindOneOptions { + /** + * @description + * An optional channelId to limit results to entities assigned to the given Channel. Should + * only be used when getting entities that implement the {@link ChannelAware} interface. + */ channelId?: ID; + /** + * @description + * If set to a positive integer, it will retry getting the entity in case it is initially not + * found. This can be useful when working with the {@link EventBus} and subscribing to the + * creation of new Entities which may on first attempt be inaccessible due to an ongoing + * transaction. + * + * @default 0 + */ + retries?: number; + /** + * @description + * Specifies the delay in ms to wait between retries. + * + * @default 25 + */ + retryDelay?: number; } /** @@ -128,13 +153,43 @@ export class TransactionalConnection { /** * @description * Finds an entity of the given type by ID, or throws an `EntityNotFoundError` if none - * is found. + * is found. Can be configured to retry (using the `retries` option) in the event of the + * entity not being found on the first attempt. This can be useful when attempting to access + * an entity which was just created and may be inaccessible due to an ongoing transaction. */ async getEntityOrThrow( ctx: RequestContext, entityType: Type, id: ID, - options: FindEntityOptions = {}, + options: GetEntityOrThrowOptions = {}, + ): Promise { + const { retries, retryDelay } = options; + if (retries == null || retries <= 0) { + return this.getEntityOrThrowInternal(ctx, entityType, id, options); + } else { + let err: any; + 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; + } + } + + private async getEntityOrThrowInternal( + ctx: RequestContext, + entityType: Type, + id: ID, + options: GetEntityOrThrowOptions = {}, ): Promise { let entity: T | undefined; if (options.channelId != null) {