{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "storage-postgres",
  "title": "CodeRabbit PostgreSQL Adapter",
  "author": "Ray <hello@ramonclaudio.com>",
  "description": "PostgreSQL adapter with connection pooling. Works with Neon, Vercel Postgres, Railway, AWS RDS, Google Cloud SQL, etc.",
  "dependencies": [
    "pg",
    "@types/pg"
  ],
  "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-postgres.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 } from 'pg'\n\nexport interface PostgresStorageConfig {\n  pool: Pool\n  tableName?: string\n}\n\n/**\n * Database row type matching the PostgreSQL schema (snake_case columns)\n */\ninterface DatabaseReportRow {\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 {\n  count: string\n}\n\n/**\n * PostgreSQL storage adapter for CodeRabbit reports\n *\n * @example\n * ```typescript\n * import { Pool } from 'pg'\n * import { PostgresStorageAdapter } from '@/registry/default/lib/storage-postgres'\n *\n * const pool = new Pool({\n *   host: process.env.POSTGRES_HOST,\n *   port: parseInt(process.env.POSTGRES_PORT || '5432'),\n *   database: process.env.POSTGRES_DATABASE,\n *   user: process.env.POSTGRES_USER,\n *   password: process.env.POSTGRES_PASSWORD,\n * })\n *\n * const storage = new PostgresStorageAdapter({ pool })\n * ```\n *\n * Database schema:\n * ```sql\n * CREATE TABLE coderabbit_reports (\n *   id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n *   status VARCHAR(20) NOT NULL CHECK (status IN ('pending', 'completed', 'failed')),\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 JSONB DEFAULT '[]'::jsonb,\n *   results JSONB,\n *   error TEXT,\n *   duration_ms INTEGER,\n *   created_at TIMESTAMPTZ DEFAULT NOW(),\n *   user_id VARCHAR(255)\n * );\n *\n * -- Indexes for better performance\n * CREATE INDEX idx_reports_status ON coderabbit_reports(status);\n * CREATE INDEX idx_reports_user_id ON coderabbit_reports(user_id);\n * CREATE INDEX idx_reports_created_at ON coderabbit_reports(created_at DESC);\n * ```\n */\nexport class PostgresStorageAdapter implements ReportStorageAdapter {\n  private pool: Pool\n  private tableName: string\n\n  constructor(config: PostgresStorageConfig) {\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        status, from_date, to_date, prompt_template, custom_prompt,\n        group_by, subgroup_by, org_id, parameters, results, error, duration_ms\n      )\n      VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)\n      RETURNING id\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      const result = await this.pool.query<{ id: string }>(query, values)\n      return result.rows[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 = $1, results = $2, duration_ms = $3\n      WHERE id = $4\n    `\n\n    const values = ['completed', JSON.stringify(results), durationMs, id]\n\n    try {\n      await this.pool.query(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 = $1, error = $2, duration_ms = $3\n      WHERE id = $4\n    `\n\n    const values = ['failed', errorMessage, durationMs, id]\n\n    try {\n      await this.pool.query(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 = $1`\n\n    try {\n      const result = await this.pool.query<DatabaseReportRow>(query, [id])\n\n      if (result.rows.length === 0) {\n        return null\n      }\n\n      return this.mapToStoredReport(result.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(*) FROM ${this.tableName}`\n    const values: (string | number)[] = []\n\n    if (options?.status) {\n      query += ' WHERE status = $1'\n      countQuery += ' WHERE status = $1'\n      values.push(options.status)\n    }\n\n    query += ' ORDER BY created_at DESC LIMIT $' + (values.length + 1) + ' OFFSET $' + (values.length + 2)\n    values.push(limit, offset)\n\n    try {\n      const [dataResult, countResult] = await Promise.all([\n        this.pool.query<DatabaseReportRow>(query, values),\n        this.pool.query<CountRow>(countQuery, options?.status ? [options.status] : []),\n      ])\n\n      return {\n        reports: dataResult.rows.map((row) => this.mapToStoredReport(row)),\n        total: parseInt(countResult.rows[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 = $1`\n\n    try {\n      await this.pool.query(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 PostgreSQL connection pool and pass to PostgresStorageAdapter. Run the SQL schema from the adapter file to create the coderabbit_reports table.",
  "categories": [
    "storage",
    "database"
  ],
  "type": "registry:lib"
}