UNPKG

defarm-sdk

Version:

DeFarm SDK - On-premise blockchain data processing and tokenization engine for agriculture supply chain

203 lines (173 loc) 4.94 kB
import type { DataProcessor, Asset, ValidationResult, ProcessingResult, ValidationError } from '../types'; /** * Base processor class that other processors can extend */ export abstract class BaseProcessor implements DataProcessor { abstract name: string; abstract version: string; async validate(asset: Asset): Promise<ValidationResult> { const errors: ValidationError[] = []; // Common validations if (!asset.name || asset.name.trim().length === 0) { errors.push({ field: 'name', message: 'Asset name is required', code: 'REQUIRED_FIELD' }); } if (!asset.category) { errors.push({ field: 'category', message: 'Asset category is required', code: 'REQUIRED_FIELD' }); } if (!asset.ownership?.ownerId) { errors.push({ field: 'ownership.ownerId', message: 'Owner ID is required', code: 'REQUIRED_FIELD' }); } // Category-specific validations const categoryErrors = await this.validateCategory(asset); errors.push(...categoryErrors); return { valid: errors.length === 0, errors: errors.length > 0 ? errors : undefined }; } async process(asset: Asset): Promise<Asset> { // Run pre-processing hook if defined if (this.beforeProcess) { asset = await this.beforeProcess(asset); } // Ensure metadata exists and is properly structured asset.metadata = { ...asset.metadata, updated: new Date(), version: (asset.metadata?.version || 0) + 1 }; // Run category-specific processing asset = await this.processCategory(asset); // Run post-processing hook if defined if (this.afterProcess) { asset = await this.afterProcess(asset); } return asset; } async processBatch(assets: Asset[]): Promise<ProcessingResult> { const startTime = Date.now(); const errors: any[] = []; let processedCount = 0; for (const asset of assets) { try { const validation = await this.validate(asset); if (!validation.valid) { errors.push({ assetId: asset.id, code: 'VALIDATION_ERROR', message: 'Asset validation failed', details: validation.errors }); continue; } await this.process(asset); processedCount++; } catch (error) { errors.push({ assetId: asset.id, code: 'PROCESSING_ERROR', message: error instanceof Error ? error.message : 'Unknown error', details: error }); } } return { success: errors.length === 0, processedCount, errors, duration: Date.now() - startTime }; } // Optional hooks that subclasses can override beforeProcess?(asset: Asset): Promise<Asset>; afterProcess?(asset: Asset): Promise<Asset>; // Abstract methods that subclasses must implement protected abstract validateCategory(asset: Asset): Promise<ValidationError[]>; protected abstract processCategory(asset: Asset): Promise<Asset>; // Helper methods for subclasses protected ensureDataField(asset: Asset, field: string, defaultValue: any): void { if (!asset.data) { asset.data = {}; } if (asset.data[field] === undefined) { asset.data[field] = defaultValue; } } protected validateNumericField( asset: Asset, field: string, min?: number, max?: number ): ValidationError | null { const value = asset.data?.[field]; if (value === undefined || value === null) { return null; // Field is optional } if (typeof value !== 'number') { return { field: `data.${field}`, message: `${field} must be a number`, code: 'INVALID_TYPE' }; } if (min !== undefined && value < min) { return { field: `data.${field}`, message: `${field} must be at least ${min}`, code: 'VALUE_TOO_LOW' }; } if (max !== undefined && value > max) { return { field: `data.${field}`, message: `${field} must be at most ${max}`, code: 'VALUE_TOO_HIGH' }; } return null; } protected validateDateField( asset: Asset, field: string, allowFuture: boolean = false ): ValidationError | null { const value = asset.data?.[field]; if (value === undefined || value === null) { return null; // Field is optional } const date = new Date(value); if (isNaN(date.getTime())) { return { field: `data.${field}`, message: `${field} must be a valid date`, code: 'INVALID_DATE' }; } if (!allowFuture && date > new Date()) { return { field: `data.${field}`, message: `${field} cannot be in the future`, code: 'FUTURE_DATE' }; } return null; } }