# Design: `@suaveplan/utils` Migration — genesis **Date:** 2026-04-20 **Branch:** genesis **Source:** `@suaveplan/utils` from main (`/mnt/s/web/suaveplan/packages/universal/utils`) **Reference:** `/mnt/s/web/suaveplan/enterprise/utils.md` --- ## Context The old `@suaveplan/utils` package is a 14-module grab-bag imported by 109 reverse-consumer workspaces. On genesis, we rewrite it fresh at enterprise quality and split it into four packages, each with a clear, single responsibility. Nothing is ported mechanically — every module is rewritten to meet the genesis enterprise checklist. --- ## Decision: Split into 4 packages | Package | Domain path | Purpose | |---------|-------------|---------| | `@suaveplan/utils` | `universal/data/utils` | Zero-external-dep odds-and-sods utilities | | `@suaveplan/json` | `universal/data/json` | Prototype-pollution-safe JSON parse/stringify | | `@suaveplan/bytes` | `universal/primitives/bytes` | Byte encoding / format conversion | | `@suaveplan/locale-data` | `universal/i18n/locale-data` | ISO country + language static data | **Scrapped:** `string/parseTemplate` only — duplicate of `@suaveplan/templates` which already handles `{{var}}` interpolation. `string/parseTextWithLinks` is distinct (link annotation, not template interpolation) and is retained in `@suaveplan/utils`. --- ## Package 1: `@suaveplan/utils` **Principle:** Zero external deps beyond the genesis standard (`zod`, `@opentelemetry/api`, `@suaveplan/error`, `@suaveplan/types`). Odds-and-sods utilities that are not worth a dedicated package. ### Modules | Module | Key exports | |--------|-------------| | `debug` | `debug.log/warn/error` — dedup-buffered console logger (suppresses repeated messages within a rolling 10-item buffer) | | `object` | `deepMerge`, `deepMergeAll`, `deepClone`, `pick`, `omit`, `flattenObject`, `getNestedValue`, `setNestedValue`, `hasPath`, `isEmpty`, `isObject`, `isPlainObject`, `isObjectOrArray`, `keepKeys`, `removeKeys` | | `platform` | `getPlatform`, `getMemoryUsage`, `getCpuUsage`, `isNode`, `isBrowser`, `isBun` — injectable platform adapter | | `number` | `packFloat16/32/64`, `unpackFloat16/32/64`, `isInFloat16/32/64Range`, `min16/32/64`, `max16/32/64` | | `random` | `pseudoRandom(arr, index, seed?)` — seeded deterministic array element selection | | `email` | `normalizeEmail` — Gmail dots/plus, Yahoo dashes, Microsoft plus, generic normalization | | `sound` | `parseSoundVariations`, `getSoundKey`, `convertSprites` — audio sprite key parsing + variation selection | | `string` | `generateId`, `parseTextWithLinks` | | `validate` | `isHex` | | `validation` | `validateSize`, `formatSize`, `parseSize`, `truncateToSize`, `isWithinSizeLimit`, `getDataSize`, `MessageSizeError`, `SIZE_LIMITS`, `DEFAULT_MAX_MESSAGE_SIZE` | ### Dependencies ```text @suaveplan/error workspace:^ @suaveplan/types workspace:^ zod catalog:zod @opentelemetry/api catalog:otel-api ``` --- ## Package 2: `@suaveplan/json` **Principle:** Safe, hardened JSON operations. No external deps. ### Modules | Module | Key exports | |--------|-------------| | `safe-json` | `safeParse`, `safeStringify`, `tryParse`, `isValidJSON`, `getStringifySize`, `SafeJSONError` | ### Features - Prototype pollution guard: rejects `__proto__`, `constructor`, `prototype` keys by default (configurable) - Circular reference detection with structured `SafeJSONError` - `tryParse` returns `undefined` instead of throwing - `getStringifySize` measures serialised byte length without full allocation - All errors extend `@suaveplan/error` with stable codes (`JSON.PARSE_FAILED`, `JSON.CIRCULAR`, `JSON.PROTOTYPE_POLLUTION`) ### Dependencies ```text @suaveplan/error workspace:^ @suaveplan/types workspace:^ zod catalog:zod @opentelemetry/api catalog:otel-api ``` --- ## Package 3: `@suaveplan/bytes` **Principle:** Single canonical home for byte encoding and format conversion in the monorepo. `@suaveplan/crypto-utils` currently duplicates some of this; a follow-up task updates `crypto-utils` to consume `@suaveplan/bytes` instead. ### Modules | Module | Key exports | |--------|-------------| | `encoding` | `bytesToHex`, `hexToBytes`, `bytesToBase64`, `base64ToBytes`, `bytesToUtf8`, `utf8ToBytes` | | `uint8` | `concatUint8Arrays`, `sliceUint8Array`, `uint8ArraysEqual`, `inputToUint8Array`, `detectInputFormat`, `validateInputTypeFormat`, `formatOutput`, `convertDataFormat` | | `uuid` | `uint8ArrayToUuid`, `uuidToUint8Array`, `generateUuid` — uses `crypto.randomUUID()` natively; no `uuid` npm package needed | ### Features - Format auto-detection (`detectInputFormat`: hex, base64, utf8, Uint8Array) - Round-trip guarantees tested via fast-check property tests - All errors extend `@suaveplan/error` with stable codes (`BYTES.INVALID_FORMAT`, `BYTES.ENCODE_FAILED`, etc.) ### Dependencies ```text @suaveplan/error workspace:^ @suaveplan/types workspace:^ zod catalog:zod @opentelemetry/api catalog:otel-api ``` ### Follow-up After `@suaveplan/bytes` is published, open an OpenSpec change to update `@suaveplan/crypto-utils` to depend on `@suaveplan/bytes` and remove the duplicate hex/base64/utf8/bytes/concat encoding utilities. **`base58` stays in `crypto-utils`** — it is blockchain/crypto-specific (Bitcoin address encoding) and not a general-purpose byte encoding concern; moving it to `bytes` would introduce a crypto-domain concept into a general primitive. --- ## Package 4: `@suaveplan/locale-data` **Principle:** Static ISO reference data. Too large and domain-specific to be an odds-and-sods utility. Feeds `@suaveplan/i18n`. ### Modules | Module | Key exports | |--------|-------------| | `countries` | `CountryItem` type, per-country named exports (`AD`, `AE`, ...), `getCountry(code)`, `getAllCountries()`, `getCountriesByContinent(continent)`, `getCountriesByCurrency(currency)` | | `languages` | `LanguageItem` type, `getLanguages()`, `getLanguage(code)`, `getRtlLanguages()`, `isRtlLanguage(code)` | ### Features - All data typed with Zod schemas - Lookup helpers with structured errors for unknown codes - Zero runtime deps — data is bundled at build time, not fetched ### Dependencies ```text @suaveplan/error workspace:^ @suaveplan/types workspace:^ zod catalog:zod @opentelemetry/api catalog:otel-api ``` --- ## Build sequence All four packages share the same dependency set (genesis standard), so they can land in parallel or any order. Recommended sequence: 1. `@suaveplan/bytes` — unblocks `crypto-utils` refactor 2. `@suaveplan/json` — self-contained 3. `@suaveplan/locale-data` — self-contained; enables `i18n` to consume it 4. `@suaveplan/utils` — self-contained; the catch-all --- ## Enterprise checklist (applies to all four) - [ ] 100% statement + branch + function + line coverage (Istanbul) - [ ] 100% docs coverage (`bun run docs:check`) - [ ] Every `.ts` implementation file has a sibling `.md` (≥200 words, simple + advanced examples, API reference) - [ ] All public types have Zod schemas in sibling `*.schemas.ts` - [ ] All thrown errors extend `@suaveplan/error` with stable `PACKAGE.DOMAIN.REASON` codes - [ ] Injectable `clock`, `rng`, `signal` where applicable - [ ] `AbortSignal` on every iterative helper - [ ] OTel counters + histograms + spans on public entrypoints - [ ] fast-check property tests for pure functions - [ ] mitata benchmarks with CI regression gate - [ ] `sideEffects: false`; one subpath export per feature module - [ ] `@vitest/coverage-istanbul` in `devDependencies` - [ ] OpenSpec change created, verified, archived --- ## OpenSpec changes to create | Change ID | Scope | |-----------|-------| | `enterprise-bytes` | New `@suaveplan/bytes` package | | `enterprise-json` | New `@suaveplan/json` package | | `enterprise-locale-data` | New `@suaveplan/locale-data` package | | `enterprise-utils` | New `@suaveplan/utils` package (genesis rewrite) | | `crypto-utils-bytes-refactor` | Update `@suaveplan/crypto-utils` to consume `@suaveplan/bytes` (follow-up) |