A high-level SDK for building IPP (Internet Printing Protocol) printer servers
npm install @filed/printer-sdkA high-level TypeScript SDK for building IPP (Internet Printing Protocol)
printer servers. This SDK acts as a "File Acceptor" that allows customers to
"print" documents from their computers directly to your backend infrastructure.
š Architecture Diagrams ā
The IPP Printer Server SDK provides a minimalist, developer-friendly interface
for receiving print jobs from any IPP-compatible client (macOS, Windows, Linux).
Instead of printing to paper, documents are received by your backend for
processing, storage, or further handling.
Key Design Principles:
- File Acceptor Focus: Designed specifically for document ingestion, not
traditional printing
- Dynamic Virtual Queues: Unlimited, on-the-fly queues based on URL paths
(e.g., /{customerId}/{authToken})
- Stateless & Scalable: Horizontal scaling with no hard dependencies on
in-memory storage
- IPP Abstraction: Hide IPP protocol complexity behind a simple, intuitive
API
- Storage Adapter Pattern: Use your own storage backend (Postgres, S3,
DynamoDB, etc.)
- Single Logical Printer: One printer instance handles all queues
- Dynamic Virtual Queues: Unlimited queues based on URL paths - each
customer gets their own queue (extracted from URL path)
- Non-Blocking Processing: Requests to different queues are processed
independently and concurrently
- Automatic Job Management: Jobs are automatically completed or failed based on adapter operations
- RFC Compliant: Full IPP protocol support (RFC 8010, 8011, 3510, 7472)
- Native OS Integration: Works seamlessly with standard OS print drivers
- TypeScript-First: Full type safety and IntelliSense support
This SDK is compatible with Deno and Node.js 18+. It uses standard Web
APIs (Request, Response) that are available in all modern runtimes.
``bash`
npm install @filed/printer-sdk
Then import using npm specifier:
`typescript`
import { getJobDocument, IppPrinter } from "npm:@filed/printer-sdk@0.1.0";
`bash`
npm install @filed/printer-sdk
`typescript`
import { getJobDocument, IppPrinter } from "@filed/printer-sdk";
`typescript
// server.ts
import { getJobDocument, IppPrinter } from "npm:@filed/printer-sdk@0.1.0";
import type { PrinterStorageAdapter } from "npm:@filed/printer-sdk@0.1.0";
// Create a storage adapter (implement your own for production)
const adapter: PrinterStorageAdapter = {
// ... implement adapter methods
};
// Create the printer
const printer = new IppPrinter({
name: "My Printer",
adapter,
});
// Initialize and start the server
Deno.serve({ port: 8631 }, async (req) => {
return await printer.handle(req, async (req) => {
// Extract queue ID from URL path (e.g., /customer123/authkey456 -> "customer123")
const url = new URL(req.url);
const queue = url.pathname.split("/")[1];
return queue || "default";
});
});
`
Run with:
`bash`
deno run -A server.ts
Node.js 18+ includes native Web API support. Use the built-in fetch API or
convert Node.js http to Web APIs:
`typescript
// server.ts
import { getJobDocument, IppPrinter } from "@filed/printer-sdk";
import type { PrinterStorageAdapter } from "@filed/printer-sdk";
import { createServer } from "node:http";
// Create a storage adapter (implement your own for production)
const adapter: PrinterStorageAdapter = {
// ... implement adapter methods
};
// Create the printer
const printer = new IppPrinter({
name: "My Printer",
adapter,
});
// Create HTTP server and convert to Web API Request/Response
const server = createServer(async (req, res) => {
// Convert Node.js request to Web API Request
const url = http://${req.headers.host}${req.url};
const body =
req.method !== "GET" && req.method !== "HEAD"
? await streamToBuffer(req)
: undefined;
const request = new Request(url, {
method: req.method,
headers: req.headers as HeadersInit,
body,
});
// Extract queue ID from URL path
const extractQueue = async (req: Request) => {
const url = new URL(req.url);
return url.pathname.split("/")[1] || "default";
};
// Handle with printer
const response = await printer.handle(request, extractQueue);
// Convert Web API Response to Node.js response
res.statusCode = response.status;
response.headers.forEach((value, key) => {
res.setHeader(key, value);
});
const responseBody = await response.arrayBuffer();
res.end(Buffer.from(responseBody));
});
server.listen(8631, () => {
console.log("IPP Printer Server running on http://localhost:8631");
});
// Helper to convert Node.js stream to buffer
async function streamToBuffer(stream: any): Promise
const chunks: Buffer[] = [];
for await (const chunk of stream) {
chunks.push(chunk);
}
return new Uint8Array(Buffer.concat(chunks));
}
`
Run with:
`bash`
node server.js
`typescript
// server.ts
import express from "express";
import { getJobDocument, IppPrinter } from "@filed/printer-sdk";
const app = express();
const printer = new IppPrinter({
/ ... config ... /
});
// Important: IPP uses binary data (application/ipp)
// Use raw parser to handle the binary body
app.post(
"/:customerId/:authToken",
express.raw({ type: "application/ipp" }),
async (req, res) => {
// Convert Express Request to Web API Request
const webReq = new Request(
${req.protocol}://${req.get("host")}${req.originalUrl},
{
method: req.method,
headers: req.headers as HeadersInit,
body: req.body, // req.body is a Buffer thanks to express.raw()
}
);
const response = await printer.handle(webReq);
// Send response
res.status(response.status);
response.headers.forEach((value, key) => res.setHeader(key, value));
res.send(Buffer.from(await response.arrayBuffer()));
}
);
app.listen(8631);
`
`bashClone the repository
git clone https://github.com/filedcom/printer-sdk.git
cd printer-sdk
Architecture
$3
The SDK supports unlimited, on-the-fly queues based on URL paths. Each customer
gets their own queue:
`
ipp://your-server:8631/{customerId}/{authToken}
`- Non-blocking: Requests to different queues are processed independently and
concurrently
- Stateless: No in-memory state dependencies - perfect for horizontal
scaling
- Automatic Extraction: Queue ID is extracted from the URL path (first path segment)
$3
The SDK uses a storage adapter interface to abstract persistence. You can
implement your own adapter for any storage backend:
- Postgres - Store job metadata and document references
- S3/GCS - Store document files
- DynamoDB - Fully managed NoSQL storage
- Redis - Fast in-memory storage (for development)
$3
The SDK extracts queue IDs directly from the URL path. You provide a queue extraction function when calling
printer.handle():`typescript
await printer.handle(req, async (req) => {
const url = new URL(req.url);
// Extract queue from path: /customer123/authkey456 -> "customer123"
return url.pathname.split("/")[1] || "default";
});
`$3
Jobs are automatically managed by the SDK:
- Success: When all documents are successfully stored via the adapter, jobs are automatically marked as
completed
- Failure: If any adapter operation fails (e.g., addDocument throws), the job is automatically marked as aborted with an error message
- State Tracking: All job state transitions are persisted through the storage adapterUsage
$3
`typescript
const printer = new IppPrinter({
name: "My Printer", // Display name shown to users
// uri is optional - will be derived from request URL if not provided
adapter: myAdapter, // Storage adapter instance
});
`$3
`typescript
// Extract queue ID from URL path
const extractQueue = async (req: Request) => {
const url = new URL(req.url);
// /customer123/authkey456 -> "customer123"
return url.pathname.split("/")[1] || "default";
};// Handle requests
const response = await printer.handle(req, extractQueue);
`$3
You must implement a
PrinterStorageAdapter to handle job metadata and document
storage. The adapter interface allows you to use any storage backend.Development (using SimpleAdapter from examples):
If you're working from the repository, you can import
SimpleAdapter:`typescript
import { SimpleAdapter } from "./src/services/sdk/examples/simple-adapter.ts";// SimpleAdapter stores files on disk in print-jobs/{queue}/{jobId}/ structure
// Documents are automatically detected by MIME type (magic bytes)
const adapter = new SimpleAdapter("./print-jobs"); // Optional: specify output directory
`Production (implement your own):
`typescript
import type {
DocumentData,
DocumentInfo,
JobData,
JobFilter,
JobInfo,
PrinterStateData,
PrinterStorageAdapter,
} from "npm:@filed/printer-sdk@0.1.0";class MyAdapter implements PrinterStorageAdapter {
async createJob(data: JobData, printerUri: string): Promise {
// Store job metadata in your database
// Return job info with unique ID
}
async addDocument(jobId: number, data: DocumentData): Promise {
// Store document data (S3, GCS, etc.)
// Return document info
// If this throws, the job will be automatically marked as aborted
}
async getJob(jobId: number): Promise {
// Retrieve job from storage
}
async getJobs(filter?: JobFilter): Promise {
// List jobs (optionally filtered by queue, state, etc.)
}
async updateJob(
jobId: number,
updates: Partial
): Promise {
// Update job state
// Called automatically by SDK when jobs complete/fail
}
async getPrinterState(): Promise {
// Return current printer state
}
}
`src/services/sdk/examples/simple-adapter.ts
for a complete reference implementation.$3
For advanced use cases, you can use the low-level IPP parser (all exports are
available from the main package):
`typescript
import {
decodeRequest,
encodeResponse,
Operation,
StatusCode,
} from "npm:@filed/printer-sdk@0.1.0";// Decode an IPP request
const request = await decodeRequest(requestBody);
// Create a response
const response = encodeResponse({
version: { major: 2, minor: 0 },
statusCode: StatusCode.SuccessfulOk,
requestId: request.requestId,
operationAttributes: [],
groups: [],
});
`Customer Setup
Customers configure their printer URI as:
`
ipp://your-server:8631/{customerId}/{authToken}
`$3
Command Line:
`bash
lpadmin -p MyPrinter -E -v ipp://localhost:8631/customer123/authkey456 -o printer-is-shared=false
`GUI:
1. Open System Settings ā Printers & Scanners
2. Click + ā IP tab
3. Address:
localhost:8631
4. Protocol: Internet Printing Protocol - IPP
5. Queue: customer123/authkey456$3
1. Settings ā Printers & scanners ā Add device ā Add manually
2. Select Add a printer using an IP address or hostname
3. Choose Internet Printing Protocol (IPP)
4. Enter:
- Hostname:
localhost
- Port: 8631
- Queue: customer123/authkey456RFC Compliance
| RFC | Title | Status |
| --------------------------------------------------------- | ---------------------- | ------ |
| RFC 8010 | Encoding and Transport | ā
|
| RFC 8011 | Model and Semantics | ā
|
| RFC 3510 | IPP URL Scheme | ā
|
| RFC 7472 | IPPS URL Scheme | ā
|
Supported Operations: Print-Job, Create-Job, Send-Document,
Get-Printer-Attributes, Get-Jobs, Get-Job-Attributes, Cancel-Job, Validate-Job
API Reference
$3
-
IppPrinter: Main printer class
- IppHandler: Low-level request handler (advanced)
- getJobDocument(doc): Helper to get document data and filename$3
-
PrinterConfig: Configuration for IppPrinter (name, adapter, optional uri)
- PrinterStorageAdapter: Interface for storage adapters
- JobInfo: Job metadata
- DocumentInfo: Document metadata$3
All parser exports are available from the main package:
-
decodeRequest(), decodeResponse(): Decode IPP messages
- encodeRequest(), encodeResponse(): Encode IPP messages
- Operation, StatusCode: IPP constants
- All IPP types and utilitiesExamples
examples/deno.ts - Complete production-ready example
serverDocumentation
- AGENTS.md - Context and instructions for AI coding agents
-
docs/architecture.md - Detailed architecture and
design
- docs/requirements.md - Requirements and design
goalsDevelopment
`bash
Run development server with watch mode
deno task devRun tests
deno testType check
deno task checkLint
deno task lint
``MIT