@herowcode/utils
v1.7.0
Published
A lightweight collection of utility functions for everyday JavaScript/TypeScript development
Maintainers
Readme
@herowcode/utils
A tree-shakable TypeScript utility library for dates, strings, arrays, API calls, YouTube, and files — one install, every project.
📖 Context
The Problem
Every application I built required the same utility functions: format a date, debounce an event, extract a YouTube video ID, wrap an API call with error handling. The result was copy-pasted code scattered across dozens of projects — and when something needed fixing, I had to hunt down every copy.
The Goal
One library, one update, every project benefits. @herowcode/utils is built around two constraints:
- Zero unnecessary overhead — tree-shakable,
sideEffects: false, import only what you need - Universal — works in Node.js and browsers, with conditional exports per environment
Solo Project
Designed, built, and maintained by Judson Cairo as the shared foundation for all HerowCode projects — and open to the community.
🛠️ Stack
| Layer | Technology |
|-------|------------|
| Language | TypeScript 5.x |
| Build | tsup — CJS + ESM + .d.ts declarations |
| Testing | Vitest + jsdom |
| Linting | Biome 2.x |
| Package manager | pnpm |
| CI/CD | GitHub Actions + npm OIDC provenance |
Why this stack?
tsup over Rollup/Webpack — Zero-config dual CJS/ESM output with automatic .d.ts generation, code splitting, and treeshaking in a single config file.
Biome over ESLint + Prettier — One tool for linting and formatting with near-instant feedback. Enforces naming conventions (I* interfaces, T* types, E* enums) across the codebase.
dayjs over date-fns — Smaller base size with a plugin system. The intl plugin enables Intl-based formatting without shipping a full locale registry.
📦 Modules
| Module | What it does | Docs |
|--------|-------------|------|
| /api | HTTP client with retry, auth tokens, and standardized errors | docs/api.md |
| /array | shuffle, unique, markdownToText | docs/array.md |
| /date | Format, relative time, timezone correction, time-spent parsing | docs/date.md |
| /files | Compress images, download URLs, format bytes (browser + Node) | docs/files.md |
| /function | debounce, throttle, tryCatch | docs/function.md |
| /logger | Structured logger (Pino on Node, console-based on browser) | docs/logger.md |
| /nextjs | OptimizedImage component | docs/nextjs.md |
| /string | Case conversion, slugify, truncate, time formatting | docs/string.md |
| /youtube | Extract IDs, generate URLs, video info with caching | docs/youtube.md |
🚀 Installation
npm install @herowcode/utils
# or
pnpm add @herowcode/utils
# or
yarn add @herowcode/utilsPeer dependencies (only needed for specific modules):
# /nextjs and /youtube React hook
npm install react next
# /logger pretty output in dev (Node only — JSON in prod always works)
npm install -D pino-prettyA Python template covering the same contract is shipped in
templates/python/logger.py— copy it into your Python services. See docs/logger.md.
⚡ Quick Start
Always import from the specific module to keep your bundle small:
import { formatDate, getRelativeTime } from '@herowcode/utils/date';
import { capitalize, slugify, truncate } from '@herowcode/utils/string';
import { debounce, tryCatch } from '@herowcode/utils/function';
import { apiClient } from '@herowcode/utils/api';
import { extractYouTubeId, getYoutubeVideoInfo } from '@herowcode/utils/youtube';Or import from the root entry (all modules bundled together):
import { formatDate, capitalize, debounce } from '@herowcode/utils';💡 Key Challenge: Universal Exports
The hardest part of building this library was making modules like files and youtube work seamlessly in both browser and Node.js environments — without requiring consumers to configure anything.
Some functions only make sense in a browser (compressImage, downloadUrl); others only in Node.js (fileExists, fileDelete). The solution is conditional exports in package.json, resolved automatically by bundlers and the Node.js runtime:
"./files": {
"browser": { "import": "./dist/files/index.browser.js" },
"node": { "import": "./dist/files/index.node.js" },
"import": { "default": "./dist/files/index.js" }
}The exports field is auto-generated by scripts/sync-exports.cjs on every build — never edit it manually.
For getYoutubeVideoInfo specifically, the Node.js environment queries four sources in parallel and merges the results — each field picks the first non-empty value and thumbnails are deduped across sources:
1. InnerTube player endpoint (ANDROID → WEB client) → full metadata (primary)
2. Watch page HTML (ytInitialPlayerResponse) → full metadata
3. YouTube oEmbed API → title + thumbnail
4. NoEmbed public API → title + description + upload dateThe InnerTube endpoint is used because it works reliably from datacenter/containerized environments where YouTube serves a consent interstitial instead of the normal watch page. Sources complement each other: if InnerTube returns metadata but oEmbed contributes an extra thumbnail size, the merged result has both.
Results are cached by video ID as a Promise — concurrent calls for the same video hit the network only once, and failed requests evict themselves from the cache automatically.
📋 Development
pnpm install # Install dependencies
pnpm test # Run tests in watch mode
pnpm test:run # Single test run
pnpm test:coverage # Coverage report (V8)
pnpm build # Compile CJS + ESM + declarations
pnpm lint # Biome check + tsc --noEmitRun a single test file:
pnpm vitest run src/string/capitalize.test.tsNote:
package.jsonexports are auto-generated on every build. Do not edit theexportsfield manually.
📊 Metrics
| Metric | Value |
|--------|-------|
| npm downloads / month | |
| npm downloads / week |
|
| Latest release |
|
| Last commit |
|
