Files
RSSuper/tasks/native-business-logic-migration/02-design-shared-data-models.md
Michael Freno af87f9f571 Design shared data models for native platforms
- Define core entities: FeedItem, FeedSubscription, SearchHistoryItem
- Document entity relationships with diagram
- Create database schema with SQLite and FTS5
- Provide type mapping guide for Swift/Kotlin/C
- Define value types vs reference types
- Document enumeration types for all platforms
- Specify serialization/deserialization requirements
- Outline indexing strategy for performance
- Include memory considerations and future extensibility
2026-03-29 14:02:34 -04:00

17 KiB

02. Design shared data models for all platforms

meta: id: native-business-logic-migration-02 feature: native-business-logic-migration priority: P0 depends_on: [native-business-logic-migration-01] tags: [design, data-models]

objective:

  • Design platform-agnostic data models that can be implemented natively on each platform

deliverables:

  • Data model specification document
  • Entity relationship diagram
  • Type mapping guide (TypeScript → Swift/Kotlin/C)
  • Database schema design

steps:

  • Review TypeScript types from src/types/feed.ts
  • Identify core entities: Feed, FeedItem, FeedSubscription, SearchResult
  • Design normalized database schema
  • Create type mapping documentation
  • Define value types vs reference types
  • Design enumeration types for each platform
  • Document serialization/deserialization requirements

acceptance_criteria:

  • All data models documented with properties and types
  • Entity relationships clearly defined
  • Type mapping guide complete for all three platforms
  • Database schema designed with proper indexing
  • Value types identified (Date, UUID, Enums)

notes:

  • Use UUID for identifiers across all platforms
  • Date handling needs platform-specific implementation
  • Consider memory footprint for large feed items
  • Plan for future extensibility

Shared Data Models Design

Overview

This document defines platform-agnostic data models for RSSuper, enabling native implementations on iOS (Swift), Android (Kotlin), and Linux (C). The models are designed for SQLite persistence with full-text search support.


1. Core Entities

1.1 FeedItem

Represents a single article or episode from a feed.

Property Type Required Description
id UUID Yes Unique identifier
title String Yes Item title
link String No URL to full article
description String No Short summary
content String No Full content (may be large)
author String No Author name
published DateTime No Publication date
updated DateTime No Last update date
categories String[] No Category tags
enclosure Enclosure No Media attachment
guid String No Global unique identifier from feed
subscriptionId UUID Yes Parent subscription
subscriptionTitle String No Denormalized for display

Enclosure (nested)

Property Type Required Description
url String Yes Media URL
type String Yes MIME type (audio/mpeg, image/jpeg, etc.)
length Int64 No File size in bytes

1.2 FeedSubscription

Represents a feed subscription with configuration.

Property Type Required Description
id UUID Yes Unique identifier
url String Yes Feed URL (unique)
title String Yes Display title
category String No User-defined category
enabled Boolean Yes Active/inactive
fetchInterval Int32 Yes Fetch interval in minutes
createdAt DateTime Yes Creation timestamp
updatedAt DateTime Yes Last modification
lastFetchedAt DateTime No Last successful fetch
nextFetchAt DateTime No Next scheduled fetch
error String No Last error message
httpAuth HttpAuth No HTTP authentication

HttpAuth (nested)

Property Type Required Description
username String Yes Username
password String Yes Password (should be encrypted)

1.3 SearchHistoryItem

Property Type Required Description
id UUID Yes Unique identifier
query String Yes Search query text
timestamp DateTime Yes When query was executed

2. Entity Relationship Diagram

┌─────────────────────┐       ┌─────────────────────┐
│  FeedSubscription   │       │    SearchHistory    │
├─────────────────────┤       ├─────────────────────┤
│ id (PK)             │       │ id (PK)             │
│ url (UNIQUE)        │       │ query               │
│ title               │       │ timestamp           │
│ category            │       └─────────────────────┘
│ enabled             │
│ fetch_interval      │              ┌─────────────────────┐
│ created_at          │              │       FeedItem     │
│ updated_at          │              ├─────────────────────┤
│ last_fetched_at     │              │ id (PK)            │
│ next_fetch_at       │──────┐      │ subscription_id(FK)│◄──┐
│ error               │      │      │ title             │   │
│ http_auth_username  │      │      │ link              │   │
│ http_auth_password  │      │      │ description       │   │
└─────────────────────┘      │      │ content           │   │
                             │      │ author            │   │
                             │      │ published         │   │
                             │      │ updated           │   │
                             │      │ guid (UNIQUE)     │   │
                             │      │ enclosure_url     │   │
                             │      │ enclosure_type    │   │
                             │      │ enclosure_length  │   │
                             │      │ categories (JSON) │   │
                             │      │ created_at        │   │
                             │      └─────────────────────┘   │
                             │                                │
                             └────────────────────────────────┘

3. Database Schema Design

3.1 subscriptions

CREATE TABLE subscriptions (
    id TEXT PRIMARY KEY,
    url TEXT UNIQUE NOT NULL,
    title TEXT NOT NULL,
    category TEXT,
    enabled INTEGER NOT NULL DEFAULT 1,
    fetch_interval INTEGER NOT NULL DEFAULT 60,
    created_at INTEGER NOT NULL,
    updated_at INTEGER NOT NULL,
    last_fetched_at INTEGER,
    next_fetch_at INTEGER,
    error TEXT,
    http_auth_username TEXT,
    http_auth_password TEXT
);

CREATE INDEX idx_subscriptions_url ON subscriptions(url);
CREATE INDEX idx_subscriptions_category ON subscriptions(category);
CREATE INDEX idx_subscriptions_next_fetch ON subscriptions(next_fetch_at) WHERE enabled = 1;
CREATE INDEX idx_subscriptions_enabled ON subscriptions(enabled);

3.2 feed_items

CREATE TABLE feed_items (
    id TEXT PRIMARY KEY,
    subscription_id TEXT NOT NULL,
    title TEXT NOT NULL,
    link TEXT,
    description TEXT,
    content TEXT,
    author TEXT,
    published INTEGER,
    updated INTEGER,
    guid TEXT,
    enclosure_url TEXT,
    enclosure_type TEXT,
    enclosure_length INTEGER,
    categories TEXT,
    created_at INTEGER NOT NULL,
    FOREIGN KEY (subscription_id) REFERENCES subscriptions(id) ON DELETE CASCADE
);

CREATE UNIQUE INDEX idx_feed_items_guid ON feed_items(guid) WHERE guid IS NOT NULL;
CREATE INDEX idx_feed_items_subscription ON feed_items(subscription_id);
CREATE INDEX idx_feed_items_published ON feed_items(published DESC);
CREATE INDEX idx_feed_items_created ON feed_items(created_at DESC);

3.3 search_history

CREATE TABLE search_history (
    id TEXT PRIMARY KEY,
    query TEXT NOT NULL,
    timestamp INTEGER NOT NULL
);

CREATE INDEX idx_search_history_timestamp ON search_history(timestamp DESC);

3.4 Full-Text Search (FTS5)

CREATE VIRTUAL TABLE feed_items_fts USING fts5(
    title,
    description,
    content,
    author,
    categories,
    content='feed_items',
    content_rowid='rowid'
);

CREATE VIRTUAL TABLE subscriptions_fts USING fts5(
    title,
    url,
    category,
    content='subscriptions',
    content_rowid='rowid'
);

4. Type Mapping Guide

4.1 Primitive Types

TypeScript Swift (Kotlin/C) SQLite Notes
string String (String/char*) TEXT UTF-8 encoded
number Int/Double (Int/Double/int64_t) INTEGER/REAL Use REAL for decimals
boolean Bool (Boolean/int) INTEGER 0 or 1
Date Date (LocalDateTime/time_t) INTEGER Unix timestamp (milliseconds)
UUID UUID (UUID/uuid_t) TEXT String format: "550e8400-e29b-41d4-a716-446655440000"
string[] [String] (List<String>/char**) TEXT JSON array

4.2 Core Model Mapping

FeedItem

Property TypeScript Swift Kotlin C (struct)
id string String String char id[37]
title string String String char title[512]
link string? String? String? char* link
description string? String? String? char* description
content string? String? String? char* content
author string? String? String? char* author
published Date? Date? Long? int64_t published
updated Date? Date? Long? int64_t updated
categories string[]? [String]? String? char* categories
enclosure Enclosure? Enclosure? Enclosure? Enclosure* enclosure
guid string? String? String? char* guid
subscriptionId string String String char subscription_id[37]

FeedSubscription

Property TypeScript Swift Kotlin C (struct)
id string String String char id[37]
url string String String char url[2048]
title string String String char title[512]
category string? String? String? char* category
enabled boolean Bool Boolean int enabled
fetchInterval number Int Int int fetch_interval
createdAt Date Date Long int64_t created_at
updatedAt Date Date Long int64_t updated_at
lastFetchedAt Date? Date? Long? int64_t last_fetched_at
nextFetchAt Date? Date? Long? int64_t next_fetch_at
error string? String? String? char* error
httpAuth HttpAuth? HttpAuth? HttpAuth? HttpAuth* http_auth

5. Value Types vs Reference Types

5.1 Value Types (Copy on assign)

All primitive types should be treated as value types:

  • UUID (string representation)
  • Date (Unix timestamp)
  • Boolean
  • Integer
  • Float

5.2 Reference Types (Pointer/Heap)

Complex types should be reference types:

  • FeedItem (stored by ID reference)
  • FeedSubscription (stored by ID reference)
  • Enclosure (inline in FeedItem)
  • HttpAuth (inline in FeedSubscription)

5.3 Implementation Notes

Swift: Use struct for value types, class for reference types Kotlin: Use data class for all models, pass by value C: Use struct with explicit memory management


6. Enumeration Types

6.1 ContentType

type ContentType = 'article' | 'audio' | 'video';
TypeScript Swift Kotlin C
'article' ContentType.article ContentType.ARTICLE CONTENT_TYPE_ARTICLE
'audio' ContentType.audio ContentType.AUDIO CONTENT_TYPE_AUDIO
'video' ContentType.video ContentType.VIDEO CONTENT_TYPE_VIDEO

6.2 Theme

type Theme = 'system' | 'light' | 'dark';
TypeScript Swift Kotlin C
'system' Theme.system Theme.SYSTEM THEME_SYSTEM
'light' Theme.light Theme.LIGHT THEME_LIGHT
'dark' Theme.dark Theme.DARK THEME_DARK

6.3 Privacy

type Privacy = 'public' | 'private' | 'anonymous';
TypeScript Swift Kotlin C
'public' Privacy.public Privacy.PUBLIC PRIVACY_PUBLIC
'private' Privacy.private Privacy.PRIVATE PRIVACY_PRIVATE
'anonymous' Privacy.anonymous Privacy.ANONYMOUS PRIVACY_ANONYMOUS

6.4 NotificationType

enum NotificationType {
  NEW_ARTICLE = 'NEW_ARTICLE',
  EPISODE_RELEASE = 'EPISODE_RELEASE',
  CUSTOM_ALERT = 'CUSTOM_ALERT',
  UPGRADE_PROMO = 'UPGRADE_PROMO'
}
TypeScript Swift Kotlin C
NEW_ARTICLE NotificationType.newArticle NotificationType.NEW_ARTICLE NOTIFICATION_TYPE_NEW_ARTICLE
EPISODE_RELEASE NotificationType.episodeRelease NotificationType.EPISODE_RELEASE NOTIFICATION_TYPE_EPISODE_RELEASE
CUSTOM_ALERT NotificationType.customAlert NotificationType.CUSTOM_ALERT NOTIFICATION_TYPE_CUSTOM_ALERT
UPGRADE_PROMO NotificationType.upgradePromo NotificationType.UPGRADE_PROMO NOTIFICATION_TYPE_UPGRADE_PROMO

7. Serialization/Deserialization

7.1 Date Handling

All dates stored as Unix timestamp (milliseconds) in SQLite. Convert to/from platform-specific types:

Platform Conversion
Swift Date(timeIntervalSince1970: timestamp / 1000)
Kotlin Date(timestamp)
C time_t with timestamp / 1000

7.2 JSON Fields

Arrays and nested objects stored as JSON strings:

-- Categories stored as JSON array
categories = '["tech", "programming", "rust"]'

-- Enclosure stored as JSON object
enclosure = '{"url": "...", "type": "audio/mpeg", "length": 1234567}'

7.3 UUID Handling

Store UUIDs as strings in canonical format:

550e8400-e29b-41d4-a716-446655440000

Generate using platform UUID libraries:

  • Swift: UUID().uuidString
  • Kotlin: UUID.randomUUID().toString()
  • C: Use uuid_generate() from libuuid

8. Indexing Strategy

8.1 Primary Indexes

  • subscriptions.id (auto-created for PRIMARY KEY)
  • feed_items.id (auto-created for PRIMARY KEY)

8.2 Secondary Indexes

Table Index Purpose
subscriptions idx_subscriptions_url URL lookups
subscriptions idx_subscriptions_category Category filtering
subscriptions idx_subscriptions_next_fetch Sync scheduling
feed_items idx_feed_items_guid Deduplication
feed_items idx_feed_items_subscription Feed-specific items
feed_items idx_feed_items_published Sorting by date
search_history idx_search_history_timestamp History sorting

8.3 FTS Indexes

Virtual Table Purpose
feed_items_fts Full-text search on articles
subscriptions_fts Full-text search on subscriptions

9. Memory Considerations

9.1 Large Content Handling

  • content field can be very large (full article HTML)
  • Use lazy loading for content display
  • Consider compressing content with zlib for storage
  • Limit in-memory cache to N most recent items

9.2 Streaming for Large Result Sets

Use cursor-based pagination:

-- Instead of OFFSET
SELECT * FROM feed_items
WHERE subscription_id = ?
ORDER BY published DESC
LIMIT 20;
-- Use last_published as cursor for next page

10. Future Extensibility

10.1 Schema Versioning

Add version table for migrations:

CREATE TABLE schema_version (
    version INTEGER PRIMARY KEY,
    applied_at INTEGER NOT NULL
);

10.2 Adding New Fields

  • New optional fields: Add column with NULL default
  • New required fields: Use migration with DEFAULT values
  • Breaking changes: New table with shared ID

10.3 Feed Extensions

Support for future feed types:

  • Podcast: Already supported via enclosure
  • Video feeds: Use content type detection
  • Media RSS: Extend enclosure model

11. Implementation Checklist

  • Define UUID type alias for each platform
  • Create Date conversion utilities
  • Implement FeedItem model (struct/class)
  • Implement FeedSubscription model
  • Implement SearchHistoryItem model
  • Create SQLite table definitions
  • Implement FTS5 triggers for feed_items
  • Implement FTS5 triggers for subscriptions
  • Add database indexes
  • Write serialization/deserialization code
  • Implement pagination helpers