@filed/printer-sdk
v0.2.1
Published
A high-level SDK for building IPP (Internet Printing Protocol) printer servers
Maintainers
Readme
IPP Printer Server SDK
A 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.
Overview
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.)
Features
- 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
Installation
This SDK is compatible with Deno and Node.js 18+. It uses standard Web
APIs (Request, Response) that are available in all modern runtimes.
Deno
npm install @filed/printer-sdkThen import using npm specifier:
import { getJobDocument, IppPrinter } from "npm:@filed/[email protected]";Node.js (18+)
npm install @filed/printer-sdkimport { getJobDocument, IppPrinter } from "@filed/printer-sdk";Quick Start
Deno Example
// server.ts
import { getJobDocument, IppPrinter } from "npm:@filed/[email protected]";
import type { PrinterStorageAdapter } from "npm:@filed/[email protected]";
// 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:
deno run -A server.tsNode.js Example (18+)
Node.js 18+ includes native Web API support. Use the built-in fetch API or
convert Node.js http to Web APIs:
// 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<Uint8Array> {
const chunks: Buffer[] = [];
for await (const chunk of stream) {
chunks.push(chunk);
}
return new Uint8Array(Buffer.concat(chunks));
}Run with:
node server.jsNode.js with Express
// 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);Run the Repository Example
# Clone the repository
git clone https://github.com/filedcom/printer-sdk.git
cd printer-sdk
# Run the example server
deno run -A examples/deno.ts
# Or use the dev task with watch mode
deno task devArchitecture
Dynamic Virtual Queues
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)
Storage Adapter Pattern
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)
Queue Extraction
The SDK extracts queue IDs directly from the URL path. You provide a queue extraction function when calling printer.handle():
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";
});Automatic Job Management
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.,
addDocumentthrows), the job is automatically marked asabortedwith an error message - State Tracking: All job state transitions are persisted through the storage adapter
Usage
Creating a Printer
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
});Handling Requests
// 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);Storage Adapters
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:
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 directoryProduction (implement your own):
import type {
DocumentData,
DocumentInfo,
JobData,
JobFilter,
JobInfo,
PrinterStateData,
PrinterStorageAdapter,
} from "npm:@filed/[email protected]";
class MyAdapter implements PrinterStorageAdapter {
async createJob(data: JobData, printerUri: string): Promise<JobInfo> {
// Store job metadata in your database
// Return job info with unique ID
}
async addDocument(jobId: number, data: DocumentData): Promise<DocumentInfo> {
// 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<JobInfo | null> {
// Retrieve job from storage
}
async getJobs(filter?: JobFilter): Promise<JobInfo[]> {
// List jobs (optionally filtered by queue, state, etc.)
}
async updateJob(
jobId: number,
updates: Partial<JobInfo>
): Promise<JobInfo> {
// Update job state
// Called automatically by SDK when jobs complete/fail
}
async getPrinterState(): Promise<PrinterStateData> {
// Return current printer state
}
}See
src/services/sdk/examples/simple-adapter.ts
for a complete reference implementation.
Low-Level IPP Parser
For advanced use cases, you can use the low-level IPP parser (all exports are available from the main package):
import {
decodeRequest,
encodeResponse,
Operation,
StatusCode,
} from "npm:@filed/[email protected]";
// 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}macOS Setup
Command Line:
lpadmin -p MyPrinter -E -v ipp://localhost:8631/customer123/authkey456 -o printer-is-shared=falseGUI:
- Open System Settings → Printers & Scanners
- Click + → IP tab
- Address:
localhost:8631 - Protocol: Internet Printing Protocol - IPP
- Queue:
customer123/authkey456
Windows Setup
- Settings → Printers & scanners → Add device → Add manually
- Select Add a printer using an IP address or hostname
- Choose Internet Printing Protocol (IPP)
- Enter:
- Hostname:
localhost - Port:
8631 - Queue:
customer123/authkey456
- Hostname:
RFC 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
Main Exports
IppPrinter: Main printer classIppHandler: Low-level request handler (advanced)getJobDocument(doc): Helper to get document data and filename
Types
PrinterConfig: Configuration forIppPrinter(name, adapter, optional uri)PrinterStorageAdapter: Interface for storage adaptersJobInfo: Job metadataDocumentInfo: Document metadata
Parser Exports
All parser exports are available from the main package:
decodeRequest(),decodeResponse(): Decode IPP messagesencodeRequest(),encodeResponse(): Encode IPP messagesOperation,StatusCode: IPP constants- All IPP types and utilities
Examples
examples/deno.ts- Complete production-ready example server
Documentation
- AGENTS.md - Context and instructions for AI coding agents
docs/architecture.md- Detailed architecture and designdocs/requirements.md- Requirements and design goals
Development
# Run development server with watch mode
deno task dev
# Run tests
deno test
# Type check
deno task check
# Lint
deno task lintLicense
MIT
