query.ts68 lines · main
| 1 | import * as Sentry from '@sentry/nextjs' |
| 2 | |
| 3 | import { constructHeaders } from '../apiHelpers' |
| 4 | import { databaseErrorSchema, PgMetaDatabaseError, WrappedResult } from './types' |
| 5 | import { assertSelfHosted, encryptString, getConnectionString } from './util' |
| 6 | import { PG_META_URL } from '@/lib/constants/index' |
| 7 | |
| 8 | export 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 | */ |
| 20 | export 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 | } |