util.ts 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. import chalk from 'chalk'
  2. import type { Code } from 'mdast'
  3. import { fromMarkdown } from 'mdast-util-from-markdown'
  4. import { format } from 'sql-formatter'
  5. declare global {
  6. interface ReadableStream<R = any> {
  7. [Symbol.asyncIterator](): AsyncIterableIterator<R>
  8. }
  9. }
  10. /**
  11. * Formats Postgres SQL into a consistent format.
  12. *
  13. * @returns The formatted SQL.
  14. */
  15. export const formatSql = (sql: string) =>
  16. format(sql, { language: 'postgresql', keywordCase: 'lower' })
  17. /**
  18. * Collects an `ArrayBuffer` stream into a single decoded string.
  19. *
  20. * @returns A single string combining all the decoded stream chunks.
  21. */
  22. export async function collectStream<R extends BufferSource>(stream: ReadableStream<R>) {
  23. const textDecoderStream = new TextDecoderStream()
  24. let content = ''
  25. for await (const chunk of stream.pipeThrough(textDecoderStream)) {
  26. const text = chunk.split('0:')[1]
  27. content += text.slice(1, text.length - 2)
  28. }
  29. return content.replaceAll('\\n', '\n').replaceAll('\\"', '"')
  30. }
  31. /**
  32. * Parses markdown and extracts all SQL code blocks.
  33. *
  34. * @returns An array of string content from each SQL code block.
  35. */
  36. export function extractMarkdownSql(markdown: string) {
  37. const mdTree = fromMarkdown(markdown)
  38. return mdTree.children
  39. .filter((node): node is Code => node.type === 'code' && node.lang === 'sql')
  40. .map(({ value }) => value)
  41. }
  42. /**
  43. * Prints the provided metadata along with any assertion errors.
  44. * Works both synchronously and asynchronously.
  45. *
  46. * Useful for providing extra context for failed tests.
  47. */
  48. export function withMetadata<T extends void | Promise<void>>(
  49. metadata: Record<string, string>,
  50. fn: () => T
  51. ): T {
  52. /**
  53. * Prepends metadata to an Error's stack trace.
  54. */
  55. function modifyError(err: unknown) {
  56. if (err instanceof Error && err.stack) {
  57. const formattedMetadata = Object.entries(metadata).map(
  58. ([key, value]) => `${chalk.bold.dim(key)}:\n\n${chalk.green.dim(value)}`
  59. )
  60. err.stack = `${formattedMetadata.join('\n\n')}\n\n${err.stack}`
  61. }
  62. return err
  63. }
  64. // Execute the function and handle both
  65. // synchronous or asynchronous scenarios
  66. try {
  67. const maybePromise = fn()
  68. if (maybePromise instanceof Promise) {
  69. return maybePromise.catch((err) => {
  70. // Re-throw the error
  71. throw modifyError(err)
  72. }) as T
  73. }
  74. return maybePromise
  75. } catch (err) {
  76. // Re-throw the error
  77. throw modifyError(err)
  78. }
  79. }