Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 2 additions & 2 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
"date-fns": "^2.29.2",
"dotenv": "^12.0.4",
"dotenv-expand": "^11.0.6",
"drizzle-orm": "^0.31.2",
"drizzle-orm": "^0.45.2",
"exponential-backoff": "^3.1.1",
"hono": "4.6.12",
"http-assert": "^1.5.0",
Expand Down Expand Up @@ -128,7 +128,7 @@
"@types/tar": "^6.1.13",
"@typescript-eslint/eslint-plugin": "^7.12.0",
"@vitest/coverage-v8": "^4.1.5",
"drizzle-kit": "^0.22.7",
"drizzle-kit": "^0.31.10",
"eslint": "^8.57.0",
"eslint-config-next": "^15.5.18",
"eslint-plugin-simple-import-sort": "^12.1.0",
Expand Down
15 changes: 11 additions & 4 deletions apps/api/src/core/repositories/base.repository.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { AnyAbility } from "@casl/ability";
import type { DBQueryConfig } from "drizzle-orm";
import { and, eq, inArray, isNull, sql } from "drizzle-orm";
import type { PgTableWithColumns } from "drizzle-orm/pg-core";
import type { PgTable, PgTableWithColumns } from "drizzle-orm/pg-core";
import type { SQL } from "drizzle-orm/sql/sql";
import { PostgresError } from "postgres";

Expand Down Expand Up @@ -93,7 +93,7 @@ export abstract class BaseRepository<
const items: T["$inferSelect"][] | undefined = await this.txManager
.getPgTx()
?.select()
.from(this.table)
.from(this.table as PgTable)
.where(this.queryToWhere(query))
.limit(1)
.for("update");
Expand Down Expand Up @@ -187,7 +187,7 @@ export abstract class BaseRepository<
async count(query?: Partial<Output>): Promise<number> {
const [result] = await this.cursor
.select({ count: sql<number>`count(*)::int` })
.from(this.table)
.from(this.table as PgTable)
.where(this.queryToWhere(query));
return result?.count ?? 0;
}
Expand Down Expand Up @@ -242,6 +242,13 @@ type TableNameInSchema<T extends PgTableWithColumns<any>> = {
}[TableName<T>];

const UNIQUE_VIOLATION_CODE = "23505";
// drizzle-orm >=0.44 wraps driver errors in DrizzleQueryError; the underlying PostgresError lives on .cause
export function getPostgresError(error: unknown): PostgresError | undefined {
if (error instanceof PostgresError) return error;
if (error instanceof Error && error.cause instanceof PostgresError) return error.cause;
return undefined;
}

export function isUniqueViolation(error: unknown): error is PostgresError {
return error instanceof PostgresError && error.code === UNIQUE_VIOLATION_CODE;
return getPostgresError(error)?.code === UNIQUE_VIOLATION_CODE;
}
4 changes: 2 additions & 2 deletions apps/api/src/user/services/user/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { singleton } from "tsyringe";
import { Auth0Service } from "@src/auth/services/auth0/auth0.service";
import { EmailVerificationCodeService } from "@src/auth/services/email-verification-code/email-verification-code.service";
import { LoggerService } from "@src/core/providers/logging.provider";
import { isUniqueViolation } from "@src/core/repositories/base.repository";
import { getPostgresError, isUniqueViolation } from "@src/core/repositories/base.repository";
import { AnalyticsService } from "@src/core/services/analytics/analytics.service";
import { NotificationService } from "@src/notifications/services/notification/notification.service";
import { UserInput, type UserOutput, UserRepository } from "../../repositories/user/user.repository";
Expand Down Expand Up @@ -89,7 +89,7 @@ export class UserService {
try {
return await this.userRepository.upsertOnExternalIdConflict(userDetails);
} catch (error) {
if (userDetails.username && isUniqueViolation(error) && error.constraint_name?.includes("username") && attempt < 10) {
if (userDetails.username && isUniqueViolation(error) && getPostgresError(error)?.constraint_name?.includes("username") && attempt < 10) {
return this.upsertUser(
{
...userDetails,
Expand Down
4 changes: 2 additions & 2 deletions apps/indexer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@
"@types/semver": "^7.5.6",
"@types/validator": "^13.7.8",
"@typescript-eslint/eslint-plugin": "^7.12.0",
"drizzle-kit": "^0.31.0",
"drizzle-orm": "^0.42.0",
"drizzle-kit": "^0.31.10",
"drizzle-orm": "^0.45.2",
"eslint": "^8.57.0",
"eslint-config-next": "^15.5.18",
"eslint-plugin-simple-import-sort": "^12.1.0",
Expand Down
4 changes: 2 additions & 2 deletions apps/notifications/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@
"@ucast/js": "^3.0.4",
"cockatiel": "^3.2.1",
"date-fns": "^4.1.0",
"drizzle-kit": "^0.30.5",
"drizzle-orm": "^0.41.0",
"drizzle-kit": "^0.31.10",
"drizzle-orm": "^0.45.2",
"exponential-backoff": "^3.1.2",
"express": "^4.13.1",
"handlebars": "^4.7.8",
Expand Down
4 changes: 2 additions & 2 deletions apps/provider-inventory/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"async-sema": "^3.1.1",
"cockatiel": "^3.2.1",
"dataloader": "^2.2.3",
"drizzle-orm": "^0.31.2",
"drizzle-orm": "^0.45.2",
"hono": "4.6.12",
"http-errors": "^2.0.0",
"lodash": "^4.17.21",
Expand All @@ -56,7 +56,7 @@
"@vitest/coverage-v8": "^4.0.0",
"dotenv": "^16.5.0",
"dotenv-expand": "^12.0.2",
"drizzle-kit": "^0.22.7",
"drizzle-kit": "^0.31.10",
"eslint": "^8.57.0",
"eslint-plugin-simple-import-sort": "^12.1.0",
"prettier": "^3.3.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ export const jsonbBigint = customType<{ data: unknown; driverData: unknown }>({
return "jsonb";
},
toDriver(value) {
// pass value as is because then it's handled by postgres.js jsonb serializer
// Pass through: the postgres.js client serializes jsonb via serializeJsonb (see postgres.provider.ts),
// which preserves bigints. drizzle.provider re-asserts that serializer after drizzle clobbers it.
return value;
},
fromDriver(value) {
// postgres.js returns parsed JSON
// postgres.js parses jsonb via parseJsonb (see postgres.provider.ts); value is already parsed.
return value;
}
});
18 changes: 16 additions & 2 deletions apps/provider-inventory/src/providers/drizzle.provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,33 @@ import { drizzle } from "drizzle-orm/postgres-js";
import type { InjectionToken } from "tsyringe";
import { container, instancePerContainerCachingFactory } from "tsyringe";

import { parseJsonb, serializeJsonb } from "@src/lib/jsonb-bigint/jsonb-bigint";
import { APP_CONFIG } from "@src/providers/app-config.provider";
import { LOGGER_FACTORY } from "@src/providers/logger-factory.provider";
import { PG_CLIENT } from "@src/providers/postgres.provider";
import { JSON_OIDS, PG_CLIENT } from "@src/providers/postgres.provider";
import * as modelsSchema from "../model-schemas";

export const DRIZZLE_DB: InjectionToken<PostgresJsDatabase> = Symbol("DRIZZLE_DB");

container.register(DRIZZLE_DB, {
useFactory: instancePerContainerCachingFactory(c => {
const config = c.resolve(APP_CONFIG);
return drizzle(c.resolve(PG_CLIENT), {
const client = c.resolve(PG_CLIENT);
const db = drizzle(client, {
logger: new PostgresLoggerService(c.resolve(LOGGER_FACTORY), { useFormat: config.SQL_LOG_FORMAT === "pretty" }),
schema: modelsSchema
});

// drizzle 0.45's construct() clobbers the shared client's json/jsonb serializers with a passthrough.
// Re-assert ours so bigint-preserving (de)serialization stays a postgres.js client concern for BOTH
// the ORM (jsonbBigint passes through to here) and raw `sql` queries (e.g. bid-screening's sql.json).
// NOTE: this means jsonb columns must use the jsonbBigint customType (passthrough), never drizzle's
// native jsonb(), which would double-encode by stringifying in toDriver before this serializer runs.
for (const oid of JSON_OIDS) {
client.options.serializers[oid] = serializeJsonb;
client.options.parsers[oid] = parseJsonb;
}

return db;
})
});
8 changes: 6 additions & 2 deletions apps/provider-inventory/src/providers/postgres.provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ const logger = container.resolve(LOGGER_FACTORY)({ context: "POSTGRES" });
export type Database = postgres.Sql<{ bigint: bigint }>;
export const PG_CLIENT = Symbol("APP_PG_CLIENT") as InjectionToken<Database>;

// json (OID 114) and jsonb (OID 3802). drizzle's construct() overrides the serializers for both
// with a passthrough on the shared client, expecting column types to own serialization.
export const JSON_OIDS = [114, 3802];

container.register(PG_CLIENT, {
useFactory: instancePerContainerCachingFactory(c => {
const config = c.resolve(APP_CONFIG);
Expand All @@ -25,8 +29,8 @@ container.register(PG_CLIENT, {
types: {
bigint: postgres.BigInt,
json: {
to: 114, // OID 114 = json
from: [114, 3802], // 114 = json, 3802 = jsonb
to: 114,
from: JSON_OIDS,
serialize: serializeJsonb,
parse: parseJsonb
}
Expand Down
Loading
Loading