Architecture
The ecosystem is layered so application code changes rarely when you add a printer family or a new transport.
Layer 1 — @thermal-label/contracts
Pure TypeScript types describing:
Transport— bidirectional byte channel (read,write,close).PrinterAdapter— high-level printer:print,createPreview,getStatus,close.PrinterDiscovery— enumerate and open printers for a driver family.DeviceDescriptor,MediaDescriptor,PrinterStatus,PrintOptions,PreviewOptions,PreviewResult— structured media and preview planes.- Errors —
TransportError,TransportTimeoutError,DeviceNotFoundError,UnsupportedOperationError,MediaNotSpecifiedError, … for programmatic handling instead of string matching.
Bitmap-related types (LabelBitmap, RawImageData) re-export from @mbtech-nl/bitmap so consumers import once.
Layer 2 — @thermal-label/transport
Runtime implementations of Transport:
| Class | Runtime | Role |
|---|---|---|
UsbTransport | Node | libusb, interface 0, bulk IN/OUT; optional kernel driver detach on Linux |
TcpTransport | Node | Raw TCP (JetDirect-style, default port 9100) with partial-read buffering |
WebUsbTransport | Browser | navigator.usb picker and USBDevice wrapper |
WebBluetoothTransport | Browser | GATT write/notify with configurable MTU |
WebSerialTransport | Browser | navigator.serial picker for Web Serial-capable devices |
Subpath imports matter: @thermal-label/transport/node vs /web so bundlers never see the optional usb native peer dependency unless you are on Node.
Discovery helpers (matchDevice, buildUsbFilters, buildBluetoothRequestOptions, discoverAll) live at the root entry and are safe in either environment.
Layer 3 — Device driver monorepos
Each family publishes core, node, web packages:
- Brother QL — USB + TCP on Node; WebUSB in browser; media registry in core.
- DYMO LabelWriter — USB + TCP on Node; WebUSB in browser; hardware notes for NFC-locked models documented upstream.
- DYMO LabelManager — USB on Node and browser for D1 tape printers.
Cores hold protocol encoding and device registries; runtimes wrap transports and expose ergonomic openPrinter / requestPrinter APIs.
Per-driver *-cli packages were retired in the 0.2 release pass — the unified thermal-label-cli auto-discovers any installed driver via its discovery named export.
Layer 4 — thermal-label-cli
A single global CLI that auto-detects installed driver packages and exposes list, status, and print text / print image. It is the fastest way to prove cabling, permissions, and driver exports — not the place for template design.
Mental model for your app
- Pick driver packages (
*-nodeand/or*-web). - Use
PrinterDiscoveryfrom those packages (ordiscoverAllwith a curated list). - Call
PrinterAdaptermethods; handlePrinterStatus.errorsas structured data. - For previews, render
PreviewResultplanes in your UI; respect theassumedflag when media is unknown.
When you outgrow quick text rendering, emit PNGs (or integrate burnmark) and feed print() image data — the adapter does not care how pixels were produced.