Files
supabase/packages/ai-commands/test/util.ts
Greg RichardsonandIvan Vasilov 801ad65603 RLS AI improvements (#26895)
* chore: libpg-query types

* feat: improved rls ai tests

* feat: simplify assertitions for sql expressions

* refactor: split ai logic into separate files

* feat(rls-ai): test select, insert, update, delete policies

* feat: adds withMetadata wrapper for failed tests

* chore: remove custom libpg-query types

* feat(rls-ai): make tests concurrent

* fix(rls-ai): rules for 'using' and 'with check'

* feat(rls-ai): discourages restrictive policies

* feat(ai): makes withMetadata util sync or async

* refactor(ai): moves withMetadata to generic test utils

* feat(rls-ai): test joined table

* feat(rls-ai): test mfa

* chore(ai): make all tests concurrent

* fix(ai): chatSql function rename in studio

* refactor(ai): isolate logic into separate files

* chore(ai): remove trailing comma in tsconfig

* chore(ai): remove unused dep

* Reset some of the package versions to the versions from master.

* chore(ai): remove unused imports

---------

Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
2024-06-05 14:33:56 +02:00

91 lines
2.3 KiB
TypeScript

import chalk from 'chalk'
import { fromMarkdown } from 'mdast-util-from-markdown'
import type { Code } from 'mdast-util-from-markdown/lib'
import { format } from 'sql-formatter'
declare global {
interface ReadableStream<R = any> {
[Symbol.asyncIterator](): AsyncIterableIterator<R>
}
}
/**
* Formats Postgres SQL into a consistent format.
*
* @returns The formatted SQL.
*/
export const formatSql = (sql: string) =>
format(sql, { language: 'postgresql', keywordCase: 'lower' })
/**
* Collects an `ArrayBuffer` stream into a single decoded string.
*
* @returns A single string combining all the decoded stream chunks.
*/
export async function collectStream<R extends BufferSource>(stream: ReadableStream<R>) {
const textDecoderStream = new TextDecoderStream()
let content = ''
for await (const chunk of stream.pipeThrough(textDecoderStream)) {
content += chunk
}
return content
}
/**
* Parses markdown and extracts all SQL code blocks.
*
* @returns An array of string content from each SQL code block.
*/
export function extractMarkdownSql(markdown: string) {
const mdTree = fromMarkdown(markdown)
return mdTree.children
.filter((node): node is Code => node.type === 'code' && node.lang === 'sql')
.map(({ value }) => value)
}
/**
* Prints the provided metadata along with any assertion errors.
* Works both synchronously and asynchronously.
*
* Useful for providing extra context for failed tests.
*/
export function withMetadata<T extends void | Promise<void>>(
metadata: Record<string, string>,
fn: () => T
): T {
/**
* Prepends metadata to an Error's stack trace.
*/
function modifyError(err: unknown) {
if (err instanceof Error && err.stack) {
const formattedMetadata = Object.entries(metadata).map(
([key, value]) => `${chalk.bold.dim(key)}:\n\n${chalk.green.dim(value)}`
)
err.stack = `${formattedMetadata.join('\n\n')}\n\n${err.stack}`
}
return err
}
// Execute the function and handle both
// synchronous or asynchronous scenarios
try {
const maybePromise = fn()
if (maybePromise instanceof Promise) {
return maybePromise.catch((err) => {
// Re-throw the error
throw modifyError(err)
}) as T
}
return maybePromise
} catch (err) {
// Re-throw the error
throw modifyError(err)
}
}