refactor(dataApi): streamline Data API schema and type definitions

- Removed outdated API model and schema files to simplify the structure.
- Consolidated API types and schemas for better organization and clarity.
- Updated import paths across the codebase to reflect the new structure.
- Enhanced documentation in related README files to guide usage of the new API schema organization.
This commit is contained in:
fullex 2025-12-26 12:52:32 +08:00
parent 8292958c0d
commit 18df6085d7
22 changed files with 1331 additions and 1017 deletions

View File

@ -91,9 +91,7 @@
"indexes": {
"entity_tag_tag_id_idx": {
"name": "entity_tag_tag_id_idx",
"columns": [
"tag_id"
],
"columns": ["tag_id"],
"isUnique": false
}
},
@ -102,23 +100,15 @@
"name": "entity_tag_tag_id_tag_id_fk",
"tableFrom": "entity_tag",
"tableTo": "tag",
"columnsFrom": [
"tag_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["tag_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"entity_tag_entity_type_entity_id_tag_id_pk": {
"columns": [
"entity_type",
"entity_id",
"tag_id"
],
"columns": ["entity_type", "entity_id", "tag_id"],
"name": "entity_tag_entity_type_entity_id_tag_id_pk"
}
},
@ -175,10 +165,7 @@
"indexes": {
"group_entity_sort_idx": {
"name": "group_entity_sort_idx",
"columns": [
"entity_type",
"sort_order"
],
"columns": ["entity_type", "sort_order"],
"isUnique": false
}
},
@ -314,24 +301,17 @@
"indexes": {
"message_parent_id_idx": {
"name": "message_parent_id_idx",
"columns": [
"parent_id"
],
"columns": ["parent_id"],
"isUnique": false
},
"message_topic_created_idx": {
"name": "message_topic_created_idx",
"columns": [
"topic_id",
"created_at"
],
"columns": ["topic_id", "created_at"],
"isUnique": false
},
"message_trace_id_idx": {
"name": "message_trace_id_idx",
"columns": [
"trace_id"
],
"columns": ["trace_id"],
"isUnique": false
}
},
@ -340,12 +320,8 @@
"name": "message_topic_id_topic_id_fk",
"tableFrom": "message",
"tableTo": "topic",
"columnsFrom": [
"topic_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["topic_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
},
@ -353,12 +329,8 @@
"name": "message_parent_id_message_id_fk",
"tableFrom": "message",
"tableTo": "message",
"columnsFrom": [
"parent_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["parent_id"],
"columnsTo": ["id"],
"onDelete": "set null",
"onUpdate": "no action"
}
@ -420,10 +392,7 @@
"foreignKeys": {},
"compositePrimaryKeys": {
"preference_scope_key_pk": {
"columns": [
"scope",
"key"
],
"columns": ["scope", "key"],
"name": "preference_scope_key_pk"
}
},
@ -472,9 +441,7 @@
"indexes": {
"tag_name_unique": {
"name": "tag_name_unique",
"columns": [
"name"
],
"columns": ["name"],
"isUnique": true
}
},
@ -585,40 +552,27 @@
"indexes": {
"topic_group_updated_idx": {
"name": "topic_group_updated_idx",
"columns": [
"group_id",
"updated_at"
],
"columns": ["group_id", "updated_at"],
"isUnique": false
},
"topic_group_sort_idx": {
"name": "topic_group_sort_idx",
"columns": [
"group_id",
"sort_order"
],
"columns": ["group_id", "sort_order"],
"isUnique": false
},
"topic_updated_at_idx": {
"name": "topic_updated_at_idx",
"columns": [
"updated_at"
],
"columns": ["updated_at"],
"isUnique": false
},
"topic_is_pinned_idx": {
"name": "topic_is_pinned_idx",
"columns": [
"is_pinned",
"pinned_order"
],
"columns": ["is_pinned", "pinned_order"],
"isUnique": false
},
"topic_assistant_id_idx": {
"name": "topic_assistant_id_idx",
"columns": [
"assistant_id"
],
"columns": ["assistant_id"],
"isUnique": false
}
},
@ -627,12 +581,8 @@
"name": "topic_group_id_group_id_fk",
"tableFrom": "topic",
"tableTo": "group",
"columnsFrom": [
"group_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["group_id"],
"columnsTo": ["id"],
"onDelete": "set null",
"onUpdate": "no action"
}
@ -652,4 +602,4 @@
"internal": {
"indexes": {}
}
}
}

View File

@ -17,4 +17,4 @@
"breakpoints": true
}
]
}
}

View File

@ -2,17 +2,20 @@
This directory contains shared type definitions and schemas for the Cherry Studio data management systems. These files provide type safety and consistency across the entire application.
## 📁 Directory Structure
## Directory Structure
```
packages/shared/data/
├── api/ # Data API type system
│ ├── index.ts # Barrel exports for clean imports
│ ├── apiSchemas.ts # API endpoint definitions and mappings
│ ├── apiTypes.ts # Core request/response infrastructure types
│ ├── apiModels.ts # Business entity types and DTOs
│ ├── apiPaths.ts # API path definitions and utilities
│ └── errorCodes.ts # Standardized error handling
├── api/ # Data API type system (see api/README.md)
│ ├── index.ts # Barrel exports for infrastructure types
│ ├── apiTypes.ts # Core request/response types and utilities
│ ├── apiPaths.ts # Path template literal type utilities
│ ├── errorCodes.ts # Error handling utilities
│ ├── schemas/ # Domain-specific API schemas
│ │ ├── index.ts # Schema composition
│ │ ├── test.ts # Test API schema and DTOs
│ │ └── batch.ts # Batch/transaction operations
│ └── README.md # Detailed API documentation
├── cache/ # Cache system type definitions
│ ├── cacheTypes.ts # Core cache infrastructure types
│ ├── cacheSchemas.ts # Cache key schemas and type mappings
@ -24,7 +27,7 @@ packages/shared/data/
└── README.md # This file
```
## 🏗️ System Overview
## System Overview
This directory provides type definitions for four main data management systems:
@ -35,8 +38,8 @@ This directory provides type definitions for four main data management systems:
### API System (`api/`)
- **Purpose**: Type-safe IPC communication between Main and Renderer processes
- **Features**: RESTful patterns, error handling, business entity definitions
- **Usage**: Ensures type safety for all data API operations
- **Features**: RESTful patterns, modular schema design, error handling
- **Documentation**: See [`api/README.md`](./api/README.md) for detailed usage
### Cache System (`cache/`)
- **Purpose**: Type definitions for three-layer caching architecture
@ -48,7 +51,7 @@ This directory provides type definitions for four main data management systems:
- **Features**: 158 configuration items, default values, nested key support
- **Usage**: Type-safe preference access and synchronization
## 📋 File Categories
## File Categories
**Framework Infrastructure** - These are TypeScript type definitions that:
- ✅ Exist only at compile time
@ -56,12 +59,16 @@ This directory provides type definitions for four main data management systems:
- ✅ Define contracts between application layers
- ✅ Enable static analysis and error detection
## 📖 Usage Examples
## Usage Examples
### API Types
```typescript
// Import API types
import type { DataRequest, DataResponse, ApiSchemas } from '@shared/data/api'
// Infrastructure types from barrel export
import type { DataRequest, DataResponse, ApiClient } from '@shared/data/api'
import { DataApiErrorFactory, ErrorCode } from '@shared/data/api'
// Domain DTOs directly from schema files
import type { TestItem, CreateTestItemDto } from '@shared/data/api/schemas/test'
```
### Cache Types
@ -76,7 +83,7 @@ import type { UseCacheKey, UseSharedCacheKey } from '@shared/data/cache'
import type { PreferenceKeyType, PreferenceDefaultScopeType } from '@shared/data/preference'
```
## 🔧 Development Guidelines
## Development Guidelines
### Adding Shared Types
1. Create or update type file in `types/` directory
@ -94,24 +101,25 @@ import type { PreferenceKeyType, PreferenceDefaultScopeType } from '@shared/data
3. Preference system automatically picks up new keys
### Adding API Types
1. Define business entities in `api/apiModels.ts`
2. Add endpoint definitions to `api/apiSchemas.ts`
3. Export types from `api/index.ts`
1. Create schema file in `api/schemas/` (e.g., `topic.ts`)
2. Define domain models, DTOs, and API schema in the file
3. Register schema in `api/schemas/index.ts` using intersection type
4. See [`api/README.md`](./api/README.md) for detailed guide
### Best Practices
- Use `import type` for type-only imports
- Infrastructure types from barrel, domain DTOs from schema files
- Follow existing naming conventions
- Document complex types with JSDoc
- Maintain type safety across all imports
## 🔗 Related Implementation
## Related Implementation
### Main Process Services
- `src/main/data/CacheService.ts` - Main process cache management
- `src/main/data/PreferenceService.ts` - Preference management service
- `src/main/data/DataApiService.ts` - Data API coordination service
### Main Process
- `src/main/data/api/` - API server, handlers, and IPC adapter
- `src/main/data/cache/` - Cache service implementation
- `src/main/data/preference/` - Preference service implementation
### Renderer Process Services
- `src/renderer/src/data/CacheService.ts` - Renderer cache service
- `src/renderer/src/data/PreferenceService.ts` - Renderer preference service
- `src/renderer/src/data/DataApiService.ts` - Renderer API client
### Renderer Process
- `src/renderer/src/services/DataApiService.ts` - API client
- `src/renderer/src/services/CacheService.ts` - Cache service
- `src/renderer/src/services/PreferenceService.ts` - Preference service

View File

@ -0,0 +1,189 @@
# Data API Type System
This directory contains the type definitions and utilities for Cherry Studio's Data API system, which provides type-safe IPC communication between renderer and main processes.
## Directory Structure
```
packages/shared/data/api/
├── index.ts # Barrel export for infrastructure types
├── apiTypes.ts # Core request/response types and API utilities
├── apiPaths.ts # Path template literal type utilities
├── errorCodes.ts # Error handling utilities and factories
└── schemas/
├── index.ts # Schema composition (merges all domain schemas)
├── test.ts # Test API schema and DTOs
└── batch.ts # Batch/transaction API schema
```
## File Responsibilities
| File | Purpose |
|------|---------|
| `apiTypes.ts` | Core types (`DataRequest`, `DataResponse`, `ApiClient`) and schema utilities |
| `apiPaths.ts` | Template literal types for path resolution (`/items/:id` → `/items/${string}`) |
| `errorCodes.ts` | `DataApiErrorFactory`, error codes, and error handling utilities |
| `index.ts` | Unified export of infrastructure types (not domain DTOs) |
| `schemas/index.ts` | Composes all domain schemas into `ApiSchemas` using intersection types |
| `schemas/*.ts` | Domain-specific API definitions and DTOs |
## Import Conventions
### Infrastructure Types (via barrel export)
Use the barrel export for common API infrastructure:
```typescript
import type {
DataRequest,
DataResponse,
ApiClient,
PaginatedResponse,
ErrorCode
} from '@shared/data/api'
import { DataApiErrorFactory, isDataApiError } from '@shared/data/api'
```
### Domain DTOs (directly from schema files)
Import domain-specific types directly from their schema files:
```typescript
// Topic domain
import type { Topic, CreateTopicDto, UpdateTopicDto } from '@shared/data/api/schemas/topic'
// Message domain
import type { Message, CreateMessageDto } from '@shared/data/api/schemas/message'
// Test domain (development)
import type { TestItem, CreateTestItemDto } from '@shared/data/api/schemas/test'
```
## Adding a New Domain Schema
1. Create the schema file (e.g., `schemas/topic.ts`):
```typescript
import type { PaginatedResponse } from '../apiTypes'
// Domain models
export interface Topic {
id: string
name: string
createdAt: string
}
export interface CreateTopicDto {
name: string
}
// API Schema - validation happens via AssertValidSchemas in index.ts
export interface TopicSchemas {
'/topics': {
GET: {
response: PaginatedResponse<Topic> // response is required
}
POST: {
body: CreateTopicDto
response: Topic
}
}
'/topics/:id': {
GET: {
params: { id: string }
response: Topic
}
}
}
```
**Validation**: Schemas are validated at composition level via `AssertValidSchemas` in `schemas/index.ts`:
- Ensures only valid HTTP methods (GET, POST, PUT, DELETE, PATCH)
- Requires `response` field for each endpoint
- Invalid schemas cause TypeScript errors at the composition point
2. Register in `schemas/index.ts`:
```typescript
import type { TopicSchemas } from './topic'
// AssertValidSchemas provides fallback validation even if ValidateSchema is forgotten
export type ApiSchemas = AssertValidSchemas<TestSchemas & BatchSchemas & TopicSchemas>
```
3. Implement handlers in `src/main/data/api/handlers/`
## Type Safety Features
### Path Resolution
The system uses template literal types to map concrete paths to schema paths:
```typescript
// Concrete path '/topics/abc123' maps to schema path '/topics/:id'
api.get('/topics/abc123') // TypeScript knows this returns Topic
```
### Exhaustive Handler Checking
`ApiImplementation` type ensures all schema endpoints have handlers:
```typescript
// TypeScript will error if any endpoint is missing
const handlers: ApiImplementation = {
'/topics': {
GET: async () => { /* ... */ },
POST: async ({ body }) => { /* ... */ }
}
// Missing '/topics/:id' would cause compile error
}
```
### Type-Safe Client
`ApiClient` provides fully typed methods:
```typescript
const topic = await api.get('/topics/123') // Returns Topic
const topics = await api.get('/topics', { query: { page: 1 } }) // Returns PaginatedResponse<Topic>
await api.post('/topics', { body: { name: 'New' } }) // Body is typed as CreateTopicDto
```
## Error Handling
Use `DataApiErrorFactory` for consistent error creation:
```typescript
import { DataApiErrorFactory, ErrorCode } from '@shared/data/api'
// Create errors
throw DataApiErrorFactory.notFound('Topic', id)
throw DataApiErrorFactory.validationError('Name is required')
throw DataApiErrorFactory.fromCode(ErrorCode.DATABASE_ERROR, 'Connection failed')
// Check errors
if (isDataApiError(error)) {
console.log(error.code, error.status)
}
```
## Architecture Overview
```
Renderer Main
────────────────────────────────────────────────────
DataApiService ──IPC──► IpcAdapter ──► ApiServer
│ │
│ ▼
ApiClient MiddlewareEngine
(typed) │
Handlers
(typed)
```
- **Renderer**: Uses `DataApiService` with type-safe `ApiClient` interface
- **IPC**: Requests serialized via `IpcAdapter`
- **Main**: `ApiServer` routes to handlers through `MiddlewareEngine`
- **Type Safety**: End-to-end types from client call to handler implementation

View File

@ -1,107 +0,0 @@
/**
* Generic test model definitions
* Contains flexible types for comprehensive API testing
*/
/**
* Generic test item entity - flexible structure for testing various scenarios
*/
export interface TestItem {
/** Unique identifier */
id: string
/** Item title */
title: string
/** Optional description */
description?: string
/** Type category */
type: string
/** Current status */
status: string
/** Priority level */
priority: string
/** Associated tags */
tags: string[]
/** Creation timestamp */
createdAt: string
/** Last update timestamp */
updatedAt: string
/** Additional metadata */
metadata: Record<string, any>
}
/**
* Data Transfer Objects (DTOs) for test operations
*/
/**
* DTO for creating a new test item
*/
export interface CreateTestItemDto {
/** Item title */
title: string
/** Optional description */
description?: string
/** Type category */
type?: string
/** Current status */
status?: string
/** Priority level */
priority?: string
/** Associated tags */
tags?: string[]
/** Additional metadata */
metadata?: Record<string, any>
}
/**
* DTO for updating an existing test item
*/
export interface UpdateTestItemDto {
/** Updated title */
title?: string
/** Updated description */
description?: string
/** Updated type */
type?: string
/** Updated status */
status?: string
/** Updated priority */
priority?: string
/** Updated tags */
tags?: string[]
/** Updated metadata */
metadata?: Record<string, any>
}
/**
* Bulk operation types for batch processing
*/
/**
* Request for bulk operations on multiple items
*/
export interface BulkOperationRequest<TData = any> {
/** Type of bulk operation to perform */
operation: 'create' | 'update' | 'delete' | 'archive' | 'restore'
/** Array of data items to process */
data: TData[]
}
/**
* Response from a bulk operation
*/
export interface BulkOperationResponse {
/** Number of successfully processed items */
successful: number
/** Number of items that failed processing */
failed: number
/** Array of errors that occurred during processing */
errors: Array<{
/** Index of the item that failed */
index: number
/** Error message */
error: string
/** Optional additional error data */
data?: any
}>
}

View File

@ -1,4 +1,4 @@
import type { ApiSchemas } from './apiSchemas'
import type { ApiSchemas } from './schemas'
/**
* Template literal type utilities for converting parameterized paths to concrete paths

View File

@ -1,487 +0,0 @@
// NOTE: Types are defined inline in the schema for simplicity
// If needed, specific types can be imported from './apiModels'
import type { BodyForPath, ConcreteApiPaths, QueryParamsForPath, ResponseForPath } from './apiPaths'
import type { HttpMethod, PaginatedResponse, PaginationParams } from './apiTypes'
// Re-export for external use
export type { ConcreteApiPaths } from './apiPaths'
/**
* Complete API Schema definitions for Test API
*
* Each path defines the supported HTTP methods with their:
* - Request parameters (params, query, body)
* - Response types
* - Type safety guarantees
*
* This schema serves as the contract between renderer and main processes,
* enabling full TypeScript type checking across IPC boundaries.
*/
export interface ApiSchemas {
/**
* Test items collection endpoint
* @example GET /test/items?page=1&limit=10&search=hello
* @example POST /test/items { "title": "New Test Item" }
*/
'/test/items': {
/** List all test items with optional filtering and pagination */
GET: {
query?: PaginationParams & {
/** Search items by title or description */
search?: string
/** Filter by item type */
type?: string
/** Filter by status */
status?: string
}
response: PaginatedResponse<any>
}
/** Create a new test item */
POST: {
body: {
title: string
description?: string
type?: string
status?: string
priority?: string
tags?: string[]
metadata?: Record<string, any>
}
response: any
}
}
/**
* Individual test item endpoint
* @example GET /test/items/123
* @example PUT /test/items/123 { "title": "Updated Title" }
* @example DELETE /test/items/123
*/
'/test/items/:id': {
/** Get a specific test item by ID */
GET: {
params: { id: string }
response: any
}
/** Update a specific test item */
PUT: {
params: { id: string }
body: {
title?: string
description?: string
type?: string
status?: string
priority?: string
tags?: string[]
metadata?: Record<string, any>
}
response: any
}
/** Delete a specific test item */
DELETE: {
params: { id: string }
response: void
}
}
/**
* Test search endpoint
* @example GET /test/search?query=hello&page=1&limit=20
*/
'/test/search': {
/** Search test items */
GET: {
query: {
/** Search query string */
query: string
/** Page number for pagination */
page?: number
/** Number of results per page */
limit?: number
/** Additional filters */
type?: string
status?: string
}
response: PaginatedResponse<any>
}
}
/**
* Test statistics endpoint
* @example GET /test/stats
*/
'/test/stats': {
/** Get comprehensive test statistics */
GET: {
response: {
/** Total number of items */
total: number
/** Item count grouped by type */
byType: Record<string, number>
/** Item count grouped by status */
byStatus: Record<string, number>
/** Item count grouped by priority */
byPriority: Record<string, number>
/** Recent activity timeline */
recentActivity: Array<{
/** Date of activity */
date: string
/** Number of items on that date */
count: number
}>
}
}
}
/**
* Test bulk operations endpoint
* @example POST /test/bulk { "operation": "create", "data": [...] }
*/
'/test/bulk': {
/** Perform bulk operations on test items */
POST: {
body: {
/** Operation type */
operation: 'create' | 'update' | 'delete'
/** Array of data items to process */
data: any[]
}
response: {
successful: number
failed: number
errors: string[]
}
}
}
/**
* Test error simulation endpoint
* @example POST /test/error { "errorType": "timeout" }
*/
'/test/error': {
/** Simulate various error scenarios for testing */
POST: {
body: {
/** Type of error to simulate */
errorType:
| 'timeout'
| 'network'
| 'server'
| 'notfound'
| 'validation'
| 'unauthorized'
| 'ratelimit'
| 'generic'
}
response: never
}
}
/**
* Test slow response endpoint
* @example POST /test/slow { "delay": 2000 }
*/
'/test/slow': {
/** Test slow response for performance testing */
POST: {
body: {
/** Delay in milliseconds */
delay: number
}
response: {
message: string
delay: number
timestamp: string
}
}
}
/**
* Test data reset endpoint
* @example POST /test/reset
*/
'/test/reset': {
/** Reset all test data to initial state */
POST: {
response: {
message: string
timestamp: string
}
}
}
/**
* Test config endpoint
* @example GET /test/config
* @example PUT /test/config { "setting": "value" }
*/
'/test/config': {
/** Get test configuration */
GET: {
response: Record<string, any>
}
/** Update test configuration */
PUT: {
body: Record<string, any>
response: Record<string, any>
}
}
/**
* Test status endpoint
* @example GET /test/status
*/
'/test/status': {
/** Get system test status */
GET: {
response: {
status: string
timestamp: string
version: string
uptime: number
environment: string
}
}
}
/**
* Test performance endpoint
* @example GET /test/performance
*/
'/test/performance': {
/** Get performance metrics */
GET: {
response: {
requestsPerSecond: number
averageLatency: number
memoryUsage: number
cpuUsage: number
uptime: number
}
}
}
/**
* Batch execution of multiple requests
* @example POST /batch { "requests": [...], "parallel": true }
*/
'/batch': {
/** Execute multiple API requests in a single call */
POST: {
body: {
/** Array of requests to execute */
requests: Array<{
/** HTTP method for the request */
method: HttpMethod
/** API path for the request */
path: string
/** URL parameters */
params?: any
/** Request body */
body?: any
}>
/** Execute requests in parallel vs sequential */
parallel?: boolean
}
response: {
/** Results array matching input order */
results: Array<{
/** HTTP status code */
status: number
/** Response data if successful */
data?: any
/** Error information if failed */
error?: any
}>
/** Batch execution metadata */
metadata: {
/** Total execution duration in ms */
duration: number
/** Number of successful requests */
successCount: number
/** Number of failed requests */
errorCount: number
}
}
}
}
/**
* Atomic transaction of multiple operations
* @example POST /transaction { "operations": [...], "options": { "rollbackOnError": true } }
*/
'/transaction': {
/** Execute multiple operations in a database transaction */
POST: {
body: {
/** Array of operations to execute atomically */
operations: Array<{
/** HTTP method for the operation */
method: HttpMethod
/** API path for the operation */
path: string
/** URL parameters */
params?: any
/** Request body */
body?: any
}>
/** Transaction configuration options */
options?: {
/** Database isolation level */
isolation?: 'read-uncommitted' | 'read-committed' | 'repeatable-read' | 'serializable'
/** Rollback all operations on any error */
rollbackOnError?: boolean
/** Transaction timeout in milliseconds */
timeout?: number
}
}
response: Array<{
/** HTTP status code */
status: number
/** Response data if successful */
data?: any
/** Error information if failed */
error?: any
}>
}
}
}
/**
* Simplified type extraction helpers
*/
export type ApiPaths = keyof ApiSchemas
export type ApiMethods<TPath extends ApiPaths> = keyof ApiSchemas[TPath] & HttpMethod
export type ApiResponse<TPath extends ApiPaths, TMethod extends string> = TPath extends keyof ApiSchemas
? TMethod extends keyof ApiSchemas[TPath]
? ApiSchemas[TPath][TMethod] extends { response: infer R }
? R
: never
: never
: never
export type ApiParams<TPath extends ApiPaths, TMethod extends string> = TPath extends keyof ApiSchemas
? TMethod extends keyof ApiSchemas[TPath]
? ApiSchemas[TPath][TMethod] extends { params: infer P }
? P
: never
: never
: never
export type ApiQuery<TPath extends ApiPaths, TMethod extends string> = TPath extends keyof ApiSchemas
? TMethod extends keyof ApiSchemas[TPath]
? ApiSchemas[TPath][TMethod] extends { query: infer Q }
? Q
: never
: never
: never
export type ApiBody<TPath extends ApiPaths, TMethod extends string> = TPath extends keyof ApiSchemas
? TMethod extends keyof ApiSchemas[TPath]
? ApiSchemas[TPath][TMethod] extends { body: infer B }
? B
: never
: never
: never
/**
* Type-safe API client interface using concrete paths
* Accepts actual paths like '/test/items/123' instead of '/test/items/:id'
* Automatically infers query, body, and response types from ApiSchemas
*/
export interface ApiClient {
get<TPath extends ConcreteApiPaths>(
path: TPath,
options?: {
query?: QueryParamsForPath<TPath>
headers?: Record<string, string>
}
): Promise<ResponseForPath<TPath, 'GET'>>
post<TPath extends ConcreteApiPaths>(
path: TPath,
options: {
body?: BodyForPath<TPath, 'POST'>
query?: Record<string, any>
headers?: Record<string, string>
}
): Promise<ResponseForPath<TPath, 'POST'>>
put<TPath extends ConcreteApiPaths>(
path: TPath,
options: {
body: BodyForPath<TPath, 'PUT'>
query?: Record<string, any>
headers?: Record<string, string>
}
): Promise<ResponseForPath<TPath, 'PUT'>>
delete<TPath extends ConcreteApiPaths>(
path: TPath,
options?: {
query?: Record<string, any>
headers?: Record<string, string>
}
): Promise<ResponseForPath<TPath, 'DELETE'>>
patch<TPath extends ConcreteApiPaths>(
path: TPath,
options: {
body?: BodyForPath<TPath, 'PATCH'>
query?: Record<string, any>
headers?: Record<string, string>
}
): Promise<ResponseForPath<TPath, 'PATCH'>>
}
/**
* Helper types to determine if parameters are required based on schema
*/
type HasRequiredQuery<Path extends ApiPaths, Method extends ApiMethods<Path>> = Path extends keyof ApiSchemas
? Method extends keyof ApiSchemas[Path]
? ApiSchemas[Path][Method] extends { query: any }
? true
: false
: false
: false
type HasRequiredBody<Path extends ApiPaths, Method extends ApiMethods<Path>> = Path extends keyof ApiSchemas
? Method extends keyof ApiSchemas[Path]
? ApiSchemas[Path][Method] extends { body: any }
? true
: false
: false
: false
type HasRequiredParams<Path extends ApiPaths, Method extends ApiMethods<Path>> = Path extends keyof ApiSchemas
? Method extends keyof ApiSchemas[Path]
? ApiSchemas[Path][Method] extends { params: any }
? true
: false
: false
: false
/**
* Handler function for a specific API endpoint
* Provides type-safe parameter extraction based on ApiSchemas
* Parameters are required or optional based on the schema definition
*/
export type ApiHandler<Path extends ApiPaths, Method extends ApiMethods<Path>> = (
params: (HasRequiredParams<Path, Method> extends true
? { params: ApiParams<Path, Method> }
: { params?: ApiParams<Path, Method> }) &
(HasRequiredQuery<Path, Method> extends true
? { query: ApiQuery<Path, Method> }
: { query?: ApiQuery<Path, Method> }) &
(HasRequiredBody<Path, Method> extends true ? { body: ApiBody<Path, Method> } : { body?: ApiBody<Path, Method> })
) => Promise<ApiResponse<Path, Method>>
/**
* Complete API implementation that must match ApiSchemas structure
* TypeScript will error if any endpoint is missing - this ensures exhaustive coverage
*/
export type ApiImplementation = {
[Path in ApiPaths]: {
[Method in ApiMethods<Path>]: ApiHandler<Path, Method>
}
}

View File

@ -8,6 +8,75 @@
*/
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
// ============================================================================
// Schema Constraint Types
// ============================================================================
/**
* Constraint for a single endpoint method definition.
* Requires `response` field, allows optional `params`, `query`, and `body`.
*/
export type EndpointMethodConstraint = {
params?: Record<string, any>
query?: Record<string, any>
body?: any
response: any // response is required
}
/**
* Constraint for a single API path - only allows valid HTTP methods.
*/
export type EndpointConstraint = {
[Method in HttpMethod]?: EndpointMethodConstraint
}
/**
* Validates that a schema only contains valid HTTP methods.
* Used in AssertValidSchemas for compile-time validation.
*/
type ValidateMethods<T> = {
[Path in keyof T]: {
[Method in keyof T[Path]]: Method extends HttpMethod ? T[Path][Method] : never
}
}
/**
* Validates that all endpoints have a `response` field.
* Returns the original type if valid, or `never` if any endpoint lacks response.
*/
type ValidateResponses<T> = {
[Path in keyof T]: {
[Method in keyof T[Path]]: T[Path][Method] extends { response: any }
? T[Path][Method]
: { error: `Endpoint ${Path & string}.${Method & string} is missing 'response' field` }
}
}
/**
* Validates that a schema conforms to expected structure:
* 1. All methods must be valid HTTP methods (GET, POST, PUT, DELETE, PATCH)
* 2. All endpoints must have a `response` field
*
* This is applied at the composition level (schemas/index.ts) to catch
* invalid schemas even if individual schema files don't use validation.
*
* @example
* ```typescript
* // In schemas/index.ts
* export type ApiSchemas = AssertValidSchemas<TestSchemas & BatchSchemas>
*
* // Invalid method will cause error:
* // Type 'never' is not assignable to type...
* ```
*/
export type AssertValidSchemas<T> = ValidateMethods<T> & ValidateResponses<T> extends infer R
? { [K in keyof R]: R[K] }
: never
// ============================================================================
// Core Request/Response Types
// ============================================================================
/**
* Request object structure for Data API calls
*/
@ -30,8 +99,6 @@ export interface DataRequest<T = any> {
timestamp: number
/** OpenTelemetry span context for tracing */
spanContext?: any
/** Cache options for this specific request */
cache?: CacheOptions
}
}
@ -101,22 +168,6 @@ export enum ErrorCode {
CONCURRENT_MODIFICATION = 'CONCURRENT_MODIFICATION'
}
/**
* Cache configuration options
*/
export interface CacheOptions {
/** Cache TTL in seconds */
ttl?: number
/** Return stale data while revalidating in background */
staleWhileRevalidate?: boolean
/** Custom cache key override */
cacheKey?: string
/** Operations that should invalidate this cache entry */
invalidateOn?: string[]
/** Whether to bypass cache entirely */
noCache?: boolean
}
/**
* Transaction request wrapper for atomic operations
*/
@ -274,16 +325,169 @@ export interface ServiceOptions {
metadata?: Record<string, any>
}
// ============================================================================
// API Schema Type Utilities
// ============================================================================
import type { BodyForPath, ConcreteApiPaths, QueryParamsForPath, ResponseForPath } from './apiPaths'
import type { ApiSchemas } from './schemas'
// Re-export for external use
export type { ConcreteApiPaths } from './apiPaths'
export type { ApiSchemas } from './schemas'
/**
* Standard service response wrapper
* All available API paths
*/
export interface ServiceResult<T = any> {
/** Whether operation was successful */
success: boolean
/** Result data if successful */
data?: T
/** Error information if failed */
error?: DataApiError
/** Additional metadata */
metadata?: Record<string, any>
export type ApiPaths = keyof ApiSchemas
/**
* Available HTTP methods for a specific path
*/
export type ApiMethods<TPath extends ApiPaths> = keyof ApiSchemas[TPath] & HttpMethod
/**
* Response type for a specific path and method
*/
export type ApiResponse<TPath extends ApiPaths, TMethod extends string> = TPath extends keyof ApiSchemas
? TMethod extends keyof ApiSchemas[TPath]
? ApiSchemas[TPath][TMethod] extends { response: infer R }
? R
: never
: never
: never
/**
* URL params type for a specific path and method
*/
export type ApiParams<TPath extends ApiPaths, TMethod extends string> = TPath extends keyof ApiSchemas
? TMethod extends keyof ApiSchemas[TPath]
? ApiSchemas[TPath][TMethod] extends { params: infer P }
? P
: never
: never
: never
/**
* Query params type for a specific path and method
*/
export type ApiQuery<TPath extends ApiPaths, TMethod extends string> = TPath extends keyof ApiSchemas
? TMethod extends keyof ApiSchemas[TPath]
? ApiSchemas[TPath][TMethod] extends { query: infer Q }
? Q
: never
: never
: never
/**
* Request body type for a specific path and method
*/
export type ApiBody<TPath extends ApiPaths, TMethod extends string> = TPath extends keyof ApiSchemas
? TMethod extends keyof ApiSchemas[TPath]
? ApiSchemas[TPath][TMethod] extends { body: infer B }
? B
: never
: never
: never
/**
* Type-safe API client interface using concrete paths
* Accepts actual paths like '/test/items/123' instead of '/test/items/:id'
* Automatically infers query, body, and response types from ApiSchemas
*/
export interface ApiClient {
get<TPath extends ConcreteApiPaths>(
path: TPath,
options?: {
query?: QueryParamsForPath<TPath>
headers?: Record<string, string>
}
): Promise<ResponseForPath<TPath, 'GET'>>
post<TPath extends ConcreteApiPaths>(
path: TPath,
options: {
body?: BodyForPath<TPath, 'POST'>
query?: Record<string, any>
headers?: Record<string, string>
}
): Promise<ResponseForPath<TPath, 'POST'>>
put<TPath extends ConcreteApiPaths>(
path: TPath,
options: {
body: BodyForPath<TPath, 'PUT'>
query?: Record<string, any>
headers?: Record<string, string>
}
): Promise<ResponseForPath<TPath, 'PUT'>>
delete<TPath extends ConcreteApiPaths>(
path: TPath,
options?: {
query?: Record<string, any>
headers?: Record<string, string>
}
): Promise<ResponseForPath<TPath, 'DELETE'>>
patch<TPath extends ConcreteApiPaths>(
path: TPath,
options: {
body?: BodyForPath<TPath, 'PATCH'>
query?: Record<string, any>
headers?: Record<string, string>
}
): Promise<ResponseForPath<TPath, 'PATCH'>>
}
/**
* Helper types to determine if parameters are required based on schema
*/
type HasRequiredQuery<Path extends ApiPaths, Method extends ApiMethods<Path>> = Path extends keyof ApiSchemas
? Method extends keyof ApiSchemas[Path]
? ApiSchemas[Path][Method] extends { query: any }
? true
: false
: false
: false
type HasRequiredBody<Path extends ApiPaths, Method extends ApiMethods<Path>> = Path extends keyof ApiSchemas
? Method extends keyof ApiSchemas[Path]
? ApiSchemas[Path][Method] extends { body: any }
? true
: false
: false
: false
type HasRequiredParams<Path extends ApiPaths, Method extends ApiMethods<Path>> = Path extends keyof ApiSchemas
? Method extends keyof ApiSchemas[Path]
? ApiSchemas[Path][Method] extends { params: any }
? true
: false
: false
: false
/**
* Handler function for a specific API endpoint
* Provides type-safe parameter extraction based on ApiSchemas
* Parameters are required or optional based on the schema definition
*/
export type ApiHandler<Path extends ApiPaths, Method extends ApiMethods<Path>> = (
params: (HasRequiredParams<Path, Method> extends true
? { params: ApiParams<Path, Method> }
: { params?: ApiParams<Path, Method> }) &
(HasRequiredQuery<Path, Method> extends true
? { query: ApiQuery<Path, Method> }
: { query?: ApiQuery<Path, Method> }) &
(HasRequiredBody<Path, Method> extends true ? { body: ApiBody<Path, Method> } : { body?: ApiBody<Path, Method> })
) => Promise<ApiResponse<Path, Method>>
/**
* Complete API implementation that must match ApiSchemas structure
* TypeScript will error if any endpoint is missing - this ensures exhaustive coverage
*/
export type ApiImplementation = {
[Path in ApiPaths]: {
[Method in ApiMethods<Path>]: ApiHandler<Path, Method>
}
}

View File

@ -1,70 +1,71 @@
/**
* Cherry Studio Data API - Barrel Exports
*
* This file provides a centralized entry point for all data API types,
* schemas, and utilities. Import everything you need from this single location.
* Exports common infrastructure types for the Data API system.
* Domain-specific DTOs should be imported directly from their schema files.
*
* @example
* ```typescript
* import { Topic, CreateTopicDto, ApiSchemas, DataRequest, ErrorCode } from '@/shared/data'
* // Infrastructure types from barrel export
* import { DataRequest, DataResponse, ErrorCode, ApiClient } from '@shared/data/api'
*
* // Domain DTOs from schema files directly
* import type { Topic, CreateTopicDto } from '@shared/data/api/schemas/topic'
* ```
*/
// Core data API types and infrastructure
// ============================================================================
// Core Request/Response Types
// ============================================================================
export type {
BatchRequest,
BatchResponse,
CacheOptions,
DataApiError,
DataRequest,
DataResponse,
HttpMethod,
Middleware,
PaginatedResponse,
PaginationParams,
RequestContext,
ServiceOptions,
ServiceResult,
SubscriptionCallback,
SubscriptionOptions,
TransactionRequest
} from './apiTypes'
export { ErrorCode, SubscriptionEvent } from './apiTypes'
// Domain models and DTOs
export type {
BulkOperationRequest,
BulkOperationResponse,
CreateTestItemDto,
TestItem,
UpdateTestItemDto
} from './apiModels'
// ============================================================================
// API Schema Type Utilities
// ============================================================================
// API schema definitions and type helpers
export type {
ApiBody,
ApiClient,
ApiHandler,
ApiImplementation,
ApiMethods,
ApiParams,
ApiPaths,
ApiQuery,
ApiResponse,
ApiSchemas
} from './apiSchemas'
ApiSchemas,
ConcreteApiPaths
} from './apiTypes'
// ============================================================================
// Path Resolution Utilities
// ============================================================================
// Path type utilities for template literal types
export type {
BodyForPath,
ConcreteApiPaths,
MatchApiPath,
QueryParamsForPath,
ResolvedPath,
ResponseForPath
} from './apiPaths'
// Error handling utilities
// ============================================================================
// Error Handling
// ============================================================================
export { ErrorCode, SubscriptionEvent } from './apiTypes'
export {
ErrorCode as DataApiErrorCode,
DataApiErrorFactory,
ERROR_MESSAGES,
ERROR_STATUS_MAP,
@ -72,50 +73,14 @@ export {
toDataApiError
} from './errorCodes'
/**
* Re-export commonly used type combinations for convenience
*/
// ============================================================================
// Subscription & Middleware (for advanced usage)
// ============================================================================
// Import types for re-export convenience types
import type { CreateTestItemDto, TestItem, UpdateTestItemDto } from './apiModels'
import type {
BatchRequest,
BatchResponse,
DataApiError,
DataRequest,
DataResponse,
ErrorCode,
PaginatedResponse,
PaginationParams,
TransactionRequest
export type {
Middleware,
RequestContext,
ServiceOptions,
SubscriptionCallback,
SubscriptionOptions
} from './apiTypes'
import type { DataApiErrorFactory } from './errorCodes'
/** All test item-related types */
export type TestItemTypes = {
TestItem: TestItem
CreateTestItemDto: CreateTestItemDto
UpdateTestItemDto: UpdateTestItemDto
}
/** All error-related types and utilities */
export type ErrorTypes = {
DataApiError: DataApiError
ErrorCode: ErrorCode
ErrorFactory: typeof DataApiErrorFactory
}
/** All request/response types */
export type RequestTypes = {
DataRequest: DataRequest
DataResponse: DataResponse
BatchRequest: BatchRequest
BatchResponse: BatchResponse
TransactionRequest: TransactionRequest
}
/** All pagination-related types */
export type PaginationTypes = {
PaginationParams: PaginationParams
PaginatedResponse: PaginatedResponse<any>
}

View File

@ -0,0 +1,140 @@
/**
* Batch and Transaction API Schema definitions
*
* Contains cross-domain operations for batch processing and atomic transactions.
* These endpoints are domain-agnostic and work with any API path.
*/
import type { HttpMethod } from '../apiTypes'
// ============================================================================
// Domain Models & DTOs
// ============================================================================
/**
* Request for bulk operations on multiple items
*/
export interface BulkOperationRequest<TData = any> {
/** Type of bulk operation to perform */
operation: 'create' | 'update' | 'delete' | 'archive' | 'restore'
/** Array of data items to process */
data: TData[]
}
/**
* Response from a bulk operation
*/
export interface BulkOperationResponse {
/** Number of successfully processed items */
successful: number
/** Number of items that failed processing */
failed: number
/** Array of errors that occurred during processing */
errors: Array<{
/** Index of the item that failed */
index: number
/** Error message */
error: string
/** Optional additional error data */
data?: any
}>
}
// ============================================================================
// API Schema Definitions
// ============================================================================
/**
* Batch and Transaction API Schema definitions
*
* Validation is performed at composition level via AssertValidSchemas
* in schemas/index.ts, which ensures:
* - All methods are valid HTTP methods (GET, POST, PUT, DELETE, PATCH)
* - All endpoints have a `response` field
*/
export interface BatchSchemas {
/**
* Batch execution of multiple requests
* @example POST /batch { "requests": [...], "parallel": true }
*/
'/batch': {
/** Execute multiple API requests in a single call */
POST: {
body: {
/** Array of requests to execute */
requests: Array<{
/** HTTP method for the request */
method: HttpMethod
/** API path for the request */
path: string
/** URL parameters */
params?: any
/** Request body */
body?: any
}>
/** Execute requests in parallel vs sequential */
parallel?: boolean
}
response: {
/** Results array matching input order */
results: Array<{
/** HTTP status code */
status: number
/** Response data if successful */
data?: any
/** Error information if failed */
error?: any
}>
/** Batch execution metadata */
metadata: {
/** Total execution duration in ms */
duration: number
/** Number of successful requests */
successCount: number
/** Number of failed requests */
errorCount: number
}
}
}
}
/**
* Atomic transaction of multiple operations
* @example POST /transaction { "operations": [...], "options": { "rollbackOnError": true } }
*/
'/transaction': {
/** Execute multiple operations in a database transaction */
POST: {
body: {
/** Array of operations to execute atomically */
operations: Array<{
/** HTTP method for the operation */
method: HttpMethod
/** API path for the operation */
path: string
/** URL parameters */
params?: any
/** Request body */
body?: any
}>
/** Transaction configuration options */
options?: {
/** Database isolation level */
isolation?: 'read-uncommitted' | 'read-committed' | 'repeatable-read' | 'serializable'
/** Rollback all operations on any error */
rollbackOnError?: boolean
/** Transaction timeout in milliseconds */
timeout?: number
}
}
response: Array<{
/** HTTP status code */
status: number
/** Response data if successful */
data?: any
/** Error information if failed */
error?: any
}>
}
}
}

View File

@ -0,0 +1,43 @@
/**
* Schema Index - Composes all domain schemas into unified ApiSchemas
*
* This file has ONE responsibility: compose domain schemas into ApiSchemas.
*
* Import conventions (see api/README.md for details):
* - Infrastructure types: import from '@shared/data/api'
* - Domain DTOs: import directly from schema files (e.g., '@shared/data/api/schemas/topic')
*
* @example
* ```typescript
* // Infrastructure types via barrel export
* import type { ApiSchemas, DataRequest } from '@shared/data/api'
*
* // Domain DTOs directly from schema files
* import type { TestItem, CreateTestItemDto } from '@shared/data/api/schemas/test'
* import type { Topic, CreateTopicDto } from '@shared/data/api/schemas/topic'
* ```
*/
import type { AssertValidSchemas } from '../apiTypes'
import type { BatchSchemas } from './batch'
import type { TestSchemas } from './test'
/**
* Merged API Schemas - single source of truth for all API endpoints
*
* All domain schemas are composed here using intersection types.
* AssertValidSchemas provides compile-time validation:
* - Invalid HTTP methods become `never` type
* - Missing `response` field causes type errors
*
* When adding a new domain:
* 1. Create the schema file (e.g., topic.ts)
* 2. Import and add to intersection below
*
* @example
* ```typescript
* import type { TopicSchemas } from './topic'
* export type ApiSchemas = AssertValidSchemas<TestSchemas & BatchSchemas & TopicSchemas>
* ```
*/
export type ApiSchemas = AssertValidSchemas<TestSchemas & BatchSchemas>

View File

@ -0,0 +1,322 @@
/**
* Test API Schema definitions
*
* Contains all test-related endpoints for development and testing purposes.
* These endpoints demonstrate the API patterns and provide testing utilities.
*/
import type { PaginatedResponse, PaginationParams } from '../apiTypes'
// ============================================================================
// Domain Models & DTOs
// ============================================================================
/**
* Generic test item entity - flexible structure for testing various scenarios
*/
export interface TestItem {
/** Unique identifier */
id: string
/** Item title */
title: string
/** Optional description */
description?: string
/** Type category */
type: string
/** Current status */
status: string
/** Priority level */
priority: string
/** Associated tags */
tags: string[]
/** Creation timestamp */
createdAt: string
/** Last update timestamp */
updatedAt: string
/** Additional metadata */
metadata: Record<string, any>
}
/**
* DTO for creating a new test item
*/
export interface CreateTestItemDto {
/** Item title */
title: string
/** Optional description */
description?: string
/** Type category */
type?: string
/** Current status */
status?: string
/** Priority level */
priority?: string
/** Associated tags */
tags?: string[]
/** Additional metadata */
metadata?: Record<string, any>
}
/**
* DTO for updating an existing test item
*/
export interface UpdateTestItemDto {
/** Updated title */
title?: string
/** Updated description */
description?: string
/** Updated type */
type?: string
/** Updated status */
status?: string
/** Updated priority */
priority?: string
/** Updated tags */
tags?: string[]
/** Updated metadata */
metadata?: Record<string, any>
}
// ============================================================================
// API Schema Definitions
// ============================================================================
/**
* Test API Schema definitions
*
* Validation is performed at composition level via AssertValidSchemas
* in schemas/index.ts, which ensures:
* - All methods are valid HTTP methods (GET, POST, PUT, DELETE, PATCH)
* - All endpoints have a `response` field
*/
export interface TestSchemas {
/**
* Test items collection endpoint
* @example GET /test/items?page=1&limit=10&search=hello
* @example POST /test/items { "title": "New Test Item" }
*/
'/test/items': {
/** List all test items with optional filtering and pagination */
GET: {
query?: PaginationParams & {
/** Search items by title or description */
search?: string
/** Filter by item type */
type?: string
/** Filter by status */
status?: string
}
response: PaginatedResponse<TestItem>
}
/** Create a new test item */
POST: {
body: CreateTestItemDto
response: TestItem
}
}
/**
* Individual test item endpoint
* @example GET /test/items/123
* @example PUT /test/items/123 { "title": "Updated Title" }
* @example DELETE /test/items/123
*/
'/test/items/:id': {
/** Get a specific test item by ID */
GET: {
params: { id: string }
response: TestItem
}
/** Update a specific test item */
PUT: {
params: { id: string }
body: UpdateTestItemDto
response: TestItem
}
/** Delete a specific test item */
DELETE: {
params: { id: string }
response: void
}
}
/**
* Test search endpoint
* @example GET /test/search?query=hello&page=1&limit=20
*/
'/test/search': {
/** Search test items */
GET: {
query: {
/** Search query string */
query: string
/** Page number for pagination */
page?: number
/** Number of results per page */
limit?: number
/** Additional filters */
type?: string
status?: string
}
response: PaginatedResponse<TestItem>
}
}
/**
* Test statistics endpoint
* @example GET /test/stats
*/
'/test/stats': {
/** Get comprehensive test statistics */
GET: {
response: {
/** Total number of items */
total: number
/** Item count grouped by type */
byType: Record<string, number>
/** Item count grouped by status */
byStatus: Record<string, number>
/** Item count grouped by priority */
byPriority: Record<string, number>
/** Recent activity timeline */
recentActivity: Array<{
/** Date of activity */
date: string
/** Number of items on that date */
count: number
}>
}
}
}
/**
* Test bulk operations endpoint
* @example POST /test/bulk { "operation": "create", "data": [...] }
*/
'/test/bulk': {
/** Perform bulk operations on test items */
POST: {
body: {
/** Operation type */
operation: 'create' | 'update' | 'delete'
/** Array of data items to process */
data: Array<CreateTestItemDto | UpdateTestItemDto | string>
}
response: {
/** Number of successfully processed items */
successful: number
/** Number of items that failed processing */
failed: number
/** Array of error messages */
errors: string[]
}
}
}
/**
* Test error simulation endpoint
* @example POST /test/error { "errorType": "timeout" }
*/
'/test/error': {
/** Simulate various error scenarios for testing */
POST: {
body: {
/** Type of error to simulate */
errorType:
| 'timeout'
| 'network'
| 'server'
| 'notfound'
| 'validation'
| 'unauthorized'
| 'ratelimit'
| 'generic'
}
response: never
}
}
/**
* Test slow response endpoint
* @example POST /test/slow { "delay": 2000 }
*/
'/test/slow': {
/** Test slow response for performance testing */
POST: {
body: {
/** Delay in milliseconds */
delay: number
}
response: {
message: string
delay: number
timestamp: string
}
}
}
/**
* Test data reset endpoint
* @example POST /test/reset
*/
'/test/reset': {
/** Reset all test data to initial state */
POST: {
response: {
message: string
timestamp: string
}
}
}
/**
* Test config endpoint
* @example GET /test/config
* @example PUT /test/config { "setting": "value" }
*/
'/test/config': {
/** Get test configuration */
GET: {
response: Record<string, any>
}
/** Update test configuration */
PUT: {
body: Record<string, any>
response: Record<string, any>
}
}
/**
* Test status endpoint
* @example GET /test/status
*/
'/test/status': {
/** Get system test status */
GET: {
response: {
status: string
timestamp: string
version: string
uptime: number
environment: string
}
}
}
/**
* Test performance endpoint
* @example GET /test/performance
*/
'/test/performance': {
/** Get performance metrics */
GET: {
response: {
requestsPerSecond: number
averageLatency: number
memoryUsage: number
cpuUsage: number
uptime: number
}
}
}
}

View File

@ -12,8 +12,10 @@ src/main/data/
│ │ ├── MiddlewareEngine.ts # Request/response middleware
│ │ └── adapters/ # Communication adapters (IPC)
│ ├── handlers/ # API endpoint implementations
│ │ └── index.ts # Thin handlers: param extraction, DTO conversion
│ └── index.ts # API framework exports
│ │ ├── index.ts # Handler aggregation and exports
│ │ ├── test.ts # Test endpoint handlers
│ │ └── batch.ts # Batch/transaction handlers
│ └── index.ts # API framework exports
├── services/ # Business logic layer
│ ├── base/ # Service base classes and interfaces
@ -34,6 +36,12 @@ src/main/data/
│ ├── schemas/ # Drizzle table definitions
│ │ ├── preference.ts # Preference configuration table
│ │ ├── appState.ts # Application state table
│ │ ├── topic.ts # Topic/conversation table
│ │ ├── message.ts # Message table
│ │ ├── group.ts # Group table
│ │ ├── tag.ts # Tag table
│ │ ├── entityTag.ts # Entity-tag relationship table
│ │ ├── messageFts.ts # Message full-text search table
│ │ └── columnHelpers.ts # Reusable column definitions
│ ├── seeding/ # Database initialization
│ └── DbService.ts # Database connection and management
@ -94,8 +102,8 @@ The API framework provides the interface layer for data access:
- Delegating to business services
- Transforming responses for IPC
- **Anti-pattern**: Do NOT put business logic in handlers
- **Currently**: Contains test handlers (production handlers pending)
- **Type Safety**: Must implement all endpoints defined in `@shared/data/api`
- **Currently**: Contains test and batch handlers (business handlers pending)
- **Type Safety**: Must implement all endpoints defined in `@shared/data/api/schemas/`
### Business Logic Layer (`services/`)
@ -217,6 +225,12 @@ export class SimpleService extends BaseService {
### Current Tables
- `preference`: User configuration storage
- `appState`: Application state persistence
- `topic`: Conversation/topic storage
- `message`: Message storage with full-text search
- `group`: Group organization
- `tag`: Tag definitions
- `entityTag`: Entity-tag relationships
- `messageFts`: Message full-text search index
## Usage Examples
@ -231,11 +245,12 @@ import { dataApiService } from '@/data/DataApiService'
```
### Adding New API Endpoints
1. Define endpoint in `@shared/data/api/apiSchemas.ts`
2. Implement handler in `api/handlers/index.ts` (thin layer, delegate to service)
3. Create business service in `services/` for domain logic
4. Create repository in `repositories/` if domain is complex (optional)
5. Add database schema in `db/schemas/` if required
1. Create or update schema in `@shared/data/api/schemas/` (see `@shared/data/api/README.md`)
2. Register schema in `@shared/data/api/schemas/index.ts`
3. Implement handler in `api/handlers/` (thin layer, delegate to service)
4. Create business service in `services/` for domain logic
5. Create repository in `repositories/` if domain is complex (optional)
6. Add database schema in `db/schemas/` if required
### Adding Database Tables
1. Create schema in `db/schemas/{tableName}.ts`
@ -271,11 +286,13 @@ export class ExampleService {
}
// 3. Create handler: api/handlers/example.ts
import { ExampleService } from '../../services/ExampleService'
import { ExampleService } from '@data/services/ExampleService'
export const exampleHandlers = {
'POST /examples': async ({ body }) => {
return await ExampleService.getInstance().createExample(body)
export const exampleHandlers: Partial<ApiImplementation> = {
'/examples': {
POST: async ({ body }) => {
return await ExampleService.getInstance().createExample(body)
}
}
}
```
@ -294,9 +311,11 @@ export class SimpleService extends BaseService {
}
// 2. Create handler: api/handlers/simple.ts
export const simpleHandlers = {
'GET /items/:id': async ({ params }) => {
return await SimpleService.getInstance().getItem(params.id)
export const simpleHandlers: Partial<ApiImplementation> = {
'/items/:id': {
GET: async ({ params }) => {
return await SimpleService.getInstance().getItem(params.id)
}
}
}
```

View File

@ -1,5 +1,5 @@
import { loggerService } from '@logger'
import type { ApiImplementation } from '@shared/data/api/apiSchemas'
import type { ApiImplementation } from '@shared/data/api/apiTypes'
import type { DataRequest, DataResponse, HttpMethod, RequestContext } from '@shared/data/api/apiTypes'
import { DataApiErrorFactory, ErrorCode } from '@shared/data/api/errorCodes'

View File

@ -0,0 +1,55 @@
/**
* Batch and Transaction API Handlers
*
* Implements cross-domain batch processing and atomic transaction operations.
*/
import type { ApiHandler, ApiMethods } from '@shared/data/api/apiTypes'
import type { BatchSchemas } from '@shared/data/api/schemas/batch'
/**
* Handler type for a specific batch endpoint
*/
type BatchHandler<Path extends keyof BatchSchemas, Method extends ApiMethods<Path>> = ApiHandler<Path, Method>
/**
* Batch API handlers implementation
*/
export const batchHandlers: {
[Path in keyof BatchSchemas]: {
[Method in keyof BatchSchemas[Path]]: BatchHandler<Path, Method & ApiMethods<Path>>
}
} = {
'/batch': {
POST: async ({ body }) => {
// Mock batch implementation - can be enhanced with actual batch processing
const { requests } = body
const results = requests.map(() => ({
status: 200,
data: { processed: true, timestamp: new Date().toISOString() }
}))
return {
results,
metadata: {
duration: Math.floor(Math.random() * 500) + 100,
successCount: requests.length,
errorCount: 0
}
}
}
},
'/transaction': {
POST: async ({ body }) => {
// Mock transaction implementation - can be enhanced with actual transaction support
const { operations } = body
return operations.map(() => ({
status: 200,
data: { executed: true, timestamp: new Date().toISOString() }
}))
}
}
}

View File

@ -1,210 +1,38 @@
/**
* Complete API handler implementation
* API Handlers Index
*
* This file implements ALL endpoints defined in ApiSchemas.
* TypeScript will error if any endpoint is missing.
* Combines all domain-specific handlers into a unified apiHandlers object.
* TypeScript will error if any endpoint from ApiSchemas is missing.
*
* Handler files are organized by domain:
* - test.ts - Test API handlers
* - batch.ts - Batch and transaction handlers
*
* @example Adding a new domain:
* ```typescript
* import { topicHandlers } from './topic'
*
* export const apiHandlers: ApiImplementation = {
* ...testHandlers,
* ...batchHandlers,
* ...topicHandlers // Add new domain handlers here
* }
* ```
*/
import { TestService } from '@data/services/TestService'
import type { ApiImplementation } from '@shared/data/api/apiSchemas'
import type { ApiImplementation } from '@shared/data/api/apiTypes'
// Service instances
const testService = TestService.getInstance()
import { batchHandlers } from './batch'
import { testHandlers } from './test'
/**
* Complete API handlers implementation
* Must implement every path+method combination from ApiSchemas
*
* Handlers are spread from individual domain modules for maintainability.
* TypeScript ensures exhaustive coverage - missing handlers cause compile errors.
*/
export const apiHandlers: ApiImplementation = {
'/test/items': {
GET: async ({ query }) => {
return await testService.getItems({
page: (query as any)?.page,
limit: (query as any)?.limit,
search: (query as any)?.search,
type: (query as any)?.type,
status: (query as any)?.status
})
},
POST: async ({ body }) => {
return await testService.createItem({
title: body.title,
description: body.description,
type: body.type,
status: body.status,
priority: body.priority,
tags: body.tags,
metadata: body.metadata
})
}
},
'/test/items/:id': {
GET: async ({ params }) => {
const item = await testService.getItemById(params.id)
if (!item) {
throw new Error(`Test item not found: ${params.id}`)
}
return item
},
PUT: async ({ params, body }) => {
const item = await testService.updateItem(params.id, {
title: body.title,
description: body.description,
type: body.type,
status: body.status,
priority: body.priority,
tags: body.tags,
metadata: body.metadata
})
if (!item) {
throw new Error(`Test item not found: ${params.id}`)
}
return item
},
DELETE: async ({ params }) => {
const deleted = await testService.deleteItem(params.id)
if (!deleted) {
throw new Error(`Test item not found: ${params.id}`)
}
return undefined
}
},
'/test/search': {
GET: async ({ query }) => {
return await testService.searchItems(query.query, {
page: query.page,
limit: query.limit,
filters: {
type: query.type,
status: query.status
}
})
}
},
'/test/stats': {
GET: async () => {
return await testService.getStats()
}
},
'/test/bulk': {
POST: async ({ body }) => {
return await testService.bulkOperation(body.operation, body.data)
}
},
'/test/error': {
POST: async ({ body }) => {
return await testService.simulateError(body.errorType)
}
},
'/test/slow': {
POST: async ({ body }) => {
const delay = body.delay
await new Promise((resolve) => setTimeout(resolve, delay))
return {
message: `Slow response completed after ${delay}ms`,
delay,
timestamp: new Date().toISOString()
}
}
},
'/test/reset': {
POST: async () => {
await testService.resetData()
return {
message: 'Test data reset successfully',
timestamp: new Date().toISOString()
}
}
},
'/test/config': {
GET: async () => {
return {
environment: 'test',
version: '1.0.0',
debug: true,
features: {
bulkOperations: true,
search: true,
statistics: true
}
}
},
PUT: async ({ body }) => {
return {
...body,
updated: true,
timestamp: new Date().toISOString()
}
}
},
'/test/status': {
GET: async () => {
return {
status: 'healthy',
timestamp: new Date().toISOString(),
version: '1.0.0',
uptime: Math.floor(process.uptime()),
environment: 'test'
}
}
},
'/test/performance': {
GET: async () => {
const memUsage = process.memoryUsage()
return {
requestsPerSecond: Math.floor(Math.random() * 100) + 50,
averageLatency: Math.floor(Math.random() * 200) + 50,
memoryUsage: memUsage.heapUsed / 1024 / 1024, // MB
cpuUsage: Math.random() * 100,
uptime: Math.floor(process.uptime())
}
}
},
'/batch': {
POST: async ({ body }) => {
// Mock batch implementation - can be enhanced with actual batch processing
const { requests } = body
const results = requests.map(() => ({
status: 200,
data: { processed: true, timestamp: new Date().toISOString() }
}))
return {
results,
metadata: {
duration: Math.floor(Math.random() * 500) + 100,
successCount: requests.length,
errorCount: 0
}
}
}
},
'/transaction': {
POST: async ({ body }) => {
// Mock transaction implementation - can be enhanced with actual transaction support
const { operations } = body
return operations.map(() => ({
status: 200,
data: { executed: true, timestamp: new Date().toISOString() }
}))
}
}
...testHandlers,
...batchHandlers
}

View File

@ -0,0 +1,185 @@
/**
* Test API Handlers
*
* Implements all test-related API endpoints for development and testing purposes.
*/
import { TestService } from '@data/services/TestService'
import type { ApiHandler, ApiMethods } from '@shared/data/api/apiTypes'
import type { TestSchemas } from '@shared/data/api/schemas/test'
// Service instance
const testService = TestService.getInstance()
/**
* Handler type for a specific test endpoint
*/
type TestHandler<Path extends keyof TestSchemas, Method extends ApiMethods<Path>> = ApiHandler<Path, Method>
/**
* Test API handlers implementation
*/
export const testHandlers: {
[Path in keyof TestSchemas]: {
[Method in keyof TestSchemas[Path]]: TestHandler<Path, Method & ApiMethods<Path>>
}
} = {
'/test/items': {
GET: async ({ query }) => {
return await testService.getItems({
page: (query as any)?.page,
limit: (query as any)?.limit,
search: (query as any)?.search,
type: (query as any)?.type,
status: (query as any)?.status
})
},
POST: async ({ body }) => {
return await testService.createItem({
title: body.title,
description: body.description,
type: body.type,
status: body.status,
priority: body.priority,
tags: body.tags,
metadata: body.metadata
})
}
},
'/test/items/:id': {
GET: async ({ params }) => {
const item = await testService.getItemById(params.id)
if (!item) {
throw new Error(`Test item not found: ${params.id}`)
}
return item
},
PUT: async ({ params, body }) => {
const item = await testService.updateItem(params.id, {
title: body.title,
description: body.description,
type: body.type,
status: body.status,
priority: body.priority,
tags: body.tags,
metadata: body.metadata
})
if (!item) {
throw new Error(`Test item not found: ${params.id}`)
}
return item
},
DELETE: async ({ params }) => {
const deleted = await testService.deleteItem(params.id)
if (!deleted) {
throw new Error(`Test item not found: ${params.id}`)
}
return undefined
}
},
'/test/search': {
GET: async ({ query }) => {
return await testService.searchItems(query.query, {
page: query.page,
limit: query.limit,
filters: {
type: query.type,
status: query.status
}
})
}
},
'/test/stats': {
GET: async () => {
return await testService.getStats()
}
},
'/test/bulk': {
POST: async ({ body }) => {
return await testService.bulkOperation(body.operation, body.data)
}
},
'/test/error': {
POST: async ({ body }) => {
return await testService.simulateError(body.errorType)
}
},
'/test/slow': {
POST: async ({ body }) => {
const delay = body.delay
await new Promise((resolve) => setTimeout(resolve, delay))
return {
message: `Slow response completed after ${delay}ms`,
delay,
timestamp: new Date().toISOString()
}
}
},
'/test/reset': {
POST: async () => {
await testService.resetData()
return {
message: 'Test data reset successfully',
timestamp: new Date().toISOString()
}
}
},
'/test/config': {
GET: async () => {
return {
environment: 'test',
version: '1.0.0',
debug: true,
features: {
bulkOperations: true,
search: true,
statistics: true
}
}
},
PUT: async ({ body }) => {
return {
...body,
updated: true,
timestamp: new Date().toISOString()
}
}
},
'/test/status': {
GET: async () => {
return {
status: 'healthy',
timestamp: new Date().toISOString(),
version: '1.0.0',
uptime: Math.floor(process.uptime()),
environment: 'test'
}
}
},
'/test/performance': {
GET: async () => {
const memUsage = process.memoryUsage()
return {
requestsPerSecond: Math.floor(Math.random() * 100) + 50,
averageLatency: Math.floor(Math.random() * 200) + 50,
memoryUsage: memUsage.heapUsed / 1024 / 1024, // MB
cpuUsage: Math.random() * 100,
uptime: Math.floor(process.uptime())
}
}
}
}

View File

@ -20,7 +20,6 @@ export { apiHandlers } from './handlers'
export { TestService } from '@data/services/TestService'
// Re-export types for convenience
export type { CreateTestItemDto, TestItem, UpdateTestItemDto } from '@shared/data/api'
export type {
DataRequest,
DataResponse,
@ -30,3 +29,4 @@ export type {
RequestContext,
ServiceOptions
} from '@shared/data/api/apiTypes'
export type { CreateTestItemDto, TestItem, UpdateTestItemDto } from '@shared/data/api/schemas/test'

View File

@ -31,7 +31,7 @@
*/
import { loggerService } from '@logger'
import type { ApiClient, ConcreteApiPaths } from '@shared/data/api/apiSchemas'
import type { ApiClient, ConcreteApiPaths } from '@shared/data/api/apiTypes'
import type {
BatchRequest,
BatchResponse,

View File

@ -1,5 +1,5 @@
import type { BodyForPath, QueryParamsForPath, ResponseForPath } from '@shared/data/api/apiPaths'
import type { ConcreteApiPaths } from '@shared/data/api/apiSchemas'
import type { ConcreteApiPaths } from '@shared/data/api/apiTypes'
import type { PaginatedResponse } from '@shared/data/api/apiTypes'
import { useState } from 'react'
import type { KeyedMutator } from 'swr'

View File

@ -1,4 +1,4 @@
import type { ApiClient, ConcreteApiPaths } from '@shared/data/api/apiSchemas'
import type { ApiClient, ConcreteApiPaths } from '@shared/data/api/apiTypes'
import type { DataResponse } from '@shared/data/api/apiTypes'
import { vi } from 'vitest'

View File

@ -1,4 +1,4 @@
import type { ConcreteApiPaths } from '@shared/data/api/apiSchemas'
import type { ConcreteApiPaths } from '@shared/data/api/apiTypes'
import type { PaginatedResponse } from '@shared/data/api/apiTypes'
import { vi } from 'vitest'