### Initialize SQLite Database with Feed Schema Source: https://context7.com/sid-newby/feeds-fetcher-3.0.4-llun/llms.txt Creates and initializes a SQLite database for storing feed data with full schema setup. Handles category, site, and entry insertion with proper key generation. Includes database cleanup and optimization operations. ```typescript import { getDatabase, createTables, insertCategory, insertSite, insertEntry, getCategorySites, cleanup, hash } from './action/feeds/database' async function createFeedDatabase() { // Initialize database connection const db = getDatabase('./public') try { // Create database schema await createTables(db) // Insert category await insertCategory(db, 'Tech') // Insert site const site = { title: 'Hacker News', link: 'https://news.ycombinator.com', description: 'News for hackers', updatedAt: Math.floor(Date.now() / 1000), generator: 'RSS 2.0', entries: [] } const siteKey = await insertSite(db, 'Tech', site) // Insert entries const entry = { title: 'Sample Article', link: 'https://example.com/article', date: Math.floor(Date.now() / 1000), content: '

Sanitized HTML content

', author: 'John Doe' } const entryKey = await insertEntry(db, siteKey, site.title, 'Tech', entry) console.log(`Entry inserted with key: ${entryKey}`) // Query sites in category const sites = await getCategorySites(db, 'Tech') sites.forEach(site => { console.log(`${site.title}: ${site.totalEntries} entries`) }) // Cleanup and optimize database await cleanup(db) console.log('Database created at: ./public/data.sqlite3') } finally { await db.destroy() } } // Generate hash for consistent keys const siteHash = hash('Hacker News') const entryHash = hash('Sample Article' + 'https://example.com/article') ``` -------------------------------- ### GitHub Actions Workflow Example Source: https://github.com/sid-newby/feeds-fetcher-3.0.4-llun/blob/main/cartography/report.md This example demonstrates how to incorporate the Feeds Fetcher Action into a GitHub workflow. It defines a workflow that runs on push events or on a scheduled basis, fetching feeds and storing data either in a database or as JSON files. ```yaml name: Build Feeds on: push: schedule: - cron: "0 * * * *" jobs: build: runs-on: ubuntu-latest steps: - name: FeedsFetcher uses: llun/feeds@v3 with: opmlFile: feeds.opml storageType: database # or files branch: contents token: ${{ secrets.GITHUB_TOKEN }} customDomain: '' ``` -------------------------------- ### Complete Feed Aggregation Workflow (TypeScript) Source: https://context7.com/sid-newby/feeds-fetcher-3.0.4-llun/llms.txt Orchestrates the end-to-end process of fetching, processing, and publishing RSS feeds. It involves repository setup, OPML parsing, database operations (create, insert, cleanup), feed loading, static site building, and deployment to GitHub Pages. Dependencies include file system operations, database drivers, and repository management tools. ```typescript import { setup, buildSite, publish, getGithubActionPath } from './action/repository' import { readOpml, loadFeed, OpmlCategory } from './action/feeds/opml' import { getDatabase, createTables, insertCategory, insertSite, insertEntry, cleanup } from './action/feeds/database' import fs from 'fs' async function completeWorkflow() { try { console.log('Starting feed aggregation workflow...') // 1. Setup repository and branch await setup() console.log('✓ Repository setup complete') // 2. Load OPML configuration const opmlPath = './feeds.opml' const opmlContent = await fs.promises.readFile(opmlPath, 'utf-8') const categories: OpmlCategory[] = await readOpml(opmlContent) console.log(`✓ Loaded ${categories.length} categories from OPML`) // 3. Initialize database const actionPath = getGithubActionPath() const publicPath = `${actionPath}/public` const db = getDatabase(publicPath) await createTables(db) console.log('✓ Database initialized') // 4. Fetch and store all feeds let totalEntries = 0 for (const category of categories) { await insertCategory(db, category.category) console.log(` Processing category: ${category.category}`) for (const item of category.items) { console.log(` Fetching: ${item.title}`) const site = await loadFeed(item.title, item.xmlUrl) if (!site) { console.log(` ✗ Failed to load ${item.title}`) continue } const siteKey = await insertSite(db, category.category, site) if (!siteKey) continue for (const entry of site.entries) { await insertEntry(db, siteKey, site.title, category.category, entry) totalEntries++ } console.log(` ✓ Loaded ${site.entries.length} entries from ${item.title}`) } } // 5. Optimize database await cleanup(db) await db.destroy() console.log(` ✓ Database optimized with ${totalEntries} total entries`) // 6. Build static site await buildSite() console.log('✓ Static site built successfully') // 7. Publish to GitHub Pages await publish() console.log('✓ Site published to GitHub Pages') console.log(' 🎉 Feed aggregation workflow completed successfully!') } catch (error) { console.error('❌ Workflow failed:', error) process.exit(1) } } // Run the workflow completeWorkflow() // Expected output structure: // - Database: ./public/data.sqlite3 (3.5MB example) // - Static site: Copied to workspace root // - GitHub Pages: Deployed to 'contents' branch // - URL: https://username.github.io/repo-name/ ``` -------------------------------- ### GitHub Action Workflow with Custom Configurations Source: https://github.com/sid-newby/feeds-fetcher-3.0.4-llun/blob/main/README.md This GitHub Actions workflow showcases advanced configurations for the 'llun/feeds' action. It specifies 'files' as the storage type, a custom OPML file named 'site.opml', and directs the output to the 'public' branch. This allows for a tailored static site generation process. ```yaml name: Schedule on: schedule: - cron: '0 * * * *' jobs: playground: runs-on: ubuntu-latest name: Generate Feeds steps: - name: Run Action uses: llun/feeds@3.0.0 with: storageType: files opmlFile: site.opml branch: public ``` -------------------------------- ### GitHub Action Workflow for Fetching Feeds Source: https://github.com/sid-newby/feeds-fetcher-3.0.4-llun/blob/main/README.md This GitHub Actions workflow demonstrates how to use the 'llun/feeds' action to fetch RSS/Atom feeds. It's scheduled to run hourly and utilizes the default 'ubuntu-latest' runner. The action fetches data and stores it in a specified branch. ```yaml name: Schedule on: schedule: - cron: '0 * * * *' jobs: playground: runs-on: ubuntu-latest name: Test steps: - name: Run Action uses: llun/feeds@3.0.0 ``` -------------------------------- ### Minimal Workflow for Building Feeds (YAML) Source: https://github.com/sid-newby/feeds-fetcher-3.0.4-llun/blob/main/README.md An illustrative GitHub Actions workflow to build feeds using the FeedsFetcher action. It specifies the cron schedule, runner, and necessary inputs such as the OPML file location, storage type, target branch, and authentication token. ```yaml name: Build Feeds on: push: schedule: - cron: "0 * * * *" jobs: build: runs-on: ubuntu-latest steps: - name: FeedsFetcher uses: llun/feeds@v3 with: opmlFile: feeds.opml storageType: database # or files branch: contents token: ${{ secrets.GITHUB_TOKEN }} customDomain: '' ``` -------------------------------- ### Manage Git Operations and Deploy Site Source: https://context7.com/sid-newby/feeds-fetcher-3.0.4-llun/llms.txt Shows how to manage repository operations for GitHub Pages deployment including cloning, building, and publishing. Handles Next.js static site generation and deployment with support for custom domains and efficient git operations. ```typescript import { setup, buildSite, publish, getGithubActionPath, getWorkspacePath } from './action/repository' async function deployFeeds() { try { // Step 1: Clone repository and checkout target branch await setup() // - Uses @actions/github to detect repository // - Clones with --depth 1 for efficiency // - Creates or checks out the target branch (default: 'contents') console.log('Repository setup complete') // Step 2: Build Next.js static site await buildSite() // - Sets NEXT_PUBLIC_STORAGE=files if storageType='files' // - Runs 'yarn build' in action directory // - Copies output from 'out/' to GITHUB_WORKSPACE // - Includes .nojekyll file for GitHub Pages console.log('Static site built successfully') // Step 3: Publish to GitHub Pages branch await publish() // - Optionally creates CNAME file for custom domain // - Stages all changes with 'git add -f --all' // - Commits with automated message // - Force pushes to target branch: 'git push -f origin HEAD:contents' console.log('Site published to GitHub Pages') // Get action and workspace paths const actionPath = getGithubActionPath() // Returns: /home/runner/work/_actions/llun/feeds/{version} const workspacePath = getWorkspacePath() // Returns: /home/runner/work/{owner}/{repo} console.log(`Action path: ${actionPath}`) console.log(`Workspace path: ${workspacePath}`) } catch (error) { console.error('Deployment failed:', error) process.exit(1) } } // Full orchestration (from index.ts) async function run() { await setup() // Clone and setup branch await createFeedDatabase(getGithubActionPath()) // Generate SQLite database await createFeedFiles(getGithubActionPath()) // Generate JSON files await buildSite() // Build static site await publish() // Push to GitHub Pages } ``` -------------------------------- ### Access Feed Data with Storage Interface Source: https://context7.com/sid-newby/feeds-fetcher-3.0.4-llun/llms.txt Demonstrates how to use the unified storage interface to access feed data from a static site. Supports both file-based JSON storage and SQLite database storage. Provides methods for retrieving categories, site entries, and content with automatic mode detection. ```typescript import { getStorage } from './lib/storage' import type { Storage, Category, SiteEntry, Content } from './lib/storage/types' async function displayFeeds() { // Initialize storage (auto-detects files vs sqlite mode) const storage: Storage = getStorage(process.env.NEXT_PUBLIC_BASE_PATH ?? '') try { // Get all categories with site counts const categories: Category[] = await storage.getCategories() categories.forEach(category => { console.log(`\n${category.title} (${category.totalEntries} entries)`) category.sites.forEach(site => { console.log(` - ${site.title}: ${site.totalEntries} entries`) }) }) // Get entries for a specific category const techEntries: SiteEntry[] = await storage.getCategoryEntries('Tech') console.log(`\nTech entries: ${techEntries.length}`) // Get entries for a specific site const siteKey = categories[0].sites[0].key const siteEntries: SiteEntry[] = await storage.getSiteEntries(siteKey) console.log(`Site entries: ${siteEntries.length}`) // Get all entries across categories (paginated) const allEntries: SiteEntry[] = await storage.getAllEntries(0) // Get full content for an entry if (allEntries.length > 0) { const content: Content = await storage.getContent(allEntries[0].key) console.log(`\nEntry: ${content.title}`) console.log(`Site: ${content.siteTitle}`) console.log(`URL: ${content.url}`) console.log(`Date: ${new Date(content.timestamp * 1000).toISOString()}`) console.log(`Content: ${content.content.substring(0, 100)}...`) } // Count operations const totalEntries = await storage.countAllEntries() const categoryCount = await storage.countCategoryEntries('Tech') const siteCount = await storage.countSiteEntries(siteKey) console.log(`\nTotal: ${totalEntries}, Tech: ${categoryCount}, Site: ${siteCount}`) } catch (error) { console.error('Error loading feeds:', error) } } ``` -------------------------------- ### Site Build and Publish Operations (repository.ts) Source: https://github.com/sid-newby/feeds-fetcher-3.0.4-llun/blob/main/README.md Manages the process of cloning a repository, building a Next.js site, and publishing the artifacts. It handles different scenarios for existing and new branches, and includes steps for cleaning, building, and copying output. Sensitive operations like cloning with tokens and force pushing are noted. ```typescript import { Octokit } from "@octokit/rest"; import { execSync } from "child_process"; async function setup(token: string, branch: string, actor: string) { const octokit = new Octokit({ auth: token }); const repoOwner = process.env.GITHUB_REPOSITORY?.split("/")[0]; const repoName = process.env.GITHUB_REPOSITORY?.split("/")[1]; try { // Check if branch exists await octokit.repos.getBranch({ owner: repoOwner, repo: repoName, branch: branch }); console.log(`Branch '${branch}' exists. Cloning into GITHUB_WORKSPACE.`); execSync(`git clone -b ${branch} --depth 1 . GITHUB_WORKSPACE`, { stdio: 'inherit' }); } catch (error) { console.log(`Branch '${branch}' does not exist. Cloning current ref and checking out new branch.`); execSync("git clone --depth 1 . GITHUB_WORKSPACE", { stdio: 'inherit' }); process.chdir("GITHUB_WORKSPACE"); execSync(`git checkout -B ${branch}`, { stdio: 'inherit' }); } } function buildSite() { execSync("rm -rf _next; touch .nojekyll", { stdio: 'inherit' }); if (process.env.storageType === 'files') { process.env.NEXT_PUBLIC_STORAGE = 'files'; } execSync("yarn build", { stdio: 'inherit' }); execSync("cp -rT out GITHUB_WORKSPACE", { stdio: 'inherit' }); } function publish(customDomain?: string, branch: string = 'gh-pages') { if (customDomain) { // Logic for custom domain } execSync("git config user.email", { stdio: 'inherit' }); execSync("git config user.name", { stdio: 'inherit' }); execSync("git add -f --all", { stdio: 'inherit' }); execSync("git commit -m 'Publish static site'", { stdio: 'inherit' }); execSync(`git push -f HEAD:${branch}`, { stdio: 'inherit' }); } export { setup, buildSite, publish }; ``` -------------------------------- ### Generate Structured JSON Files for Feed Storage Source: https://context7.com/sid-newby/feeds-fetcher-3.0.4-llun/llms.txt Implements file-based storage by generating structured JSON files organized by categories and sites. Creates snapshot files, category metadata, site data, and aggregated entries files in a hierarchical directory structure. ```typescript import { createFeedFiles, loadOPMLAndWriteFiles, createCategoryData, createAllEntriesData } from './action/feeds/file' async function generateJSONFiles() { const contentDir = './contents' const publicPath = './public' const opmlPath = './feeds.opml' // Step 1: Load OPML and write feed snapshots await loadOPMLAndWriteFiles(contentDir, opmlPath) // Creates: contents/Tech/{siteHash}.json // contents/Apple/{siteHash}.json // Step 2: Create derived data files await prepareDirectories({ content: contentDir, public: publicPath, data: `${publicPath}/data`, categories: `${publicPath}/data/categories`, sites: `${publicPath}/data/sites`, entries: `${publicPath}/data/entries` }) // Step 3: Generate category metadata await createCategoryData(paths) // Creates: public/data/categories.json // public/data/categories/Tech.json // public/data/sites/{hash}.json // public/data/entries/{hash}.json // Step 4: Create aggregated entries file await createAllEntriesData() // Creates: public/data/all.json console.log('JSON files generated successfully') } // Example file structure: // contents/ // Tech/ // a3f2c1e...json (site snapshot) // Apple/ // b7d8f2a...json (site snapshot) // public/data/ // categories.json (all categories summary) // categories/ // Tech.json (Tech category entries) // Apple.json (Apple category entries) // sites/ // a3f2c1e...json (site with entries) // entries/ // c9d4e2f...json (individual entry) // all.json (all entries sorted) ``` -------------------------------- ### Configure GitHub Action for Feed Aggregation Source: https://context7.com/sid-newby/feeds-fetcher-3.0.4-llun/llms.txt This YAML configuration sets up a GitHub Actions workflow to automatically run the FEEDS action on a schedule or manually. It specifies the OPML file to use, the storage type (files or database), the output branch for GitHub Pages, and an optional custom domain. ```yaml name: Schedule on: schedule: - cron: '0 * * * *' # Run hourly workflow_dispatch: # Manual trigger jobs: generate-feeds: runs-on: ubuntu-latest name: Generate Feeds Site steps: - name: Run FeedsFetcher uses: llun/feeds@3.0.0 with: opmlFile: feeds.opml storageType: files # 'files' or 'database' branch: contents # Output branch for GitHub Pages token: ${{ secrets.GITHUB_TOKEN }} customDomain: feeds.example.com # Optional CNAME ``` -------------------------------- ### Load and Parse RSS/Atom Feeds and OPML Files in TypeScript Source: https://context7.com/sid-newby/feeds-fetcher-3.0.4-llun/llms.txt These TypeScript functions demonstrate how to load and parse individual RSS/Atom feeds, and how to read and process an OPML file to extract feed subscriptions. The `loadFeed` function fetches and parses a single feed, while `readOpml` parses the OPML content into structured categories and feed items. Error handling for feed loading is included. ```typescript import { loadFeed, readOpml, OpmlCategory, OpmlItem } from './action/feeds/opml' // Load and parse a single feed async function fetchSingleFeed() { const site = await loadFeed( 'Hacker News', 'https://news.ycombinator.com/rss' ) if (!site) { console.error('Failed to load feed') return } console.log(`Site: ${site.title}`) console.log(`Description: ${site.description}`) console.log(`Entries: ${site.entries.length}`) site.entries.forEach(entry => { console.log(`- ${entry.title}`) console.log(` Link: ${entry.link}`) console.log(` Date: ${new Date(entry.date * 1000).toISOString()}`) console.log(` Author: ${entry.author}`) }) } // Parse OPML file and extract all feed subscriptions async function parseOpmlFile() { const opmlContent = await fs.promises.readFile('feeds.opml', 'utf-8') const categories: OpmlCategory[] = await readOpml(opmlContent) categories.forEach(category => { console.log(`Category: ${category.category}`) category.items.forEach((item: OpmlItem) => { console.log(` - ${item.title}: ${item.xmlUrl}`) }) }) // Load all feeds from OPML for (const category of categories) { for (const item of category.items) { const site = await loadFeed(item.title, item.xmlUrl) if (site) { console.log(`Loaded ${site.entries.length} entries from ${site.title}`) } } } } ``` -------------------------------- ### Database Model Definition (database.ts) Source: https://github.com/sid-newby/feeds-fetcher-3.0.4-llun/blob/main/README.md Defines the relational database schema for storing feed data. It includes tables for schema versions, categories, sites, site-category mappings, entries, and entry-category mappings. The schema is designed to be created dynamically. ```sql -- SchemaVersions table for versioning CREATE TABLE SchemaVersions ( version INTEGER PRIMARY KEY ); -- Categories table CREATE TABLE Categories ( name TEXT PRIMARY KEY ); -- Sites table CREATE TABLE Sites ( "key" TEXT PRIMARY KEY, title TEXT, url TEXT, description TEXT, createdAt DATETIME ); -- SiteCategories table for many-to-many relationship CREATE TABLE SiteCategories ( category TEXT, siteKey TEXT, siteTitle TEXT, FOREIGN KEY (category) REFERENCES Categories(name), FOREIGN KEY (siteKey) REFERENCES Sites(key) ); -- Entries table CREATE TABLE Entries ( "key" TEXT PRIMARY KEY, siteKey TEXT, siteTitle TEXT, title TEXT, url TEXT, content TEXT, contentTime DATETIME, createdAt DATETIME, FOREIGN KEY (siteKey) REFERENCES Sites(key) ); -- EntryCategories table for many-to-many relationship CREATE TABLE EntryCategories ( category TEXT, entryKey TEXT, entryTitle TEXT, siteKey TEXT, siteTitle TEXT, entryContentTime DATETIME, entryCreatedAt DATETIME, FOREIGN KEY (category) REFERENCES Categories(name), FOREIGN KEY (entryKey) REFERENCES Entries(key), FOREIGN KEY (siteKey) REFERENCES Sites(key) ); -- Example indices (assuming common query patterns) CREATE INDEX IF NOT EXISTS idx_sitecategories_category ON SiteCategories(category); CREATE INDEX IF NOT EXISTS idx_sitecategories_siteKey ON SiteCategories(siteKey); CREATE INDEX IF NOT EXISTS idx_entries_siteKey ON Entries(siteKey); CREATE INDEX IF NOT EXISTS idx_entrycategories_category ON EntryCategories(category); CREATE INDEX IF NOT EXISTS idx_entrycategories_entryKey ON EntryCategories(entryKey); CREATE INDEX IF NOT EXISTS idx_entrycategories_siteKey ON EntryCategories(siteKey); ``` -------------------------------- ### Define RSS/Atom Feed Subscriptions in OPML Source: https://context7.com/sid-newby/feeds-fetcher-3.0.4-llun/llms.txt This XML document follows the OPML 2.0 specification to outline a list of RSS/Atom feed subscriptions, organized into categories. Each feed entry includes its title, type (rss), the XML URL for fetching feed data, and the HTML URL for the website. ```xml My Feeds ``` -------------------------------- ### Parse RSS and Atom Feeds with TypeScript Source: https://context7.com/sid-newby/feeds-fetcher-3.0.4-llun/llms.txt Fetches and parses RSS and Atom feed formats using provided utility functions. It automatically sanitizes and normalizes feed content, making it ready for display. The function handles fetching feed content, parsing XML, and extracting site and entry data. It requires the `fetch` API and custom parser functions. ```typescript import { parseRss, parseAtom, parseXML } from './action/feeds/parsers' import type { Site, Entry } from './action/feeds/parsers' async function parseFeedContent() { // Fetch and parse RSS feed const rssUrl = 'https://news.ycombinator.com/rss' const rssResponse = await fetch(rssUrl, { headers: { 'User-Agent': 'llun/feed' } }) const rssText = await rssResponse.text() const rssXml = await parseXML(rssText) if ('rss' in rssXml) { const rssSite: Site = parseRss('Hacker News', rssXml) console.log(`RSS Site: ${rssSite.title}`) console.log(`Link: ${rssSite.link}`) console.log(`Description: ${rssSite.description}`) console.log(`Generator: ${rssSite.generator}`) console.log(`Updated: ${new Date(rssSite.updatedAt * 1000).toISOString()}`) rssSite.entries.forEach((entry: Entry) => { console.log(`\nTitle: ${entry.title}`) console.log(`Link: ${entry.link}`) console.log(`Author: ${entry.author}`) console.log(`Date: ${new Date(entry.date * 1000).toISOString()}`) console.log(`Content: ${entry.content.substring(0, 100)}...`) }) } // Fetch and parse Atom feed const atomUrl = 'https://www.llun.me/feeds/atom.xml' const atomResponse = await fetch(atomUrl, { headers: { 'User-Agent': 'llun/feed' } }) const atomText = await atomResponse.text() const atomXml = await parseXML(atomText) if ('feed' in atomXml) { const atomSite: Site = parseAtom('Personal Blog', atomXml) console.log(`\nAtom Site: ${atomSite.title}`) console.log(`Entries: ${atomSite.entries.length}`) atomSite.entries.forEach(entry => { console.log(`- ${entry.title}`) }) } } // Entry interface structure: interface Entry { title: string // Article title link: string // Article URL date: number // Unix timestamp in seconds content: string // Sanitized HTML content author: string // Author name } // Site interface structure: interface Site { title: string // Feed title link: string // Feed homepage URL description: string // Feed description updatedAt: number // Last update timestamp generator: string // Feed generator entries: Entry[] // Array of feed entries } // Content sanitization: // - Uses sanitize-html library // - Allows default safe tags plus // - Removes dangerous attributes and scripts // - Preserves links with target="_blank" and rel="noopener" ``` -------------------------------- ### Next.js Runtime Data Access (lib/page.tsx) Source: https://github.com/sid-newby/feeds-fetcher-3.0.4-llun/blob/main/README.md Handles data fetching for a Next.js application, supporting both 'files' (JSON) and 'sqlite' storage types. It dynamically selects the appropriate storage implementation based on environment variables. For SQLite, it requires specific worker and WASM files to be served. ```typescript import { FileStorage } from './storage/file'; import { SqliteStorage } from './storage/sqlite'; interface Storage { getCategoryList(): Promise; getItemList(category: string): Promise; getItemContent(category: string, itemKey: string): Promise; } function getStorage(basePath: string): Storage { const storageType = process.env.NEXT_PUBLIC_STORAGE; if (storageType === 'files') { return new FileStorage(basePath); } else if (storageType === 'sqlite') { return new SqliteStorage(basePath); } else { // Default fallback or error handling console.warn("NEXT_PUBLIC_STORAGE not set or invalid, defaulting to FileStorage."); return new FileStorage(basePath); } } // Example usage in a Page component // async function Page() { // const storage = getStorage('/data'); // const categories = await storage.getCategoryList(); // // ... rest of the component // } export { getStorage }; ``` -------------------------------- ### Client-Side Routing and State Management (TypeScript) Source: https://context7.com/sid-newby/feeds-fetcher-3.0.4-llun/llms.txt Manages client-side routing and state updates for a feed reader interface. It parses URL paths into structured location states and updates the UI based on navigation events. Dependencies include utility functions for parsing and controlling location, and React's useState and useReducer hooks. ```typescript import { parseLocation, locationController } from './lib/utils' import type { LocationState, PageState } from './lib/utils' async function handleNavigation() { const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? '' // Parse URL path to location state const categoryLocation: LocationState = parseLocation('/Tech') // Returns: { type: 'category', category: 'Tech' } const siteLocation: LocationState = parseLocation('/site/a3f2c1e...') // Returns: { type: 'site', siteKey: 'a3f2c1e...' } const entryLocation: LocationState = parseLocation('/Tech/entry/c9d4e2f...') // Returns: { // type: 'entry', // entryKey: 'c9d4e2f...', // parent: { type: 'category', key: 'Tech' } // } // Handle location changes with state updates const [content, setContent] = useState(null) const [pageState, setPageState] = useState('categories') await locationController( categoryLocation, basePath, setContent, setPageState ) // Updates content and page state based on location // PageState: 'categories' | 'entries' | 'article' console.log(`Page state: ${pageState}`) } // Path reducer for managing navigation state import { PathReducer, updatePath } from './lib/reducers/path' import type { PathState, Actions } from './lib/reducers/path' const [pathState, dispatch] = useReducer(PathReducer, { pathname: '/', location: null }) // Update path when user navigates dispatch(updatePath('/Tech')) // Updates: { pathname: '/Tech', location: { type: 'category', category: 'Tech' } } dispatch(updatePath('/site/a3f2c1e...')) // Updates: { pathname: '/site/a3f2c1e...', location: { type: 'site', siteKey: 'a3f2c1e...' } } // Access current state console.log(`Current path: ${pathState.pathname}`) console.log(`Location type: ${pathState.location?.type}`) ``` -------------------------------- ### TypeScript Data Model Interfaces for Feeds Fetcher Source: https://context7.com/sid-newby/feeds-fetcher-3.0.4-llun/llms.txt Defines core TypeScript interfaces for data structures used within the Feeds Fetcher application. These interfaces ensure type safety for storage operations, OPML feed parsing, and content retrieval. They cover categories, site entries, full content details, and OPML feed items. ```typescript // Storage interface (lib/storage/types.ts) interface Storage { getCategories(): Promise getCategoryEntries(category: string, page?: number): Promise getSiteEntries(siteKey: string, page?: number): Promise getAllEntries(page?: number): Promise countAllEntries(): Promise countSiteEntries(siteKey: string): Promise countCategoryEntries(category: string): Promise getContent(key: string): Promise } interface Category { title: string sites: { key: string title: string totalEntries: number }[] totalEntries: number } interface SiteEntry { key: string title: string site: { key: string title: string } timestamp?: number } interface Content { title: string content: string url: string siteKey: string siteTitle: string timestamp: number } // OPML interfaces (action/feeds/opml.ts) interface OpmlItem { type: string // Always 'rss' text: string // Display text title: string // Feed title xmlUrl: string // Feed URL htmlUrl: string // Website URL } interface OpmlCategory { category: string // Category name items: OpmlItem[] // Feeds in category } // Example usage with complete type safety: const storage: Storage = getStorage('/feeds') const categories: Category[] = await storage.getCategories() const techCategory = categories.find(c => c.title === 'Tech') if (techCategory) { console.log(`${techCategory.title}: ${techCategory.totalEntries} entries`) techCategory.sites.forEach(site => { console.log(` ${site.title} (${site.key}): ${site.totalEntries}`) }) } const entries: SiteEntry[] = await storage.getCategoryEntries('Tech', 0) if (entries.length > 0) { const firstEntry = entries[0] const content: Content = await storage.getContent(firstEntry.key) console.log(content.title) console.log(content.siteTitle) console.log(new Date(content.timestamp * 1000)) } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.