Shared types and interfaces for the thermal-label printer driver
ecosystem. Pure TypeScript — zero runtime dependencies beyond a type
re-export from @mbtech-nl/bitmap. Safe to import from Node,
the browser, or any bundler.
pnpm add @thermal-label/contractsThis package is types and interfaces only. For transport
implementations (USB, TCP, WebUSB, Web Bluetooth), see
@thermal-label/transport.
| Export | Purpose |
|---|---|
Transport |
Bidirectional byte channel to a printer. Implemented per transport type. |
PrinterAdapter |
High-level printer interface. print(), createPreview(), getStatus(), close(). |
PrinterDiscovery |
Enumerate and open printers for a driver family. |
DeviceDescriptor |
Static description of a supported printer model (VID/PID, transports, BLE config). |
MediaDescriptor |
Base media shape (width, height, type, colorCapable). Drivers extend it. |
PrinterStatus |
Runtime status including detectedMedia and structured PrinterError[]. |
PrintOptions |
Per-call print options (copies, density). |
PreviewOptions |
Media override for createPreview(). |
PreviewResult |
Preview with separated colour planes and an assumed flag. |
PreviewPlane |
One colour plane — bitmap + display colour. |
DiscoveredPrinter |
One entry returned by PrinterDiscovery.listPrinters(). |
OpenOptions |
Filter for PrinterDiscovery.openPrinter(). |
BluetoothConfig |
GATT UUIDs and MTU for a BLE-capable device. |
TransportType |
'usb' | 'tcp' | 'webusb' | 'web-bluetooth'. |
| Export | Thrown when |
|---|---|
TransportError |
Base class for all transport-layer failures. |
TransportTimeoutError |
A read timed out waiting for bytes. |
TransportClosedError |
The transport was closed mid-operation. |
DeviceNotFoundError |
No device matches the requested VID/PID filter. |
UnsupportedOperationError |
Operation not supported by this driver/printer/media. |
MediaNotSpecifiedError |
print() / createPreview() called without a known media. |
LabelBitmap and RawImageData are re-exported from
@mbtech-nl/bitmap so drivers and consumers need only one
import for everything they pass through PrinterAdapter.
A sketch — see each driver's *-core / *-node / *-web packages
for real implementations.
import type {
DeviceDescriptor,
MediaDescriptor,
PreviewOptions,
PreviewResult,
PrinterAdapter,
PrinterStatus,
PrintOptions,
RawImageData,
} from '@thermal-label/contracts';
import { MediaNotSpecifiedError } from '@thermal-label/contracts';
export class MyPrinter implements PrinterAdapter {
readonly family = 'my-driver';
readonly model: string;
readonly device: DeviceDescriptor;
private lastStatus?: PrinterStatus;
constructor(device: DeviceDescriptor) {
this.device = device;
this.model = device.name;
}
get connected(): boolean {
/* ... */
return true;
}
async print(
image: RawImageData,
media?: MediaDescriptor,
options?: PrintOptions,
): Promise<void> {
const m = media ?? this.lastStatus?.detectedMedia;
if (!m) throw new MediaNotSpecifiedError();
// render RGBA → native format, stream to the printer...
}
async createPreview(
image: RawImageData,
options?: PreviewOptions,
): Promise<PreviewResult> {
// return { planes: [...], media, assumed: ... }
}
async getStatus(): Promise<PrinterStatus> {
// query the printer, cache `lastStatus`, return it
}
async close(): Promise<void> {
/* ... */
}
}import type { PreviewResult } from '@thermal-label/contracts';
function renderPreview(canvas: HTMLCanvasElement, preview: PreviewResult): void {
const ctx = canvas.getContext('2d');
if (!ctx) return;
if (preview.assumed) {
showWarning(
'Preview may differ from print — select media or connect printer for an accurate result.',
);
}
// Each plane renders in its own colour; composite them on the canvas.
for (const plane of preview.planes) {
drawBitmap(ctx, plane.bitmap, plane.displayColor);
}
}| Family | Package | Status |
|---|---|---|
| Brother QL | @thermal-label/brother-ql-* |
Retrofitting |
| LabelWriter | @thermal-label/labelwriter-* |
Retrofitting |
| LabelManager | @thermal-label/labelmanager-* |
Retrofitting |
Applying these contracts to existing drivers is covered in separate driver retrofit amendments. This package defines the interface; each driver implements it in its own repository.
A contributor guide for writing new drivers will live in the
@thermal-label/transport package once it ships.
MediaDescriptor.heightMmis optional — undefined = continuous media, a number = fixed length. No magic zero.PrinterStatushas onlydetectedMedia?— no redundant scalarmediaWidthMm/mediaType. One source of truth.PrinterStatus.errorsisPrinterError[]with{ code, message }— programmatic branching, not string matching.PrintOptions.densityisstring— drivers validate internally.'normal'is universally supported; drivers throwUnsupportedOperationErrorfor values they don't recognise.DeviceDescriptor.vid/pidare optional — required only whentransportsincludes USB or WebUSB. Network-only printers omit them.- Two-colour splitting is driver knowledge — the contract says
colorCapable: boolean. What "red" means is up to the driver. print()is one label per call — batch = loop. Drivers manage job framing internally.
Not affiliated with Dymo, Brother, Seiko Epson, or any other printer manufacturer. Trademarks belong to their respective owners.
If you rely on this package commercially, please consider sponsoring:
MIT © Mannes Brak