{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "storage-mysql",
  "title": "CodeRabbit MySQL Adapter",
  "author": "Ray <hello@ramonclaudio.com>",
  "description": "MySQL/MariaDB adapter with connection pooling. Works with PlanetScale, AWS RDS, Google Cloud SQL, Azure, etc.",
  "dependencies": [
    "mysql2",
    "@types/mysql2"
  ],
  "registryDependencies": [
    "https://coderabbit-shadcn-registry.vercel.app/r/types.json",
    "https://coderabbit-shadcn-registry.vercel.app/r/storage-adapter.json"
  ],
  "files": [
    {
      "path": "registry/default/lib/storage-mysql.ts",
      "content": "import type {\n  ReportStorageAdapter,\n  ListReportsResponse,\n} from '@/registry/default/lib/storage-adapter'\nimport type {\n  StoredReport,\n  ReportStatus,\n  ReportResult,\n  FilterConfig,\n} from '@/registry/default/lib/types'\nimport type { Pool, RowDataPacket } from 'mysql2/promise'\n\nexport interface MySQLStorageConfig {\n  pool: Pool\n  tableName?: string\n}\n\n/**\n * Database row type matching the MySQL schema (snake_case columns)\n */\ninterface DatabaseReportRow extends RowDataPacket {\n  id: string\n  status: ReportStatus\n  from_date: string\n  to_date: string\n  prompt_template: string | null\n  custom_prompt: string | null\n  group_by: string | null\n  subgroup_by: string | null\n  org_id: string | null\n  parameters: FilterConfig[] | null\n  results: ReportResult[] | null\n  error: string | null\n  duration_ms: number | null\n  created_at: Date\n  user_id: string | null\n}\n\ninterface CountRow extends RowDataPacket {\n  count: number\n}\n\n/**\n * MySQL storage adapter for CodeRabbit reports\n *\n * @example\n * ```typescript\n * import mysql from 'mysql2/promise'\n * import { MySQLStorageAdapter } from '@/registry/default/lib/storage-mysql'\n *\n * const pool = mysql.createPool({\n *   host: process.env.MYSQL_HOST,\n *   port: parseInt(process.env.MYSQL_PORT || '3306'),\n *   database: process.env.MYSQL_DATABASE,\n *   user: process.env.MYSQL_USER,\n *   password: process.env.MYSQL_PASSWORD,\n *   waitForConnections: true,\n *   connectionLimit: 10,\n *   queueLimit: 0\n * })\n *\n * const storage = new MySQLStorageAdapter({ pool })\n * ```\n *\n * Database schema:\n * ```sql\n * CREATE TABLE coderabbit_reports (\n *   id CHAR(36) PRIMARY KEY DEFAULT (UUID()),\n *   status ENUM('pending', 'completed', 'failed') NOT NULL,\n *   from_date VARCHAR(255) NOT NULL,\n *   to_date VARCHAR(255) NOT NULL,\n *   prompt_template VARCHAR(255),\n *   custom_prompt TEXT,\n *   group_by VARCHAR(50),\n *   subgroup_by VARCHAR(50),\n *   org_id VARCHAR(255),\n *   parameters JSON,\n *   results JSON,\n *   error TEXT,\n *   duration_ms INT,\n *   created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n *   user_id VARCHAR(255),\n *   INDEX idx_status (status),\n *   INDEX idx_user_id (user_id),\n *   INDEX idx_created_at (created_at DESC)\n * ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;\n * ```\n */\nexport class MySQLStorageAdapter implements ReportStorageAdapter {\n  private pool: Pool\n  private tableName: string\n\n  constructor(config: MySQLStorageConfig) {\n    this.pool = config.pool\n    this.tableName = config.tableName || 'coderabbit_reports'\n  }\n\n  async create(data: Omit<StoredReport, 'id' | 'createdAt'>): Promise<string> {\n    const query = `\n      INSERT INTO ${this.tableName} (\n        id, status, from_date, to_date, prompt_template, custom_prompt,\n        group_by, subgroup_by, org_id, parameters, results, error, duration_ms\n      )\n      VALUES (UUID(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n    `\n\n    const values = [\n      data.status,\n      data.fromDate,\n      data.toDate,\n      data.promptTemplate,\n      data.customPrompt,\n      data.groupBy,\n      data.subgroupBy,\n      data.orgId,\n      JSON.stringify(data.parameters || []),\n      data.results ? JSON.stringify(data.results) : null,\n      data.error,\n      data.durationMs,\n    ]\n\n    try {\n      await this.pool.execute(query, values)\n\n      // For MySQL, we need to fetch the last inserted ID differently\n      const [idRows] = await this.pool.execute<DatabaseReportRow[]>(\n        `SELECT id FROM ${this.tableName} ORDER BY created_at DESC LIMIT 1`\n      )\n\n      return idRows[0].id\n    } catch (error) {\n      throw new Error(\n        `Failed to create report: ${error instanceof Error ? error.message : 'Unknown error'}`\n      )\n    }\n  }\n\n  async updateSuccess(\n    id: string,\n    results: ReportResult[],\n    durationMs: number\n  ): Promise<void> {\n    const query = `\n      UPDATE ${this.tableName}\n      SET status = ?, results = ?, duration_ms = ?\n      WHERE id = ?\n    `\n\n    const values = ['completed', JSON.stringify(results), durationMs, id]\n\n    try {\n      await this.pool.execute(query, values)\n    } catch (error) {\n      throw new Error(\n        `Failed to update report: ${error instanceof Error ? error.message : 'Unknown error'}`\n      )\n    }\n  }\n\n  async updateFailure(\n    id: string,\n    errorMessage: string,\n    durationMs: number\n  ): Promise<void> {\n    const query = `\n      UPDATE ${this.tableName}\n      SET status = ?, error = ?, duration_ms = ?\n      WHERE id = ?\n    `\n\n    const values = ['failed', errorMessage, durationMs, id]\n\n    try {\n      await this.pool.execute(query, values)\n    } catch (error) {\n      throw new Error(\n        `Failed to update report: ${error instanceof Error ? error.message : 'Unknown error'}`\n      )\n    }\n  }\n\n  async get(id: string): Promise<StoredReport | null> {\n    const query = `SELECT * FROM ${this.tableName} WHERE id = ?`\n\n    try {\n      const [rows] = await this.pool.execute<DatabaseReportRow[]>(query, [id])\n\n      if (rows.length === 0) {\n        return null\n      }\n\n      return this.mapToStoredReport(rows[0])\n    } catch (error) {\n      throw new Error(\n        `Failed to get report: ${error instanceof Error ? error.message : 'Unknown error'}`\n      )\n    }\n  }\n\n  async list(options?: {\n    limit?: number\n    offset?: number\n    status?: ReportStatus\n  }): Promise<ListReportsResponse> {\n    const limit = options?.limit || 10\n    const offset = options?.offset || 0\n\n    // Build query with optional status filter\n    let query = `SELECT * FROM ${this.tableName}`\n    let countQuery = `SELECT COUNT(*) as count FROM ${this.tableName}`\n    const values: (string | number)[] = []\n\n    if (options?.status) {\n      query += ' WHERE status = ?'\n      countQuery += ' WHERE status = ?'\n      values.push(options.status)\n    }\n\n    query += ' ORDER BY created_at DESC LIMIT ? OFFSET ?'\n    const queryValues = [...values, limit, offset]\n    const countValues = values\n\n    try {\n      const [dataRows] = await this.pool.execute<DatabaseReportRow[]>(query, queryValues)\n      const [countRows] = await this.pool.execute<CountRow[]>(countQuery, countValues)\n\n      return {\n        reports: dataRows.map((row) => this.mapToStoredReport(row)),\n        total: countRows[0].count,\n      }\n    } catch (error) {\n      throw new Error(\n        `Failed to list reports: ${error instanceof Error ? error.message : 'Unknown error'}`\n      )\n    }\n  }\n\n  async delete(id: string): Promise<void> {\n    const query = `DELETE FROM ${this.tableName} WHERE id = ?`\n\n    try {\n      await this.pool.execute(query, [id])\n    } catch (error) {\n      throw new Error(\n        `Failed to delete report: ${error instanceof Error ? error.message : 'Unknown error'}`\n      )\n    }\n  }\n\n  private mapToStoredReport(row: DatabaseReportRow): StoredReport {\n    return {\n      id: row.id,\n      status: row.status,\n      fromDate: row.from_date,\n      toDate: row.to_date,\n      promptTemplate: row.prompt_template,\n      customPrompt: row.custom_prompt,\n      groupBy: row.group_by,\n      subgroupBy: row.subgroup_by,\n      orgId: row.org_id,\n      parameters: row.parameters || [],\n      results: row.results,\n      error: row.error,\n      durationMs: row.duration_ms,\n      createdAt: new Date(row.created_at).getTime(),\n    }\n  }\n\n  /**\n   * Close the database pool\n   * Call this when your application is shutting down\n   */\n  async close(): Promise<void> {\n    await this.pool.end()\n  }\n}\n",
      "type": "registry:lib"
    }
  ],
  "docs": "Create a MySQL connection pool and pass to MySQLStorageAdapter. Run the SQL schema from the adapter file to create the coderabbit_reports table.",
  "categories": [
    "storage",
    "database"
  ],
  "type": "registry:lib"
}