npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hff-ai/media-processor-client

v1.0.1

Published

TypeScript client for communicating with the HFF Media Processor service

Readme

@hff-ai/media-processor-client

A TypeScript client library for communicating with the HFF Media Processor service. Abstracts away AMQP and REST API details, providing both async/await and event-based APIs.

Features

  • Dual API: Choose between async/await (processS3*) or event-based (queueS3*) styles
  • Clean separation: Async requests don't emit events, preventing double-handling
  • Progress callbacks: Track processing progress with optional callbacks
  • Auto-reconnect: Automatic reconnection handling with configurable retry logic
  • Type-safe: Full TypeScript support with comprehensive type definitions
  • Zero-config MIME detection: Automatically detects MIME types from file extensions

Installation

npm install @hff-ai/media-processor-client

Or if using the package locally within the monorepo:

npm install ./packages/media-processor-client

Quick Start

Async/Await (Recommended)

import { media_processor, ProcessingError } from '@hff-ai/media-processor-client';

const processor = await media_processor.connect({
  processorUrl: 'https://mp.hff.ai',
  processorAmqp: 'amqp://user:[email protected]',
  token: process.env.MEDIA_PROCESSOR_TOKEN!,
  s3Details: {
    provider: 'idrive',
    endpoint: 'https://s3.us-east-1.idrivee2-27.com',
    accessKey: process.env.S3_ACCESS_KEY!,
    secretAccessKey: process.env.S3_SECRET_KEY!,
    region: 'us-east-1',
    bucketName: 'my-media-bucket',
  },
  system: 'my-application',
});

// Process and await result
try {
  const result = await processor.processS3Image('uploads/photo.jpg', {
    timeout: 30000,
    onProgress: (p) => console.log(`${p.progress}%`),
  });
  console.log('Done:', result.result.thumbnailS3Key);
} catch (error) {
  if (error instanceof ProcessingError) {
    console.error(`Failed: ${error.errorCode}`);
  }
}

Event-Based (Fire-and-Forget)

Important: Set up handlers BEFORE connecting to capture queued messages.

import { media_processor, MediaResultMessage } from '@hff-ai/media-processor-client';

// Create without connecting
const processor = media_processor.create(config);

// Set up handlers FIRST
processor.on('image_completed', (msg: MediaResultMessage) => {
  console.log('Image processed:', msg.result.s3Key);
});

// NOW connect - handlers ready for queued messages
await processor.connect();

// Queue (returns immediately)
const mediaId = processor.queueS3Image('uploads/photo.jpg');

Configuration

MediaProcessorConfig

| Property | Type | Required | Description | | --------------- | ------------------ | -------- | ---------------------------------------- | | processorUrl | string | Yes | Base URL of the media processor REST API | | processorAmqp | string | Yes | AMQP connection URL for RabbitMQ | | token | string | Yes | JWT authentication token | | s3Details | S3Config | Yes | S3 bucket configuration | | system | string | Yes | System namespace for queue routing | | options | ProcessorOptions | No | Optional processing defaults |

S3Config

| Property | Type | Required | Description | | ----------------- | -------- | -------- | ------------------------------------------------------- | | provider | string | Yes | S3-compatible provider (e.g., 'idrive', 'aws', 'minio') | | endpoint | string | Yes | S3 endpoint URL | | accessKey | string | Yes | S3 access key ID | | secretAccessKey | string | Yes | S3 secret access key | | region | string | Yes | S3 region | | bucketName | string | Yes | Bucket name |

ProcessorOptions

| Property | Type | Default | Description | | ---------------------- | --------- | ------------- | -------------------------------------------- | | maxConcurrentJobs | number | 5 | Maximum concurrent processing jobs | | defaultUserId | string | 'anonymous' | Default user ID for requests | | autoReconnect | boolean | true | Auto-reconnect on connection loss | | reconnectDelay | number | 5000 | Reconnection delay in milliseconds | | maxReconnectAttempts | number | 10 | Maximum reconnection attempts (0 = infinite) | | requestTimeout | number | 30000 | Request timeout in milliseconds |

API Reference

Factory Methods

media_processor.connect(config)

Create and connect to a MediaProcessor instance.

const processor = await media_processor.connect(config);

media_processor.create(config)

Create a MediaProcessor instance without connecting. Useful for setting up event handlers before connecting.

const processor = media_processor.create(config);
processor.on('connected', () => console.log('Connected!'));
await processor.connect();

Async Processing Methods (Promise-based)

These methods return a Promise that resolves when processing completes.

Note: Results from async methods are delivered only via the Promise - no events are emitted. This allows safe mixing of both API styles in the same application.

processor.processS3Image(s3Key, options?): Promise<MediaResultMessage>

Process an image and await the result.

const result = await processor.processS3Image('uploads/photo.jpg', {
  userId: 'user-123',
  timeout: 30000,
  onProgress: (p) => console.log(`${p.progress}%`),
});
console.log('Thumbnail:', result.result.thumbnailS3Key);

processor.processS3Video(s3Key, options?): Promise<MediaResultMessage>

Process a video and await the result.

const result = await processor.processS3Video('uploads/video.mp4', {
  userId: 'user-123',
  targetQualities: ['1080p', '720p', '480p'],
  timeout: 300000, // 5 minutes
  onProgress: (p) => updateProgressBar(p.progress),
});

processor.processS3Zip(s3Key, options?): Promise<MediaResultMessage>

Process a zip archive and await the result.

const result = await processor.processS3Zip('uploads/photos.zip', {
  userId: 'user-123',
  processImages: true,
  processVideos: true,
  timeout: 120000,
});

Queue Methods (Fire-and-Forget)

These methods return immediately. Results are delivered exclusively via events (*_completed, *_error, *_progress).

processor.queueS3Image(s3Key, options?): string

Queue an image for processing.

const mediaId = processor.queueS3Image('uploads/photo.jpg', {
  userId: 'user-123',
});
// Handle result via processor.on('image_completed', ...)

processor.queueS3Video(s3Key, options?): string

Queue a video for processing.

const mediaId = processor.queueS3Video('uploads/video.mp4', {
  userId: 'user-123',
  targetQualities: ['1080p', '720p'],
});

processor.queueS3Zip(s3Key, options?): string

Queue a zip archive for processing.

const mediaId = processor.queueS3Zip('uploads/photos.zip', {
  userId: 'user-123',
});

Connection Methods

processor.disconnect()

Gracefully close all connections.

await processor.disconnect();

processor.isConnected()

Check connection status.

if (processor.isConnected()) {
  // Safe to submit requests
}

processor.reconnect()

Manually trigger reconnection.

await processor.reconnect();

processor.updateToken(token)

Update the authentication token (e.g., after token refresh).

processor.updateToken(newToken);

processor.getSystemStatus()

Get current system status and statistics.

const status = await processor.getSystemStatus();
console.log(`Pending jobs: ${status.pendingJobs}`);

Events

Processing Events

| Event | Payload | Description | | ------------------ | ---------------------- | -------------------------- | | image_progress | MediaProgressMessage | Image processing progress | | image_completed | MediaResultMessage | Image processing completed | | image_error | MediaErrorMessage | Image processing failed | | video_progress | MediaProgressMessage | Video processing progress | | video_completed | MediaResultMessage | Video processing completed | | video_error | MediaErrorMessage | Video processing failed | | zip_progress | MediaProgressMessage | Zip extraction progress | | zip_completed | MediaResultMessage | Zip processing completed | | zip_error | MediaErrorMessage | Zip processing failed | | progress | MediaProgressMessage | Any media type progress | | completed | MediaResultMessage | Any media type completed | | processing_error | MediaErrorMessage | Any media type failed |

Connection Events

| Event | Payload | Description | | -------------- | --------------------- | ----------------------- | | connected | void | Successfully connected | | disconnected | { reason: string } | Connection lost | | reconnecting | { attempt: number } | Attempting to reconnect | | error | Error | Connection-level error |

Warning Events

| Event | Payload | Description | | ------------------- | ------------------------- | ----------------------------------------------- | | unhandled_message | UnhandledMessagePayload | Orphaned or unhandled message (will be dropped) |

The unhandled_message event fires in two scenarios:

  1. Orphaned async messages: A processS3*() request from a previous process that died. These have a correlationId but no matching pending promise. They are NOT sent to regular event handlers.

  2. Queue messages with no listeners: A queueS3*() result where no event handler is registered.

processor.on('unhandled_message', (warning) => {
  console.warn(`Dropped: ${warning.message}`);
  // Optionally: save to dead-letter store, alert, etc.
});

Error Handling

The library provides typed error classes for different scenarios:

Error Classes

import {
  MediaProcessorError, // Base class
  ConnectionError, // Connection-related errors
  ConfigurationError, // Configuration errors
  RequestError, // Queue request errors (sync)
  ProcessingError, // Processing failures (async, from process* methods)
} from '@hff-ai/media-processor-client';

Connection Errors

| Code | Description | | ------------------- | ------------------------------ | | CONNECTION_FAILED | Cannot reach the processor | | CONNECTION_LOST | Connection dropped | | AUTH_FAILED | Invalid or expired token | | AMQP_ERROR | RabbitMQ error | | REST_API_ERROR | REST API error | | RECONNECT_FAILED | Exceeded reconnection attempts |

Processing Errors

| Code | Description | | ------------------------ | --------------------------- | | S3_ACCESS_DENIED | Insufficient S3 permissions | | S3_NOT_FOUND | Source file not found | | S3_UPLOAD_FAILED | Failed to upload results | | INVALID_FORMAT | Invalid file format | | UNSUPPORTED_CODEC | Unsupported video codec | | FILE_TOO_LARGE | File exceeds size limit | | FILE_CORRUPTED | Damaged file | | PROCESSING_TIMEOUT | Processing timed out | | TRANSCODING_FAILED | Video transcoding failed | | ZIP_EXTRACTION_FAILED | Zip extraction failed | | ZIP_PASSWORD_PROTECTED | Password-protected zip |

ProcessingError Codes (async methods)

| Code | Description | | -------------------- | -------------------------------------------------------- | | PROCESSING_FAILED | Server returned an error (check errorCode for details) | | PROCESSING_TIMEOUT | Client-side timeout exceeded | | DISCONNECTED | Connection lost during processing |

Error Handling Example

import {
  ConnectionError,
  ProcessingError,
  MediaErrorMessage,
} from '@hff-ai/media-processor-client';

// Async error handling (try/catch)
try {
  const result = await processor.processS3Image('uploads/photo.jpg', {
    timeout: 30000,
  });
} catch (error) {
  if (error instanceof ProcessingError) {
    console.error(`Processing failed: ${error.errorCode} - ${error.message}`);
    if (error.errorCode === 'S3_NOT_FOUND') {
      // Handle missing file
    }
  }
}

// Event-based error handling (for queue methods)
processor.on('image_error', (msg: MediaErrorMessage) => {
  switch (msg.errorCode) {
    case 'S3_NOT_FOUND':
      console.error('File not found:', msg.details);
      break;
    case 'INVALID_FORMAT':
      notifyUser('Invalid image format');
      break;
    default:
      logError(msg);
  }
});

// Connection errors
processor.on('error', (error: ConnectionError) => {
  if (error.code === 'AUTH_FAILED') {
    refreshToken().then((newToken) => {
      processor.updateToken(newToken);
      processor.reconnect();
    });
  }
});

Complete Example

import {
  media_processor,
  MediaProcessorConfig,
  MediaResultMessage,
  MediaProgressMessage,
  MediaErrorMessage,
  ConnectionError,
} from '@hff-ai/media-processor-client';

const config: MediaProcessorConfig = {
  processorUrl: process.env.MEDIA_PROCESSOR_URL!,
  processorAmqp: process.env.MEDIA_PROCESSOR_AMQP!,
  token: process.env.MEDIA_PROCESSOR_TOKEN!,
  s3Details: {
    provider: 'idrive',
    endpoint: process.env.S3_ENDPOINT!,
    accessKey: process.env.S3_ACCESS_KEY!,
    secretAccessKey: process.env.S3_SECRET_KEY!,
    region: process.env.S3_REGION || 'us-east-1',
    bucketName: process.env.S3_BUCKET!,
  },
  system: 'my-application',
  options: {
    maxConcurrentJobs: 10,
    autoReconnect: true,
  },
};

async function main() {
  // Create processor with event handlers before connecting
  const processor = media_processor.create(config);

  // Connection events
  processor.on('connected', () => {
    console.log('Connected to media processor');
  });

  processor.on('disconnected', ({ reason }) => {
    console.warn('Disconnected:', reason);
  });

  processor.on('reconnecting', ({ attempt }) => {
    console.log(`Reconnecting (attempt ${attempt})...`);
  });

  processor.on('error', (error: Error) => {
    console.error('Connection error:', error.message);
  });

  // Processing events
  processor.on('image_progress', (msg: MediaProgressMessage) => {
    console.log(`Image ${msg.mediaId}: ${msg.progress}% - ${msg.message}`);
  });

  processor.on('image_completed', (msg: MediaResultMessage) => {
    console.log(`Image completed: ${msg.mediaId}`);
    // Save to database, update UI, etc.
  });

  processor.on('image_error', (msg: MediaErrorMessage) => {
    console.error(`Image failed: ${msg.mediaId} - ${msg.error}`);
  });

  processor.on('video_progress', (msg: MediaProgressMessage) => {
    updateProgressBar(msg.mediaId, msg.progress);
  });

  processor.on('video_completed', (msg: MediaResultMessage) => {
    console.log(`Video completed: ${msg.mediaId}`);
  });

  // Connect
  try {
    await processor.connect();
  } catch (error) {
    if (error instanceof ConnectionError) {
      console.error(`Failed to connect: ${error.code} - ${error.message}`);
    }
    process.exit(1);
  }

  // Process some media
  try {
    const imageId = processor.processS3Image('uploads/photo.jpg', {
      userId: 'user-123',
    });
    console.log(`Processing image: ${imageId}`);

    const videoId = processor.processS3Video('uploads/video.mp4', {
      userId: 'user-123',
      targetQualities: ['1080p', '720p'],
    });
    console.log(`Processing video: ${videoId}`);
  } catch (error) {
    console.error('Failed to submit request:', error);
  }

  // Graceful shutdown
  process.on('SIGINT', async () => {
    console.log('Shutting down...');
    await processor.disconnect();
    process.exit(0);
  });
}

main();

Type Guards

The library provides type guards for narrowing message types:

import {
  isProgressMessage,
  isResultMessage,
  isErrorMessage,
  isImageResult,
  isVideoResult,
  isZipResult,
} from '@hff-ai/media-processor-client';

processor.on('completed', (msg) => {
  if (isImageResult(msg.result)) {
    console.log('Image versions:', msg.result.metadata?.imageVersions);
  } else if (isVideoResult(msg.result)) {
    console.log('Duration:', msg.result.duration);
  } else if (isZipResult(msg.result)) {
    console.log('Extracted files:', msg.result.extractedMedia.length);
  }
});

Development

Building

cd packages/media-processor-client
npm install
npm run build

Testing

npm test

Linting

npm run lint
npm run lint:fix

License

MIT