Key Source Files for Conversion Logic in p2r3/convert
The conversion logic in p2r3/convert is driven by four core modules: src/FormatHandler.ts defines the data contracts, src/TraversionGraph.ts handles route optimization via Dijkstra's algorithm, src/main.ts orchestrates the UI and execution, and the src/handlers/ directory contains tool-specific implementations.
The p2r3/convert repository is a browser-based file format router that discovers supported formats, builds a weighted conversion graph, and finds optimal conversion paths without server-side processing. Understanding the key source files for conversion logic is essential for developers looking to extend the tool or debug conversion failures. The architecture separates format definitions, graph traversal, and handler execution into distinct, testable modules.
Core Source Files for Conversion Logic
src/FormatHandler.ts: The Contract Layer
The foundation of the system resides in src/FormatHandler.ts, which exports the FormatHandler interface and the data structures that define how files and formats are represented. This file establishes the FileFormat and FileData abstractions, along with the FormatDefinition class used to declare what MIME types a handler supports for reading (from) or writing (to).
Key components include the FormatDefinition.builder() method for constructing format metadata and the FormatHandler.doConvert method signature, which every concrete handler must implement to perform actual byte-level transformations. According to the p2r3/convert source code, this contract ensures that the graph engine can treat disparate tools like FFmpeg and Pandoc as interchangeable nodes in the conversion pipeline.
src/TraversionGraph.ts: The Route Engine
The TraversionGraph class in src/TraversionGraph.ts constructs a directed graph where nodes represent MIME types and edges represent possible conversion steps. The TraversionGraph.init() method populates this graph by iterating over every handler’s supported formats and creating edges for each valid source-to-target pair.
This module implements a Dijkstra-style shortest-path search via TraversionGraph.searchPath(), using an internal costFunction() that weights edges by conversion depth, lossiness (via the LOSSY_COST_MULTIPLIER constant), category changes (CATEGORY_CHANGE_COST), and handler priorities. The file also manages dead-end tracking through addDeadEndPath() and clearDeadEndPaths(), which prunes futile branches during subsequent searches to improve performance.
src/main.ts: The Orchestration Layer
Acting as the bridge between the UI and the engine, src/main.ts initializes handlers, populates the format selector, and triggers conversions. During startup, it calls each handler’s init() method and caches the results in window.supportedFormatCache. The buildOptionList() function generates the UI dropdowns from this cache.
When a user clicks Convert, the entry point window.tryConvertByTraversing invokes the graph search and executes the resulting chain via attemptConvertPath. This function sequentially awaits each handler’s doConvert implementation, streaming the output of one step into the input of the next until the final format is reached.
src/handlers/: Tool-Specific Implementations
The src/handlers/ directory contains concrete FormatHandler implementations for external tools such as FFmpeg.ts, pandoc.ts, and ImageMagick.ts. Each file exports an object that declares its supported formats and implements the init() and doConvert() methods. The src/handlers/index.ts file aggregates these into a simple array export used by main.ts during initialization.
These handlers are the only modules that interact with external binaries or WebAssembly modules, keeping the core engine agnostic of specific conversion technologies.
src/normalizeMimeType.ts: MIME Normalization
A small but critical utility, src/normalizeMimeType.ts exports the normalizeMimeType() function. This ensures that browser-reported MIME strings are canonicalized to match the values used in FormatDefinition objects, preventing matching failures due to vendor-specific MIME variations.
How the Conversion Pipeline Executes
The conversion logic follows a strict four-phase pipeline:
- Handler Registration:
src/handlers/index.tsexports an array of all available handlers. - Initialization:
main.tscalls each handler’sinit(), populatingwindow.supportedFormatCachewith supported formats. - Graph Construction:
TraversionGraph.init()builds nodes for every distinct MIME type and edges for every handler’sfrom:true/to:truecapabilities. - Execution:
window.tryConvertByTraversingsearches the graph for the cheapest path, thenattemptConvertPathexecutes each step, recording dead ends if a step fails to avoid retrying invalid routes.
Practical Examples
Using the Public API from the Browser Console
You can trigger conversions programmatically by interacting with the global objects exposed by main.ts:
// Obtain a File object from an input element
const file = document.getElementById('fileInput').files[0];
const inputBytes = await file.arrayBuffer();
const inputData = [{ name: file.name, bytes: new Uint8Array(inputBytes) }];
// Locate formats in the global cache
const inputFormat = window.supportedFormatCache.get('FFmpeg')
.find(f => f.mime === 'image/png' && f.from);
const outputFormat = window.supportedFormatCache.get('Pandoc')
.find(f => f.mime === 'application/pdf' && f.to);
// Create path nodes (ConvertPathNode is available in the global scope)
const fromNode = new ConvertPathNode({ name: 'FFmpeg', supportedFormats: [] }, inputFormat);
const toNode = new ConvertPathNode({ name: 'Pandoc', supportedFormats: [] }, outputFormat);
// Execute the conversion
window.tryConvertByTraversing(inputData, fromNode, toNode)
.then(result => {
const out = result.files[0];
const blob = new Blob([out.bytes], { type: outputFormat.mime });
const url = URL.createObjectURL(blob);
console.log('Conversion complete:', url);
})
.catch(err => console.error('Conversion failed:', err));
Under the hood, tryConvertByTraversing calls TraversionGraph.searchPath to compute the route, then invokes each handler’s doConvert sequentially.
Adding a Custom Handler
To extend the system with a new tool, create a file in src/handlers/ that implements the FormatHandler interface:
// src/handlers/svg2pdf.ts
import type { FileData, FileFormat, FormatHandler } from '../FormatHandler.js';
import { FormatDefinition } from '../FormatHandler.js';
const SVG = new FormatDefinition('Scalable Vector Graphics', 'svg', 'svg', 'image/svg+xml')
.supported('svg2pdf', true, false);
const PDF = new FormatDefinition('PDF Document', 'pdf', 'pdf', 'application/pdf')
.supported('svg2pdf', false, true);
export const svg2pdfHandler: FormatHandler = {
name: 'svg2pdf',
supportedFormats: [SVG, PDF],
ready: false,
async init() {
// Load WASM or other resources here
this.ready = true;
},
async doConvert(inputFiles: FileData[], inFmt: FileFormat, outFmt: FileFormat) {
return inputFiles.map(f => ({
name: f.name.replace(/\.svg$/, '.pdf'),
bytes: await convertSvgToPdf(f.bytes) // Your implementation here
}));
}
};
Finally, add svg2pdfHandler to the exported array in src/handlers/index.ts. The TraversionGraph will automatically include the new edges on the next reload.
Summary
src/FormatHandler.tsdefines the FormatHandler contract, FileData structures, and the FormatDefinition builder used across the entire system.src/TraversionGraph.tsimplements the Dijkstra-based pathfinding, cost modeling viaLOSSY_COST_MULTIPLIER, and dead-end pruning to optimize conversion routes.src/main.tsprovides the UI orchestration layer and the primary entry pointwindow.tryConvertByTraversingthat drives the execution loop.src/handlers/contains concrete implementations for each external tool, withsrc/handlers/index.tsserving as the registration point.src/normalizeMimeType.tsensures reliable MIME type matching between browser inputs and format definitions.
Frequently Asked Questions
What is the role of TraversionGraph.ts in the conversion logic?
src/TraversionGraph.ts serves as the route optimization engine. It builds a weighted directed graph of all possible conversions and uses searchPath() to find the cheapest route based on configurable cost factors like lossiness and category changes. It also tracks dead ends via addDeadEndPath() to avoid repeating failed conversions.
How does main.ts interact with the conversion handlers?
src/main.ts initializes handlers by calling their init() methods during page load and caches their supported formats in window.supportedFormatCache. When a conversion is requested, it calls window.tryConvertByTraversing, which coordinates with TraversionGraph to find a path and then sequentially invokes each handler’s doConvert method to transform the bytes.
What interface must new handlers implement?
New handlers must implement the FormatHandler interface defined in src/FormatHandler.ts. This requires a name string, a supportedFormats array of FormatDefinition objects, a ready boolean, an init() method for setup, and a doConvert() method that accepts FileData arrays and returns converted FileData arrays.
How does the system handle conversion failures?
When a step in the conversion chain fails, src/main.ts records the path fragment as a dead end using TraversionGraph.addDeadEndPath(). The graph then excludes this edge from future searches during the same session, allowing the engine to find alternative routes or fail gracefully if no valid path remains.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →