Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,70 @@
# QR code batch generator
**Package**: `@suaveplan/qrcode`
**Status**: PROPOSED
**Type**: spec-conformance + feature
## Why
The 2026-05-02 audit of `@suaveplan/qrcode` against legacy `packages/universal/qrcode/src/` found that genesis ships only the single-call `generate()` and the `core/*` building blocks. Legacy ships a full `batch/batch-generator.ts` exposing three flavours of batch surface — `generateBatch`, `generateBatchAsync` (async generator with concurrency), and `generateBatchWithProgress` — used by every consumer that produces many QR codes at once: ticketing, vCard imports, asset registers, and bulk loyalty-card issuance. CLAUDE.md scopes this package as "generator only" — `qr-reader`, `styled-qr` (logos / colours / shapes), and `qr-exporter` (PNG/PDF) are intentionally out of scope. The batch generator is unambiguously part of the generator surface, not any of those scoped-out caps. Its absence forces every consumer to re-implement an error-isolating loop in user code, which §0a (migration parity) bans.
## What changes
- ADDED: `BatchQrCodeRequest` interface — `{ id?: string; value: string; options?: QRCodeOptions }`. Per-item `id` is optional; when omitted the request index is used as the result key.
- ADDED: `BatchQrCodeResult` discriminated union — `{ id: string; index: number; ok: true; result: QRCodeResult } | { id: string; index: number; ok: false; error: QRCodeError }`. Failed items carry a coded `QRCodeError`, never a bare string.
- ADDED: `BatchQrCodeOptions` interface — `{ concurrency?: number; signal?: AbortSignal; onItem?: (result: BatchQrCodeResult) => void }`. `concurrency` defaults to `1` (sequential) and is clamped to `[1, 64]`.
- ADDED: `BatchQrCodeSummary` interface — `{ total: number; succeeded: number; failed: number; durationMs: number; results: BatchQrCodeResult[] }`. `durationMs` uses an injectable `Clock`.
- ADDED: `generateBatch(requests, options?): Promise<BatchQrCodeSummary>` — produces one `BatchQrCodeResult` per input request in input order; per-item failures isolated.
- ADDED: `generateBatchAsync(requests, options?): AsyncGenerator<BatchQrCodeResult>` — async iterable that yields results as they complete (completion order, not input order); honours `concurrency` and `signal`.
- ADDED: `BatchQrCodeGenerator` class — `class BatchQrCodeGenerator { constructor(deps?: { clock?: Clock }); generate(requests, options?): Promise<BatchQrCodeSummary>; stream(requests, options?): AsyncGenerator<BatchQrCodeResult> }` 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 `QRCODE.BATCH_ABORTED` registered on `QRCODE_CODES`, thrown when the supplied `AbortSignal` fires before completion.
## Out of scope
- **QR reading / decoding (qr-reader)** — explicitly out of scope per CLAUDE.md package description ("generator only"). Tracked under separate `qrcode-scan` change already in `openspec/changes/`.
- **Styled QR (logos / colour gradients / module shapes — styled-qr)** — intentionally out of scope per CLAUDE.md. Would be its own package.
- **Raster export (PNG / PDF — qr-exporter)** — intentionally out of scope per CLAUDE.md. The package emits SVG/matrix/terminal/dataurl only; raster export belongs in a downstream package that depends on `@suaveplan/qrcode` plus a rasteriser.
- **Higher QR versions (11 – 40)** — current package supports versions 1 – 10 only. Extending the version table is its own change; batch behaviour is independent of version count and works identically once added.
## Source mapping
This change derives from `../suaveplan/packages/universal/qrcode/src/batch/`.
| Requirement | Source lines | Notes |
|---|---|---|
| `BatchQrCodeRequest` shape | `types/qr.types.ts` L94-L98 | Reworded: `data` → `value`; added optional `id`; removed `outputFormat` (legacy `BatchItem` carries it; new shape relies on `QRCodeOptions`/`QRCodeResult` having every output already) |
| `BatchQrCodeResult` discriminated union | (no source line) | Legacy returns `QrCode[]` with no per-item failure mode — every failure throws and aborts the batch. New shape adds discriminated-union failure isolation (parity with `barcode-batch-generator`). |
| `BatchQrCodeOptions` shape with `concurrency`, `signal`, `onItem` | `types/qr.types.ts` L103-L106 (legacy `BatchOptions`) | Extended: legacy has `onProgress: (completed, total) => void` and `concurrency`; new shape replaces `onProgress` with `onItem` (richer payload), adds `signal` |
| `generateBatch` Promise-based function | `batch/batch-generator.ts` L12-L29 | Verbatim semantics for the success path; new failure-isolation behaviour replaces legacy throw-and-abort (legacy bug was that one bad input poisoned the whole batch) |
| `generateBatchAsync` async-iterable function | `batch/batch-generator.ts` L35-L59 | Verbatim — yields per item, supports `concurrency` |
| `BatchQrCodeGenerator` class | (no source line) | New: legacy is free functions only; class added for clock injection |
### Requirements with no source line
| Requirement | Justification |
|---|---|
| Per-item failure isolation in `generateBatch` | Legacy throws on first per-item failure (`generator.generate` rejects the loop). Genesis-wide CLAUDE.md §Workflow rule 2 ("no half-baked libraries") plus the §0a migration-parity rule mean we keep legacy's batch surface but fix the obvious correctness bug rather than ship the broken contract. |
| `BatchQrCodeGenerator` 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`. |
| `QRCODE.BATCH_ABORTED` error code on `QRCODE_CODES` | CLAUDE.md §Workflow rule 2 — every thrown error routes through `@suaveplan/error` with a stable code. Legacy throws plain `Error` from the loop (no abort plumbed in legacy at all). |
| `concurrency` clamping to `[1, 64]` | Genesis-wide guardrail — unbounded `Promise.all` over user input is a denial-of-service vector. Legacy clamps to a default of 4 but accepts any number; genesis hardens the upper bound. |
| `AbortSignal` support | Legacy has none. Genesis-wide pattern (every async API in `@suaveplan/retry`, `@suaveplan/idempotency`, etc.) accepts an `AbortSignal`. |
## Verification approach
- `src/core/batch/batch-generator.test.ts` — covers `BatchQrCodeRequest` validation, in-order results from `generateBatch`, completion-order from `generateBatchAsync`, per-item failure isolation, `concurrency` clamping, `onItem` callback invocation, abort-via-`AbortSignal` for both shapes.
- `src/core/batch/batch-generator.spec-conformance.test.ts` — pins injectable `Clock` (deterministic `durationMs`), Zod-validated `BatchQrCodeRequest` shape, `QRCODE.BATCH_ABORTED` error code emission, OTel counter `qrcode.batch.items_total` / `qrcode.batch.items_failed` / histogram `qrcode.batch.duration_ms` recorded against an in-memory `MeterProvider`.
## Impact
### Affected dependents
Run `bunx turbo ls --filter=...@suaveplan/qrcode` to enumerate. As of 2026-05-02 there are no in-repo dependents:
- No dependents (verified by `grep -rl "@suaveplan/qrcode" /home/ubuntu/web/genesis/packages/ --include="package.json"` returning only `packages/barcode/qrcode/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 `generate()`, the `core/*` building blocks, the renderers, and `QRCODE_CODES` are unchanged. Consumers wanting batch generation opt in by importing `generateBatch` / `generateBatchAsync` from `@suaveplan/qrcode` or `@suaveplan/qrcode/batch`.