Initial Commit
This commit is contained in:
113
openspec/specs/utils/spec.md
Normal file
113
openspec/specs/utils/spec.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# @suaveplan/utils — Specification
|
||||
|
||||
**Package:** `@suaveplan/utils`
|
||||
**Version at graduation:** `0.1.0`
|
||||
**Layer:** Universal — zero browser/React/Node-specific APIs
|
||||
**Published:** 2026-04-20
|
||||
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
Zero-external-dependency utility library covering debugging, object manipulation, platform detection, IEEE 754 float packing, pseudo-random selection, email normalization, audio sprite management, string utilities, hex validation, and byte-size enforcement.
|
||||
|
||||
---
|
||||
|
||||
## Modules
|
||||
|
||||
### `./errors`
|
||||
|
||||
- `UtilsError` — typed `SuaveplanError<UtilsCode>` subclass
|
||||
- `UTILS_CODES` — stable code registry keyed by dot-path
|
||||
- `UtilsCode` — union type of all code values
|
||||
|
||||
### `./debug`
|
||||
|
||||
- `assertDefined<T>(val, message?)` — throws if val is null/undefined
|
||||
- `invariant(condition, message?)` — throws if condition is false
|
||||
- `unreachable(message?)` — always throws (exhaustiveness check)
|
||||
- `assert(condition, message?)` — alias for invariant
|
||||
|
||||
### `./object`
|
||||
|
||||
- `isObject(value)` / `isPlainObject(value)` / `isObjectOrArray(value)` / `isEmpty(obj)`
|
||||
- `deepMerge(a, b, skipUndefined?)` / `deepMergeAll(...objects)`
|
||||
- `pick(obj, keys)` / `omit(obj, keys)` / `keepKeys` / `removeKeys`
|
||||
- `flattenObject(obj, prefix?, separator?)`
|
||||
- `getNestedValue(obj, path, default?)` / `setNestedValue(obj, path, value)` / `hasPath(obj, path)`
|
||||
- `deepClone(obj)`
|
||||
|
||||
### `./platform`
|
||||
|
||||
- `isBrowser()` / `isNode()` / `isBun()` / `isEdge()`
|
||||
- `isSSR()` / `supportsWebWorkers()` / `supportsServiceWorkers()`
|
||||
- `getPlatform()` — returns `"browser" | "node" | "bun" | "edge" | "unknown"`
|
||||
|
||||
### `./number`
|
||||
|
||||
- Constants: `min16`, `max16`, `min32`, `max32`, `min64`, `max64`
|
||||
- `packFloat16(v)` / `unpackFloat16(buf)` — IEEE 754 half-precision
|
||||
- `packFloat32(v)` / `unpackFloat32(buf)` — IEEE 754 single-precision
|
||||
- `packFloat64(v)` / `unpackFloat64(buf)` — IEEE 754 double-precision
|
||||
- `isInFloat16Range(v)` / `isInFloat32Range(v)` / `isInFloat64Range(v)`
|
||||
|
||||
### `./random`
|
||||
|
||||
- `pseudoRandom<T>(arr, index, seed?, opts?)` — deterministic element selection
|
||||
|
||||
### `./email`
|
||||
|
||||
- `normalizeEmail(email)` — provider-aware canonicalization (Gmail/Yahoo/Microsoft/generic)
|
||||
|
||||
### `./sound`
|
||||
|
||||
- `parseSoundVariations(sprites)` — group `<name>_<n>` keys by base name
|
||||
- `getSoundKey(name, variations)` — random variation selection; throws `SOUND.NO_VARIATIONS`
|
||||
- `convertSprites(sprites)` — alias for `parseSoundVariations`
|
||||
- Types: `SoundSprites`, `SoundVariations`
|
||||
|
||||
### `./string`
|
||||
|
||||
- `generateId(opts?)` — random hex identifier via `crypto.randomUUID()`
|
||||
- `parseTextWithLinks(text)` — extract HTTP/HTTPS URLs → `TextLink[]`
|
||||
- Types: `GenerateIdOptions`, `TextLink`
|
||||
|
||||
### `./validate`
|
||||
|
||||
- `isHex(i)` — type-guard: non-empty, even-length, hex-only string
|
||||
|
||||
### `./validation`
|
||||
|
||||
- Constants: `SIZE_LIMITS`, `DEFAULT_MAX_MESSAGE_SIZE`
|
||||
- `getDataSize(data)` — UTF-8 bytes for string, byteLength for binary, 8 for number
|
||||
- `validateSize(data, maxSize?)` — throws `VALIDATION.SIZE_EXCEEDED`
|
||||
- `isWithinSizeLimit(data, maxSize?)` — non-throwing boolean
|
||||
- `formatSize(bytes, decimals?)` — `"1.50 KB"` format
|
||||
- `parseSize(str)` — `"1.5MB"` → integer bytes; throws `VALIDATION.PARSE_FAILED`
|
||||
- `truncateToSize(data, maxSize)` — binary-search string truncation
|
||||
|
||||
---
|
||||
|
||||
## Error Codes
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| `UTILS/OBJECT.PROTOTYPE_POLLUTION` | `__proto__` / constructor key detected |
|
||||
| `UTILS/OBJECT.CIRCULAR` | Circular reference in deepClone |
|
||||
| `UTILS/NUMBER.OUT_OF_RANGE` | Float value outside target precision range |
|
||||
| `UTILS/RANDOM.EMPTY_ARRAY` | pseudoRandom called with empty array |
|
||||
| `UTILS/RANDOM.INDEX_OUT_OF_BOUNDS` | index >= array.length |
|
||||
| `UTILS/VALIDATION.SIZE_EXCEEDED` | Data exceeds maxSize bytes |
|
||||
| `UTILS/VALIDATION.PARSE_FAILED` | Invalid size string format |
|
||||
| `UTILS/PLATFORM.UNAVAILABLE` | Requested platform feature unavailable |
|
||||
| `UTILS/SOUND.NO_VARIATIONS` | getSoundKey called for unknown sound name |
|
||||
|
||||
---
|
||||
|
||||
## Quality Gates (at ship)
|
||||
|
||||
- Istanbul coverage: 100% statements / branches / functions / lines
|
||||
- Docs coverage: 100% (12/12 files)
|
||||
- Lint: clean (biome, 0 errors)
|
||||
- TypeScript: clean (noEmit)
|
||||
- Tests: 169 passing
|
||||
Reference in New Issue
Block a user