sandbox.core.ts 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. import { PGliteWorker } from '@electric-sql/pglite/worker'
  2. import { SANDBOX_SETUP_STATEMENTS } from './sandbox.constants'
  3. import { applySchema, applySeed } from './sandbox.utils'
  4. import { type DatabaseSchemaDDLData } from '@/data/rls-tester/get-schema-ddl'
  5. import { type TableSeedData } from '@/data/rls-tester/get-seed-data'
  6. import { getErrorMessage } from '@/lib/get-error-message'
  7. type RLSTestResult = Record<string, unknown>[]
  8. export interface SandboxCore {
  9. setSchema(data: DatabaseSchemaDDLData): Promise<void>
  10. setSeed(tables: TableSeedData[]): Promise<void>
  11. destroy(): Promise<void>
  12. run: (props: { sql: string }) => Promise<{ result: RLSTestResult }>
  13. }
  14. let instance: SandboxCore | null = null
  15. let initPromise: Promise<SandboxCore> | null = null
  16. export const getSandboxCore = async () => {
  17. if (instance) return instance
  18. if (!initPromise) {
  19. initPromise = boot().finally(() => {
  20. initPromise = null
  21. })
  22. }
  23. return initPromise
  24. }
  25. const boot = async (): Promise<SandboxCore> => {
  26. const webWorker = new Worker(new URL('./pglite.worker.ts', import.meta.url), { type: 'module' })
  27. const pg = await PGliteWorker.create(webWorker)
  28. for (const sql of SANDBOX_SETUP_STATEMENTS) {
  29. try {
  30. await pg.exec(sql)
  31. } catch (err) {
  32. console.warn('[Postgres sandbox] setup:', (err as Error).message, `— ${sql.slice(0, 60)}`)
  33. }
  34. }
  35. function makeExecutor() {
  36. return { execSql: (sql: string) => pg.exec(sql).then(() => undefined as void) }
  37. }
  38. async function setSchema(data: DatabaseSchemaDDLData): Promise<void> {
  39. await applySchema(makeExecutor(), data)
  40. }
  41. async function setSeed(tables: TableSeedData[]): Promise<void> {
  42. await applySeed(makeExecutor(), tables)
  43. }
  44. const run = async ({ sql }: { sql: string }) => {
  45. try {
  46. // [Joshen] First 2 results will be from role impersonation, the actual result from the
  47. // query will be returned as the 3rd result.
  48. const results = await pg.exec(sql)
  49. return { result: results[2].rows ?? [] }
  50. } catch (error) {
  51. await pg.exec('ROLLBACK').catch(() => {})
  52. throw error instanceof Error ? error : new Error(getErrorMessage(error) ?? String(error))
  53. }
  54. }
  55. const destroy = async () => {
  56. webWorker.terminate()
  57. instance = null
  58. }
  59. instance = { run, destroy, setSchema, setSeed }
  60. return instance
  61. }