query.ts 2.1 KB

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