Files
payload/packages/db-postgres/src/connect.ts
Adler Weber da7be35a15 feat(db-postgres): dependency inject pg to allow Sentry instrumentation (#11478)
### What?

I changed the interface of `@payloadcms/db-postgres` to allow a user to
(optionally) inject their own `pg` module.

### Why?

I noticed that `@payloadcms/sentry-plugin` wasn't instrumenting
Payload's database queries through the [local payload
API](https://payloadcms.com/docs/local-api/overview):


![image](https://github.com/user-attachments/assets/425691f5-cf7e-4625-89e0-6d07dda9cbc0)

This is because Sentry applies a patch to the `pg` driver on import. For
whatever reason, it doesn't patch `pg` when imported by dependencies
(e.g. `@payloadcms/db-postgres`). After applying this fix, I can see the
underlying query traces!


![image](https://github.com/user-attachments/assets/fb6f9aef-13d9-41b1-b4cc-36c565d15930)
2025-04-14 15:27:53 -04:00

122 lines
3.2 KiB
TypeScript

import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
import type { Connect, Migration, Payload } from 'payload'
import { pushDevSchema } from '@payloadcms/drizzle'
import { drizzle } from 'drizzle-orm/node-postgres'
import type { PostgresAdapter } from './types.js'
const connectWithReconnect = async function ({
adapter,
payload,
reconnect = false,
}: {
adapter: PostgresAdapter
payload: Payload
reconnect?: boolean
}) {
let result
if (!reconnect) {
result = await adapter.pool.connect()
} else {
try {
result = await adapter.pool.connect()
} catch (ignore) {
setTimeout(() => {
payload.logger.info('Reconnecting to postgres')
void connectWithReconnect({ adapter, payload, reconnect: true })
}, 1000)
}
}
if (!result) {
return
}
result.prependListener('error', (err) => {
try {
if (err.code === 'ECONNRESET') {
void connectWithReconnect({ adapter, payload, reconnect: true })
}
} catch (ignore) {
// swallow error
}
})
}
export const connect: Connect = async function connect(
this: PostgresAdapter,
options = {
hotReload: false,
},
) {
const { hotReload } = options
this.schema = {
pgSchema: this.pgSchema,
...this.tables,
...this.relations,
...this.enums,
}
try {
if (!this.pool) {
this.pool = new this.pg.Pool(this.poolOptions)
await connectWithReconnect({ adapter: this, payload: this.payload })
}
const logger = this.logger || false
this.drizzle = drizzle({ client: this.pool, logger, schema: this.schema })
if (!hotReload) {
if (process.env.PAYLOAD_DROP_DATABASE === 'true') {
this.payload.logger.info(`---- DROPPING TABLES SCHEMA(${this.schemaName || 'public'}) ----`)
await this.dropDatabase({ adapter: this })
this.payload.logger.info('---- DROPPED TABLES ----')
}
}
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error))
if (err.message?.match(/database .* does not exist/i) && !this.disableCreateDatabase) {
// capitalize first char of the err msg
this.payload.logger.info(
`${err.message.charAt(0).toUpperCase() + err.message.slice(1)}, creating...`,
)
const isCreated = await this.createDatabase()
if (isCreated && this.connect) {
await this.connect(options)
return
}
} else {
this.payload.logger.error({
err,
msg: `Error: cannot connect to Postgres. Details: ${err.message}`,
})
}
if (typeof this.rejectInitializing === 'function') {
this.rejectInitializing()
}
process.exit(1)
}
await this.createExtensions()
// Only push schema if not in production
if (
process.env.NODE_ENV !== 'production' &&
process.env.PAYLOAD_MIGRATING !== 'true' &&
this.push !== false
) {
await pushDevSchema(this as unknown as DrizzleAdapter)
}
if (typeof this.resolveInitializing === 'function') {
this.resolveInitializing()
}
if (process.env.NODE_ENV === 'production' && this.prodMigrations) {
await this.migrate({ migrations: this.prodMigrations as unknown as Migration[] })
}
}