@kaged/utils

Shared foundation utilities — fs guards, type guards, formatting, async/stream helpers, Snowflake IDs, fetch-retry, rotating logger, XDG dirs, and more

22
source files
21
test files
~5.1k
lines
✓ 340 pass
tests
pass
typecheck
clean
lint

Test results 340

✓ AbortError > captures signal reason in message [0.220ms]
✓ AbortError > defaults to Cancelled for non-Error reasons [0.040ms]
✓ createAbortableStream > returns stream unchanged when no signal [1.04ms]
✓ createAbortableStream > wraps stream in a transform with signal [1.64ms]
✓ untilAborted > resolves normally without signal [0.150ms]
✓ untilAborted > resolves normally with non-aborted signal [0.180ms]
✓ untilAborted > rejects immediately if signal already aborted [0.100ms]
✓ untilAborted > rejects when signal fires during execution [10.85ms]
✓ untilAborted > accepts a function returning a promise [0.120ms]
✓ untilAborted > calls function lazily [0.140ms]
✓ once > calls function only once [0.080ms]
✓ once > caches undefined return value [0.040ms]
✓ withTimeout > resolves when promise completes before timeout [0.210ms]
✓ withTimeout > rejects when timeout fires first [10.30ms]
✓ withTimeout > propagates promise rejection [0.140ms]
✓ withTimeout > rejects immediately if signal already aborted [0.100ms]
✓ withTimeout > rejects when signal aborts during wait [11.16ms]
✓ withTimeout > cleans up timeout on early resolution [5.43ms]
✓ XDG / platform dirs > getDataDir returns a path containing kaged [1.77ms]
✓ XDG / platform dirs > getStateDir returns a path containing kaged [0.060ms]
✓ XDG / platform dirs > getCacheDir returns a path containing kaged [0.040ms]
✓ XDG / platform dirs > getConfigDir returns a path containing kaged [0.050ms]
✓ XDG / platform dirs > getLogsDir is under state dir [0.050ms]
✓ XDG / platform dirs > getDbDir is under data dir [0.050ms]
✓ XDG / platform dirs > getSessionsDir is under data dir [0.050ms]
✓ XDG / platform dirs > getRuntimeDir returns a path containing kaged [0.070ms]
✓ getProjectConfigDir > defaults to cwd/.kaged [0.080ms]
✓ getProjectConfigDir > uses provided root [0.030ms]
✓ resolveEquivalentPath > resolves relative paths [0.160ms]
✓ resolveEquivalentPath > resolves symlinks when possible [0.060ms]
✓ resolveEquivalentPath > returns resolved path for nonexistent paths [0.100ms]
✓ normalizePathForComparison > normalizes to absolute path [0.090ms]
✓ pathIsWithin > child is within root [0.150ms]
✓ pathIsWithin > same path is within [0.070ms]
✓ pathIsWithin > parent is not within child [0.050ms]
✓ pathIsWithin > sibling is not within [0.050ms]
✓ pathIsWithin > rejects path traversal [0.060ms]
✓ shortenHome > replaces home prefix with ~ [0.090ms]
✓ shortenHome > leaves non-home paths unchanged [0.030ms]
✓ shortenHome > does not shorten home dir itself without trailing slash [0.030ms]
✓ hashPath > returns 7-char hex string [0.410ms]
✓ hashPath > same path produces same hash [0.050ms]
✓ hashPath > different paths produce different hashes [0.040ms]
✓ isValidEnvName > valid names [0.140ms]
✓ isValidEnvName > invalid names [0.060ms]
✓ isSafeEnvName > safe names [0.080ms]
✓ isSafeEnvName > unsafe names [0.050ms]
✓ isSafeEnvValue > safe values [0.060ms]
✓ isSafeEnvValue > null byte is unsafe [0.030ms]
✓ filterProcessEnv > filters out unsafe keys and undefined values [0.210ms]
✓ filterProcessEnv > filters out values with null bytes [0.050ms]
✓ parseEnvFile > parses simple key=value [0.310ms]
✓ parseEnvFile > strips quotes [0.080ms]
✓ parseEnvFile > skips comments and blank lines [0.060ms]
✓ parseEnvFile > skips lines without = [0.040ms]
✓ parseEnvFile > skips invalid env names [0.050ms]
✓ parseEnvFile > handles values with equals signs [0.050ms]
✓ parseEnvFile > trims whitespace around key and value [0.040ms]
✓ pickEnv > returns first found non-empty value [0.160ms]
✓ pickEnv > returns undefined when none found [0.050ms]
✓ envPosInt > returns parsed value when valid positive integer [0.130ms]
✓ envPosInt > returns default for missing var [0.030ms]
✓ envPosInt > returns default for non-numeric [0.050ms]
✓ envPosInt > returns default for zero or negative [0.060ms]
✓ envFlag > truthy values [0.190ms]
✓ envFlag > falsy values [0.080ms]
✓ envFlag > missing returns default [0.040ms]
✓ isBunTestRuntime > detects test runtime [0.060ms]
✓ extractRetryHint > returns undefined for null/undefined source [0.520ms]
✓ extractRetryHint > extracts from Retry-After header (numeric seconds) [0.130ms]
✓ extractRetryHint > extracts from Retry-After header (zero) [0.070ms]
✓ extractRetryHint > extracts from x-ratelimit-reset-after header [0.060ms]
✓ extractRetryHint > extracts from quota reset body pattern [0.180ms]
✓ extractRetryHint > extracts from 'Please retry in' body pattern [0.160ms]
✓ extractRetryHint > extracts from retryDelay JSON field [0.110ms]
✓ extractRetryHint > extracts from 'try again in' body pattern [0.110ms]
✓ extractRetryHint > returns undefined when no signal found [0.070ms]
✓ extractRetryHint > headers take priority over body [0.060ms]
✓ isRetryableStatus > retryable statuses [0.100ms]
✓ isRetryableStatus > non-retryable statuses [0.070ms]
✓ extractHttpStatusFromError > extracts from status field [0.210ms]
✓ extractHttpStatusFromError > extracts from statusCode field [0.050ms]
✓ extractHttpStatusFromError > extracts from response.status [0.040ms]
✓ extractHttpStatusFromError > extracts from string status [0.040ms]
✓ extractHttpStatusFromError > extracts from error message [0.320ms]
✓ extractHttpStatusFromError > follows cause chain [0.120ms]
✓ extractHttpStatusFromError > returns undefined for non-objects [0.070ms]
✓ extractHttpStatusFromError > limits cause depth to 2 [0.050ms]
✓ isRetryableError > AbortError is retryable [0.150ms]
✓ isRetryableError > timeout messages are retryable [0.090ms]
✓ isRetryableError > retryable HTTP statuses [0.060ms]
✓ isRetryableError > 4xx (non-408/429) is not retryable [0.060ms]
✓ isRetryableError > validation messages are not retryable [0.130ms]
✓ isRetryableError > transient messages are retryable [0.390ms]
✓ isUnexpectedSocketCloseMessage > matches socket close messages [0.060ms]
✓ isUnexpectedSocketCloseMessage > rejects non-matching messages [0.060ms]
✓ fetchWithRetry > returns response on success [1.20ms]
✓ fetchWithRetry > retries on retryable status [2.67ms]
✓ fetchWithRetry > returns last response after maxAttempts [1.39ms]
✓ fetchWithRetry > throws on network error after maxAttempts [1.68ms]
✓ fetchWithRetry > throws immediately on abort [0.290ms]
✓ fetchWithRetry > supports URL function [1.52ms]
✓ fetchWithRetry > supports prepareInit for header refresh [0.500ms]
✓ fetchWithRetry > supports array defaultDelayMs [3.81ms]
✓ fetchWithRetry > returns early when server hint exceeds maxDelayMs [0.440ms]
✓ formatDuration > milliseconds [1.40ms]
✓ formatDuration > seconds [0.180ms]
✓ formatDuration > minutes [0.090ms]
✓ formatDuration > hours [0.060ms]
✓ formatDuration > days [0.090ms]
✓ formatNumber > small numbers unchanged [0.150ms]
✓ formatNumber > thousands [0.160ms]
✓ formatNumber > millions [0.090ms]
✓ formatNumber > billions [0.100ms]
✓ formatBytes > bytes [0.130ms]
✓ formatBytes > kilobytes [0.050ms]
✓ formatBytes > megabytes [0.050ms]
✓ formatBytes > gigabytes [0.060ms]
✓ truncate > short strings unchanged [0.140ms]
✓ truncate > long strings truncated with ellipsis [0.060ms]
✓ truncate > custom ellipsis [0.040ms]
✓ truncate > maxLen smaller than ellipsis [0.050ms]
✓ pluralize > singular [0.120ms]
✓ pluralize > regular plural [0.140ms]
✓ pluralize > sibilant endings (-ch, -sh, -s, -x, -z) [0.180ms]
✓ pluralize > consonant+y → ies [0.100ms]
✓ pluralize > vowel+y → ys [0.070ms]
✓ formatCount > singular [0.100ms]
✓ formatCount > plural [0.040ms]
✓ formatCount > zero [0.040ms]
✓ formatCount > non-finite falls back to 0 [0.060ms]
✓ formatAge > null/undefined/0 returns empty [0.170ms]
✓ formatAge > just now [0.040ms]
✓ formatAge > minutes [0.040ms]
✓ formatAge > hours [0.040ms]
✓ formatAge > days [0.050ms]
✓ formatAge > weeks [0.050ms]
✓ formatAge > months [0.040ms]
✓ formatPercent > formats ratio as percentage [0.110ms]
✓ parseFrontmatter > parses basic frontmatter [2.35ms]
✓ parseFrontmatter > returns empty frontmatter when no --- delimiter [0.100ms]
✓ parseFrontmatter > returns body unchanged when no closing --- [0.080ms]
✓ parseFrontmatter > normalizes kebab-case keys to camelCase [0.280ms]
✓ parseFrontmatter > strips HTML comments [0.110ms]
✓ parseFrontmatter > normalizes CRLF to LF [0.120ms]
✓ parseFrontmatter > merges with fallback values [0.120ms]
✓ parseFrontmatter > normalize=false skips HTML comment stripping and CRLF [0.080ms]
✓ parseFrontmatter > level=fatal throws on invalid YAML [0.650ms]
✓ parseFrontmatter > level=warn falls back to simple parsing on invalid YAML [0.610ms]
✓ parseFrontmatter > handles empty frontmatter block [0.090ms]
✓ parseFrontmatter > handles complex YAML values [0.150ms]
✓ parseFrontmatter > normalizes nested kebab-case keys [0.130ms]
✓ FrontmatterError > has correct name and message [0.130ms]
✓ isFsError > returns true for Error with string code [0.280ms]
✓ isFsError > returns false for plain Error [0.060ms]
✓ isFsError > returns false for non-Error objects [0.110ms]
✓ isFsError > returns false when code is not a string [0.100ms]
✓ isEnoent > matches ENOENT [0.090ms]
✓ isEnoent > rejects other codes [0.060ms]
✓ isEnoent > rejects non-errors [0.050ms]
✓ isEacces > matches EACCES [0.070ms]
✓ isEacces > rejects other codes [0.040ms]
✓ isEisdir > matches EISDIR [0.100ms]
✓ isEisdir > rejects other codes [0.040ms]
✓ isEnotdir > matches ENOTDIR [0.090ms]
✓ isEnotdir > rejects other codes [0.040ms]
✓ isEexist > matches EEXIST [0.070ms]
✓ isEexist > rejects other codes [0.040ms]
✓ isEnotempty > matches ENOTEMPTY [0.060ms]
✓ isEnotempty > rejects other codes [0.050ms]
✓ hasFsCode > matches arbitrary code [0.080ms]
✓ hasFsCode > rejects mismatched code [0.060ms]
✓ hasFsCode > rejects non-errors [0.040ms]
✓ real fs error > identifies a real ENOENT from fs [0.740ms]
✓ globPaths > matches files by glob pattern [4.28ms]
✓ globPaths > supports multiple patterns [0.790ms]
✓ globPaths > excludes .git by default [0.740ms]
✓ globPaths > excludes node_modules by default [0.650ms]
✓ globPaths > includes node_modules when pattern mentions it [0.620ms]
✓ globPaths > respects custom exclude patterns [0.700ms]
✓ globPaths > respects dot option [0.810ms]
✓ globPaths > respects abort signal [1.48ms]
✓ globPaths > respects timeout [0.600ms]
✓ loadGitignorePatterns > parses .gitignore patterns [1.67ms]
✓ loadGitignorePatterns > skips comments and empty lines [0.710ms]
✓ loadGitignorePatterns > skips negation patterns [0.670ms]
✓ loadGitignorePatterns > returns empty array when no .gitignore exists [0.490ms]
✓ snowflake > returns a 16-char hex string [1.18ms]
✓ snowflake > generates unique ids [1.98ms]
✓ snowflake > accepts custom timestamp [0.070ms]
✓ isValidSnowflake > valid snowflakes [0.090ms]
✓ isValidSnowflake > invalid snowflakes [0.060ms]
✓ snowflakeLowerBound / snowflakeUpperBound > lower bound has zero sequence [0.100ms]
✓ snowflakeLowerBound / snowflakeUpperBound > upper bound has max sequence [0.080ms]
✓ snowflakeLowerBound / snowflakeUpperBound > lower < upper for same timestamp [0.050ms]
✓ snowflakeLowerBound / snowflakeUpperBound > accepts Date objects [0.070ms]
✓ snowflakeTimestamp / snowflakeDate > round-trips through generate [0.190ms]
✓ snowflakeTimestamp / snowflakeDate > snowflakeDate returns a Date [0.180ms]
✓ SnowflakeSource > custom initial sequence [0.130ms]
✓ SnowflakeSource > sequence wraps at MAX_SEQ [0.060ms]
✓ SnowflakeSource > independent sources produce different ids [0.050ms]
✓ tryParseJson > parses valid JSON object [1.48ms]
✓ tryParseJson > parses valid JSON array [0.140ms]
✓ tryParseJson > parses valid JSON primitives [0.080ms]
✓ tryParseJson > returns null on invalid JSON [0.120ms]
✓ tryParseJson > preserves generic type parameter [0.060ms]
✓ logger > writes JSON log entries [3.35ms]
✓ logger > writes all levels [0.730ms]
✓ logger > respects minimum level [0.430ms]
✓ logger > includes context fields [0.400ms]
✓ logger > context does not override reserved fields [0.380ms]
✓ logger > creates log directory if it does not exist [0.390ms]
✓ getLogPath > returns path for today [0.250ms]
✓ getLogPath > returns path for specific date [0.110ms]
✓ getLogDir > returns configured directory [0.100ms]
✓ log rotation > prunes old log files beyond maxFiles [0.650ms]
✓ SUPPORTED_IMAGE_MIME_TYPES > contains expected types [0.110ms]
✓ parseImageMetadata > returns null for unrecognized data [1.14ms]
✓ parseImageMetadata > parses PNG with IHDR (RGBA) [0.350ms]
✓ parseImageMetadata > parses PNG with IHDR (RGB) [0.150ms]
✓ parseImageMetadata > parses PNG with IHDR (grayscale) [0.100ms]
✓ parseImageMetadata > detects PNG magic without IHDR [0.090ms]
✓ parseImageMetadata > parses JPEG with SOF0 [0.230ms]
✓ parseImageMetadata > detects JPEG magic even with minimal header [0.100ms]
✓ parseImageMetadata > parses GIF89a header [0.200ms]
✓ parseImageMetadata > parses GIF87a header [0.140ms]
✓ parseImageMetadata > detects WebP RIFF container [0.140ms]
✓ readImageMetadataSync > reads PNG metadata from file [1.16ms]
✓ readImageMetadataSync > returns null for non-image file [0.330ms]
✓ readImageMetadata > reads GIF metadata from file (async) [3.94ms]
✓ sniffMimeType > detects ZIP by magic bytes [0.240ms]
✓ sniffMimeType > detects GZIP by magic bytes [0.080ms]
✓ sniffMimeType > detects TAR by ustar at offset 257 [0.100ms]
✓ sniffMimeType > detects PDF by %PDF magic [0.060ms]
✓ sniffMimeType > delegates to image parser for PNG [0.090ms]
✓ sniffMimeType > delegates to image parser for JPEG [0.090ms]
✓ sniffMimeType > delegates to image parser for GIF [0.080ms]
✓ sniffMimeType > returns null for unrecognized bytes [0.060ms]
✓ sniffMimeType > returns null for empty buffer [0.110ms]
✓ sniffMimeType > TAR requires at least 262 bytes [0.060ms]
✓ SUPPORTED_UPLOAD_MIME_TYPES > contains image types [0.090ms]
✓ SUPPORTED_UPLOAD_MIME_TYPES > contains archive types [0.080ms]
✓ SUPPORTED_UPLOAD_MIME_TYPES > does not contain unsupported types [0.050ms]
✓ peekFileSync > reads first N bytes of a file [0.660ms]
✓ peekFileSync > reads fewer bytes if file is shorter than maxBytes [0.340ms]
✓ peekFileSync > returns op(empty) when maxBytes is 0 [0.280ms]
✓ peekFileSync > passes header to op and returns its result [0.350ms]
✓ peekFile > reads first N bytes of a file (async) [2.64ms]
✓ peekFile > reads fewer bytes if file is shorter [0.810ms]
✓ peekFile > returns op(empty) when maxBytes is 0 [0.480ms]
✓ registerCleanup > returns a cancel function [1.59ms]
✓ cleanup > returns a promise [0.560ms]
✓ RingBuffer > starts empty [1.74ms]
✓ RingBuffer > push and length [0.230ms]
✓ RingBuffer > push returns undefined when not full [0.160ms]
✓ RingBuffer > push returns overwritten item when full [0.230ms]
✓ RingBuffer > shift removes oldest [0.180ms]
✓ RingBuffer > shift returns undefined when empty [0.050ms]
✓ RingBuffer > pop removes newest [0.200ms]
✓ RingBuffer > pop returns undefined when empty [0.060ms]
✓ RingBuffer > unshift adds to front [0.160ms]
✓ RingBuffer > unshift overwrites newest when full [0.090ms]
✓ RingBuffer > at with positive index [0.140ms]
✓ RingBuffer > at with negative index [0.100ms]
✓ RingBuffer > at returns undefined for out-of-bounds [0.060ms]
✓ RingBuffer > peek and peekBack [0.140ms]
✓ RingBuffer > clear resets buffer [0.190ms]
✓ RingBuffer > iterator yields in FIFO order [0.790ms]
✓ RingBuffer > iterator after wraparound [0.170ms]
✓ RingBuffer > toArray after wraparound [0.110ms]
✓ RingBuffer > mixed push/shift operations [0.100ms]
✓ RingBuffer > works with string type [0.080ms]
✓ sanitizeText > passes through clean text unchanged [1.47ms]
✓ sanitizeText > preserves tabs and newlines [0.110ms]
✓ sanitizeText > strips ANSI escape sequences [0.150ms]
✓ sanitizeText > removes C0 control characters (except tab and newline) [0.130ms]
✓ sanitizeText > removes DEL character [0.050ms]
✓ sanitizeText > removes C1 control characters [0.060ms]
✓ sanitizeText > handles mixed ANSI and control characters [0.050ms]
✓ sanitizeText > handles empty string [0.040ms]
✓ sanitizeText > preserves U+FFFD in already well-formed strings [0.070ms]
✓ readLines > splits on newlines [5.90ms]
✓ readLines > handles chunks split mid-line [1.08ms]
✓ readLines > yields trailing data without final newline [0.560ms]
✓ readLines > empty stream yields nothing [0.340ms]
✓ readTextLines > yields decoded strings [0.920ms]
✓ readJsonl > parses JSONL lines [0.940ms]
✓ readJsonl > skips blank lines [0.590ms]
✓ parseJsonlLenient > parses valid lines [0.270ms]
✓ parseJsonlLenient > skips malformed lines [0.190ms]
✓ parseJsonlLenient > skips blank lines [0.070ms]
✓ parseJsonlLenient > returns empty for empty input [0.080ms]
✓ readSseEvents > parses basic SSE events [1.86ms]
✓ readSseEvents > parses named events [0.680ms]
✓ readSseEvents > concatenates multi-line data with newlines [0.640ms]
✓ readSseEvents > handles \r\n line endings [0.510ms]
✓ readSseEvents > skips comment lines [0.480ms]
✓ readSseEvents > flushes trailing event without final blank line [0.430ms]
✓ readSseJson > parses JSON from SSE data [1.05ms]
✓ readSseJson > stops at [DONE] sentinel [0.700ms]
✓ readSseJson > skips empty data events [0.420ms]
✓ readSseJson > calls onEvent observer [0.660ms]
✓ readSseJson > observer errors do not break stream [0.780ms]
✓ TempDir > createSync creates a directory [0.660ms]
✓ TempDir > create creates a directory [0.900ms]
✓ TempDir > path returns the directory path [0.190ms]
✓ TempDir > absolute returns an absolute path [0.190ms]
✓ TempDir > join appends to the temp path [0.240ms]
✓ TempDir > toString returns the path [0.180ms]
✓ TempDir > removeSync removes the directory [0.260ms]
✓ TempDir > remove removes the directory [0.620ms]
✓ TempDir > remove is idempotent [0.350ms]
✓ TempDir > custom prefix with @ [0.190ms]
✓ TempDir > default prefix uses kaged-temp- [0.120ms]
✓ TempDir > Symbol.dispose cleans up [0.230ms]
✓ TempDir > Symbol.asyncDispose cleans up [0.560ms]
✓ isRecord > returns true for plain objects [1.43ms]
✓ isRecord > returns true for Object.create(null) [0.070ms]
✓ isRecord > returns false for arrays [0.060ms]
✓ isRecord > returns false for null and undefined [0.060ms]
✓ isRecord > returns false for primitives [0.060ms]
✓ isRecord > returns true for class instances [0.100ms]
✓ asRecord > returns the value for records [0.090ms]
✓ asRecord > returns null for non-records [0.090ms]
✓ toError > returns Error instances as-is [0.100ms]
✓ toError > wraps strings in Error [0.090ms]
✓ toError > wraps numbers in Error [0.060ms]
✓ toError > wraps null in Error [0.050ms]
✓ toError > wraps undefined in Error [0.050ms]
✓ toError > preserves Error subclasses [0.090ms]
✓ structuredCloneJSON > clones plain objects [0.290ms]
✓ structuredCloneJSON > clones arrays [0.100ms]
✓ structuredCloneJSON > returns primitives as-is [0.090ms]
✓ structuredCloneJSON > handles objects that structuredClone rejects [0.270ms]
✓ $which > finds common binaries on PATH [1.83ms]
✓ $which > returns null for nonexistent binary [0.380ms]
✓ $which > caches results with Cached policy [0.330ms]
✓ $which > bypasses cache with Bypass policy [0.180ms]
✓ $which > respects custom PATH [0.070ms]
✓ $which > finds bun binary [0.210ms]

Mentioned in

Type Document
adr ADR-0029: Structured operational logging
adr ADR-0042: Package distribution, CI, and the release pipeline
spec Spec: Operational Logging
spec Spec: Project Terminals
spec Release pipeline