Files
suaveplan/openspec/changes/barcode-batch-generator/proposal.md
2026-05-27 23:29:53 +01:00

6.6 KiB

Barcode batch generator

Package: @suaveplan/barcode Status: PROPOSED Type: spec-conformance + feature

Why

The 2026-05-02 audit of @suaveplan/barcode against legacy packages/universal/barcode/src/ found that genesis ships only the single-call generator surface (encode, renderSvg, the per-symbology helpers, validateBarcode). Legacy ships a full batch/batch-generator.ts (generateBatch(items, options): Promise<BatchResult>) used by every consumer that needs to produce ten or ten-thousand barcodes in one pass — bulk-label printing, ticketing, asset registers, and inventory imports all rely on it. CLAUDE.md scopes this package as "generator only" (barcode reading is intentionally out of scope), but the batch generator is part of the generator surface, not the reader. Its absence forces every consumer to re-implement an error-isolating loop in user code, which is exactly the duplication §0a (migration parity) was written to prevent.

What changes

  • ADDED: BatchBarcodeRequest interface — { id?: string; symbology: BarcodeSymbology; value: string; options?: BarcodeOptions }. Per-item id is optional; when omitted the request index is used as the result key.
  • ADDED: BatchBarcodeResult discriminated union — { id: string; index: number; ok: true; result: BarcodeResult } | { id: string; index: number; ok: false; error: BarcodeError }. Failed items carry a coded BarcodeError, never a bare string.
  • ADDED: BatchBarcodeOptions interface — { concurrency?: number; signal?: AbortSignal; onItem?: (result: BatchBarcodeResult) => void }. concurrency defaults to 1 (sequential) and is clamped to [1, 64].
  • ADDED: BatchBarcodeSummary interface — { total: number; succeeded: number; failed: number; durationMs: number; results: BatchBarcodeResult[] }. durationMs uses an injectable Clock (defaults to performance.now() shim) so tests stay deterministic.
  • ADDED: generateBatch(requests, options?): Promise<BatchBarcodeSummary> — produces one BatchBarcodeResult per input request in input order; per-item failures are isolated (a thrown encode error becomes { ok: false, error }, every other item keeps generating).
  • ADDED: BatchBarcodeGenerator class — class BatchBarcodeGenerator { constructor(deps?: { clock?: Clock }); generate(requests, options?): Promise<BatchBarcodeSummary> } for callers that want to inject a deterministic clock or share a generator across calls.
  • ADDED: subpath export ./batch mapping to dist/core/batch/batch-generator.js.
  • ADDED: error code BARCODE.BATCH_ABORTED registered on BARCODE_CODES, thrown when the supplied AbortSignal fires before completion.

Out of scope

  • Barcode reading / decoding — explicitly out of scope per CLAUDE.md package description ("generator only"). Tracked separately under a future barcode-reader-port change if business requirements ever demand it. Not deferred for "complexity" reasons; deferred because the package contract intentionally excludes it.
  • 2D symbologies (DataMatrix, PDF417, Aztec) — not in legacy upstream either; would be its own package.
  • Streaming / async-iterable batch API — generateBatch returns a single resolved summary. A streaming variant would let callers consume results as they emit (useful for very large batches, > 10 000 items). Punted because no current consumer needs it; the onItem callback covers the common "show progress" case.

Source mapping

This change derives from ../suaveplan/packages/universal/barcode/src/batch/.

Requirement Source lines Notes
BatchBarcodeRequest shape batch/batch-generator.ts L9-L13 Reworded: data → value; added optional id
BatchBarcodeResult discriminated union batch/batch-generator.ts L16-L21 Reworded: legacy returns { index, result?, svg?, error?: string }; new shape uses a discriminated union with coded BarcodeError instead of a string
BatchBarcodeOptions shape with concurrency, signal, onItem batch/batch-generator.ts L24-L28 Extended: legacy has outputFormat and onProgress; new shape replaces onProgress with onItem (richer payload), drops outputFormat (caller picks via options.includeText/SVG availability already on BarcodeResult), adds concurrency and signal
generateBatch function batch/batch-generator.ts L42-L95 Verbatim semantics — per-item failure isolation, sequential default, in-input-order results
BatchBarcodeGenerator class (no source line) New: legacy is a free function only; class is added for clock injection

Requirements with no source line

Requirement Justification
BatchBarcodeGenerator class with injectable Clock Genesis-wide CLAUDE.md determinism rule (enterprise/bitpacker.md L40 "no module-scope Date.now()"). Legacy reads Date.now() implicitly through performance.now; genesis routes through @suaveplan/types::Clock.
BARCODE.BATCH_ABORTED error code on BARCODE_CODES CLAUDE.md §Workflow rule 2 — every thrown error routes through @suaveplan/error with a stable code. Legacy throws a plain Error("aborted") from the loop.
concurrency clamping to [1, 64] Genesis-wide guardrail — unbounded Promise.all over user input is a denial-of-service vector. Legacy ships sequential only (concurrency 1) so the clamp is additive.

Verification approach

  • src/core/batch/batch-generator.test.ts — covers BatchBarcodeRequest validation, in-order results, per-item failure isolation, concurrency clamping, onItem callback invocation, abort-via-AbortSignal, sequential-vs-concurrent equivalence on success-only inputs.
  • src/core/batch/batch-generator.spec-conformance.test.ts — pins injectable Clock (deterministic durationMs), Zod-validated BatchBarcodeRequest shape, BARCODE.BATCH_ABORTED error code emission, OTel counter barcode.batch.items_total / barcode.batch.items_failed recorded against an in-memory MeterProvider.

Impact

Affected dependents

Run bunx turbo ls --filter=...@suaveplan/barcode to enumerate. As of 2026-05-02 there are no in-repo dependents:

  • No dependents (verified by grep -rl "@suaveplan/barcode" /home/ubuntu/web/genesis/packages/ --include="package.json" returning only packages/barcode/barcode/package.json itself).

This change is purely additive (new exports, no signature changes to existing surface), so the absence of dependents is not a risk gate.

Migration impact

None — additive only. Existing single-call surface (encode, renderSvg, validateBarcode, per-symbology encoders) is unchanged. Consumers wanting batch generation opt in by importing generateBatch from @suaveplan/barcode or @suaveplan/barcode/batch.