@transform-kit/sdk
v0.1.0
Published
TransformKit SDK
Readme
@transform-kit/sdk
A pipeline engine for media processing. Define pipelines as JSON graphs, run them anywhere — browser, server, or desktop.
[Input] → [Filter: HEIC] → [Convert: PNG] → [Output]Instead of writing one-off scripts that are tied to a specific tool, you describe what should happen as a portable pipeline and let the host decide how. The SDK handles graph execution. You provide a Transport — the backend that does the actual file conversion. Same pipeline, any backend.
Installation
npm install @transform-kit/sdkESM-only. Node.js 20+ is required — see engines in package.json. The HTTP client uses global fetch, File, FormData, and Blob. Works in browsers (via bundler) and desktop runtimes.
Quick start
Client (recommended)
The client manages a file queue and runs pipelines for you. Two modes:
API mode — sends files to a remote endpoint:
import { createClient } from '@transform-kit/sdk';
const client = createClient({
baseUrl: 'https://your-api.com',
apiKey: 'your_api_key',
});
client.addFiles([
{
name: 'photo.heic',
size: 2_400_000,
type: 'image/heic',
file: fileFromInput,
},
]);
// pipeline is a { nodes, edges } object — see "Low-level engine" below
await client.process(pipeline);
client.subscribe((state) => {
console.log(state.files, state.isProcessing);
});Transport mode — runs the pipeline in-process:
import { createClient } from '@transform-kit/sdk';
const client = createClient({
transport: {
async convert(file, config) {
// your conversion logic
return transformedBuffer;
},
},
});Low-level engine
For full control without the client queue:
import { createContext, createDefaultRegistry, runPipeline } from '@transform-kit/sdk';
const registry = createDefaultRegistry();
const transport = {
async convert(file, config) {
// your conversion logic
return transformedBuffer;
},
};
const pipeline = {
nodes: [
{ id: '1', type: 'pipeline.input' },
{
id: '2',
type: 'image.convert',
config: {
format: { value: 'png', editable: true },
quality: { value: 90, editable: true },
},
},
{ id: '3', type: 'output.console' },
],
edges: [
{ source: '1', target: '2' },
{ source: '2', target: '3' },
],
};
const ctx = createContext(fileBuffer, 'heic');
const outputs = await runPipeline(pipeline, registry, transport, ctx);
for (const output of outputs) {
console.log(output.metadata.outputFileName, output.file.byteLength);
}Core concepts
Pipeline
A directed acyclic graph (DAG) of nodes connected by edges:
interface Pipeline {
nodes: NodeInstance[];
edges: Edge[];
}Pipelines are plain JSON — store them, share them, version them.
Nodes
Each node is an atomic operation. Every node receives an ExecutionContext and returns one of:
continue— pass the (possibly modified) context downstreamskip— this branch doesn't apply; downstream nodes inherit the skipoutput— terminal result; collected as a pipeline output
Built-in nodes are grouped by media: image, video, and audio each have filter, convert, and resize where it applies. Common nodes are pipeline.input (entry) and output.console (terminal — names the file and collects the result). Exact type strings and defaults live in NODE_CATALOG and defaultConfigForPipelineNodeType() in code — no need to duplicate them here.
Transport
The SDK never processes files directly. Nodes call transport.convert() and the host decides how:
interface Transport {
convert(file: Buffer, config: Record<string, unknown>): Promise<Buffer>;
convertFromPath?(path: string, config: Record<string, unknown>): Promise<Buffer>;
}This makes pipelines portable. The same pipeline JSON works with any backend.
For tests and quick experiments without a real encoder, use createMockTransport().
Config fields
Node configuration uses a structured format that supports both locked presets and user-editable values:
interface ConfigField<T = unknown> {
value: T;
editable: boolean;
options?: readonly T[];
label?: string;
}Validation
Validate a pipeline without executing it:
import { validatePipeline, createDefaultRegistry } from '@transform-kit/sdk';
const result = validatePipeline(pipeline, createDefaultRegistry());
if (!result.valid) {
console.log(result.errors);
// [{ code: 'NO_INPUT', message: 'Pipeline must have at least one Input node.' }]
}Checks for: missing input/output nodes, duplicate IDs, dangling edges, unknown node types, cycles, and disconnected nodes.
Custom nodes
Register your own node types:
import { createDefaultRegistry } from '@transform-kit/sdk';
const registry = createDefaultRegistry();
registry.register({
type: 'image.watermark',
async execute(ctx, config, transport) {
const text = (config.text?.value as string) ?? 'watermark';
const buffer = await transport.convert(ctx.file, {
operation: 'watermark',
text,
inputExtension: ctx.metadata.extension,
});
return {
status: 'continue',
ctx: { file: buffer, metadata: { ...ctx.metadata } },
};
},
});Testing
The SDK exports a mock transport for tests:
import { createMockTransport } from '@transform-kit/sdk';
const transport = createMockTransport(); // returns input buffer unchangedAPI reference
Engine
| Export | Description |
| ---------------------------------------------------- | ------------------------------------------ |
| runPipeline(pipeline, registry, transport, ctx) | Execute a pipeline, return output contexts |
| processFiles(files, pipeline, registry, transport) | Run a pipeline once per file |
| createContext(buffer, extension) | Create an execution context from a buffer |
| createDefaultRegistry() | Registry with all built-in nodes |
| validatePipeline(pipeline, registry?) | Structural validation without execution |
Client
| Export | Description |
| ----------------------- | ----------------------------------------------- |
| createClient(options) | File queue + processing (API or transport mode) |
MIME utilities
| Export | Description |
| ------------------------------------------ | -------------------------------- |
| IMAGE_MIME / VIDEO_MIME / AUDIO_MIME | Extension → MIME maps |
| mimeFromExtension(ext) | Look up MIME by extension |
| extensionFromMime(mime) | Reverse lookup |
| acceptString(category) | HTML <input accept="…"> string |
Pipeline defaults
| Export | Description |
| ---------------------------------------- | --------------------------------------------------- |
| defaultConfigForPipelineNodeType(type) | Default config for a node type |
| mergePipelineNodeConfig(type, config) | Merge saved config onto defaults |
| NODE_CATALOG | Built-in node types + labels/categories for editors |
Architecture decisions
Design notes live in docs/adr/ (numbered ADRs + index).
License
MIT
