7.9 KiB
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<T>(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
@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,prototypekeys by default (configurable) - Circular reference detection with structured
SafeJSONError tryParsereturnsundefinedinstead of throwinggetStringifySizemeasures serialised byte length without full allocation- All errors extend
@suaveplan/errorwith stable codes (JSON.PARSE_FAILED,JSON.CIRCULAR,JSON.PROTOTYPE_POLLUTION)
Dependencies
@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/errorwith stable codes (BYTES.INVALID_FORMAT,BYTES.ENCODE_FAILED, etc.)
Dependencies
@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
@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:
@suaveplan/bytes— unblockscrypto-utilsrefactor@suaveplan/json— self-contained@suaveplan/locale-data— self-contained; enablesi18nto consume it@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
.tsimplementation 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/errorwith stablePACKAGE.DOMAIN.REASONcodes - Injectable
clock,rng,signalwhere applicable AbortSignalon 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-istanbulindevDependencies- 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) |