defarm-sdk
Version:
DeFarm SDK - On-premise blockchain data processing and tokenization engine for agriculture supply chain
203 lines (173 loc) • 4.94 kB
text/typescript
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;
}
}