Fixes #14593 (mutate type collapses to CursorPaginationResponse, erasing subtype fields like BranchMessagesResponse.activeNodeId) and #14594 (default flatMap bakes in a "page-load order == display order" assumption that breaks on branch-walk + column-reverse layouts). Also closes a dual silent-failure where useInfiniteQuery silently accepted offset paths (stuck at page 1) and usePaginatedQuery silently accepted cursor paths (total stays 0). useInfiniteQuery now exposes raw pages: TResponse[] (no items field), preserving subtype fields and typing mutate as SWRInfiniteKeyedMutator correctly. The new useInfiniteFlatItems hook derives the flat list with explicit reversePages / reverseItems switches, so flattening is no longer hidden behind an implicit page-load ordering. Path generics on both hooks are gated via CursorPaginatedPath / OffsetPaginatedPath, built on InferPaginationMode so the optional nextCursor field cannot collapse offset paths into the cursor guard structurally. DEFAULT_SWR_OPTIONS realigned for IPC (not HTTP) semantics: DataApiService is the single retry decision point (shouldRetryOnError: false), reconnect revalidation disabled, keepPreviousData enabled to suppress search/ pagination flicker. loadNext drops its isValidating guard (SWR's dedupingInterval is the right dedup site); usePaginatedQuery reset-on-query-change uses unstable_serialize to be key-order independent. useInfiniteQuery had zero consumers when rewritten — the breaking removal of the items field carries no migration cost. Comprehensive test coverage for type contracts, flat-items behavior, infinite integration, and paginated reset; renderer data docs synced.
10 KiB
DataApi System Overview
The DataApi system provides type-safe IPC communication for business data operations between the Renderer and Main processes.
Purpose
DataApiService handles data that:
- Is business data accumulated through user activity
- Has dedicated database schemas/tables
- Users can create, delete, modify records without fixed limits
- Would be severe and irreplaceable if lost
- Can grow to large volumes (potentially GBs)
What DataApi is NOT For
DataApi must not be used as a general-purpose RPC layer. The following categories of operations belong in traditional IPC handlers (src/main/ipc.ts) or lifecycle services:
- System control: Window management, process control, app configuration changes
- External service integration: OAuth flows, WebDAV/S3 operations, backup/restore workflows
- Imperative commands: Sending notifications, opening URLs, launching external processes
- Stateless queries without database backing: System info, font lists, disk space checks
Why? DataApi's built-in retry, caching, and layered architecture (Handler → Service → SQLite) are designed for data persistence. These features become harmful or meaningless when applied to side-effectful operations. See API Design Guidelines — Scope & Boundaries for detailed anti-patterns.
Key Characteristics
Type-Safe Communication
- End-to-end TypeScript types from client call to handler
- Path parameter inference from route definitions
- Compile-time validation of request/response shapes
RESTful-Style API
- Familiar HTTP semantics (GET, POST, PUT, PATCH, DELETE)
- Resource-based URL patterns (
/topics/:id/messages) - Standard status codes and error responses
On-Demand Data Access
- No automatic caching (fetch fresh data when needed)
- Explicit cache control via query options
- Supports large datasets with pagination
Architecture Diagram
┌────────────────────────────────────────────────────────────┐
│ Renderer Process │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ React Components │ │
│ │ - useQuery('/topics') │ │
│ │ - useMutation('/topics', 'POST') │ │
│ └──────────────────────────┬─────────────────────────────┘ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ DataApiService (Renderer) │ │
│ │ - Type-safe ApiClient interface │ │
│ │ - Request serialization │ │
│ │ - Automatic retry with exponential backoff │ │
│ │ - Error handling and transformation │ │
│ └──────────────────────────┬─────────────────────────────┘ │
└────────────────────────────┼───────────────────────────────┘
│ IPC
┌────────────────────────────┼───────────────────────────────┐
│ Main Process ▼ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ IpcAdapter │ │
│ │ - Receives IPC requests │ │
│ │ - Routes to ApiServer │ │
│ └──────────────────────────┬─────────────────────────────┘ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ ApiServer │ │
│ │ - Request routing by path and method │ │
│ │ - Middleware pipeline processing │ │
│ └──────────────────────────┬─────────────────────────────┘ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Handlers (api/handlers/) │ │
│ │ - Thin layer: extract params, call service, transform │ │
│ │ - NO business logic here │ │
│ └──────────────────────────┬─────────────────────────────┘ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Services (services/) │ │
│ │ - Business logic and validation │ │
│ │ - Transaction coordination │ │
│ │ - Data access via Drizzle ORM │ │
│ └──────────────────────────┬─────────────────────────────┘ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ SQLite Database (via Drizzle ORM) │ │
│ │ - topic, message, file tables │ │
│ │ - Full-text search indexes │ │
│ └────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
Architecture Layers
1. API Layer (Handlers)
- Location:
src/main/data/api/handlers/ - Responsibility: HTTP-like interface layer
- Does: Extract parameters, call services, transform responses
- Does NOT: Contain business logic
2. Service Layer (Services)
- Location:
src/main/data/services/ - Responsibility: Domain logic, workflows, and data access
- Does: Validation, transaction coordination, orchestration, Drizzle ORM queries
Note: In rare cases, a read-only Registry Service (e.g.,
ProviderRegistryService) may exist alongside Entity Services to merge preset data with DB data. See DataApi in Main — Registry Services.
3. Database Layer
- Location:
src/main/data/db/ - Technology: SQLite + Drizzle ORM
- Schemas:
db/schemas/directory
Repository Pattern (Strongly Discouraged)
⚠️ Do NOT create Repository files by default. Services handle both business logic and data access directly via Drizzle ORM. This is an intentional design decision.
Only create a separate Repository when you are 1000% certain it is absolutely necessary — e.g., extremely complex multi-table queries with joins/CTEs that would make the Service unreadable, AND the query logic is reused across multiple services.
If in doubt, keep it in the Service. The overhead of an extra architectural layer is not justified for this project's scale (Electron desktop app + SQLite).
Key Features
Automatic Retry
- Exponential backoff for transient failures
- Configurable retry count and delays
- Skips retry for client errors (4xx)
Error Handling
- Typed error codes (
ErrorCodeenum) DataApiErrorclass with retryability detection- Factory methods for consistent error creation
Request Timeout
- Configurable per-request timeouts
- Automatic cancellation of stale requests
Dynamic Paths & Cache Invalidation
useQuery/useMutation/useInfiniteQuery/usePaginatedQueryaccept either concrete paths (/providers/abc) or template paths withparams(/providers/:providerId)- Each pagination hook constrains its path generic to the matching pagination shape — mixing cursor and offset paths is a compile-time error
refreshoption supports static paths,/*prefix for fan-out, and function form for keys computed from args/result- Details, patterns, and misuse warnings: see DataApi in Renderer → Dynamic Paths & Refresh Patterns
Usage Summary
For detailed code examples, see:
- DataApi in Renderer - Client-side usage
- DataApi in Main - Server-side implementation
- API Design Guidelines - RESTful conventions
- API Types - Type system details