@layr-labs/hourglass-performer
Version:
TypeScript SDK for building Hourglass AVS performers
278 lines • 9.47 kB
JavaScript
;
// Enhanced task processing framework with serialization and validation
Object.defineProperty(exports, "__esModule", { value: true });
exports.TaskProcessor = exports.NumberSerializationStrategy = exports.RawSerializationStrategy = exports.JsonSerializationStrategy = void 0;
const performer_1 = require("../types/performer");
const taskContext_1 = require("./taskContext");
/**
* JSON serialization strategy
*/
class JsonSerializationStrategy {
constructor() {
this.name = 'json';
}
serialize(data) {
const json = JSON.stringify(data);
return new TextEncoder().encode(json);
}
deserialize(bytes) {
const json = new TextDecoder().decode(bytes);
return JSON.parse(json);
}
canDeserialize(bytes) {
try {
const json = new TextDecoder().decode(bytes);
JSON.parse(json);
return true;
}
catch {
return false;
}
}
}
exports.JsonSerializationStrategy = JsonSerializationStrategy;
/**
* Raw bytes serialization strategy
*/
class RawSerializationStrategy {
constructor() {
this.name = 'raw';
}
serialize(data) {
if (data instanceof Uint8Array) {
return data;
}
if (typeof data === 'string') {
return new TextEncoder().encode(data);
}
throw new Error('Raw serialization only supports Uint8Array or string');
}
deserialize(bytes) {
return bytes;
}
canDeserialize(bytes) {
return true;
}
}
exports.RawSerializationStrategy = RawSerializationStrategy;
/**
* Number serialization strategy (64-bit big-endian)
*/
class NumberSerializationStrategy {
constructor() {
this.name = 'number';
}
serialize(data) {
const num = Number(data);
if (isNaN(num)) {
throw new Error('Invalid number for serialization');
}
const bytes = new Uint8Array(8);
const view = new DataView(bytes.buffer);
view.setBigUint64(0, BigInt(num), false); // big-endian
return bytes;
}
deserialize(bytes) {
if (bytes.length !== 8) {
throw new Error('Number deserialization requires exactly 8 bytes');
}
const view = new DataView(bytes.buffer);
return Number(view.getBigUint64(0, false)); // big-endian
}
canDeserialize(bytes) {
return bytes.length === 8;
}
}
exports.NumberSerializationStrategy = NumberSerializationStrategy;
/**
* Enhanced task processor with pipeline support
*/
class TaskProcessor {
constructor(worker, logger, config = {}) {
this.worker = worker;
this.logger = logger;
this.metrics = [];
// Set up default configuration
this.config = {
timeout: config.timeout ?? 5000,
enableMetrics: config.enableMetrics ?? true,
validationStages: config.validationStages ?? [],
defaultSerialization: config.defaultSerialization ?? new JsonSerializationStrategy(),
serializationStrategies: config.serializationStrategies ?? new Map([
['json', new JsonSerializationStrategy()],
['raw', new RawSerializationStrategy()],
['number', new NumberSerializationStrategy()],
]),
};
this.setupPipeline();
}
/**
* Process a task with full pipeline support
*/
async processTask(request) {
const contextBuilder = new taskContext_1.TaskContextBuilder(request, this.logger, this.config.timeout);
const context = contextBuilder.build();
try {
// Execute the processing pipeline
if (this.pipeline) {
await this.pipeline.execute(context);
}
// Execute the worker task
const response = await this.executeWorkerTask(context);
// Record success metrics
if (this.config.enableMetrics) {
this.recordTaskCompletion(context, response);
}
return response;
}
catch (error) {
// Record failure metrics
if (this.config.enableMetrics) {
this.recordTaskFailure(context, error);
}
// Re-throw the error for gRPC handling
throw error;
}
}
/**
* Execute the worker task with enhanced error handling
*/
async executeWorkerTask(context) {
const { request } = context;
try {
// Validate task using worker
await this.worker.validateTask(request);
context.logger.debug('Task validation passed');
// Handle task using worker
const response = await this.worker.handleTask(request);
context.logger.debug('Task execution completed', {
resultSize: response.result.length
});
// Validate response
this.validateResponse(response, request);
return response;
}
catch (error) {
if (error instanceof performer_1.TaskValidationError || error instanceof performer_1.TaskExecutionError) {
throw error;
}
// Wrap unknown errors
throw new performer_1.TaskExecutionError(error instanceof Error ? error.message : 'Unknown task execution error', request.taskId);
}
}
/**
* Validate task response
*/
validateResponse(response, request) {
if (response.taskId !== request.taskId) {
throw new performer_1.TaskExecutionError(`Response task ID (${response.taskId}) does not match request task ID (${request.taskId})`, request.taskId);
}
if (!response.result) {
throw new performer_1.TaskExecutionError('Task response must include a result', request.taskId);
}
}
/**
* Set up the processing pipeline
*/
setupPipeline() {
this.pipeline = new taskContext_1.TaskPipeline();
// Add built-in validation stages
this.pipeline.addStage(new taskContext_1.ValidationStage(async (context) => {
this.validateTaskRequest(context.request);
}));
// Add custom validation stages
for (const stage of this.config.validationStages) {
this.pipeline.addStage(stage);
}
// Add metrics collection stage if enabled
if (this.config.enableMetrics) {
this.pipeline.addStage({
name: 'metrics-start',
execute: async (context) => {
const metrics = {
taskId: context.request.taskId,
executionId: context.executionId,
startTime: context.startTime,
payloadSize: context.request.payload.length,
};
context.metadata.set('metrics', metrics);
},
});
}
}
/**
* Validate task request
*/
validateTaskRequest(request) {
if (!request.taskId) {
throw new performer_1.TaskValidationError('Task ID is required', '');
}
if (!request.payload) {
throw new performer_1.TaskValidationError('Task payload is required', request.taskId);
}
if (request.payload.length === 0) {
throw new performer_1.TaskValidationError('Task payload cannot be empty', request.taskId);
}
}
/**
* Record successful task completion metrics
*/
recordTaskCompletion(context, response) {
const metrics = context.metadata.get('metrics');
if (metrics) {
metrics.endTime = Date.now();
metrics.duration = metrics.endTime - metrics.startTime;
metrics.success = true;
metrics.resultSize = response.result.length;
this.metrics.push(metrics);
context.logger.info('Task completed successfully', {
duration: `${metrics.duration}ms`,
payloadSize: metrics.payloadSize,
resultSize: metrics.resultSize,
});
}
}
/**
* Record task failure metrics
*/
recordTaskFailure(context, error) {
const metrics = context.metadata.get('metrics');
if (metrics) {
metrics.endTime = Date.now();
metrics.duration = metrics.endTime - metrics.startTime;
metrics.success = false;
metrics.error = error instanceof Error ? error.message : 'Unknown error';
this.metrics.push(metrics);
context.logger.error('Task execution failed', {
duration: `${metrics.duration}ms`,
error: metrics.error,
});
}
}
/**
* Get serialization strategy by name
*/
getSerializationStrategy(name) {
return this.config.serializationStrategies.get(name);
}
/**
* Add custom serialization strategy
*/
addSerializationStrategy(strategy) {
this.config.serializationStrategies.set(strategy.name, strategy);
}
/**
* Get task execution metrics
*/
getMetrics() {
return [...this.metrics];
}
/**
* Clear accumulated metrics
*/
clearMetrics() {
this.metrics = [];
}
}
exports.TaskProcessor = TaskProcessor;
//# sourceMappingURL=taskProcessor.js.map