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

View File

@ -17,4 +17,4 @@
"breakpoints": true "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. 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/ packages/shared/data/
├── api/ # Data API type system ├── api/ # Data API type system (see api/README.md)
│ ├── index.ts # Barrel exports for clean imports │ ├── index.ts # Barrel exports for infrastructure types
│ ├── apiSchemas.ts # API endpoint definitions and mappings │ ├── apiTypes.ts # Core request/response types and utilities
│ ├── apiTypes.ts # Core request/response infrastructure types │ ├── apiPaths.ts # Path template literal type utilities
│ ├── apiModels.ts # Business entity types and DTOs │ ├── errorCodes.ts # Error handling utilities
│ ├── apiPaths.ts # API path definitions and utilities │ ├── schemas/ # Domain-specific API schemas
│ └── errorCodes.ts # Standardized error handling │ │ ├── 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 ├── cache/ # Cache system type definitions
│ ├── cacheTypes.ts # Core cache infrastructure types │ ├── cacheTypes.ts # Core cache infrastructure types
│ ├── cacheSchemas.ts # Cache key schemas and type mappings │ ├── cacheSchemas.ts # Cache key schemas and type mappings
@ -24,7 +27,7 @@ packages/shared/data/
└── README.md # This file └── README.md # This file
``` ```
## 🏗️ System Overview ## System Overview
This directory provides type definitions for four main data management systems: 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/`) ### API System (`api/`)
- **Purpose**: Type-safe IPC communication between Main and Renderer processes - **Purpose**: Type-safe IPC communication between Main and Renderer processes
- **Features**: RESTful patterns, error handling, business entity definitions - **Features**: RESTful patterns, modular schema design, error handling
- **Usage**: Ensures type safety for all data API operations - **Documentation**: See [`api/README.md`](./api/README.md) for detailed usage
### Cache System (`cache/`) ### Cache System (`cache/`)
- **Purpose**: Type definitions for three-layer caching architecture - **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 - **Features**: 158 configuration items, default values, nested key support
- **Usage**: Type-safe preference access and synchronization - **Usage**: Type-safe preference access and synchronization
## 📋 File Categories ## File Categories
**Framework Infrastructure** - These are TypeScript type definitions that: **Framework Infrastructure** - These are TypeScript type definitions that:
- ✅ Exist only at compile time - ✅ 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 - ✅ Define contracts between application layers
- ✅ Enable static analysis and error detection - ✅ Enable static analysis and error detection
## 📖 Usage Examples ## Usage Examples
### API Types ### API Types
```typescript ```typescript
// Import API types // Infrastructure types from barrel export
import type { DataRequest, DataResponse, ApiSchemas } from '@shared/data/api' 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 ### Cache Types
@ -76,7 +83,7 @@ import type { UseCacheKey, UseSharedCacheKey } from '@shared/data/cache'
import type { PreferenceKeyType, PreferenceDefaultScopeType } from '@shared/data/preference' import type { PreferenceKeyType, PreferenceDefaultScopeType } from '@shared/data/preference'
``` ```
## 🔧 Development Guidelines ## Development Guidelines
### Adding Shared Types ### Adding Shared Types
1. Create or update type file in `types/` directory 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 3. Preference system automatically picks up new keys
### Adding API Types ### Adding API Types
1. Define business entities in `api/apiModels.ts` 1. Create schema file in `api/schemas/` (e.g., `topic.ts`)
2. Add endpoint definitions to `api/apiSchemas.ts` 2. Define domain models, DTOs, and API schema in the file
3. Export types from `api/index.ts` 3. Register schema in `api/schemas/index.ts` using intersection type
4. See [`api/README.md`](./api/README.md) for detailed guide
### Best Practices ### Best Practices
- Use `import type` for type-only imports - Use `import type` for type-only imports
- Infrastructure types from barrel, domain DTOs from schema files
- Follow existing naming conventions - Follow existing naming conventions
- Document complex types with JSDoc - Document complex types with JSDoc
- Maintain type safety across all imports
## 🔗 Related Implementation ## Related Implementation
### Main Process Services ### Main Process
- `src/main/data/CacheService.ts` - Main process cache management - `src/main/data/api/` - API server, handlers, and IPC adapter
- `src/main/data/PreferenceService.ts` - Preference management service - `src/main/data/cache/` - Cache service implementation
- `src/main/data/DataApiService.ts` - Data API coordination service - `src/main/data/preference/` - Preference service implementation
### Renderer Process Services ### Renderer Process
- `src/renderer/src/data/CacheService.ts` - Renderer cache service - `src/renderer/src/services/DataApiService.ts` - API client
- `src/renderer/src/data/PreferenceService.ts` - Renderer preference service - `src/renderer/src/services/CacheService.ts` - Cache service
- `src/renderer/src/data/DataApiService.ts` - Renderer API client - `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 * 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' 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 * Request object structure for Data API calls
*/ */
@ -30,8 +99,6 @@ export interface DataRequest<T = any> {
timestamp: number timestamp: number
/** OpenTelemetry span context for tracing */ /** OpenTelemetry span context for tracing */
spanContext?: any spanContext?: any
/** Cache options for this specific request */
cache?: CacheOptions
} }
} }
@ -101,22 +168,6 @@ export enum ErrorCode {
CONCURRENT_MODIFICATION = 'CONCURRENT_MODIFICATION' 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 * Transaction request wrapper for atomic operations
*/ */
@ -274,16 +325,169 @@ export interface ServiceOptions {
metadata?: Record<string, any> 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> { export type ApiPaths = keyof ApiSchemas
/** Whether operation was successful */
success: boolean /**
/** Result data if successful */ * Available HTTP methods for a specific path
data?: T */
/** Error information if failed */ export type ApiMethods<TPath extends ApiPaths> = keyof ApiSchemas[TPath] & HttpMethod
error?: DataApiError
/** Additional metadata */ /**
metadata?: Record<string, any> * 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 * Cherry Studio Data API - Barrel Exports
* *
* This file provides a centralized entry point for all data API types, * Exports common infrastructure types for the Data API system.
* schemas, and utilities. Import everything you need from this single location. * Domain-specific DTOs should be imported directly from their schema files.
* *
* @example * @example
* ```typescript * ```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 { export type {
BatchRequest, BatchRequest,
BatchResponse, BatchResponse,
CacheOptions,
DataApiError, DataApiError,
DataRequest, DataRequest,
DataResponse, DataResponse,
HttpMethod, HttpMethod,
Middleware,
PaginatedResponse, PaginatedResponse,
PaginationParams, PaginationParams,
RequestContext,
ServiceOptions,
ServiceResult,
SubscriptionCallback,
SubscriptionOptions,
TransactionRequest TransactionRequest
} from './apiTypes' } from './apiTypes'
export { ErrorCode, SubscriptionEvent } from './apiTypes'
// Domain models and DTOs // ============================================================================
export type { // API Schema Type Utilities
BulkOperationRequest, // ============================================================================
BulkOperationResponse,
CreateTestItemDto,
TestItem,
UpdateTestItemDto
} from './apiModels'
// API schema definitions and type helpers
export type { export type {
ApiBody, ApiBody,
ApiClient, ApiClient,
ApiHandler,
ApiImplementation,
ApiMethods, ApiMethods,
ApiParams, ApiParams,
ApiPaths, ApiPaths,
ApiQuery, ApiQuery,
ApiResponse, ApiResponse,
ApiSchemas ApiSchemas,
} from './apiSchemas' ConcreteApiPaths
} from './apiTypes'
// ============================================================================
// Path Resolution Utilities
// ============================================================================
// Path type utilities for template literal types
export type { export type {
BodyForPath, BodyForPath,
ConcreteApiPaths,
MatchApiPath, MatchApiPath,
QueryParamsForPath, QueryParamsForPath,
ResolvedPath, ResolvedPath,
ResponseForPath ResponseForPath
} from './apiPaths' } from './apiPaths'
// Error handling utilities // ============================================================================
// Error Handling
// ============================================================================
export { ErrorCode, SubscriptionEvent } from './apiTypes'
export { export {
ErrorCode as DataApiErrorCode,
DataApiErrorFactory, DataApiErrorFactory,
ERROR_MESSAGES, ERROR_MESSAGES,
ERROR_STATUS_MAP, ERROR_STATUS_MAP,
@ -72,50 +73,14 @@ export {
toDataApiError toDataApiError
} from './errorCodes' } from './errorCodes'
/** // ============================================================================
* Re-export commonly used type combinations for convenience // Subscription & Middleware (for advanced usage)
*/ // ============================================================================
// Import types for re-export convenience types export type {
import type { CreateTestItemDto, TestItem, UpdateTestItemDto } from './apiModels' Middleware,
import type { RequestContext,
BatchRequest, ServiceOptions,
BatchResponse, SubscriptionCallback,
DataApiError, SubscriptionOptions
DataRequest,
DataResponse,
ErrorCode,
PaginatedResponse,
PaginationParams,
TransactionRequest
} from './apiTypes' } 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 │ │ ├── MiddlewareEngine.ts # Request/response middleware
│ │ └── adapters/ # Communication adapters (IPC) │ │ └── adapters/ # Communication adapters (IPC)
│ ├── handlers/ # API endpoint implementations │ ├── handlers/ # API endpoint implementations
│ │ └── index.ts # Thin handlers: param extraction, DTO conversion │ │ ├── index.ts # Handler aggregation and exports
│ └── index.ts # API framework exports │ │ ├── test.ts # Test endpoint handlers
│ │ └── batch.ts # Batch/transaction handlers
│ └── index.ts # API framework exports
├── services/ # Business logic layer ├── services/ # Business logic layer
│ ├── base/ # Service base classes and interfaces │ ├── base/ # Service base classes and interfaces
@ -34,6 +36,12 @@ src/main/data/
│ ├── schemas/ # Drizzle table definitions │ ├── schemas/ # Drizzle table definitions
│ │ ├── preference.ts # Preference configuration table │ │ ├── preference.ts # Preference configuration table
│ │ ├── appState.ts # Application state 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 │ │ └── columnHelpers.ts # Reusable column definitions
│ ├── seeding/ # Database initialization │ ├── seeding/ # Database initialization
│ └── DbService.ts # Database connection and management │ └── DbService.ts # Database connection and management
@ -94,8 +102,8 @@ The API framework provides the interface layer for data access:
- Delegating to business services - Delegating to business services
- Transforming responses for IPC - Transforming responses for IPC
- **Anti-pattern**: Do NOT put business logic in handlers - **Anti-pattern**: Do NOT put business logic in handlers
- **Currently**: Contains test handlers (production handlers pending) - **Currently**: Contains test and batch handlers (business handlers pending)
- **Type Safety**: Must implement all endpoints defined in `@shared/data/api` - **Type Safety**: Must implement all endpoints defined in `@shared/data/api/schemas/`
### Business Logic Layer (`services/`) ### Business Logic Layer (`services/`)
@ -217,6 +225,12 @@ export class SimpleService extends BaseService {
### Current Tables ### Current Tables
- `preference`: User configuration storage - `preference`: User configuration storage
- `appState`: Application state persistence - `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 ## Usage Examples
@ -231,11 +245,12 @@ import { dataApiService } from '@/data/DataApiService'
``` ```
### Adding New API Endpoints ### Adding New API Endpoints
1. Define endpoint in `@shared/data/api/apiSchemas.ts` 1. Create or update schema in `@shared/data/api/schemas/` (see `@shared/data/api/README.md`)
2. Implement handler in `api/handlers/index.ts` (thin layer, delegate to service) 2. Register schema in `@shared/data/api/schemas/index.ts`
3. Create business service in `services/` for domain logic 3. Implement handler in `api/handlers/` (thin layer, delegate to service)
4. Create repository in `repositories/` if domain is complex (optional) 4. Create business service in `services/` for domain logic
5. Add database schema in `db/schemas/` if required 5. Create repository in `repositories/` if domain is complex (optional)
6. Add database schema in `db/schemas/` if required
### Adding Database Tables ### Adding Database Tables
1. Create schema in `db/schemas/{tableName}.ts` 1. Create schema in `db/schemas/{tableName}.ts`
@ -271,11 +286,13 @@ export class ExampleService {
} }
// 3. Create handler: api/handlers/example.ts // 3. Create handler: api/handlers/example.ts
import { ExampleService } from '../../services/ExampleService' import { ExampleService } from '@data/services/ExampleService'
export const exampleHandlers = { export const exampleHandlers: Partial<ApiImplementation> = {
'POST /examples': async ({ body }) => { '/examples': {
return await ExampleService.getInstance().createExample(body) 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 // 2. Create handler: api/handlers/simple.ts
export const simpleHandlers = { export const simpleHandlers: Partial<ApiImplementation> = {
'GET /items/:id': async ({ params }) => { '/items/:id': {
return await SimpleService.getInstance().getItem(params.id) GET: async ({ params }) => {
return await SimpleService.getInstance().getItem(params.id)
}
} }
} }
``` ```

View File

@ -1,5 +1,5 @@
import { loggerService } from '@logger' 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 type { DataRequest, DataResponse, HttpMethod, RequestContext } from '@shared/data/api/apiTypes'
import { DataApiErrorFactory, ErrorCode } from '@shared/data/api/errorCodes' 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. * Combines all domain-specific handlers into a unified apiHandlers object.
* TypeScript will error if any endpoint is missing. * 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/apiTypes'
import type { ApiImplementation } from '@shared/data/api/apiSchemas'
// Service instances import { batchHandlers } from './batch'
const testService = TestService.getInstance() import { testHandlers } from './test'
/** /**
* Complete API handlers implementation * Complete API handlers implementation
* Must implement every path+method combination from ApiSchemas * 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 = { export const apiHandlers: ApiImplementation = {
'/test/items': { ...testHandlers,
GET: async ({ query }) => { ...batchHandlers
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() }
}))
}
}
} }

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' export { TestService } from '@data/services/TestService'
// Re-export types for convenience // Re-export types for convenience
export type { CreateTestItemDto, TestItem, UpdateTestItemDto } from '@shared/data/api'
export type { export type {
DataRequest, DataRequest,
DataResponse, DataResponse,
@ -30,3 +29,4 @@ export type {
RequestContext, RequestContext,
ServiceOptions ServiceOptions
} from '@shared/data/api/apiTypes' } 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 { loggerService } from '@logger'
import type { ApiClient, ConcreteApiPaths } from '@shared/data/api/apiSchemas' import type { ApiClient, ConcreteApiPaths } from '@shared/data/api/apiTypes'
import type { import type {
BatchRequest, BatchRequest,
BatchResponse, BatchResponse,

View File

@ -1,5 +1,5 @@
import type { BodyForPath, QueryParamsForPath, ResponseForPath } from '@shared/data/api/apiPaths' 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 type { PaginatedResponse } from '@shared/data/api/apiTypes'
import { useState } from 'react' import { useState } from 'react'
import type { KeyedMutator } from 'swr' 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 type { DataResponse } from '@shared/data/api/apiTypes'
import { vi } from 'vitest' 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 type { PaginatedResponse } from '@shared/data/api/apiTypes'
import { vi } from 'vitest' import { vi } from 'vitest'