Custom Storage Adapters

The integration uses a clean dependency injection pattern for storage, allowing you to implement custom storage backends.

Storage Interface

interface FeedbackStorageEntry {
  id?: string;
  timestamp: number;
  page: string;
  highlight?: string;
  notes?: string;
  category?: string;
  [key: string]: any; // Allow custom fields
}

interface FeedbackStorageAdapter {
  save(entry: FeedbackStorageEntry): Promise<void>;
  loadAll(): Promise<FeedbackStorageEntry[]>;
  clear?(): Promise<void>; // Optional
}

Implementing a Custom Adapter

Create a custom adapter by implementing the FeedbackStorageAdapter interface:

import type { FeedbackStorageAdapter, FeedbackStorageEntry } from 'astro-ai-coauthor';

class MyCustomStorageAdapter implements FeedbackStorageAdapter {
  async save(entry: FeedbackStorageEntry): Promise<void> {
    // Your custom save logic
    await fetch('https://api.example.com/feedback', {
      method: 'POST',
      body: JSON.stringify(entry)
    });
  }

  async loadAll(): Promise<FeedbackStorageEntry[]> {
    // Your custom load logic
    const response = await fetch('https://api.example.com/feedback');
    return response.json();
  }

  async clear(): Promise<void> {
    // Optional: clear all feedback
    await fetch('https://api.example.com/feedback', {
      method: 'DELETE'
    });
  }
}

Then use it in your configuration:

import astroAICoauthor from 'astro-ai-coauthor';
import { MyCustomStorageAdapter } from './my-storage';

export default defineConfig({
  integrations: [
    astroAICoauthor({
      storage: new MyCustomStorageAdapter()
    }),
  ],
});

Example: GitHub Issues Adapter

Store feedback as GitHub issues:

import type { FeedbackStorageAdapter, FeedbackStorageEntry } from 'astro-ai-coauthor';
import { Octokit } from '@octokit/rest';

class GitHubIssuesAdapter implements FeedbackStorageAdapter {
  private octokit: Octokit;
  private owner: string;
  private repo: string;

  constructor(token: string, owner: string, repo: string) {
    this.octokit = new Octokit({ auth: token });
    this.owner = owner;
    this.repo = repo;
  }

  async save(entry: FeedbackStorageEntry): Promise<void> {
    await this.octokit.issues.create({
      owner: this.owner,
      repo: this.repo,
      title: `Feedback: ${entry.page}`,
      body: `
**Page**: ${entry.page}
**Category**: ${entry.category || 'general'}
**Notes**: ${entry.notes || 'No notes'}
**Timestamp**: ${new Date(entry.timestamp).toISOString()}
      `.trim(),
      labels: ['documentation', 'feedback']
    });
  }

  async loadAll(): Promise<FeedbackStorageEntry[]> {
    const { data: issues } = await this.octokit.issues.listForRepo({
      owner: this.owner,
      repo: this.repo,
      labels: 'feedback',
      state: 'all'
    });

    return issues.map(issue => ({
      id: issue.id.toString(),
      page: this.extractPage(issue.body || ''),
      timestamp: new Date(issue.created_at).getTime(),
      notes: issue.body || '',
      category: 'general'
    }));
  }

  private extractPage(body: string): string {
    const match = body.match(/\*\*Page\*\*:\s*(.+)/);
    return match ? match[1] : 'unknown';
  }
}

// Usage:
export default defineConfig({
  integrations: [
    astroAICoauthor({
      storage: new GitHubIssuesAdapter(
        process.env.GITHUB_TOKEN,
        'myorg',
        'myrepo'
      )
    }),
  ],
});

Example: Cloudflare KV Adapter

Store feedback in Cloudflare KV:

import type { FeedbackStorageAdapter, FeedbackStorageEntry } from 'astro-ai-coauthor';

class CloudflareKVAdapter implements FeedbackStorageAdapter {
  private kvNamespace: KVNamespace;
  private key: string;

  constructor(kvNamespace: KVNamespace, key: string = 'feedback') {
    this.kvNamespace = kvNamespace;
    this.key = key;
  }

  async save(entry: FeedbackStorageEntry): Promise<void> {
    const entries = await this.loadAll();
    entries.push(entry);
    await this.kvNamespace.put(this.key, JSON.stringify(entries));
  }

  async loadAll(): Promise<FeedbackStorageEntry[]> {
    const data = await this.kvNamespace.get(this.key);
    return data ? JSON.parse(data) : [];
  }

  async clear(): Promise<void> {
    await this.kvNamespace.put(this.key, JSON.stringify([]));
  }
}

// Usage in Cloudflare Pages:
export default defineConfig({
  integrations: [
    astroAICoauthor({
      storage: new CloudflareKVAdapter(env.MY_KV_NAMESPACE)
    }),
  ],
});

Example: Database Adapter

Store feedback in a database (PostgreSQL, MySQL, etc.):

import type { FeedbackStorageAdapter, FeedbackStorageEntry } from 'astro-ai-coauthor';
import { sql } from 'your-db-library';

class DatabaseAdapter implements FeedbackStorageAdapter {
  async save(entry: FeedbackStorageEntry): Promise<void> {
    await sql`
      INSERT INTO feedback (page, timestamp, notes, category, rating)
      VALUES (${entry.page}, ${entry.timestamp}, ${entry.notes}, ${entry.category}, ${entry.rating})
    `;
  }

  async loadAll(): Promise<FeedbackStorageEntry[]> {
    const rows = await sql`
      SELECT * FROM feedback ORDER BY timestamp DESC
    `;
    return rows;
  }

  async clear(): Promise<void> {
    await sql`DELETE FROM feedback`;
  }
}