npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@thermal-label/contracts

v0.2.0

Published

Shared types and interfaces for thermal-label printer drivers

Readme

@thermal-label/contracts

npm version CI License: MIT

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.

Install

pnpm add @thermal-label/contracts

What's in the box

This package is types and interfaces only. For transport implementations (USB, TCP, WebUSB, Web Bluetooth), see @thermal-label/transport.

Interfaces

| 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'. |

Errors

| 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. |

Bitmap re-exports

LabelBitmap and RawImageData are re-exported from @mbtech-nl/bitmap so drivers and consumers need only one import for everything they pass through PrinterAdapter.

Example: implementing 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> {
    /* ... */
  }
}

Example: rendering PreviewResult in a UI

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);
  }
}

Drivers that implement these contracts

| 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.

Key design decisions

  • MediaDescriptor.heightMm is optional — undefined = continuous media, a number = fixed length. No magic zero.
  • PrinterStatus has only detectedMedia? — no redundant scalar mediaWidthMm / mediaType. One source of truth.
  • PrinterStatus.errors is PrinterError[] with { code, message } — programmatic branching, not string matching.
  • PrintOptions.density is string — drivers validate internally. 'normal' is universally supported; drivers throw UnsupportedOperationError for values they don't recognise.
  • DeviceDescriptor.vid / pid are optional — required only when transports includes 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.

Attribution

Not affiliated with Dymo, Brother, Seiko Epson, or any other printer manufacturer. Trademarks belong to their respective owners.

Funding

If you rely on this package commercially, please consider sponsoring:

License

MIT © Mannes Brak