Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { QueryRunnerAlreadyReleasedError } from 'typeorm';

import { computeTwentyORMException } from 'src/engine/twenty-orm/error-handling/compute-twenty-orm-exception';
import {
TwentyORMException,
TwentyORMExceptionCode,
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';

describe('computeTwentyORMException', () => {
it('should map a released query runner error to a retryable QUERY_RUNNER_RELEASED exception', async () => {
const error = new QueryRunnerAlreadyReleasedError();

const result = await computeTwentyORMException(error);

expect(result).toBeInstanceOf(TwentyORMException);
expect((result as TwentyORMException).code).toBe(
TwentyORMExceptionCode.QUERY_RUNNER_RELEASED,
);
});

it('should return unrelated errors unchanged', async () => {
const error = new Error('some other error');

const result = await computeTwentyORMException(error);

expect(result).toBe(error);
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { msg } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { QueryFailedError } from 'typeorm';
import { QueryFailedError, QueryRunnerAlreadyReleasedError } from 'typeorm';

import { type WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';

Expand All @@ -24,6 +24,17 @@ export const computeTwentyORMException = async (
entityManager?: WorkspaceEntityManager,
internalContext?: WorkspaceInternalContext,
): Promise<Error | TwentyORMException> => {
// A released query runner means the underlying connection was torn down
// mid-flight (e.g. node-postgres `query_timeout` destroying the pooled
// connection during a transaction). It is transient, so we surface it as a
// retryable error rather than an opaque failure.
if (error instanceof QueryRunnerAlreadyReleasedError) {
return new TwentyORMException(
error.message,
TwentyORMExceptionCode.QUERY_RUNNER_RELEASED,
);
}

if (error instanceof QueryFailedError) {
if (error.message.includes('Query read timeout')) {
return new TwentyORMException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export enum TwentyORMExceptionCode {
METHOD_NOT_ALLOWED = 'METHOD_NOT_ALLOWED',
ENUM_TYPE_NAME_NOT_FOUND = 'ENUM_TYPE_NAME_NOT_FOUND',
QUERY_READ_TIMEOUT = 'QUERY_READ_TIMEOUT',
QUERY_RUNNER_RELEASED = 'QUERY_RUNNER_RELEASED',
DUPLICATE_ENTRY_DETECTED = 'DUPLICATE_ENTRY_DETECTED',
TOO_MANY_RECORDS_TO_UPDATE = 'TOO_MANY_RECORDS_TO_UPDATE',
INVALID_INPUT = 'INVALID_INPUT',
Expand Down Expand Up @@ -61,6 +62,8 @@ const getTwentyORMExceptionUserFriendlyMessage = (
return msg`This operation is not allowed.`;
case TwentyORMExceptionCode.QUERY_READ_TIMEOUT:
return msg`Query timed out. Please try again.`;
case TwentyORMExceptionCode.QUERY_RUNNER_RELEASED:
return msg`We are experiencing a temporary issue with our database. Please try again later.`;
case TwentyORMExceptionCode.DUPLICATE_ENTRY_DETECTED:
return msg`A duplicate entry was detected.`;
case TwentyORMExceptionCode.TOO_MANY_RECORDS_TO_UPDATE:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export class CalendarEventImportErrorHandlerService {
);
break;
case TwentyORMExceptionCode.QUERY_READ_TIMEOUT:
case TwentyORMExceptionCode.QUERY_RUNNER_RELEASED:
case CalendarEventImportDriverExceptionCode.TEMPORARY_ERROR:
case ConnectedAccountRefreshAccessTokenExceptionCode.TEMPORARY_NETWORK_ERROR:
await this.handleTemporaryException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export class MessageImportExceptionHandlerService {
);
break;
case TwentyORMExceptionCode.QUERY_READ_TIMEOUT:
case TwentyORMExceptionCode.QUERY_RUNNER_RELEASED:
case MessageImportDriverExceptionCode.TEMPORARY_ERROR:
case ConnectedAccountRefreshAccessTokenExceptionCode.TEMPORARY_NETWORK_ERROR:
case MessageNetworkExceptionCode.ECONNABORTED:
Expand Down
Loading