AbsoluteJS

@absolutejs/machines-bridge

@absolutejs/machines-bridgev0.2.0-beta.0betaCommerce & Growth

Shop-LAN bridge agent for @absolutejs/commerce-machines: holds one live socket to your app, delivers pushed print/embroidery jobs to a hot folder, a printer port (TCP 9100), an IPP printer or the OS print queue, and pushes machine run telemetry back (production reports, Zebra alerts, SNMP traps, RIP webhooks). No polling, no arbitrary command execution.

#Installation

BASH
bun add @absolutejs/machines-bridge

#Capabilities

Overview

The shop-side half of "send straight to the machine" for @absolutejs/commerce-machines. Your app runs in the cloud; the embroidery heads, DTG printers and label printers sit on the shop's LAN. The shop runs this small agent on any always-on PC or Raspberry Pi. It holds one persistent socket to your app (no inbound ports, no VPN): the app pushes jobs down it, the agent delivers each one locally and pushes the result — and the machines' run telemetry — back up the same connection. Nothing is polled.

Options: --once (do one pass of work and exit), --list-printers, --no-printers, --no-telemetry, --webhook-port 8787, --socket-path /sync/ws, --probe , --telemetry-help , and the legacy --http-poll / --interval 3; env ABS_BRIDGE_SERVER / ABS_BRIDGE_TOKEN. Needs Bun ≥ 1.1. See install.md for running it as a service on Linux, macOS, Windows and Raspberry Pi, and for the per-machine telemetry setup.

What it will and will not do

The bridge executes exactly four typed actions and nothing else. There is no "run this command" action, jobs are validated with a type guard before they run, and every process it spawns is an argv array (never a shell string). The telemetry watchers only ever read: report files are parsed and never moved or deleted, and the local webhook/trap/alert listeners accept data, never commands:

Action — What happens locally

folder — Files are written into the folder (created if missing). Filenames are reduced to a safe base name; .. paths are refused.

Show 4 more

raw-tcp — Bytes are streamed to host:port (default 9100) over one TCP connection per file — Zebra, most label printers, RIP spoolers.

ipp — An IPP/1.1 Print-Job is posted to the printer URL (ipp://host:631/ipp/print), with Basic auth when a username is set.

os-print — Linux/macOS: lp -d [-o raw] (-o raw for ZPL/EPL/TSPL). Windows: PowerShell Get-Content -Raw \ — Out-Printer -Name for ZPL/EPL/TSPL and Start-Process -FilePath -Verb PrintTo for PDF and everything else. The printer name and file path are passed as environment variables, not interpolated into the script.

On connect it sends one heartbeat (version, platform, hostname, capabilities, discovered printers, telemetry) so the app can show the printer list in its settings — with a live socket, presence is the socket itself. Printers come from lpstat -p (CUPS) or Get-Printer (Windows).

Protocol

One WebSocket to the app's @absolutejs/sync socket (wss:///sync/ws

by default; --socket-path if you mounted it elsewhere). The bridge token is sent as the first authenticate frame — never in the URL.

The agent subscribes to bridgeJobs and bridgeTelemetrySources. A queued

Show 6 more

job arrives as a diff the moment the app queues it, files inline as base64; the agent runs it and calls the bridge.report mutation, which removes it from the collection. Telemetry events go up through bridge.telemetry in batches of at most 500, coalesced over 1 s, retried with backoff.

A dropped connection is reopened with backoff (500 ms → 10 s) and the

subscriptions resume; unfinished jobs are still in the collection, so nothing is lost. Malformed jobs and sources are ignored and logged.

Legacy fallback: --http-poll polls POST /bridge/poll every

3 s with { token, info } and reports to /bridge/report, pushing telemetry to /bridge/telemetry. Use it only where WebSockets are blocked.

The server side is createBridgeSync (+ createMemoryBridgeStore, withBridgeSyncPublishing) from @absolutejs/commerce-machines/bridge — see that README for the store interface and the socket wiring.

Machine run telemetry

Alongside the jobs, the agent watches whatever telemetry sources the app pushes down for this bridge, so the shop's real machine minutes are measured instead of typed. Every path is event-driven — the agent never asks a machine for its status on a timer:

Source — How it works locally

report-folder — fs.watch on the folder, events coalesced over 250 ms, then each new matching file is parsed and emitted as a finish event with the design reference, stitches, pieces and run time. A 5-minute rescan runs only to heal filesystem events the OS dropped (common on network shares). Files present the first time are adopted, not replayed; nothing is ever moved or deleted, and a .absolutejs-seen sidecar keeps a restart from re-importing history.

Show 6 more

raw-tcp-status — Holds a connection to the printer's port open and reads unsolicited Zebra alerts (PAPER OUT SET, HEAD OPEN SET, PQ COMPLETED, and their CLEARs), and also listens on the alert port (default 9200) for printers configured to dial the bridge PC. A repeated condition does not emit twice. Reconnects with backoff.

snmp-printer — Binds UDP 162 (trapPort) and decodes SNMP v1/v2c traps and informs with a built-in BER codec — no dependency. Informs are acknowledged with a Response. Traps are mapped through the Host Resources / Printer MIB OIDs to state and lifetime page count.

http-status — Serves a small local endpoint (http://:8787/telemetry/ by default) for the RIP or controller to POST to, secret-checked. JSON bodies are read through the same field vocabulary as production reports; a plain status word works too.

manual — No watcher. The operator types the time.

State transitions are collapsed into runs on the server (readingsToRuns), not here — the agent only reports what it saw and when. --no-telemetry turns the whole thing off. --probe runs a single reading and prints it, for setup:

A probe is the only time this agent queries a machine.

What is tested, and what is not

Bun tests cover: the folder and raw-tcp executors against a temp dir and a local Bun.listen; the os-print executor with a fake spawner (asserting the exact lp argv, the PowerShell script shape and that the printer name never enters the script); lpstat parsing; the full socket path — a fake WebSocket speaking the sync wire protocol against the reference bridge collections and mutations, pushing a job down, running it, reporting it and pushing a telemetry event back; the report-folder watcher over a temp dir with a fake clock and a driven watcher (baseline, new file, duplicate notification, sidecar restart, files left untouched); the Zebra alert stream over a fake connection; the SNMP BER encode/decode round-trip and trap/inform handling against a fake UDP socket; the webhook receiver's routing, secret check and body parsing; the legacy HTTP-poll loop against the reference handlers over a fake fetch; CLI and probe argument parsing.

Not tested here — no real hardware was in the loop:

No Tajima, Melco, Barudan or Ricoma machine wrote a report into the watched

Show 10 more

folder. The parsers follow the layouts those packages export; confirm against the shop's own file before trusting a number.

No Zebra printer pushed an alert. The HS decode follows the ZPL II

programming guide field by field; the alert decode is keyword-based because the message wording varies by firmware, and an unrecognised message is logged, not guessed. SX alert configuration has not been exercised on a device.

No printer sent an SNMP trap. The codec round-trips against itself and the

OIDs are the standard Host Resources / Printer MIB ones.

No RIP posted to the webhook. The endpoint is exercised with synthetic

requests only.

The Windows executors have not been run against a live spooler (the

PowerShell path follows Microsoft's documented cmdlets), nor lp against a real CUPS queue, nor IPP against a physical printer. Raw ZPL through Out-Printer on Windows depends on the driver passing text through; for Zebra printers on Windows prefer raw-tcp to port 9100.

Until a path is confirmed on the shop's actual machine, leave that machine on manual: it measures nothing, and it says so.

Outcomes

What you can build

Build on the supported package contract

Use @absolutejs/machines-bridge through its supported public entry points.

Hardening checklist

Production guidance

Make every external boundary explicitPin the deployed @absolutejs/machines-bridge version, replace example or memory-backed dependencies with durable implementations, bound external calls, protect credentials, and emit enough evidence to retry or recover safely.

Follow in order

Troubleshooting path

1
Trace from the first failed boundary
Reproduce the smallest canonical @absolutejs/machines-bridge example, confirm the supported entry point and version in the API explorer, then inspect the first boundary that did not produce its documented result.

#@absolutejs/machines-bridge quick start

Partial snippet

# @absolutejs/machines-bridge

SH
bunx @absolutejs/machines-bridge --server https://shop.example --token XXXX

#Machine run telemetry

Partial snippet

Alongside the jobs, the agent watches whatever telemetry sources the app pushes down for this bridge, so the shop's real machine minutes are measured instead of typed. Every path is event-driven — the agent never asks a machine for its status on a timer:

SH
bunx @absolutejs/machines-bridge --probe report-folder --path /mnt/reports --parser tajima-report
bunx @absolutejs/machines-bridge --probe raw-tcp-status --host 192.168.1.50
bunx @absolutejs/machines-bridge --probe snmp-printer --host 192.168.1.60
bunx @absolutejs/machines-bridge --telemetry-help snmp-printer

#Programmatic use

Partial snippet

Working example for Programmatic use.

TS
import {
  runBridge,
  connectBridge,
  executeJob,
  listPrinters,
  probeSource,
} from "@absolutejs/machines-bridge";

await runBridge({
  server: "https://shop.example",
  token: process.env.ABS_BRIDGE_TOKEN!,
  signal: controller.signal, // closes the socket
  log: (line) => console.log(line),
  // transport: "http-poll", intervalSeconds: 3 — legacy fallback only
});

// Or drive the connection yourself:
const connection = await connectBridge({ server, token });
await connection.ready; // first snapshots have landed
connection.counters(); // { executed, failed, events }
connection.close();

// executeJob(job) runs one BridgeJob locally; listPrinters() lists OS queues;
// probeSource(source) takes one reading for a settings screen.

#Public entry points

Supported entry points declared by this package manifest.

@absolutejs/machines-bridgePackage entry point declared in package.json.

#Package commands

Scripts declared by this package manifest.

bun run buildrm -rf dist && bun build ./src/index.ts ./src/cli.ts --root ./src --outdir dist --target bun --external @absolutejs/commerce-machines --external '@absolutejs/commerce-machines/*' --external @absolutejs/sync --external '@absolutejs/sync/*' && tsc --emitDeclarationOnly --project tsconfig.json && chmod +x dist/cli.js
bun run check:packagebun run format && bun run typecheck && bun run test && bun run build
bun run formatprettier --write "./**/*.{js,ts,json,md}"
bun run testbun test
bun run typechecktsc --noEmit