Skip to content

Commit

Permalink
feat(core): Add retries to TransactionalConnection.getEntityOrThrow()
Browse files Browse the repository at this point in the history
Closes #937
  • Loading branch information
michaelbromley committed Jun 22, 2021
1 parent 8e9b72f commit 1e3ba7b
Showing 1 changed file with 58 additions and 3 deletions.
61 changes: 58 additions & 3 deletions packages/core/src/service/transaction/transactional-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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<T extends VendureEntity>(
ctx: RequestContext,
entityType: Type<T>,
id: ID,
options: FindEntityOptions = {},
options: GetEntityOrThrowOptions = {},
): Promise<T> {
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<T extends VendureEntity>(
ctx: RequestContext,
entityType: Type<T>,
id: ID,
options: GetEntityOrThrowOptions = {},
): Promise<T> {
let entity: T | undefined;
if (options.channelId != null) {
Expand Down

0 comments on commit 1e3ba7b

Please sign in to comment.