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

@rosepetal/barcode-engine-client

v0.4.0

Published

Node client of rp-barcode serve, the persistent decode and verify service of rosepetal-barcode-sdk: one shared engine process per Node-RED runtime, framed requests over stdio, timeouts, bounded queue, restart with backoff

Readme

@rosepetal/barcode-engine-client

Node client of rp-barcode serve, the persistent decode and verify service of rosepetal-barcode-sdk (Go, static binary). It spawns the binary once, talks protocol 1 over its stdin/stdout (length-prefixed frames, see below), keeps one request timer per request (decode, verify, analyze), bounds what is in flight, restarts a dead engine with a growing wait and stops it cleanly. CommonJS, no runtime dependencies, Node >= 18.

It exists as a package because node-red-contrib-barcode-reader and node-red-contrib-barcode-verifier share the same binary and must share one engine process per Node-RED runtime: both require this module and acquire() its singleton, so the process is started by the first decode or verify and stopped by the last release().

Install

npm i @rosepetal/barcode-engine-client

The binary is not in this package. It comes from one of the platform packages, declared here as optional dependencies and installed next to the client when the registry that carries them is configured for the @rosepetal scope (they are published by the SDK's release workflow, 0.2.x = the SDK version):

  • @rosepetal/barcode-engine-linux-x64
  • @rosepetal/barcode-engine-linux-arm64

each with the static binary at bin/rp-barcode (musl uses the same one). Without them, RP_BARCODE_ENGINE points at an rp-barcode binary (SDK >= 0.2.0), or rp-barcode is looked up in PATH. resolveBinary() tries in that order:

  1. RP_BARCODE_ENGINE (explicit: when set and unusable the start fails, it never falls back),
  2. the platform package for <platform>-<arch> (${process.platform}-${process.arch}: linux-x64, linux-arm64), along this module's node_modules chain,
  3. rp-barcode in PATH,

and throws EngineError unavailable listing every place it tried. The binary runs as <path> serve.

API

const rp = require('@rosepetal/barcode-engine-client');

// One shared engine per process (started lazily by the first decode or verify)
const engine = rp.acquire();                 // refs + 1, returns the singleton (rp.getEngine() returns it without counting)
rp.setEngineOptions({ defaultTimeoutMs: 8000 });   // merges into the singleton's options (see the table below)

const image = { width: 640, height: 480, channels: 1, colorSpace: 'GRAY' };   // encoding defaults to "raw"
const { symbols, image: size, timing } = await engine.decode(image, pixels, { symbologies: ['EAN13'], effort: 'robust' },
                                                              { timeoutMs: 5000 });
// symbols: schema-1.0 symbol objects as the SDK writes them ([] when none); size: {width, height}; timing: {totalMs, decodeMs, queuedMs}

const { result } = await engine.verify(image, pixels, { region: { x: 120, y: 80, w: 400, h: 200, orientation: 0 },
                                                         options: { spec: { kind: 'gs1_table', table: '1' } },
                                                         acquisition: { milsPerPixel: 3.25 } }, { timeoutMs: 10000 });
// result: the schema-1.0 VerifyResult as the SDK writes it (status, reasons, symbol, quality, dimensions, gs1, report, …)

await rp.release();                          // refs - 1; the last release() stops the process

After its release(), a holder must not call decode()/verify()/ping() on the engine: a request after the last release starts a process that no release will stop (it lives until the runtime exits). The reader node guards this with its rpClosed flag; the verifier needs the same rule.

engine.decode(image, payload, options, { timeoutMs }): image is {width, height, channels, colorSpace, encoding?} (encoding raw, payload = packed rows of width·height·channels bytes, colorSpace GRAY|RGB|BGR|RGBA|BGRA; or encoded, payload = a JPEG/PNG/BMP/TIFF file); options is the decodeOptions object of the SDK's result schema 1.0 §12.1 (known keys only, every key optional, the server rejects unknown ones); timeoutMs (default defaultTimeoutMs) is the client's timer and also travels as deadlineMs (sent as an integer number of milliseconds, rounded up; the timer is capped at 2^31 − 1 ms). A payload larger than the engine's advertised limits.maxPayload is refused with invalid_input before anything is sent.

The client passes options through as given, so a key newer than the engine is the caller's to gate. An engine rejects a key it does not know with invalid_input even at its default value, and its hello does not list the keys it takes: send such a key only away from its default, and only to an engine of the version that brings it (this client pins its engine to its own exact version). That is the case of edges and projection (SDK 0.3.0) and of the projection retries of SDK 0.4.0: projectionSharpen (off | retry) with projectionSharpenSigma (px, 0 = 2, otherwise 0.5 to 4), projectionMarginVote, projectionEdges, projectionGuardZone, projectionHalves and projectionAngle (off | retry). The retries run only with projection: "fallback" and no region (otherwise they are accepted and ignored), after everything else read nothing, in a fixed order where the first that reads decides; they add time only on the images nothing reads (the SDK's docs/api.md §4.1, «Los reintentos de la proyección»). The reader node sends them through its «Projection retries» level (off, sharpen, full), each key only as retry (its NEWER_OPTIONS). projectionHalves is not in any level of the node: an individual opt-in of the engine, outside full.

engine.verify(image, payload, { region, options, acquisition, analyze }, { timeoutMs }) (SDK service 2D): the ISO/IEC 15416 verification of the symbol in region. image/payload as decode; region is {x, y, w, h, orientation} in image pixels: w/h are extents along the image axes whatever the orientation, orientation is the reading direction in clockwise degrees (a multiple of 90, default 0). The client checks and rounds the region before sending (Math.round on x, y, w, h; the orientation never rounded, an integer multiple of 90 normalised to 0/90/180/270, so −90 is 270 and 450 is 90; w and h at least 2 px after rounding; a missing or non-finite coordinate, an orientation that is not an integer multiple of 90 (90.4 included) or an unknown key is invalid_input without anything sent), so a normalised region scaled to pixels never reaches the server as a fraction; the bounds against the image are the server's check (barcode: region {…} outside image WxH). options is the verifyOptions object of the schema (§12.2, known keys only, every key optional; the server refuses unknown keys and values outside their vocabularies) and acquisition the acquisition object (§12.3, calibration §12.4) or null (no physical scale, 660 nm, no calibration); analyze: true sends analyze and the result carries scans. timeoutMs as decode (the verifier node passes its own 10 s). Resolves { result, image, timing }: result is the VerifyResult (or AnalysisResult) exactly as the SDK writes it, report included; status: "not_detected" is a result, not an error; image the real size; timing is {totalMs, verifyMs, queuedMs}. An engine whose hello does not list the op (an SDK 0.2.0 built before 2D) rejects with unsupported before anything is sent.

Other members of an Engine: start() (resolves with the hello; idempotent, never two processes), ping() (a liveness probe the server answers without queueing), stop() (drain → shutdown → SIGTERM → SIGKILL, resolves when the child is gone; what was pending rejects with exited), running, hello, pid, refs, lastError, configure(options).

new Engine(options) and setEngineOptions(options) take (defaults in DEFAULTS):

| Option | Default | Meaning | |---|---|---| | command, args | null, ['serve'] | null resolves the binary at every start (RP_BARCODE_ENGINE may change) | | env | null (= process.env) | environment of the child | | maxQueue | 128 | requests in flight before overloaded | | maxBufferedBytes | 256 MiB | bytes still unwritten to the engine's stdin before overloaded (an idle pipe always takes one frame) | | helloTimeoutMs | 5000 | wait for the hello after the spawn | | defaultTimeoutMs | 5000 | timeoutMs of decode/verify/ping when not given (the verifier node passes its own 10 s) | | backoff | {initialMs: 1000, maxMs: 30000} | wait before restarting after a failed start or a crash, doubled each time, reset by a good hello | | drainMs, exitMs | 2000, 1000 | stop(): wait for the requests in flight; wait after shutdown, then after SIGTERM |

Every failure is an EngineError with a code:

| code | From | When | |---|---|---| | unavailable | client | no binary, no hello in time, the spawn failed, or a start inside the wait after a failure (retry in N ms) | | protocol | client | hello with another protocol, a bad frame from the engine, a reply that carries a payload, a verify reply without a result object, an unprompted bad_frame | | exited | client | the process went away with the request in flight, stop(), or a stop() that landed while the request waited for a broken child to die (the engine is not relaunched for a holder that let go) | | timeout | client | the request timer | | overloaded | client or server | maxQueue / maxBufferedBytes reached, or the server's queue is full | | invalid_input | client or server | client: image is not an object, payload is not a Buffer/Uint8Array, the region of a verify is not an object, has an unknown key, a missing or non-finite coordinate, a side under 2 px or an orientation that is not a multiple of 90, the payload exceeds the engine's limits.maxPayload (all refused before anything is sent) or the header cannot be framed; server: verbatim (a bad image, a payload of another length, an unknown options/acquisition key or a value outside its vocabulary, a region outside the image), the engine keeps running | | unsupported | client or server | client: the hello does not list verify/analyze (refused before anything is sent); server: verbatim, the engine keeps running | | deadline, internal | server | verbatim, the engine keeps running |

Events of an Engine (never 'error'): 'started' ({pid, hello, source}) after each good hello (source is RP_BARCODE_ENGINE, the package name, PATH or command), 'exit' ({code, signal}) when the child is gone, 'stderr' (text) with the engine's log lines.

Also exported: resolveBinary({paths}), ENGINE_PACKAGES, PROTOCOL (1), DEFAULT_TIMEOUT_MS, INSTALL_HINT (the sentence a node prints next to an unavailable), the frame codec encodeFrame, FrameParser, FramingError, and formatName(symbol) / FORMAT_NAMES / ADDON_NAMES: the format string the nodes print for a schema-1.0 symbol (ZXing's spelling, EAN-2/EAN-5 for the add-ons by their identifier), so the reader and the verifier agree.

Protocol 1 in short

Specified in the SDK's docs/service.md (a private repository: the link works for the organisation's members); this client implements it as written.

  • Everything on stdin/stdout is a frame: u32 BE headerLen | u32 BE payloadLen | header (UTF-8 JSON object) | payload. headerLen 2..1 MiB, payloadLen <= maxPayload (256 MiB by default, --max-payload).
  • The server writes a hello first (id 0): protocol, sdk, engineVersion, schema, ops, workers, queue, limits {maxHeader, maxPayload, maxSide, maxPixels}. The client requires protocol === 1.
  • decode (id, image, options, deadlineMs) with the pixels as payload → {id, ok: true, image, symbols, timing}, never with a payload. ping → {id, ok: true}, answered without queueing. shutdown → {id, ok: true}; what is in flight finishes, what is still queued is answered unsupported, then exit 0.
  • verify/analyze (id, image, region, options, acquisition, deadlineMs) with the pixels as payload → {id, ok: true, image, result, timing}: result the schema-1.0 VerifyResult/AnalysisResult (report included; not_detected is a result), timing {totalMs, verifyMs, queuedMs}. Since SDK 0.2.0 with 2D: the hello lists them in ops (a 2A engine answers unsupported; the client checks ops first).
  • Errors: {id, ok: false, error: {code, message}} with invalid_input, unsupported, overloaded, deadline, internal (the server keeps running) or bad_frame (id 0, then the server exits 3: the stream is no longer delimited, the client restarts it).
  • EOF on stdin: the server decodes what is in flight and what is already queued (a queued request whose deadlineMs expired answers deadline), then exits 0; a broken stdout is exit 1.
  • Replies come out in the order they finish; deadlineMs only rejects a request still queued when it expires.

Testing with the fake engine

FAKE_ENGINE is the path of test/fake-engine.js, a fake rp-barcode serve in Node (protocol 1, one fixed EAN-13 per decode, canned VerifyResults for verify/analyze) that ships with the package so the tests of the nodes never need a real binary:

const rp = require('@rosepetal/barcode-engine-client');
process.env.RP_BARCODE_ENGINE = rp.FAKE_ENGINE;      // the shared engine spawns the fake (executable, #!/usr/bin/env node)
rp.setEngineOptions({ helloTimeoutMs: 3000, backoff: { initialMs: 50, maxMs: 200 }, drainMs: 500, exitMs: 300 });
// or, for an engine of your own: new Engine({ command: process.execPath, args: [rp.FAKE_ENGINE, 'serve'], env: { ...process.env, FAKE_DELAY_MS: '300' } })

Its behaviour is driven by the child's environment: FAKE_DELAY_MS, FAKE_CRASH_AFTER, FAKE_PROTOCOL, FAKE_GARBAGE, FAKE_OVERLOAD, FAKE_NO_HELLO, FAKE_IGNORE_STOP, FAKE_MAX_PAYLOAD, FAKE_REPLY_PAYLOAD, FAKE_STALL, and for the verifier FAKE_VERIFY_STATUS=pass|fail|not_detected (the canned result of verify/analyze, default pass), FAKE_DECODE_SYMBOLS=n (copies of the EAN-13 a decode answers, default 1, 0 = none) and FAKE_OPS=a,b,c (the ops the hello announces: FAKE_OPS=decode,ping,shutdown is a 2A engine without verify), all documented at the top of the file. verify/analyze answer the canned results of test/fixtures/*.json (verify-pass.json, verify-fail.json, verify-not-detected.json, analyze-pass.json: what the CLI printed for synthetic renders, generated by test/tools/make-verify-fixtures.sh from the SDK checkout, shipped in the package so a consumer's tests can require('@rosepetal/barcode-engine-client/test/fixtures/verify-pass.json')), with the request's region echoed in result.symbol.region; analyze under fail/not_detected is the verify fixture plus scans: []. Like the real server it takes encoding raw (payload.length === width·height·channels) and encoded (a non-empty PNG/JPEG/BMP/TIFF file told by its magic bytes; the size answered is the PNG's IHDR, else the header's width/height; with no size at all the bounds check is skipped), rejects unknown keys inside image, region, options and acquisition, a region under 2 px, off a multiple of 90 or outside the image, and a non-integer deadlineMs with invalid_input and the server's wording (options: json: unknown field "scans"); unlike it, it matches keys case-sensitively (the binary's encoding/json also takes TryInvert for tryInvert), does not check option values against their vocabularies (edition: "2020" passes the fake, the server refuses it) nor that options/acquisition (and their spec/calibration) are objects.

Development

cd clients/node
npm test        # node --test: framing (10) + engine (32, verify/analyze included) on the fake engine, no child left behind

To regenerate the canned results after a change of the SDK's verification (never by npm test):

make build GO=/usr/local/go/bin/go                                             # from the SDK root: bin/rp-barcode
cd clients/node && RP_BARCODE=../../bin/rp-barcode sh test/tools/make-verify-fixtures.sh

No lockfile: the package has zero dependencies, so there is nothing to install and nothing to npm ci; the tests need only Node's built-ins and the package's own files. The optional platform packages are resolved by each consumer's own install (its lock), never through a lock of this package.

CI runs npm test in the node-client job of .github/workflows/ci.yml (Node 22, no install step). The Go targets of the repository (make lint, make test) do not touch this directory.

The SDK's release workflow (.github/workflows/publish-engine.yml, on every v* tag) publishes this package to npmjs (npm test, then npm publish --access public with NPM_TOKEN; a prerelease tag goes to the dist-tag of its preid) in the same run that publishes the platform packages to the private registry, and installs both in clean containers afterwards (scripts/verify-published.sh). Its version is pinned to the SDK's by TestNpmClientVersion. Install it with npm i @rosepetal/barcode-engine-client.