Files
CherryHQ-cherry-studio/docs/references/data/data-api-overview.md
fullex 0dc1533aa7 fix(data-api): rebuild useInfiniteQuery and harden pagination guards
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.
2026-04-25 07:33:33 -07:00

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 (ErrorCode enum)
  • DataApiError class 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 / usePaginatedQuery accept either concrete paths (/providers/abc) or template paths with params (/providers/:providerId)
  • Each pagination hook constrains its path generic to the matching pagination shape — mixing cursor and offset paths is a compile-time error
  • refresh option 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: