fix(PrismaClient): fix a bug where transactionClients where instantiated with the wrong Prisma imports

This commit is contained in:
Matthieu Haineault 2026-02-05 11:31:47 -05:00
parent d9f0362cb9
commit b8157b78d1
32 changed files with 70 additions and 68 deletions

View File

@ -4,9 +4,6 @@ import { defineConfig } from 'prisma/config';
export default defineConfig({
schema: "prisma/mariadb",
migrations: {
path: "prisma/mariadb/migrations",
},
datasource: {
url: process.env["DATABASE_URL_MARIADB"],
},

View File

@ -1,6 +1,6 @@
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { adapterMariaDb } from 'prisma.config.mariadb';
import { PrismaClient } from '@prisma/client';
import { PrismaClient } from 'prisma/mariadb/generated/prisma/client/mariadb/client';
@Injectable()
export class PrismaMariaDbService extends PrismaClient implements OnModuleInit, OnModuleDestroy {

View File

@ -1,6 +1,6 @@
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import { adapterPostgres } from 'prisma.config.postgres';
import { PrismaClient } from 'prisma/postgres/generated/prisma/client/postgres/client';
@Injectable()
export class PrismaPostgresService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
@ -8,15 +8,16 @@ export class PrismaPostgresService extends PrismaClient implements OnModuleInit,
readonly client: PrismaClient;
constructor() {
super({ adapter: adapterPostgres }),
this.client = new PrismaClient({ adapter: adapterPostgres });
super({ adapter: adapterPostgres })
}
async onModuleInit() {
await this.client.$connect();
await this.$connect();
}
async onModuleDestroy() {
await this.client.$disconnect();
await this.$disconnect();
}
}
export type TransactionClient = Parameters<Parameters<PrismaClient['$transaction']>[0]>[0];

View File

@ -1,21 +1,21 @@
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import { adapterLegacy } from 'prisma.config.legacy';
import { PrismaClient } from 'prisma/prisma-legacy/generated/prisma/client/legacy/client';
@Injectable()
export class PrismaLegacyService implements OnModuleInit, OnModuleDestroy {
export class PrismaLegacyService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
readonly client: PrismaClient;
constructor() {
this.client = new PrismaClient({adapter: adapterLegacy })
super({adapter: adapterLegacy})
}
async onModuleInit() {
await this.client.$connect();
await this.$connect();
}
async onModuleDestroy() {
await this.client.$disconnect();
await this.$disconnect();
}
}

View File

@ -1,7 +1,6 @@
import { NotFoundException } from "@nestjs/common";
import { Prisma, PrismaClient } from "@prisma/client";
import { PrismaClientKnownRequestError } from "@prisma/client/runtime/client";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
import { PrismaPostgresService, TransactionClient } from "prisma/postgres/prisma-postgres.service";
type UpdatableDelegate<T> = {
@ -13,21 +12,21 @@ type UpdatableDelegate<T> = {
//abstract class for approving or rejecting a shift, expense, timesheet or pay-period
export abstract class BaseApprovalService<T> {
protected constructor(protected readonly prisma: PrismaPostgresService) {}
protected constructor(protected readonly prisma: PrismaPostgresService) { }
//returns the corresponding Prisma delegate
protected abstract get delegate(): UpdatableDelegate<T>;
protected abstract delegateFor(tx: Prisma.TransactionClient | PrismaClient): UpdatableDelegate<T>;
protected abstract delegateFor(tx: TransactionClient): UpdatableDelegate<T>;
//standard update Aproval
async updateApproval(id: number, is_approved: boolean): Promise<T> {
try{
try {
return await this.delegate.update({
where: { id },
data: { is_approved: is_approved },
});
}catch (error: any) {
} catch (error: any) {
if (error instanceof PrismaClientKnownRequestError && error.code === "P2025") {
throw new NotFoundException(`Entity #${id} not found`);
}
@ -35,15 +34,14 @@ export abstract class BaseApprovalService<T> {
}
}
//approval with transaction to avoid many requests
async updateApprovalWithTransaction(tx: Prisma.TransactionClient, id: number, is_approved: boolean): Promise<T> {
async updateApprovalWithTransaction(tx: TransactionClient, id: number, is_approved: boolean): Promise<T> {
try {
return await this.delegateFor(tx).update({
where: { id },
data: { is_approved: is_approved },
});
} catch (error: any ){
if(error instanceof PrismaClientKnownRequestError && error.code === 'P2025') {
} catch (error: any) {
if (error instanceof PrismaClientKnownRequestError && error.code === 'P2025') {
throw new NotFoundException(`Entity #${id} not found`);
}
throw error;

View File

@ -1,12 +1,12 @@
import { Injectable } from "@nestjs/common";
import { PrismaClient } from "@prisma/client";
import { PrismaClient as MariadbClient } from "prisma/mariadb/generated/prisma/client/mariadb/client";
import { Result } from "src/common/errors/result-error.factory";
import { Account, AccountMemo } from "src/customer-support/accounts/account.dto";
@Injectable()
export class AccountService {
constructor(private readonly prismaMariaDb: PrismaClient) { }
constructor(private readonly prismaMariaDb: MariadbClient) { }
findAllAccounts = async (): Promise<Result<Account[], string>> => {
const listOfAccounts: Account[] = [];

View File

@ -1,5 +1,5 @@
import { Controller, Get, Query, Body, Post, Patch } from "@nestjs/common";
import { Modules as ModulesEnum } from "@prisma/client";
import { Modules as ModulesEnum } from "prisma/postgres/generated/prisma/client/postgres/client";
import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
import { Access } from "src/common/decorators/module-access.decorators";
import { Result } from "src/common/errors/result-error.factory";

View File

@ -1,11 +1,11 @@
import { Injectable } from "@nestjs/common";
import { Users } from "@prisma/client";
import { Result } from "src/common/errors/result-error.factory";
import { toDateFromString } from "src/common/utils/date-utils";
import { EmployeeDetailedDto } from "src/identity-and-account/employees/employee-detailed.dto";
import { toCompanyCodeFromString } from "src/identity-and-account/employees/employee.utils";
import { toBooleanFromString } from "src/identity-and-account/employees/services/employees-get.service";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
import { Users } from "prisma/postgres/generated/prisma/client/postgres/client";
@Injectable()
export class EmployeesCreateService {

View File

@ -8,7 +8,6 @@ import { Result } from "src/common/errors/result-error.factory";
import { toStringFromCompanyCode } from "src/identity-and-account/employees/employee.utils";
import { EmployeeDetailedDto } from "src/identity-and-account/employees/employee-detailed.dto";
import { toKeysFromBoolean } from "src/common/utils/boolean-utils";
@Injectable()
export class EmployeesGetService {

View File

@ -9,6 +9,8 @@ import { toCompanyCodeFromString } from "src/identity-and-account/employees/empl
import { EmployeeDetailedUpsertDto } from "src/identity-and-account/employees/employee-detailed.dto";
import { toBooleanFromString } from "src/identity-and-account/employees/services/employees-get.service";
import { PaidTimeOffDto } from "src/time-and-attendance/paid-time-off/paid-time-off.dto";
@Injectable()
export class EmployeesUpdateService {
constructor(

View File

@ -1,7 +1,7 @@
import { Controller, Get } from "@nestjs/common";
import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
import { HomePageService } from "src/identity-and-account/help/help-page.service";
import { Modules as ModulesEnum } from "@prisma/client";
import { Modules as ModulesEnum } from "prisma/postgres/generated/prisma/client/postgres/client";
import { Access } from "src/common/decorators/module-access.decorators";
@Controller()

View File

@ -4,7 +4,7 @@ import { PreferencesDto } from "./preferences.dto";
import { Result } from "src/common/errors/result-error.factory";
import { Access } from "src/common/decorators/module-access.decorators";
import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
import { Modules as ModulesEnum } from "@prisma/client";
import { Modules as ModulesEnum } from "prisma/postgres/generated/prisma/client/postgres/client";
@Controller('preferences')
export class PreferencesController {

View File

@ -5,7 +5,7 @@ import { ModuleAccess } from "src/identity-and-account/user-module-access/module
import { AccessGetService } from "src/identity-and-account/user-module-access/services/module-access-get.service";
import { AccessUpdateService } from "src/identity-and-account/user-module-access/services/module-access-update.service";
import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
import { Modules as ModulesEnum } from ".prisma/client";
import { Modules as ModulesEnum } from "prisma/postgres/generated/prisma/client/postgres/client";
@Controller('module_access')
export class ModuleAccessController {

View File

@ -1,5 +1,5 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { Modules, Users } from '@prisma/client';
import { Modules, Users } from 'prisma/postgres/generated/prisma/client/postgres/client';
import { toKeysFromBoolean } from 'src/common/utils/boolean-utils';
import { PrismaPostgresService } from 'prisma/postgres/prisma-postgres.service';

View File

@ -1,4 +1,4 @@
import { Modules, Roles } from "@prisma/client";
import { Modules, Roles } from "prisma/postgres/generated/prisma/client/postgres/client";
import { IsArray, IsEmail, IsEnum, IsString } from "class-validator";
export class UserDto {

View File

@ -1,19 +1,20 @@
import 'dotenv/config';
import { Worker } from 'bullmq';
import sharp from 'sharp';
import { PrismaClient } from '@prisma/client';
import { PrismaClient } from 'prisma/postgres/generated/prisma/client/postgres/client';
import * as path from 'node:path';
import { promises as fsp } from 'node:fs';
import { resolveAttachmentsRoot } from 'src/time-and-attendance/attachments/config/attachment.config';
import { adapterPostgres } from 'prisma.config.postgres';
const prisma = new PrismaClient();
const prisma = new PrismaClient({ adapter: adapterPostgres });
const q_name = `${process.env.BULL_PREFIX || 'attachments'}:variants`;
const root = resolveAttachmentsRoot();
const root = resolveAttachmentsRoot();
const variants = [
{ name: 'thumb.jpeg', build: (s:sharp.Sharp) => s.rotate().jpeg({quality:80}).resize({width:128}) },
{ name: '256w.webp' , build: (s:sharp.Sharp) => s.rotate().webp({quality:80}).resize({width:256}) },
{ name: '1024w.webp', build: (s:sharp.Sharp) => s.rotate().webp({quality:82}).resize({width:1024}) },
{ name: 'thumb.jpeg', build: (s: sharp.Sharp) => s.rotate().jpeg({ quality: 80 }).resize({ width: 128 }) },
{ name: '256w.webp', build: (s: sharp.Sharp) => s.rotate().webp({ quality: 80 }).resize({ width: 256 }) },
{ name: '1024w.webp', build: (s: sharp.Sharp) => s.rotate().webp({ quality: 82 }).resize({ width: 1024 }) },
]
new Worker(q_name, async job => {
@ -21,19 +22,19 @@ new Worker(q_name, async job => {
if (!attachment_id) return;
const attachment = await prisma.attachments.findUnique({
where: { id: attachment_id },
where: { id: attachment_id },
include: { blob: true },
});
if(!attachment) return;
if (!attachment) return;
const source_abs = path.join(root, attachment.blob.storage_path);
for(const variant of variants) {
for (const variant of variants) {
const relative = `${attachment.blob.storage_path}.${variant.name}`;
const out_Abs = path.join(root, relative);
//try for idem paths
try{ await fsp.stat(out_Abs); continue; } catch{}
try { await fsp.stat(out_Abs); continue; } catch { }
await fsp.mkdir(path.dirname(out_Abs), { recursive: true });
@ -41,14 +42,15 @@ new Worker(q_name, async job => {
await variant.build(sharp(source_abs)).toFile(out_Abs);
//meta data of generated variant file
const meta = await sharp(out_Abs).metadata();
const meta = await sharp(out_Abs).metadata();
const bytes = (await fsp.stat(out_Abs)).size;
await prisma.attachmentVariants.upsert({
where: { attachment_id_variant: { attachment_id: attachment_id, variant: variant.name } },
where: { attachment_id_variant: { attachment_id: attachment_id, variant: variant.name } },
update: { path: relative, bytes, width: meta.width ?? null, height: meta.height ?? null },
create: { path: relative, bytes, width: meta.width ?? null, height: meta.height ?? null, attachment_id: attachment_id, variant: variant.name },
} as any );
} as any);
}
}, {
connection: { url: process.env.REDIS_URL }, concurrency: 3 }
connection: { url: process.env.REDIS_URL }, concurrency: 3
}
);

View File

@ -1,7 +1,7 @@
import { Body, Controller, Get, Param, ParseIntPipe, Patch, Post } from "@nestjs/common";
import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
import { Result } from "src/common/errors/result-error.factory";
import { Modules as ModulesEnum, Prisma } from ".prisma/client";
import { Modules as ModulesEnum, Prisma } from "prisma/postgres/generated/prisma/client/postgres/client";
import { BankCodesService } from "src/time-and-attendance/bank-codes/bank-codes.service";
@Controller('bank-codes')

View File

@ -1,5 +1,5 @@
import { Injectable } from "@nestjs/common";
import { Prisma } from "@prisma/client";
import { Prisma } from "prisma/postgres/generated/prisma/client/postgres/client";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
import { Result } from "src/common/errors/result-error.factory";

View File

@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { Prisma, PrismaClient } from '@prisma/client';
import { Prisma, PrismaClient } from 'prisma/postgres/generated/prisma/client/postgres/client';
import { getWeekStart, getWeekEnd, computeHours } from 'src/common/utils/date-utils';
import { PrismaPostgresService } from 'prisma/postgres/prisma-postgres.service';
import { DAILY_LIMIT_HOURS, WEEKLY_LIMIT_HOURS } from 'src/common/utils/constants.utils';

View File

@ -1,5 +1,5 @@
import { Injectable } from "@nestjs/common";
import { Prisma } from "@prisma/client";
import { Prisma } from "prisma/postgres/generated/prisma/client/postgres/client";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
import { Result } from "src/common/errors/result-error.factory";

View File

@ -2,7 +2,7 @@ import { Controller, Post, Param, Body, Patch, Delete, Req, UnauthorizedExceptio
import { ExpenseDto } from "src/time-and-attendance/expenses/expense-create.dto";
import { Result } from "src/common/errors/result-error.factory";
import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
import { Modules as ModulesEnum } from ".prisma/client";
import { Modules as ModulesEnum } from "prisma/postgres/generated/prisma/client/postgres/client";
import { Access } from "src/common/decorators/module-access.decorators";
import { ExpenseUpdateService } from "src/time-and-attendance/expenses/services/expense-update.service";
import { ExpenseCreateService } from "src/time-and-attendance/expenses/services/expense-create.service";

View File

@ -1,7 +1,7 @@
import { Controller, Get, Param, Query, Res } from "@nestjs/common";
import { CsvExportService } from "./services/csv-exports.service";
import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
import { Modules as ModulesEnum } from "@prisma/client";
import { Modules as ModulesEnum } from "prisma/postgres/generated/prisma/client/postgres/client";
import { Response } from "express";
import { CsvGeneratorService } from "src/time-and-attendance/exports/services/csv-builder.service";

View File

@ -4,7 +4,7 @@ import { PayPeriodsQueryService } from "./services/pay-periods-query.service";
import { PayPeriodsCommandService } from "./services/pay-periods-command.service";
import { Result } from "src/common/errors/result-error.factory";
import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
import { Modules as ModulesEnum } from "@prisma/client";
import { Modules as ModulesEnum } from "prisma/postgres/generated/prisma/client/postgres/client";
import { GetOverviewService } from "src/time-and-attendance/pay-period/services/pay-periods-build-overview.service";
import { map, Observable } from "rxjs";
import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service";

View File

@ -1,4 +1,4 @@
import { PayPeriods } from "@prisma/client";
import { PayPeriods } from "prisma/postgres/generated/prisma/client/postgres/client";
import { PayPeriodDto } from "src/time-and-attendance/pay-period/dtos/overview-pay-period.dto";
const toDateString = (date: Date) => date.toISOString().slice(0, 10); // "YYYY-MM-DD"

View File

@ -2,7 +2,7 @@ import { Injectable } from "@nestjs/common";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
import { Result } from "src/common/errors/result-error.factory";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { Prisma } from "@prisma/client";
import { Prisma } from "prisma/postgres/generated/prisma/client/postgres/client";
//change promise to return result pattern

View File

@ -7,7 +7,7 @@ import { SchedulePresetsGetService } from "src/time-and-attendance/schedule-pres
import { SchedulePresetsDto } from "src/time-and-attendance/schedule-presets/schedule-presets.dto";
import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
import { Modules as ModulesEnum } from "@prisma/client";
import { Modules as ModulesEnum } from "prisma/postgres/generated/prisma/client/postgres/client";
import { Access } from "src/common/decorators/module-access.decorators";
import { SchedulePresetsApplyService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-apply.service";

View File

@ -1,4 +1,4 @@
import { Weekday } from "@prisma/client";
import { Weekday } from "prisma/postgres/generated/prisma/client/postgres/client";
import { ArrayMinSize, IsArray, IsBoolean, IsEnum, IsInt, IsOptional, IsString, Matches} from "class-validator";
import { HH_MM_REGEX } from "src/common/utils/constants.utils";

View File

@ -1,5 +1,5 @@
import { Body, Controller, Delete, Param, Patch, Post } from "@nestjs/common";
import { Modules as ModulesEnum } from "@prisma/client";
import { Modules as ModulesEnum } from "prisma/postgres/generated/prisma/client/postgres/client";
import { ShiftDto } from "src/time-and-attendance/shifts/shift.dto";
import { ShiftsCreateService } from "src/time-and-attendance/shifts/services/shifts-create.service";

View File

@ -1,9 +1,12 @@
import { Injectable, NotFoundException } from "@nestjs/common";
import { Prisma, PrismaClient, Timesheets } from "@prisma/client";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
import { Prisma, Timesheets } from "prisma/postgres/generated/prisma/client/postgres/client";
import { PrismaPostgresService, TransactionClient } from "prisma/postgres/prisma-postgres.service";
import { BaseApprovalService } from "src/common/shared/base-approval.service";
import { timesheet_select } from "src/time-and-attendance/utils/selects.utils";
@Injectable()
export class TimesheetApprovalService extends BaseApprovalService<Timesheets>{
constructor(
@ -17,7 +20,7 @@ import { timesheet_select } from "src/time-and-attendance/utils/selects.utils";
return this.prisma.client.timesheets;
}
protected delegateFor(tx: Prisma.TransactionClient | PrismaClient) {
protected delegateFor(tx: TransactionClient) {
return tx.timesheets;
}
@ -27,13 +30,13 @@ import { timesheet_select } from "src/time-and-attendance/utils/selects.utils";
);
}
async cascadeApprovalWithtx(tx: Prisma.TransactionClient, timesheet_id: number, is_approved: boolean): Promise<Timesheets> {
async cascadeApprovalWithtx(tx: TransactionClient, timesheet_id: number, is_approved: boolean): Promise<Timesheets> {
const timesheet = await this.updateApprovalWithTransaction(tx, timesheet_id, is_approved);
await tx.shifts.updateMany({
where: { timesheet_id: timesheet_id },
data: { is_approved: is_approved },
});
await tx.expenses.updateManyAndReturn({
await tx.expenses.updateMany({
where: { timesheet_id: timesheet_id },
data: { is_approved: is_approved },
});

View File

@ -2,7 +2,7 @@ import { Body, Controller, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Query
import { GetTimesheetsOverviewService } from "src/time-and-attendance/timesheets/services/timesheet-employee-overview.service";
import { TimesheetApprovalService } from "src/time-and-attendance/timesheets/services/timesheet-approval.service";
import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
import { Modules as ModulesEnum } from "@prisma/client";
import { Modules as ModulesEnum } from "prisma/postgres/generated/prisma/client/postgres/client";
import { Access } from "src/common/decorators/module-access.decorators";

View File

@ -1,5 +1,5 @@
import { Prisma } from "@prisma/client";
import { Prisma } from "prisma/postgres/generated/prisma/client/postgres/client";
import { toDateFromString, sevenDaysFrom, toStringFromDate, toHHmmFromDate } from "src/common/utils/date-utils";
import { Timesheet } from "src/time-and-attendance/timesheets/timesheet.dto";

View File

@ -1,4 +1,4 @@
import { Prisma } from "@prisma/client";
import { Prisma } from "prisma/postgres/generated/prisma/client/postgres/client";
export const expense_select = {