- Remove unused imports (run, trackDuration, PerformanceTracker, ConfigManager, disconnectDb) flagged by ESLint - Remove unused isSlow variable in performance-monitor catch block - Add eslint-disable for require() in Playwright JS script - Fix logger-performance test flakiness by using vi.resetModules() with dynamic imports to prevent cached logger references across test files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
697 lines
21 KiB
TypeScript
697 lines
21 KiB
TypeScript
/**
|
|
* Data Access Object for ExtractorOperationHistory table
|
|
*
|
|
* Handles database operations for tracking extraction operation history:
|
|
* - Batch record insertion
|
|
* - Batch status updates
|
|
* - Querying batches (with user filtering for non-admin users)
|
|
* - Getting batch details
|
|
* - Deleting batches
|
|
*/
|
|
|
|
import { create, type IDatabaseService } from './index'
|
|
import { createDialect, type SqlDialect } from './dialects'
|
|
import { createLogger, getRequestId, trackDuration } from '../logger'
|
|
import type {
|
|
OperationHistoryRecord,
|
|
BatchStats,
|
|
InsertBatchRecordInput,
|
|
UpdateBatchStatusResult,
|
|
GetBatchesOptions
|
|
} from '../../types/operation-history.types'
|
|
|
|
const log = createLogger('ExtractorOperationHistoryDAO')
|
|
|
|
/**
|
|
* Format datetime value from database to ISO string
|
|
* mssql driver returns Date objects in UTC format
|
|
*/
|
|
function formatDateTime(value: unknown): string {
|
|
if (value instanceof Date) {
|
|
return value.toISOString()
|
|
}
|
|
return value ? String(value) : new Date().toISOString()
|
|
}
|
|
|
|
/**
|
|
* Configuration for ExtractorOperationHistory table
|
|
*/
|
|
export const EXTRACTOR_OPERATION_HISTORY_CONFIG = {
|
|
COLUMNS: {
|
|
ID: 'ID',
|
|
BATCH_ID: 'BatchId',
|
|
USER_ID: 'UserId',
|
|
USERNAME: 'Username',
|
|
PRODUCTION_ID: 'ProductionId',
|
|
ORDER_NUMBER: 'OrderNumber',
|
|
OPERATION_TIME: 'OperationTime',
|
|
STATUS: 'Status',
|
|
RECORD_COUNT: 'RecordCount',
|
|
ERROR_MESSAGE: 'ErrorMessage'
|
|
}
|
|
} as const
|
|
|
|
/**
|
|
* ExtractorOperationHistory DAO Class
|
|
*/
|
|
export class ExtractorOperationHistoryDAO {
|
|
private dbService: IDatabaseService | null = null
|
|
private dialect: SqlDialect | null = null
|
|
|
|
private getDialect(): SqlDialect {
|
|
if (!this.dialect) {
|
|
this.dialect = createDialect(this.dbService!.type)
|
|
}
|
|
return this.dialect
|
|
}
|
|
|
|
/**
|
|
* Get the appropriate table name based on database type
|
|
*/
|
|
private getTableName(): string {
|
|
return this.getDialect().quoteTableName('dbo', 'ExtractorOperationHistory')
|
|
}
|
|
|
|
/**
|
|
* Get database service instance using DatabaseFactory
|
|
*/
|
|
private async getDatabaseService(): Promise<IDatabaseService> {
|
|
if (this.dbService && this.dbService.isConnected()) {
|
|
return this.dbService
|
|
}
|
|
|
|
this.dbService = await create()
|
|
return this.dbService
|
|
}
|
|
|
|
// ==================== INSERT ====================
|
|
|
|
/**
|
|
* Insert batch records for a single extraction operation
|
|
* @param batchId - Unique batch identifier
|
|
* @param userId - User ID performing the operation
|
|
* @param username - Username performing the operation
|
|
* @param records - Array of order records to insert
|
|
* @returns True if successful
|
|
*/
|
|
async insertBatchRecords(
|
|
batchId: string,
|
|
userId: number,
|
|
username: string,
|
|
records: InsertBatchRecordInput[]
|
|
): Promise<boolean> {
|
|
if (!records || records.length === 0) {
|
|
log.warn('No records to insert', {
|
|
batchId,
|
|
tableName: this.getTableName(),
|
|
requestId: getRequestId()
|
|
})
|
|
return false
|
|
}
|
|
|
|
const requestId = getRequestId() || `insert-${Date.now()}`
|
|
|
|
try {
|
|
const dbService = await this.getDatabaseService()
|
|
const tableName = this.getTableName()
|
|
const dialect = this.getDialect()
|
|
|
|
log.info('Batch records insertion started', {
|
|
tableName,
|
|
operationType: 'INSERT',
|
|
requestId,
|
|
batchId,
|
|
userId,
|
|
username,
|
|
recordCount: records.length
|
|
})
|
|
|
|
for (const record of records) {
|
|
try {
|
|
const sqlString = `
|
|
INSERT INTO ${tableName}
|
|
(BatchId, UserId, Username, ProductionId, OrderNumber, OperationTime, Status)
|
|
VALUES
|
|
(${dialect.param(0)}, ${dialect.param(1)}, ${dialect.param(2)}, ${dialect.param(3)}, ${dialect.param(4)}, ${dialect.currentTimestamp()}, 'pending')
|
|
`
|
|
await trackDuration(
|
|
async () =>
|
|
await dbService.query(sqlString, [
|
|
batchId,
|
|
userId,
|
|
username,
|
|
record.productionId || null,
|
|
record.orderNumber
|
|
]),
|
|
{
|
|
operationName: 'ExtractorOperationHistoryDAO.insertBatchRecords',
|
|
context: { tableName, operationType: 'INSERT', batchId }
|
|
}
|
|
)
|
|
} catch (error) {
|
|
log.error('Error inserting individual record', {
|
|
tableName,
|
|
operationType: 'INSERT',
|
|
requestId,
|
|
batchId,
|
|
orderNumber: record.orderNumber,
|
|
error: error instanceof Error ? error.message : String(error)
|
|
})
|
|
}
|
|
}
|
|
|
|
log.info('Batch records inserted', {
|
|
tableName,
|
|
operationType: 'INSERT',
|
|
requestId,
|
|
batchId,
|
|
count: records.length
|
|
})
|
|
return true
|
|
} catch (error) {
|
|
log.error('Insert batch records error', {
|
|
tableName: this.getTableName(),
|
|
operationType: 'INSERT',
|
|
requestId,
|
|
batchId,
|
|
recordCount: records.length,
|
|
error: error instanceof Error ? error.message : String(error)
|
|
})
|
|
return false
|
|
}
|
|
}
|
|
|
|
// ==================== UPDATE ====================
|
|
|
|
/**
|
|
* Update the status of all records in a batch
|
|
* @param batchId - Batch identifier
|
|
* @param status - New status (success, failed, partial)
|
|
* @returns Update result
|
|
*/
|
|
async updateBatchStatus(batchId: string, status: string): Promise<UpdateBatchStatusResult> {
|
|
try {
|
|
const dbService = await this.getDatabaseService()
|
|
const tableName = this.getTableName()
|
|
const dialect = this.getDialect()
|
|
|
|
const sqlString = `
|
|
UPDATE ${tableName}
|
|
SET Status = ${dialect.param(0)}
|
|
WHERE BatchId = ${dialect.param(1)}
|
|
`
|
|
const params = [status, batchId]
|
|
|
|
await trackDuration(async () => await dbService.query(sqlString, params), {
|
|
operationName: 'ExtractorOperationHistoryDAO.updateBatchStatus',
|
|
context: { tableName, operationType: 'UPDATE', batchId }
|
|
})
|
|
|
|
log.info('Batch status updated', {
|
|
tableName,
|
|
operationType: 'UPDATE',
|
|
requestId: getRequestId(),
|
|
batchId,
|
|
status
|
|
})
|
|
return { success: true, updatedCount: 1 }
|
|
} catch (error) {
|
|
log.error('Update batch status error', {
|
|
tableName: this.getTableName(),
|
|
operationType: 'UPDATE',
|
|
requestId: getRequestId(),
|
|
batchId,
|
|
error: error instanceof Error ? error.message : String(error)
|
|
})
|
|
return { success: false, updatedCount: 0 }
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update a single record's status, error message, and optional record count
|
|
* @param batchId - Batch identifier
|
|
* @param orderNumber - Order number
|
|
* @param status - New status
|
|
* @param errorMessage - Optional error message
|
|
* @param recordCount - Optional per-order record count
|
|
* @returns True if successful
|
|
*/
|
|
async updateRecordStatus(
|
|
batchId: string,
|
|
orderNumber: string,
|
|
status: string,
|
|
errorMessage?: string,
|
|
recordCount?: number
|
|
): Promise<boolean> {
|
|
try {
|
|
const dbService = await this.getDatabaseService()
|
|
const tableName = this.getTableName()
|
|
const dialect = this.getDialect()
|
|
|
|
let sqlString: string
|
|
let params: (string | number | null)[]
|
|
|
|
if (recordCount !== undefined) {
|
|
sqlString = `
|
|
UPDATE ${tableName}
|
|
SET Status = ${dialect.param(0)},
|
|
ErrorMessage = ${dialect.param(1)},
|
|
RecordCount = ${dialect.param(2)}
|
|
WHERE BatchId = ${dialect.param(3)}
|
|
AND OrderNumber = ${dialect.param(4)}
|
|
`
|
|
params = [status, errorMessage || null, recordCount, batchId, orderNumber]
|
|
} else {
|
|
sqlString = `
|
|
UPDATE ${tableName}
|
|
SET Status = ${dialect.param(0)},
|
|
ErrorMessage = ${dialect.param(1)}
|
|
WHERE BatchId = ${dialect.param(2)}
|
|
AND OrderNumber = ${dialect.param(3)}
|
|
`
|
|
params = [status, errorMessage || null, batchId, orderNumber]
|
|
}
|
|
|
|
await trackDuration(async () => await dbService.query(sqlString, params), {
|
|
operationName: 'ExtractorOperationHistoryDAO.updateRecordStatus',
|
|
context: { tableName, operationType: 'UPDATE', batchId }
|
|
})
|
|
|
|
return true
|
|
} catch (error) {
|
|
log.error('Update record status error', {
|
|
tableName: this.getTableName(),
|
|
operationType: 'UPDATE',
|
|
requestId: getRequestId(),
|
|
batchId,
|
|
orderNumber,
|
|
error: error instanceof Error ? error.message : String(error)
|
|
})
|
|
return false
|
|
}
|
|
}
|
|
|
|
// ==================== READ ====================
|
|
|
|
/**
|
|
* Get batch statistics with optional user filtering
|
|
* @param userId - Optional user ID for filtering (Admin gets all, User gets own)
|
|
* @param options - Query options (limit, offset, usernames)
|
|
* @returns Array of batch statistics
|
|
*/
|
|
async getBatches(userId?: number, options?: GetBatchesOptions): Promise<BatchStats[]> {
|
|
try {
|
|
const dbService = await this.getDatabaseService()
|
|
const tableName = this.getTableName()
|
|
const dialect = this.getDialect()
|
|
|
|
let sqlString = `
|
|
SELECT
|
|
BatchId,
|
|
UserId,
|
|
Username,
|
|
MIN(OperationTime) as OperationTime,
|
|
MAX(Status) as Status,
|
|
COUNT(*) as TotalOrders,
|
|
SUM(COALESCE(RecordCount, 0)) as TotalRecords,
|
|
SUM(CASE WHEN Status = 'success' THEN 1 ELSE 0 END) as SuccessCount,
|
|
SUM(CASE WHEN Status = 'failed' THEN 1 ELSE 0 END) as FailedCount
|
|
FROM ${tableName}
|
|
`
|
|
|
|
const params: (number | string)[] = []
|
|
|
|
if (userId !== undefined) {
|
|
sqlString += ` WHERE UserId = ${dialect.param(params.length)} `
|
|
params.push(userId)
|
|
} else if (options?.usernames && options.usernames.length > 0) {
|
|
sqlString += ` WHERE Username IN (${dialect.params(options.usernames.length)}) `
|
|
params.push(...options.usernames)
|
|
}
|
|
|
|
sqlString += `
|
|
GROUP BY BatchId, UserId, Username
|
|
ORDER BY OperationTime DESC
|
|
`
|
|
|
|
if (options?.limit) {
|
|
const safeLimit = Math.floor(options.limit)
|
|
const safeOffset = options.offset !== undefined ? Math.floor(options.offset) : undefined
|
|
|
|
const result = dialect.paginate({
|
|
sql: sqlString,
|
|
limit: safeLimit,
|
|
offset: safeOffset,
|
|
paramIndex: params.length
|
|
})
|
|
sqlString = result.sql
|
|
|
|
if (dialect.dbType === 'sqlserver') {
|
|
if (safeOffset !== undefined) {
|
|
params.push(safeOffset)
|
|
}
|
|
params.push(safeLimit)
|
|
}
|
|
}
|
|
|
|
const result = await trackDuration(async () => await dbService.query(sqlString, params), {
|
|
operationName: 'ExtractorOperationHistoryDAO.getBatches',
|
|
context: { tableName, operationType: 'SELECT', userId }
|
|
})
|
|
|
|
return result.result.rows.map((row) => ({
|
|
batchId: row.BatchId as string,
|
|
userId: row.UserId as number,
|
|
username: row.Username as string,
|
|
operationTime: formatDateTime(row.OperationTime),
|
|
status: row.Status as string,
|
|
totalOrders: row.TotalOrders as number,
|
|
totalRecords: (row.TotalRecords as number) || 0,
|
|
successCount: (row.SuccessCount as number) || 0,
|
|
failedCount: (row.FailedCount as number) || 0
|
|
}))
|
|
} catch (error) {
|
|
log.error('Get batches error', {
|
|
tableName: this.getTableName(),
|
|
operationType: 'SELECT',
|
|
requestId: getRequestId(),
|
|
userId,
|
|
error: error instanceof Error ? error.message : String(error)
|
|
})
|
|
return []
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get detailed records for a specific batch
|
|
* @param batchId - Batch identifier
|
|
* @returns Array of operation records
|
|
*/
|
|
async getBatchDetails(batchId: string): Promise<OperationHistoryRecord[]> {
|
|
try {
|
|
const dbService = await this.getDatabaseService()
|
|
const tableName = this.getTableName()
|
|
const dialect = this.getDialect()
|
|
|
|
const placeholder = dialect.param(0)
|
|
const sqlString = `
|
|
SELECT
|
|
ID,
|
|
BatchId,
|
|
UserId,
|
|
Username,
|
|
ProductionId,
|
|
OrderNumber,
|
|
OperationTime,
|
|
Status,
|
|
RecordCount,
|
|
ErrorMessage
|
|
FROM ${tableName}
|
|
WHERE BatchId = ${placeholder}
|
|
ORDER BY ID
|
|
`
|
|
|
|
const result = await trackDuration(async () => await dbService.query(sqlString, [batchId]), {
|
|
operationName: 'ExtractorOperationHistoryDAO.getBatchDetails',
|
|
context: { tableName, operationType: 'SELECT', batchId }
|
|
})
|
|
|
|
return result.result.rows.map((row) => ({
|
|
id: row.ID as number,
|
|
batchId: row.BatchId as string,
|
|
userId: row.UserId as number,
|
|
username: row.Username as string,
|
|
productionId: row.ProductionId as string | null,
|
|
orderNumber: row.OrderNumber as string,
|
|
operationTime: new Date(row.OperationTime as string),
|
|
status: row.Status as string,
|
|
recordCount: row.RecordCount as number | null,
|
|
errorMessage: row.ErrorMessage as string | null
|
|
}))
|
|
} catch (error) {
|
|
log.error('Get batch details error', {
|
|
tableName: this.getTableName(),
|
|
operationType: 'SELECT',
|
|
requestId: getRequestId(),
|
|
batchId,
|
|
error: error instanceof Error ? error.message : String(error)
|
|
})
|
|
return []
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a single batch's statistics
|
|
* @param batchId - Batch identifier
|
|
* @returns Batch statistics or null
|
|
*/
|
|
async getBatchStats(batchId: string): Promise<BatchStats | null> {
|
|
try {
|
|
const dbService = await this.getDatabaseService()
|
|
const tableName = this.getTableName()
|
|
const dialect = this.getDialect()
|
|
|
|
const placeholder = dialect.param(0)
|
|
const sqlString = `
|
|
SELECT
|
|
BatchId,
|
|
UserId,
|
|
Username,
|
|
MIN(OperationTime) as OperationTime,
|
|
MAX(Status) as Status,
|
|
COUNT(*) as TotalOrders,
|
|
SUM(COALESCE(RecordCount, 0)) as TotalRecords,
|
|
SUM(CASE WHEN Status = 'success' THEN 1 ELSE 0 END) as SuccessCount,
|
|
SUM(CASE WHEN Status = 'failed' THEN 1 ELSE 0 END) as FailedCount
|
|
FROM ${tableName}
|
|
WHERE BatchId = ${placeholder}
|
|
GROUP BY BatchId, UserId, Username
|
|
`
|
|
|
|
const result = await trackDuration(async () => await dbService.query(sqlString, [batchId]), {
|
|
operationName: 'ExtractorOperationHistoryDAO.getBatchStats',
|
|
context: { tableName, operationType: 'SELECT', batchId }
|
|
})
|
|
|
|
if (result.result.rows.length === 0) {
|
|
return null
|
|
}
|
|
|
|
const row = result.result.rows[0]
|
|
return {
|
|
batchId: row.BatchId as string,
|
|
userId: row.UserId as number,
|
|
username: row.Username as string,
|
|
operationTime: formatDateTime(row.OperationTime),
|
|
status: row.Status as string,
|
|
totalOrders: row.TotalOrders as number,
|
|
totalRecords: (row.TotalRecords as number) || 0,
|
|
successCount: (row.SuccessCount as number) || 0,
|
|
failedCount: (row.FailedCount as number) || 0
|
|
}
|
|
} catch (error) {
|
|
log.error('Get batch stats error', {
|
|
tableName: this.getTableName(),
|
|
operationType: 'SELECT',
|
|
requestId: getRequestId(),
|
|
batchId,
|
|
error: error instanceof Error ? error.message : String(error)
|
|
})
|
|
return null
|
|
}
|
|
}
|
|
|
|
// ==================== DELETE ====================
|
|
|
|
/**
|
|
* Delete a batch with permission checking
|
|
* @param batchId - Batch identifier
|
|
* @param requestingUserId - User ID requesting the deletion
|
|
* @param isAdmin - Whether the requesting user is an admin
|
|
* @returns True if successful
|
|
*/
|
|
async deleteBatch(
|
|
batchId: string,
|
|
requestingUserId: number,
|
|
isAdmin: boolean
|
|
): Promise<{ success: boolean; error?: string }> {
|
|
const requestId = getRequestId() || `delete-${Date.now()}`
|
|
|
|
try {
|
|
const dbService = await this.getDatabaseService()
|
|
const tableName = this.getTableName()
|
|
const dialect = this.getDialect()
|
|
|
|
// First check if the batch exists and if the user has permission
|
|
const batchStats = await this.getBatchStats(batchId)
|
|
|
|
if (!batchStats) {
|
|
return { success: false, error: '批次不存在' }
|
|
}
|
|
|
|
// Non-admin users can only delete their own batches
|
|
if (!isAdmin && batchStats.userId !== requestingUserId) {
|
|
return { success: false, error: '没有权限删除此批次' }
|
|
}
|
|
|
|
// Delete the batch
|
|
const placeholder = dialect.param(0)
|
|
const sqlString = `
|
|
DELETE FROM ${tableName}
|
|
WHERE BatchId = ${placeholder}
|
|
`
|
|
|
|
const result = await trackDuration(async () => await dbService.query(sqlString, [batchId]), {
|
|
operationName: 'ExtractorOperationHistoryDAO.deleteBatch',
|
|
context: { tableName, operationType: 'DELETE', batchId, requestingUserId }
|
|
})
|
|
|
|
log.info('Batch deleted', {
|
|
tableName,
|
|
operationType: 'DELETE',
|
|
requestId,
|
|
batchId,
|
|
rowCount: result.result.rowCount
|
|
})
|
|
return { success: true }
|
|
} catch (error) {
|
|
log.error('Delete batch error', {
|
|
tableName: this.getTableName(),
|
|
operationType: 'DELETE',
|
|
requestId,
|
|
batchId,
|
|
error: error instanceof Error ? error.message : String(error)
|
|
})
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : String(error)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete all batches for a specific user
|
|
* @param userId - User ID
|
|
* @returns Number of batches deleted
|
|
*/
|
|
async deleteByUser(userId: number): Promise<number> {
|
|
try {
|
|
const dbService = await this.getDatabaseService()
|
|
const tableName = this.getTableName()
|
|
const dialect = this.getDialect()
|
|
|
|
const placeholder = dialect.param(0)
|
|
const sqlString = `
|
|
DELETE FROM ${tableName}
|
|
WHERE UserId = ${placeholder}
|
|
`
|
|
|
|
const result = await trackDuration(async () => await dbService.query(sqlString, [userId]), {
|
|
operationName: 'ExtractorOperationHistoryDAO.deleteByUser',
|
|
context: { tableName, operationType: 'DELETE', userId }
|
|
})
|
|
return result.result.rowCount
|
|
} catch (error) {
|
|
log.error('Delete by user error', {
|
|
tableName: this.getTableName(),
|
|
operationType: 'DELETE',
|
|
requestId: getRequestId(),
|
|
userId,
|
|
error: error instanceof Error ? error.message : String(error)
|
|
})
|
|
return 0
|
|
}
|
|
}
|
|
|
|
// ==================== UTILITIES ====================
|
|
|
|
/**
|
|
* Check if a batch exists
|
|
* @param batchId - Batch identifier
|
|
* @returns True if batch exists
|
|
*/
|
|
async batchExists(batchId: string): Promise<boolean> {
|
|
try {
|
|
const dbService = await this.getDatabaseService()
|
|
const tableName = this.getTableName()
|
|
const dialect = this.getDialect()
|
|
|
|
const placeholder = dialect.param(0)
|
|
const sqlString = `
|
|
SELECT COUNT(*) as count
|
|
FROM ${tableName}
|
|
WHERE BatchId = ${placeholder}
|
|
`
|
|
|
|
const result = await trackDuration(async () => await dbService.query(sqlString, [batchId]), {
|
|
operationName: 'ExtractorOperationHistoryDAO.batchExists',
|
|
context: { tableName, operationType: 'SELECT', batchId }
|
|
})
|
|
return result.result.rows.length > 0 && (result.result.rows[0].count as number) > 0
|
|
} catch (error) {
|
|
log.error('Batch exists error', {
|
|
tableName: this.getTableName(),
|
|
operationType: 'SELECT',
|
|
requestId: getRequestId(),
|
|
batchId,
|
|
error: error instanceof Error ? error.message : String(error)
|
|
})
|
|
return false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Count total batches with optional user filtering
|
|
* @param userId - Optional user ID for filtering
|
|
* @param usernames - Optional usernames filter for Admin users
|
|
* @returns Total number of batches
|
|
*/
|
|
async countBatches(userId?: number, usernames?: string[]): Promise<number> {
|
|
try {
|
|
const dbService = await this.getDatabaseService()
|
|
const tableName = this.getTableName()
|
|
const dialect = this.getDialect()
|
|
|
|
let sqlString = `
|
|
SELECT COUNT(DISTINCT BatchId) as count
|
|
FROM ${tableName}
|
|
`
|
|
|
|
const params: (number | string)[] = []
|
|
|
|
if (userId !== undefined) {
|
|
sqlString += ` WHERE UserId = ${dialect.param(params.length)} `
|
|
params.push(userId)
|
|
} else if (usernames && usernames.length > 0) {
|
|
sqlString += ` WHERE Username IN (${dialect.params(usernames.length)}) `
|
|
params.push(...usernames)
|
|
}
|
|
|
|
const result = await trackDuration(async () => await dbService.query(sqlString, params), {
|
|
operationName: 'ExtractorOperationHistoryDAO.countBatches',
|
|
context: { tableName, operationType: 'SELECT', userId }
|
|
})
|
|
return result.result.rows.length > 0 ? (result.result.rows[0].count as number) : 0
|
|
} catch (error) {
|
|
log.error('Count batches error', {
|
|
tableName: this.getTableName(),
|
|
operationType: 'SELECT',
|
|
requestId: getRequestId(),
|
|
error: error instanceof Error ? error.message : String(error)
|
|
})
|
|
return 0
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Disconnect from database
|
|
*/
|
|
async disconnect(): Promise<void> {
|
|
if (this.dbService) {
|
|
await this.dbService.disconnect()
|
|
this.dbService = null
|
|
this.dialect = null
|
|
}
|
|
}
|
|
}
|