diff --git a/migrations/sqlite-drizzle/meta/0001_snapshot.json b/migrations/sqlite-drizzle/meta/0001_snapshot.json index 533c642b04..83ac3db1ac 100644 --- a/migrations/sqlite-drizzle/meta/0001_snapshot.json +++ b/migrations/sqlite-drizzle/meta/0001_snapshot.json @@ -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": {} } -} \ No newline at end of file +} diff --git a/migrations/sqlite-drizzle/meta/_journal.json b/migrations/sqlite-drizzle/meta/_journal.json index c42fbfed83..960ba8a1e0 100644 --- a/migrations/sqlite-drizzle/meta/_journal.json +++ b/migrations/sqlite-drizzle/meta/_journal.json @@ -17,4 +17,4 @@ "breakpoints": true } ] -} \ No newline at end of file +} diff --git a/packages/shared/data/README.md b/packages/shared/data/README.md index c522d3138c..1be09a01c1 100644 --- a/packages/shared/data/README.md +++ b/packages/shared/data/README.md @@ -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 \ No newline at end of file +### 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 \ No newline at end of file diff --git a/packages/shared/data/api/README.md b/packages/shared/data/api/README.md new file mode 100644 index 0000000000..b2e9c7da1e --- /dev/null +++ b/packages/shared/data/api/README.md @@ -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 // 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 +``` + +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 +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 diff --git a/packages/shared/data/api/apiModels.ts b/packages/shared/data/api/apiModels.ts deleted file mode 100644 index 08107a9729..0000000000 --- a/packages/shared/data/api/apiModels.ts +++ /dev/null @@ -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 -} - -/** - * 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 -} - -/** - * 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 -} - -/** - * Bulk operation types for batch processing - */ - -/** - * Request for bulk operations on multiple items - */ -export interface BulkOperationRequest { - /** 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 - }> -} diff --git a/packages/shared/data/api/apiPaths.ts b/packages/shared/data/api/apiPaths.ts index a947157869..7cd5397e02 100644 --- a/packages/shared/data/api/apiPaths.ts +++ b/packages/shared/data/api/apiPaths.ts @@ -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 diff --git a/packages/shared/data/api/apiSchemas.ts b/packages/shared/data/api/apiSchemas.ts deleted file mode 100644 index e405af806e..0000000000 --- a/packages/shared/data/api/apiSchemas.ts +++ /dev/null @@ -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 - } - /** Create a new test item */ - POST: { - body: { - title: string - description?: string - type?: string - status?: string - priority?: string - tags?: string[] - metadata?: Record - } - 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 - } - 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 - } - } - - /** - * 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 - /** Item count grouped by status */ - byStatus: Record - /** Item count grouped by priority */ - byPriority: Record - /** 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 - } - /** Update test configuration */ - PUT: { - body: Record - response: Record - } - } - - /** - * 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 = keyof ApiSchemas[TPath] & HttpMethod -export type ApiResponse = 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 keyof ApiSchemas - ? TMethod extends keyof ApiSchemas[TPath] - ? ApiSchemas[TPath][TMethod] extends { params: infer P } - ? P - : never - : never - : never - -export type ApiQuery = 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 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( - path: TPath, - options?: { - query?: QueryParamsForPath - headers?: Record - } - ): Promise> - - post( - path: TPath, - options: { - body?: BodyForPath - query?: Record - headers?: Record - } - ): Promise> - - put( - path: TPath, - options: { - body: BodyForPath - query?: Record - headers?: Record - } - ): Promise> - - delete( - path: TPath, - options?: { - query?: Record - headers?: Record - } - ): Promise> - - patch( - path: TPath, - options: { - body?: BodyForPath - query?: Record - headers?: Record - } - ): Promise> -} - -/** - * Helper types to determine if parameters are required based on schema - */ -type HasRequiredQuery> = Path extends keyof ApiSchemas - ? Method extends keyof ApiSchemas[Path] - ? ApiSchemas[Path][Method] extends { query: any } - ? true - : false - : false - : false - -type HasRequiredBody> = Path extends keyof ApiSchemas - ? Method extends keyof ApiSchemas[Path] - ? ApiSchemas[Path][Method] extends { body: any } - ? true - : false - : false - : false - -type HasRequiredParams> = 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> = ( - params: (HasRequiredParams extends true - ? { params: ApiParams } - : { params?: ApiParams }) & - (HasRequiredQuery extends true - ? { query: ApiQuery } - : { query?: ApiQuery }) & - (HasRequiredBody extends true ? { body: ApiBody } : { body?: ApiBody }) -) => Promise> - -/** - * 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]: ApiHandler - } -} diff --git a/packages/shared/data/api/apiTypes.ts b/packages/shared/data/api/apiTypes.ts index e45c45603c..e6e1217c5f 100644 --- a/packages/shared/data/api/apiTypes.ts +++ b/packages/shared/data/api/apiTypes.ts @@ -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 + query?: Record + 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 = { + [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 = { + [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 + * + * // Invalid method will cause error: + * // Type 'never' is not assignable to type... + * ``` + */ +export type AssertValidSchemas = ValidateMethods & ValidateResponses 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 { 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 } +// ============================================================================ +// 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 { - /** Whether operation was successful */ - success: boolean - /** Result data if successful */ - data?: T - /** Error information if failed */ - error?: DataApiError - /** Additional metadata */ - metadata?: Record +export type ApiPaths = keyof ApiSchemas + +/** + * Available HTTP methods for a specific path + */ +export type ApiMethods = keyof ApiSchemas[TPath] & HttpMethod + +/** + * Response type for a specific path and method + */ +export type ApiResponse = 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 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 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 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( + path: TPath, + options?: { + query?: QueryParamsForPath + headers?: Record + } + ): Promise> + + post( + path: TPath, + options: { + body?: BodyForPath + query?: Record + headers?: Record + } + ): Promise> + + put( + path: TPath, + options: { + body: BodyForPath + query?: Record + headers?: Record + } + ): Promise> + + delete( + path: TPath, + options?: { + query?: Record + headers?: Record + } + ): Promise> + + patch( + path: TPath, + options: { + body?: BodyForPath + query?: Record + headers?: Record + } + ): Promise> +} + +/** + * Helper types to determine if parameters are required based on schema + */ +type HasRequiredQuery> = Path extends keyof ApiSchemas + ? Method extends keyof ApiSchemas[Path] + ? ApiSchemas[Path][Method] extends { query: any } + ? true + : false + : false + : false + +type HasRequiredBody> = Path extends keyof ApiSchemas + ? Method extends keyof ApiSchemas[Path] + ? ApiSchemas[Path][Method] extends { body: any } + ? true + : false + : false + : false + +type HasRequiredParams> = 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> = ( + params: (HasRequiredParams extends true + ? { params: ApiParams } + : { params?: ApiParams }) & + (HasRequiredQuery extends true + ? { query: ApiQuery } + : { query?: ApiQuery }) & + (HasRequiredBody extends true ? { body: ApiBody } : { body?: ApiBody }) +) => Promise> + +/** + * 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]: ApiHandler + } } diff --git a/packages/shared/data/api/index.ts b/packages/shared/data/api/index.ts index 3b00e37473..d3c4c8afde 100644 --- a/packages/shared/data/api/index.ts +++ b/packages/shared/data/api/index.ts @@ -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 -} diff --git a/packages/shared/data/api/schemas/batch.ts b/packages/shared/data/api/schemas/batch.ts new file mode 100644 index 0000000000..c2fc3e0162 --- /dev/null +++ b/packages/shared/data/api/schemas/batch.ts @@ -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 { + /** 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 + }> + } + } +} diff --git a/packages/shared/data/api/schemas/index.ts b/packages/shared/data/api/schemas/index.ts new file mode 100644 index 0000000000..0af34a84e5 --- /dev/null +++ b/packages/shared/data/api/schemas/index.ts @@ -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 + * ``` + */ +export type ApiSchemas = AssertValidSchemas diff --git a/packages/shared/data/api/schemas/test.ts b/packages/shared/data/api/schemas/test.ts new file mode 100644 index 0000000000..6fd8681633 --- /dev/null +++ b/packages/shared/data/api/schemas/test.ts @@ -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 +} + +/** + * 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 +} + +/** + * 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 +} + +// ============================================================================ +// 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 + } + /** 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 + } + } + + /** + * 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 + /** Item count grouped by status */ + byStatus: Record + /** Item count grouped by priority */ + byPriority: Record + /** 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 + } + 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 + } + /** Update test configuration */ + PUT: { + body: Record + response: Record + } + } + + /** + * 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 + } + } + } +} diff --git a/src/main/data/README.md b/src/main/data/README.md index be16e72bf3..e6d52e83f2 100644 --- a/src/main/data/README.md +++ b/src/main/data/README.md @@ -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 = { + '/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 = { + '/items/:id': { + GET: async ({ params }) => { + return await SimpleService.getInstance().getItem(params.id) + } } } ``` diff --git a/src/main/data/api/core/ApiServer.ts b/src/main/data/api/core/ApiServer.ts index 038f7cc1b8..b2317e1395 100644 --- a/src/main/data/api/core/ApiServer.ts +++ b/src/main/data/api/core/ApiServer.ts @@ -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' diff --git a/src/main/data/api/handlers/batch.ts b/src/main/data/api/handlers/batch.ts new file mode 100644 index 0000000000..8ae302462e --- /dev/null +++ b/src/main/data/api/handlers/batch.ts @@ -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> = ApiHandler + +/** + * Batch API handlers implementation + */ +export const batchHandlers: { + [Path in keyof BatchSchemas]: { + [Method in keyof BatchSchemas[Path]]: BatchHandler> + } +} = { + '/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() } + })) + } + } +} diff --git a/src/main/data/api/handlers/index.ts b/src/main/data/api/handlers/index.ts index 817a882be8..79824b209a 100644 --- a/src/main/data/api/handlers/index.ts +++ b/src/main/data/api/handlers/index.ts @@ -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 } diff --git a/src/main/data/api/handlers/test.ts b/src/main/data/api/handlers/test.ts new file mode 100644 index 0000000000..a9522cf2a9 --- /dev/null +++ b/src/main/data/api/handlers/test.ts @@ -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> = ApiHandler + +/** + * Test API handlers implementation + */ +export const testHandlers: { + [Path in keyof TestSchemas]: { + [Method in keyof TestSchemas[Path]]: TestHandler> + } +} = { + '/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()) + } + } + } +} diff --git a/src/main/data/api/index.ts b/src/main/data/api/index.ts index 1bd4d3b7a5..c479db0050 100644 --- a/src/main/data/api/index.ts +++ b/src/main/data/api/index.ts @@ -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' diff --git a/src/renderer/src/data/DataApiService.ts b/src/renderer/src/data/DataApiService.ts index 2d3791139a..008a6e5baa 100644 --- a/src/renderer/src/data/DataApiService.ts +++ b/src/renderer/src/data/DataApiService.ts @@ -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, diff --git a/src/renderer/src/data/hooks/useDataApi.ts b/src/renderer/src/data/hooks/useDataApi.ts index de30473ab0..599a7aee42 100644 --- a/src/renderer/src/data/hooks/useDataApi.ts +++ b/src/renderer/src/data/hooks/useDataApi.ts @@ -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' diff --git a/tests/__mocks__/renderer/DataApiService.ts b/tests/__mocks__/renderer/DataApiService.ts index 122a2c03d0..e1bc58ed36 100644 --- a/tests/__mocks__/renderer/DataApiService.ts +++ b/tests/__mocks__/renderer/DataApiService.ts @@ -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' diff --git a/tests/__mocks__/renderer/useDataApi.ts b/tests/__mocks__/renderer/useDataApi.ts index 9b85d25550..53048738c8 100644 --- a/tests/__mocks__/renderer/useDataApi.ts +++ b/tests/__mocks__/renderer/useDataApi.ts @@ -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'