| name | stacks-utils |
| description | Use when needing general utility functions in Stacks — deep merge, debounce/throttle, color output, byte formatting, markdown tables, YAML parsing, Pipeline class, ResizeObserver, Macroable, project initialization, indentation detection, or the comprehensive utility toolkit. Covers @stacksjs/utils. |
| license | MIT |
| compatibility | Bun >= 1.3.0, TypeScript |
| allowed-tools | Read Edit Write Bash Grep Glob |
Stacks Utilities
Umbrella package providing utility functions plus re-exports from specialized packages.
Key Paths
- Core package:
storage/framework/core/utils/src/
- Package:
@stacksjs/utils
Architecture
The index.ts re-exports from these local modules:
clean.ts — project cleanup (cleanProject())
config.ts — config builder re-exports from @stacksjs/config
equal.ts — deep equality
export-size.ts — re-exports from size.ts
find.ts — find Stacks projects on filesystem
git.ts — git status check
hash.ts — path hashing for cloud deploy
helpers.ts — project helpers (env, YAML, version, npm scripts, etc.)
macroable.ts — dynamic method registration on classes
versions.ts — semver comparison via Bun.semver
merge.ts — deep object merge (defu replacement)
detect.ts — indentation and newline detection
debounce.ts — debounce, throttle, delay
bytes.ts — byte formatting and parsing
colors.ts — ANSI terminal color output
markdown.ts — markdown table generation
size.ts — file/export size calculation
observer.ts — ResizeObserver polyfill
pipeline.ts — Pipeline class for data transformation chains
Also re-exports from @stacksjs/browser:
export * as browserUtils from '@stacksjs/browser'
export { clamp, notNullish, retry, useOnline } from '@stacksjs/browser'
Deep Merge (merge.ts)
Replacement for defu. Arrays are concatenated, objects are recursively merged, special objects (Date, RegExp, Error, Promise, Map, Set, WeakMap, WeakSet, ArrayBuffer views) are NOT deeply merged.
import { merge, mergeDefaults, createMerger, defu } from '@stacksjs/utils'
merge({ a: 1 }, { b: 2 })
merge({ a: [1] }, { a: [2] })
merge({ a: { x: 1 } }, { a: { y: 2 } })
mergeDefaults(obj, defaults1, defaults2)
const merger = createMerger(defaults)
defu(obj, defaults1, defaults2)
Key behavior: undefined values in source objects are skipped. Later arguments take precedence in merge(), but the first argument takes precedence in mergeDefaults()/defu().
Debounce & Throttle (debounce.ts)
interface DebounceOptions {
leading?: boolean
trailing?: boolean
}
const debouncedFn = debounce(fn, 300, { leading: false, trailing: true })
debouncedFn.cancel()
debouncedFn.flush()
const throttledFn = throttle(fn, 100)
throttledFn.cancel()
await delay(1000)
Note: unlike some implementations, this debounce does NOT support maxWait. The throttle uses Date.now() for timing, not setTimeout stacking.
Color Output (colors.ts)
ANSI terminal color library. Auto-detects support via process.env.NO_COLOR, process.env.FORCE_COLOR, and process.stdout.isTTY.
Text Colors
black('text'), red('text'), green('text'), yellow('text')
blue('text'), magenta('text'), cyan('text'), white('text')
gray('text'), grey('text')
Bright Text Colors
lightRed('text'), lightGreen('text'), lightYellow('text'), lightBlue('text')
lightMagenta('text'), lightCyan('text'), lightGray('text'), lightGrey('text')
Background Colors
bgBlack('text'), bgRed('text'), bgGreen('text'), bgYellow('text')
bgBlue('text'), bgMagenta('text'), bgCyan('text'), bgWhite('text')
Bright Background Colors
bgLightRed('text'), bgLightGreen('text'), bgLightYellow('text'), bgLightBlue('text')
bgLightMagenta('text'), bgLightCyan('text')
Text Formatting
bold('text'), dim('text'), italic('text'), underline('text')
inverse('text'), hidden('text'), strikethrough('text'), reset('text')
Utilities
stripColors(coloredText)
supportsColor()
All color functions accept string | number and return string. When colors are unsupported, they return String(text) unchanged.
Byte Formatting (bytes.ts)
interface BytesOptions {
precision?: number
binary?: boolean
space?: boolean
locale?: string
minimumFractionDigits?: number
maximumFractionDigits?: number
}
formatBytes(1024)
formatBytes(1024, { binary: true })
formatBytes(1234567, { precision: 2 })
formatBytes(-1024)
formatBytes(0.5)
parseBytes('1 KB')
parseBytes('1 KiB')
parseBytes('1.5 MB')
(, )
(, )
()
()
Units: B, KB, MB, GB, TB, PB, EB, ZB, YB (decimal) or B, KiB, MiB, GiB, TiB, PiB, EiB, ZiB, YiB (binary).
Export Size Calculation (size.ts)
interface ExportSize {
name: string
size: number
readable: string
}
interface PackageExportsSizeResult {
totalSize: number
totalReadable: string
exports: ExportSize[]
}
await getFileSize('/path/to/file')
const result = await getExportsSize('./dist')
estimateGzipSize(size)
estimateBrotliSize(size)
formatExportSizes(result)
getExportsSize scans a directory for .js, .mjs, .cjs files and calculates their sizes.
Pipeline (pipeline.ts)
Laravel-inspired pipeline for chaining data transformations.
type PipeFunction<T> = (data: T, next: (data: T) => T) => T
interface PipeClass<T> { handle: PipeFunction<T> }
const result = Pipeline.send(data)
.through([transform1, transform2, transform3])
.via('process')
.then((data) => data)
const result2 = Pipeline.send(data)
.pipe(fn1)
.pipe(fn2)
.thenReturn()
Pipeline.send('hello')
.through([
(data, next) => next(data.toUpperCase()),
(data, next) => next(data + '!'),
])
.then(data => data)
Internally uses reduceRight so pipes execute in order (first pipe wraps outermost).
Markdown Tables (markdown.ts)
interface MarkdownTableOptions {
align?: Array<'l' | 'c' | 'r' | 'left' | 'center' | 'right' | null>
padding?: boolean
delimiter?: string
delimiterStart?: boolean
delimiterEnd?: boolean
alignDelimiter?: string
}
markdownTable([
['Name', 'Age', 'City'],
['Alice', '30', 'NYC'],
['Bob', '25', 'LA']
])
markdownTable([['Left', 'Center', 'Right'], ['a', 'b', 'c']], { align: ['l', 'c', 'r'] })
([
{ : , : },
{ : , : }
])
Cell values can be string | number | boolean | null | undefined -- all are converted to strings.
YAML (helpers.ts)
Uses Bun.YAML natively:
parseYaml(yamlString)
loadYaml(content)
dumpYaml(object)
Macroable (macroable.ts)
Laravel-inspired dynamic method registration on classes.
import { Macroable, createMacroable, macroable } from '@stacksjs/utils'
class MyClass extends Macroable {}
MyClass.macro('greet', function() { return 'Hello!' })
const instance = new MyClass()
;(instance as any).greet()
MyClass.hasMacro('greet')
MyClass.getMacros()
MyClass.flushMacros()
MyClass.mixin({ hello() { return 'hi' }, bye() { return 'bye' } })
MyClass.macro('greet', newFn, true)
= ()
= ()
.(, fn)
Static methods: macro(name, fn, replace?), mixin(obj, replace?), hasMacro(name), flushMacros(), getMacros().
ResizeObserver (observer.ts)
isResizeObserverSupported()
const RO = getResizeObserver()
const cleanup = createResizeObserver(callback, element)
const cleanup2 = createResizeObserver(callback, [el1, el2])
cleanup()
const stop = observeElementSize(element, (entry) => {
console.log(entry.contentRect.width, entry.contentRect.height)
})
stop()
The polyfill uses requestAnimationFrame (or setTimeout fallback) to poll element sizes via getBoundingClientRect().
Deep Equality (equal.ts)
isDeepEqual(obj1, obj2)
isDeepEqual([1, 2], [1, 2])
isDeepEqual({ a: 1 }, { a: 1 })
isDeepEqual(NaN, NaN)
Compares arrays element-by-element, objects key-by-key, primitives with Object.is.
Version Comparison (versions.ts)
Uses Bun.semver natively:
const semver: typeof Bun.semver = Bun.semver
isVersionGreaterThan('2.0.0', '1.0.0')
isVersionLessThan('1.0.0', '2.0.0')
isVersionEqual('1.0.0', '1.0.0')
isVersionGreaterThanOrEqual('1.0.0', '1.0.0')
isVersionLessThanOrEqual('1.0.0', '2.0.0')
import { version } from '@stacksjs/utils'
Detection Utilities (detect.ts)
Separate from the strings package detection -- this is the utils-specific implementation:
interface IndentInfo {
amount: number
type: 'space' | 'tab' | null
indent: string
}
detectIndent(text: string): IndentInfo
detectNewline(text: string): '\r\n' | '\n' | '\r' | null
detectNewlineGraceful(text, fallback?): '\r\n' | '\n' | '\r'
normalizeNewlines(text, '\n'): string
countNewlines(text): { crlf, lf, cr, total }
Project Utilities (helpers.ts)
await packageManager()
await frameworkVersion()
await isAppKeySet()
await initProject()
await ensureProjectIsInitialized()
await installIfVersionMismatch()
await setEnvValue('KEY', 'value')
await runNpmScript('build', options?)
hasScript(manifest, 'test')
determineDebugLevel(options?)
determineResetPreset(preset?)
isManifest(obj)
isOptionalString(value)
isIpv6(address)
Find Stacks Projects (find.ts)
await findStacksProjects(dir?, options?)
Git Utilities (git.ts)
isGitClean(): boolean
Clean Project (clean.ts)
await cleanProject()
Config Builders (re-exported from @stacksjs/config)
defineApp()
defineCache()
defineCdn()
defineChat()
defineCli()
defineDatabase()
defineDependencies()
defineDns()
defineEmail()
defineEmailConfig()
defineGit()
defineHashing()
defineLibrary()
defineNotification()
definePayment()
defineQueue()
defineSearchEngine()
defineServices()
defineSms()
defineFilesystems()
defineUi()
Hash Utilities (hash.ts)
Internal cloud deployment helpers:
originRequestFunctionHash()
websiteSourceHash()
docsSourceHash()
Glob (re-export)
export { glob } from '@stacksjs/storage'
Gotchas
merge() concatenates arrays instead of replacing them -- this differs from spread/Object.assign behavior
mergeDefaults()/defu() gives precedence to the FIRST argument (the object), not the defaults
- Color functions only work in terminal environments -- check
supportsColor() first
debounce does NOT support maxWait -- use throttle if you need guaranteed maximum delay
throttle.cancel() exists but there is no throttle.flush()
- Pipeline uses
reduceRight internally so pipes execute in array order, not reverse
- YAML operations use
Bun.YAML -- dumpYaml falls back to JSON.stringify if Bun.YAML.stringify is unavailable
detectIndent in utils (detect.ts) is a different, simpler implementation than the one in strings (detect-indent.ts)
isDeepEqual uses getTypeName from @stacksjs/types for type checking
- Version comparison uses
Bun.semver.order() which returns -1, 0, or 1
cleanProject() uses Bun.$ shell -- destructive operation that removes lock files, node_modules, and dist