-
Notifications
You must be signed in to change notification settings - Fork 0
Storage Adapters
BLUECARBONS Digital Agency edited this page Jul 3, 2026
·
1 revision
The StorageAdapter interface is duck-typed — any object implementing these six methods is accepted by runAgenticParser({ storage: myAdapter }).
interface StorageAdapter {
hasProcessed(id: string): boolean;
markProcessed(item: {
id: string; feedUrl: string; title: string;
link: string; publishedAt?: string | null;
processedAt?: string; // ISO 8601 — optional backfill/migration timestamp
}): void;
saveAnalysis(itemId: string, analysis: {
id: string; decision: string; confidence: number;
summary: string; impact: string;
actionItems: string[]; tags: string[];
}): void;
getAnalyses(opts?: GetAnalysesOptions): StorageAnalysisRow[];
pruneOlderThan(ttlDays: number): { deletedItems: number; deletedAnalyses: number };
close(): void;
}Backed by the built-in node:sqlite module. Data persists across runs.
import { createStorage } from 'agentic-rss-parser';
const storage = createStorage('/var/lib/myapp/rss.db');The schema is created automatically on first use (two tables: processed_items, analyses; two indexes).
Pruning old records:
const { deletedItems, deletedAnalyses } = storage.pruneOlderThan(30); // 30-day TTLData stored in Map instances. Lost when the process exits. Ideal for:
- Unit and integration tests
- Node 18/20 environments
- Stateless / serverless deployments
import { createMemoryStorage } from 'agentic-rss-parser';
const storage = createMemoryStorage();
// Backdate for migration / testing
storage.markProcessed({
id: 'abc',
feedUrl: 'https://example.com/feed',
title: 'Old item',
link: 'https://example.com/1',
processedAt: '2024-01-01T00:00:00.000Z' // honour past timestamp
});const rows = storage.getAnalyses({
feedUrl: 'https://example.com/feed', // filter by feed
decision: 'relevant', // 'relevant' | 'ignore'
limit: 20, // default 50, max 1000
offset: 0
});Each returned row contains:
-
id,item_id,decision,confidence,summary,impact -
actionItems(Array),tags(Array) -
created_at,feed_url,title,link,published_at,processed_at
Any backend works. Example using better-sqlite3 for Node 18/20:
import Database from 'better-sqlite3';
function createBetterSqliteStorage(dbPath) {
const db = new Database(dbPath);
db.exec(`
CREATE TABLE IF NOT EXISTS processed_items (
id TEXT PRIMARY KEY,
feed_url TEXT, title TEXT, link TEXT,
published_at TEXT, processed_at TEXT DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS analyses (
id TEXT PRIMARY KEY, item_id TEXT,
decision TEXT, confidence INTEGER,
summary TEXT, impact TEXT,
action_items TEXT, tags TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
`);
return {
hasProcessed: (id) => Boolean(db.prepare('SELECT 1 FROM processed_items WHERE id=?').get(id)),
markProcessed: (item) => db.prepare(
'INSERT OR IGNORE INTO processed_items (id,feed_url,title,link,published_at) VALUES (?,?,?,?,?)'
).run(item.id, item.feedUrl, item.title, item.link, item.publishedAt),
saveAnalysis: (itemId, a) => db.prepare(
'INSERT OR IGNORE INTO analyses (id,item_id,decision,confidence,summary,impact,action_items,tags) VALUES (?,?,?,?,?,?,?,?)'
).run(a.id, itemId, a.decision, a.confidence, a.summary, a.impact, JSON.stringify(a.actionItems), JSON.stringify(a.tags)),
getAnalyses: (opts = {}) => db.prepare('SELECT * FROM analyses LIMIT ? OFFSET ?').all(opts.limit ?? 50, opts.offset ?? 0),
pruneOlderThan: (days) => {
const cut = new Date(Date.now() - days * 86400000).toISOString();
const da = db.prepare("DELETE FROM analyses WHERE item_id IN (SELECT id FROM processed_items WHERE processed_at < ?)").run(cut).changes;
const di = db.prepare("DELETE FROM processed_items WHERE processed_at < ?").run(cut).changes;
return { deletedItems: di, deletedAnalyses: da };
},
close: () => db.close()
};
}
import { runAgenticParser } from 'agentic-rss-parser';
const { results } = await runAgenticParser({
feedUrls: ['https://example.com/feed'],
storage: createBetterSqliteStorage('./rss.db')
});