diff --git a/src/modules/analytics/infrastructure/prisma-analytics-reader.ts b/src/modules/analytics/infrastructure/prisma-analytics-reader.ts index b84f876..9ddac40 100644 --- a/src/modules/analytics/infrastructure/prisma-analytics-reader.ts +++ b/src/modules/analytics/infrastructure/prisma-analytics-reader.ts @@ -1,4 +1,5 @@ import type { PrismaClient } from '@prisma/client'; +import { decimalToBigInt } from '../../../shared/database/index.js'; import type { AnalyticsReader } from '../domain/index.js'; /** @@ -18,10 +19,7 @@ export function createPrismaAnalyticsReader(prisma: PrismaClient): AnalyticsRead }); return grouped.map((row) => ({ token: row.token, - // See src/modules/escrow/infrastructure/prisma-escrow-repository.ts's - // comment: Decimal.toString() switches to exponential notation past - // 21 digits, which BigInt() can't parse — .toFixed() can't. - releasedAmount: BigInt(row._sum.amount?.toFixed() ?? '0'), + releasedAmount: row._sum.amount === null ? 0n : decimalToBigInt(row._sum.amount), releasedCount: row._count._all, })); }, diff --git a/src/modules/escrow/infrastructure/prisma-escrow-repository.ts b/src/modules/escrow/infrastructure/prisma-escrow-repository.ts index 0c4c4d6..e21a971 100644 --- a/src/modules/escrow/infrastructure/prisma-escrow-repository.ts +++ b/src/modules/escrow/infrastructure/prisma-escrow-repository.ts @@ -1,4 +1,5 @@ import type { Escrow as PrismaEscrow, PrismaClient } from '@prisma/client'; +import { decimalToBigInt } from '../../../shared/database/index.js'; import type { Escrow, EscrowRepository } from '../domain/index.js'; function toDomain(record: PrismaEscrow): Escrow { @@ -9,12 +10,8 @@ function toDomain(record: PrismaEscrow): Escrow { recipientAddress: record.recipientAddress, driverAddress: record.driverAddress, token: record.token, - // Prisma's Decimal (decimal.js) switches to exponential notation past 21 - // digits by default — `.toString()` on an i128::MAX-sized value (39 - // digits) yields "1.7...e+38", which BigInt() can't parse. `.toFixed()` - // always returns a plain fixed-point string regardless of magnitude. - amount: BigInt(record.amount.toFixed()), - platformFee: record.platformFee === null ? null : BigInt(record.platformFee.toFixed()), + amount: decimalToBigInt(record.amount), + platformFee: record.platformFee === null ? null : decimalToBigInt(record.platformFee), status: record.status, disputedBy: record.disputedBy, disputedAt: record.disputedAt, diff --git a/src/shared/database/decimal.spec.ts b/src/shared/database/decimal.spec.ts new file mode 100644 index 0000000..283776e --- /dev/null +++ b/src/shared/database/decimal.spec.ts @@ -0,0 +1,24 @@ +import { Prisma } from '@prisma/client'; +import { describe, expect, it } from 'vitest'; +import { decimalToBigInt } from './decimal.js'; + +describe('decimalToBigInt', () => { + it('converts a small integer Decimal', () => { + expect(decimalToBigInt(new Prisma.Decimal('42'))).toBe(42n); + }); + + it('converts zero', () => { + expect(decimalToBigInt(new Prisma.Decimal(0))).toBe(0n); + }); + + it('converts a negative value', () => { + expect(decimalToBigInt(new Prisma.Decimal('-123456789'))).toBe(-123456789n); + }); + + it('converts an i128::MAX-sized value that .toString() would render exponentially', () => { + // The bug this helper guards against: Decimal.toString() on a 39-digit + // value yields "1.7014118346046923e+38", which BigInt() can't parse. + const i128Max = '170141183460469231731687303715884105727'; + expect(decimalToBigInt(new Prisma.Decimal(i128Max))).toBe(BigInt(i128Max)); + }); +}); diff --git a/src/shared/database/decimal.ts b/src/shared/database/decimal.ts new file mode 100644 index 0000000..c3d7c62 --- /dev/null +++ b/src/shared/database/decimal.ts @@ -0,0 +1,15 @@ +import type { Prisma } from '@prisma/client'; + +/** + * Converts a Prisma `Decimal` to a `BigInt`. + * + * Prisma's Decimal (decimal.js) switches to exponential notation past 21 + * significant digits by default — `.toString()` on an i128::MAX-sized amount + * (39 digits; the columns are `Decimal(39, 0)`, see prisma/schema.prisma) + * yields "1.7...e+38", which `BigInt()` can't parse. `.toFixed()` always + * returns a plain fixed-point string regardless of magnitude, so Decimal + * amounts must be converted to BigInt via `.toFixed()`, never `.toString()`. + */ +export function decimalToBigInt(value: Prisma.Decimal): bigint { + return BigInt(value.toFixed()); +} diff --git a/src/shared/database/index.ts b/src/shared/database/index.ts index 0846d5f..394046d 100644 --- a/src/shared/database/index.ts +++ b/src/shared/database/index.ts @@ -1 +1,2 @@ export { getPrismaClient, disconnectPrisma } from './prisma-client.js'; +export { decimalToBigInt } from './decimal.js';