feat: add Data API channels and integrate swr for data fetching
- Introduced new IPC channels for Data API requests and responses in IpcChannel. - Added swr library to package.json for improved data fetching capabilities. - Updated preload API to include Data API related methods for handling requests and subscriptions. - Removed deprecated pending_default_values.ts file as part of data refactor.
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
import type { PaginationParams, ServiceOptions } from '@shared/data/api/apiTypes'
|
||||
|
||||
/**
|
||||
* Standard service interface for data operations
|
||||
* Defines the contract that all services should implement
|
||||
*/
|
||||
export interface IBaseService<T = any, TCreate = any, TUpdate = any> {
|
||||
/**
|
||||
* Find entity by ID
|
||||
*/
|
||||
findById(id: string, options?: ServiceOptions): Promise<T | null>
|
||||
|
||||
/**
|
||||
* Find multiple entities with pagination
|
||||
*/
|
||||
findMany(
|
||||
params: PaginationParams & Record<string, any>,
|
||||
options?: ServiceOptions
|
||||
): Promise<{
|
||||
items: T[]
|
||||
total: number
|
||||
hasNext?: boolean
|
||||
nextCursor?: string
|
||||
}>
|
||||
|
||||
/**
|
||||
* Create new entity
|
||||
*/
|
||||
create(data: TCreate, options?: ServiceOptions): Promise<T>
|
||||
|
||||
/**
|
||||
* Update existing entity
|
||||
*/
|
||||
update(id: string, data: TUpdate, options?: ServiceOptions): Promise<T>
|
||||
|
||||
/**
|
||||
* Delete entity (hard or soft delete depending on implementation)
|
||||
*/
|
||||
delete(id: string, options?: ServiceOptions): Promise<void>
|
||||
|
||||
/**
|
||||
* Check if entity exists
|
||||
*/
|
||||
exists(id: string, options?: ServiceOptions): Promise<boolean>
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended service interface for soft delete operations
|
||||
*/
|
||||
export interface ISoftDeleteService<T = any, TCreate = any, TUpdate = any> extends IBaseService<T, TCreate, TUpdate> {
|
||||
/**
|
||||
* Archive entity (soft delete)
|
||||
*/
|
||||
archive(id: string, options?: ServiceOptions): Promise<T | void>
|
||||
|
||||
/**
|
||||
* Restore archived entity
|
||||
*/
|
||||
restore(id: string, options?: ServiceOptions): Promise<T | void>
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended service interface for search operations
|
||||
*/
|
||||
export interface ISearchableService<T = any, TCreate = any, TUpdate = any> extends IBaseService<T, TCreate, TUpdate> {
|
||||
/**
|
||||
* Search entities by query
|
||||
*/
|
||||
search(
|
||||
query: string,
|
||||
params?: PaginationParams,
|
||||
options?: ServiceOptions
|
||||
): Promise<{
|
||||
items: T[]
|
||||
total: number
|
||||
hasNext?: boolean
|
||||
nextCursor?: string
|
||||
}>
|
||||
}
|
||||
|
||||
/**
|
||||
* Service interface for hierarchical data (parent-child relationships)
|
||||
*/
|
||||
export interface IHierarchicalService<TParent = any, TChild = any, TChildCreate = any> extends IBaseService<TParent> {
|
||||
/**
|
||||
* Get child entities for a parent
|
||||
*/
|
||||
getChildren(
|
||||
parentId: string,
|
||||
params?: PaginationParams,
|
||||
options?: ServiceOptions
|
||||
): Promise<{
|
||||
items: TChild[]
|
||||
total: number
|
||||
hasNext?: boolean
|
||||
nextCursor?: string
|
||||
}>
|
||||
|
||||
/**
|
||||
* Add child entity to parent
|
||||
*/
|
||||
addChild(parentId: string, childData: TChildCreate, options?: ServiceOptions): Promise<TChild>
|
||||
|
||||
/**
|
||||
* Remove child entity from parent
|
||||
*/
|
||||
removeChild(parentId: string, childId: string, options?: ServiceOptions): Promise<void>
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
import { loggerService } from '@logger'
|
||||
|
||||
const logger = loggerService.withContext('TestService')
|
||||
|
||||
/**
|
||||
* Test service for API testing scenarios
|
||||
* Provides mock data and various test cases for comprehensive API testing
|
||||
*/
|
||||
export class TestService {
|
||||
private static instance: TestService
|
||||
private testItems: any[] = []
|
||||
private nextId = 1
|
||||
|
||||
private constructor() {
|
||||
this.initializeMockData()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get singleton instance
|
||||
*/
|
||||
public static getInstance(): TestService {
|
||||
if (!TestService.instance) {
|
||||
TestService.instance = new TestService()
|
||||
}
|
||||
return TestService.instance
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize mock test data
|
||||
*/
|
||||
private initializeMockData() {
|
||||
// Initialize test items with various types
|
||||
for (let i = 1; i <= 20; i++) {
|
||||
this.testItems.push({
|
||||
id: `test-item-${i}`,
|
||||
title: `Test Item ${i}`,
|
||||
description: `This is test item ${i} for comprehensive API testing`,
|
||||
type: ['data', 'config', 'user', 'system'][i % 4],
|
||||
status: ['active', 'inactive', 'pending', 'archived'][i % 4],
|
||||
priority: ['low', 'medium', 'high'][i % 3],
|
||||
tags: [`tag${(i % 3) + 1}`, `category${(i % 2) + 1}`],
|
||||
createdAt: new Date(Date.now() - i * 24 * 60 * 60 * 1000).toISOString(),
|
||||
updatedAt: new Date(Date.now() - i * 12 * 60 * 60 * 1000).toISOString(),
|
||||
metadata: {
|
||||
version: `1.${i % 10}.0`,
|
||||
size: Math.floor(Math.random() * 1000) + 100,
|
||||
author: `TestUser${(i % 5) + 1}`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.nextId = 100
|
||||
logger.info('Mock test data initialized', {
|
||||
itemCount: this.testItems.length,
|
||||
types: ['data', 'config', 'user', 'system'],
|
||||
statuses: ['active', 'inactive', 'pending', 'archived']
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate new test ID
|
||||
*/
|
||||
private generateId(prefix: string = 'test-item'): string {
|
||||
return `${prefix}-${this.nextId++}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate network delay for realistic testing
|
||||
*/
|
||||
private async simulateDelay(min = 100, max = 500): Promise<void> {
|
||||
const delay = Math.floor(Math.random() * (max - min + 1)) + min
|
||||
await new Promise((resolve) => setTimeout(resolve, delay))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get paginated list of test items
|
||||
*/
|
||||
async getItems(
|
||||
params: {
|
||||
page?: number
|
||||
limit?: number
|
||||
type?: string
|
||||
status?: string
|
||||
search?: string
|
||||
} = {}
|
||||
): Promise<{
|
||||
items: any[]
|
||||
total: number
|
||||
page: number
|
||||
pageCount: number
|
||||
hasNext: boolean
|
||||
hasPrev: boolean
|
||||
}> {
|
||||
await this.simulateDelay()
|
||||
|
||||
const { page = 1, limit = 20, type, status, search } = params
|
||||
let filteredItems = [...this.testItems]
|
||||
|
||||
// Apply filters
|
||||
if (type) {
|
||||
filteredItems = filteredItems.filter((item) => item.type === type)
|
||||
}
|
||||
if (status) {
|
||||
filteredItems = filteredItems.filter((item) => item.status === status)
|
||||
}
|
||||
if (search) {
|
||||
const searchLower = search.toLowerCase()
|
||||
filteredItems = filteredItems.filter(
|
||||
(item) => item.title.toLowerCase().includes(searchLower) || item.description.toLowerCase().includes(searchLower)
|
||||
)
|
||||
}
|
||||
|
||||
// Apply pagination
|
||||
const startIndex = (page - 1) * limit
|
||||
const items = filteredItems.slice(startIndex, startIndex + limit)
|
||||
const total = filteredItems.length
|
||||
const pageCount = Math.ceil(total / limit)
|
||||
|
||||
logger.debug('Retrieved test items', {
|
||||
page,
|
||||
limit,
|
||||
filters: { type, status, search },
|
||||
total,
|
||||
itemCount: items.length
|
||||
})
|
||||
|
||||
return {
|
||||
items,
|
||||
total,
|
||||
page,
|
||||
pageCount,
|
||||
hasNext: startIndex + limit < total,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get single test item by ID
|
||||
*/
|
||||
async getItemById(id: string): Promise<any | null> {
|
||||
await this.simulateDelay()
|
||||
|
||||
const item = this.testItems.find((item) => item.id === id)
|
||||
|
||||
if (!item) {
|
||||
logger.warn('Test item not found', { id })
|
||||
return null
|
||||
}
|
||||
|
||||
logger.debug('Retrieved test item by ID', { id, title: item.title })
|
||||
return item
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new test item
|
||||
*/
|
||||
async createItem(data: {
|
||||
title: string
|
||||
description?: string
|
||||
type?: string
|
||||
status?: string
|
||||
priority?: string
|
||||
tags?: string[]
|
||||
metadata?: Record<string, any>
|
||||
}): Promise<any> {
|
||||
await this.simulateDelay()
|
||||
|
||||
const newItem = {
|
||||
id: this.generateId(),
|
||||
title: data.title,
|
||||
description: data.description || '',
|
||||
type: data.type || 'data',
|
||||
status: data.status || 'active',
|
||||
priority: data.priority || 'medium',
|
||||
tags: data.tags || [],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
metadata: {
|
||||
version: '1.0.0',
|
||||
size: Math.floor(Math.random() * 1000) + 100,
|
||||
author: 'TestUser',
|
||||
...data.metadata
|
||||
}
|
||||
}
|
||||
|
||||
this.testItems.unshift(newItem)
|
||||
logger.info('Created new test item', { id: newItem.id, title: newItem.title })
|
||||
|
||||
return newItem
|
||||
}
|
||||
|
||||
/**
|
||||
* Update existing test item
|
||||
*/
|
||||
async updateItem(
|
||||
id: string,
|
||||
data: Partial<{
|
||||
title: string
|
||||
description: string
|
||||
type: string
|
||||
status: string
|
||||
priority: string
|
||||
tags: string[]
|
||||
metadata: Record<string, any>
|
||||
}>
|
||||
): Promise<any | null> {
|
||||
await this.simulateDelay()
|
||||
|
||||
const itemIndex = this.testItems.findIndex((item) => item.id === id)
|
||||
|
||||
if (itemIndex === -1) {
|
||||
logger.warn('Test item not found for update', { id })
|
||||
return null
|
||||
}
|
||||
|
||||
const updatedItem = {
|
||||
...this.testItems[itemIndex],
|
||||
...data,
|
||||
updatedAt: new Date().toISOString(),
|
||||
metadata: {
|
||||
...this.testItems[itemIndex].metadata,
|
||||
...data.metadata
|
||||
}
|
||||
}
|
||||
|
||||
this.testItems[itemIndex] = updatedItem
|
||||
logger.info('Updated test item', { id, changes: Object.keys(data) })
|
||||
|
||||
return updatedItem
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete test item
|
||||
*/
|
||||
async deleteItem(id: string): Promise<boolean> {
|
||||
await this.simulateDelay()
|
||||
|
||||
const itemIndex = this.testItems.findIndex((item) => item.id === id)
|
||||
|
||||
if (itemIndex === -1) {
|
||||
logger.warn('Test item not found for deletion', { id })
|
||||
return false
|
||||
}
|
||||
|
||||
this.testItems.splice(itemIndex, 1)
|
||||
logger.info('Deleted test item', { id })
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Get test statistics
|
||||
*/
|
||||
async getStats(): Promise<{
|
||||
total: number
|
||||
byType: Record<string, number>
|
||||
byStatus: Record<string, number>
|
||||
byPriority: Record<string, number>
|
||||
recentActivity: Array<{
|
||||
date: string
|
||||
count: number
|
||||
}>
|
||||
}> {
|
||||
await this.simulateDelay()
|
||||
|
||||
const byType: Record<string, number> = {}
|
||||
const byStatus: Record<string, number> = {}
|
||||
const byPriority: Record<string, number> = {}
|
||||
|
||||
this.testItems.forEach((item) => {
|
||||
byType[item.type] = (byType[item.type] || 0) + 1
|
||||
byStatus[item.status] = (byStatus[item.status] || 0) + 1
|
||||
byPriority[item.priority] = (byPriority[item.priority] || 0) + 1
|
||||
})
|
||||
|
||||
// Generate recent activity (mock data)
|
||||
const recentActivity: Array<{ date: string; count: number }> = []
|
||||
for (let i = 6; i >= 0; i--) {
|
||||
const date = new Date(Date.now() - i * 24 * 60 * 60 * 1000).toISOString().split('T')[0]
|
||||
recentActivity.push({
|
||||
date,
|
||||
count: Math.floor(Math.random() * 10) + 1
|
||||
})
|
||||
}
|
||||
|
||||
const stats = {
|
||||
total: this.testItems.length,
|
||||
byType,
|
||||
byStatus,
|
||||
byPriority,
|
||||
recentActivity
|
||||
}
|
||||
|
||||
logger.debug('Retrieved test statistics', stats)
|
||||
return stats
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk operations on test items
|
||||
*/
|
||||
async bulkOperation(
|
||||
operation: 'create' | 'update' | 'delete',
|
||||
data: any[]
|
||||
): Promise<{
|
||||
successful: number
|
||||
failed: number
|
||||
errors: string[]
|
||||
}> {
|
||||
await this.simulateDelay(200, 800)
|
||||
|
||||
let successful = 0
|
||||
let failed = 0
|
||||
const errors: string[] = []
|
||||
|
||||
for (const item of data) {
|
||||
try {
|
||||
switch (operation) {
|
||||
case 'create':
|
||||
await this.createItem(item)
|
||||
successful++
|
||||
break
|
||||
case 'update': {
|
||||
const updated = await this.updateItem(item.id, item)
|
||||
if (updated) successful++
|
||||
else {
|
||||
failed++
|
||||
errors.push(`Item not found: ${item.id}`)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const deleted = await this.deleteItem(item.id)
|
||||
if (deleted) successful++
|
||||
else {
|
||||
failed++
|
||||
errors.push(`Item not found: ${item.id}`)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
failed++
|
||||
errors.push(`Error processing item: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
||||
}
|
||||
}
|
||||
|
||||
logger.info('Completed bulk operation', { operation, successful, failed, errorCount: errors.length })
|
||||
|
||||
return { successful, failed, errors }
|
||||
}
|
||||
|
||||
/**
|
||||
* Search test items
|
||||
*/
|
||||
async searchItems(
|
||||
query: string,
|
||||
options: {
|
||||
page?: number
|
||||
limit?: number
|
||||
filters?: Record<string, any>
|
||||
} = {}
|
||||
): Promise<{
|
||||
items: any[]
|
||||
total: number
|
||||
page: number
|
||||
pageCount: number
|
||||
hasNext: boolean
|
||||
hasPrev: boolean
|
||||
}> {
|
||||
await this.simulateDelay()
|
||||
|
||||
const { page = 1, limit = 20, filters = {} } = options
|
||||
const queryLower = query.toLowerCase()
|
||||
|
||||
const results = this.testItems.filter((item) => {
|
||||
// Text search
|
||||
const matchesQuery =
|
||||
item.title.toLowerCase().includes(queryLower) ||
|
||||
item.description.toLowerCase().includes(queryLower) ||
|
||||
item.tags.some((tag: string) => tag.toLowerCase().includes(queryLower))
|
||||
|
||||
// Apply additional filters
|
||||
let matchesFilters = true
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
if (value && item[key] !== value) {
|
||||
matchesFilters = false
|
||||
}
|
||||
})
|
||||
|
||||
return matchesQuery && matchesFilters
|
||||
})
|
||||
|
||||
// Apply pagination
|
||||
const startIndex = (page - 1) * limit
|
||||
const items = results.slice(startIndex, startIndex + limit)
|
||||
const total = results.length
|
||||
const pageCount = Math.ceil(total / limit)
|
||||
|
||||
logger.debug('Search completed', { query, total, itemCount: items.length })
|
||||
|
||||
return {
|
||||
items,
|
||||
total,
|
||||
page,
|
||||
pageCount,
|
||||
hasNext: startIndex + limit < total,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate error scenarios for testing
|
||||
*/
|
||||
async simulateError(errorType: string): Promise<never> {
|
||||
await this.simulateDelay()
|
||||
|
||||
logger.warn('Simulating error scenario', { errorType })
|
||||
|
||||
switch (errorType) {
|
||||
case 'timeout':
|
||||
await new Promise((resolve) => setTimeout(resolve, 35000))
|
||||
throw new Error('Request timeout')
|
||||
case 'network':
|
||||
throw new Error('Network connection failed')
|
||||
case 'server':
|
||||
throw new Error('Internal server error (500)')
|
||||
case 'notfound':
|
||||
throw new Error('Resource not found (404)')
|
||||
case 'validation':
|
||||
throw new Error('Validation failed: Invalid input data')
|
||||
case 'unauthorized':
|
||||
throw new Error('Unauthorized access (401)')
|
||||
case 'ratelimit':
|
||||
throw new Error('Rate limit exceeded (429)')
|
||||
default:
|
||||
throw new Error('Generic test error occurred')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all test data to initial state
|
||||
*/
|
||||
async resetData(): Promise<void> {
|
||||
await this.simulateDelay()
|
||||
|
||||
this.testItems = []
|
||||
this.nextId = 1
|
||||
this.initializeMockData()
|
||||
|
||||
logger.info('Test data reset to initial state')
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user