/** * TailwindSQL DB Component * * A React Server Component that parses Tailwind-style class names, * executes SQL queries, and renders the results. * * Usage: * * * * * With JOIN: * * * */ import { parseClassNames, JoinConfig } from '@/lib/parser'; import { buildQuery } from '@/lib/query-builder'; import db from '@/lib/db'; import { ReactNode, ReactElement, Children, isValidElement } from 'react'; import { JoinProps } from './Join'; type RenderAs = 'span' | 'div' | 'ul' | 'ol' | 'table' | 'json' | 'code'; interface DBProps { className: string; as?: RenderAs; style?: React.CSSProperties; children?: ReactNode; } function parseJoinChild(props: JoinProps): JoinConfig { const [parentColumn, childColumn] = props.on.split('-'); const columns = props.select ? props.select.split(',').map(c => c.trim()) : []; const typeMap = { inner: 'INNER' as const, left: 'LEFT' as const, right: 'RIGHT' as const, }; return { table: props.table, parentColumn: parentColumn || 'id', childColumn: childColumn || `${props.table}_id`, columns, type: typeMap[props.type || 'left'], }; } export async function DB({ className, as = 'span', style, children }: DBProps): Promise { // Parse the className to extract query config const config = parseClassNames(className); if (!config) { return ( ⚠️ Invalid TailwindSQL class: {className} ); } // Extract Join children const joins: JoinConfig[] = []; Children.forEach(children, (child) => { if (isValidElement(child) && (child.type as { name?: string }).name === 'Join') { const joinElement = child as ReactElement; joins.push(parseJoinChild(joinElement.props)); } }); if (joins.length > 0) { config.joins = joins; } try { // Build the SQL query const { sql, params } = buildQuery(config); // Execute the query const stmt = db.prepare(sql); const results = stmt.all(...params) as Record[]; // Determine columns to display (including join columns) let displayColumns = [...config.columns]; if (config.joins) { for (const join of config.joins) { displayColumns.push(...join.columns); } } // Render the results return renderResults(results, displayColumns, as, style); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return ( 🔥 Query error: {errorMessage} ); } } function renderResults( results: Record[], columns: string[], as: RenderAs, style?: React.CSSProperties ): JSX.Element { if (results.length === 0) { return ( No results ); } // If selecting a single column and single row, just return the value if (columns.length === 1 && results.length === 1) { const value = results[0][columns[0]]; return ( {String(value)} ); } // If selecting a single column with multiple rows if (columns.length === 1) { const values = results.map(row => row[columns[0]]); switch (as) { case 'ul': return (
    {values.map((v, i) => (
  • {String(v)}
  • ))}
); case 'ol': return (
    {values.map((v, i) => (
  1. {String(v)}
  2. ))}
); case 'json': case 'code': return ( {JSON.stringify(values, null, 2)} ); default: return ( {values.map(String).join(', ')} ); } } // Multiple columns - render as table or JSON switch (as) { case 'table': const headers = columns.length > 0 ? columns : Object.keys(results[0]); return (
{headers.map((h) => ( ))} {results.map((row, i) => ( {headers.map((h) => ( ))} ))}
{h}
{String(row[h] ?? '')}
); case 'json': case 'code': return ( {JSON.stringify(results, null, 2)} ); case 'ul': return (
    {results.map((row, i) => (
  • {JSON.stringify(row)}
  • ))}
); case 'ol': return (
    {results.map((row, i) => (
  1. {JSON.stringify(row)}
  2. ))}
); default: // Default: show first row's values comma-separated const allHeaders = columns.length > 0 ? columns : Object.keys(results[0]); return (
{results.map((row, i) => (
{allHeaders.map(h => String(row[h] ?? '')).join(', ')}
))}
); } } export default DB;