query.ts68 lines · main
1import * as Sentry from '@sentry/nextjs'
2
3import { constructHeaders } from '../apiHelpers'
4import { databaseErrorSchema, PgMetaDatabaseError, WrappedResult } from './types'
5import { assertSelfHosted, encryptString, getConnectionString } from './util'
6import { PG_META_URL } from '@/lib/constants/index'
7
8export type QueryOptions = {
9 query: string
10 parameters?: unknown[]
11 readOnly?: boolean
12 headers?: HeadersInit
13}
14
15/**
16 * Executes a SQL query against the self-hosted Postgres instance via pg-meta service.
17 *
18 * _Only call this from server-side self-hosted code._
19 */
20export async function executeQuery<T = unknown>({
21 query,
22 parameters,
23 readOnly = false,
24 headers,
25}: QueryOptions): Promise<WrappedResult<T[]>> {
26 assertSelfHosted()
27
28 const connectionString = getConnectionString({ readOnly })
29 const connectionStringEncrypted = encryptString(connectionString)
30
31 const requestBody: { query: string; parameters?: unknown[] } = { query }
32 if (parameters !== undefined) {
33 requestBody.parameters = parameters
34 }
35
36 return await Sentry.startSpan({ name: 'pg-meta.query', op: 'db.query' }, async (span) => {
37 const response = await fetch(`${PG_META_URL}/query`, {
38 method: 'POST',
39 headers: constructHeaders({
40 ...headers,
41 'Content-Type': 'application/json',
42 'x-connection-encrypted': connectionStringEncrypted,
43 }),
44 body: JSON.stringify(requestBody),
45 })
46
47 try {
48 const result = await response.json()
49
50 if (!response.ok) {
51 const { message, code, formattedError } = databaseErrorSchema.parse(result)
52 span.setAttribute('db.error', 1)
53 span.setAttribute('db.status_code', response.status)
54 const error = new PgMetaDatabaseError(message, code, response.status, formattedError)
55 return { data: undefined, error }
56 }
57
58 span.setAttribute('db.status_code', response.status)
59 return { data: result, error: undefined }
60 } catch (error) {
61 span.setAttribute('db.error', 1)
62 if (error instanceof Error) {
63 return { data: undefined, error }
64 }
65 throw error
66 }
67 })
68}