Sign In

@bfra.me/es

Package Overview
Dependencies
Maintainers
2
Versions
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@bfra.me/es

High-quality reusable types and utilities for ES development

latest
Source
npmnpm
Version
0.1.0
Version published
Weekly downloads
1.8K
20.11%
Maintainers
2
Weekly downloads
 
Created
Source

@bfra.me/es

npm version License: MIT

High-quality reusable types and utilities for ES development (JavaScript and TypeScript). Zero runtime dependencies for core utilities, tree-shakeable via subpath exports.

Features

  • 🎯 Result Type — Discriminated union for type-safe error handling without exceptions
  • 🔧 Functional Utilitiespipe, compose, curry, memoize with full TypeScript inference
  • 📦 Module Interop — ES/CommonJS interoperability helpers
  • ⏱️ Async Utilitiesretry, timeout, debounce, throttle, concurrency control
  • 🏷️ Branded Types — Compile-time type safety with zero runtime cost
  • Validation — Path validation, input sanitization, common validators
  • 🔍 Environment Detection — CI, editor, git lifecycle detection
  • 📁 File Watcher — Debounced file watching with change detection (optional chokidar peer)
  • ⚠️ Error Utilities — Structured errors with codes, context, and cause chain

Installation

# pnpm (recommended)
pnpm add @bfra.me/es

# npm
npm install @bfra.me/es

# yarn
yarn add @bfra.me/es

Optional Peer Dependencies

# For file watcher functionality
pnpm add chokidar

Quick Start

import {err, isOk, ok, pipe, retry} from '@bfra.me/es'

// Type-safe error handling with Result
function divide(a: number, b: number) {
  return b === 0 ? err(new Error('Division by zero')) : ok(a / b)
}

const result = divide(10, 2)
if (isOk(result)) {
  console.log(result.data) // 5
}

// Functional composition
const transform = pipe(
  (x: number) => x + 1,
  (x: number) => x * 2
)
transform(5) // 12

// Async with retry
const data = await retry(() => fetch('/api/data'), {maxAttempts: 3})

Subpath Exports

All utilities are organized into tree-shakeable subpath exports. Import only what you need:

ExportDescription
@bfra.me/es/resultResult type for error handling
@bfra.me/es/functionalFunctional programming utilities
@bfra.me/es/asyncAsync utilities (retry, timeout, debounce)
@bfra.me/es/moduleES module interoperability
@bfra.me/es/typesBranded types and type guards
@bfra.me/es/validationPath validation and sanitization
@bfra.me/es/errorStructured error utilities
@bfra.me/es/envEnvironment detection
@bfra.me/es/watcherFile watcher abstraction

API Reference

Result Type (@bfra.me/es/result)

A discriminated union type for error handling without exceptions. Inspired by Rust's Result type.

import type {Err, Ok, Result} from '@bfra.me/es/result'
import {err, flatMap, isErr, isOk, map, ok, unwrap, unwrapOr} from '@bfra.me/es/result'

Creating Results

// Create success result
const success = ok(42)              // Ok<number>

// Create error result
const failure = err(new Error('failed'))  // Err<Error>

Type Guards for Results

const result = divide(10, 2)

if (isOk(result)) {
  // TypeScript knows result.data is available
  console.log(result.data)
}

if (isErr(result)) {
  // TypeScript knows result.error is available
  console.error(result.error)
}

Transforming Results

// map: Transform success values
const doubled = map(ok(5), x => x * 2)  // Ok(10)
const mapped = map(err('fail'), x => x * 2)  // Err('fail') - unchanged

// flatMap: Chain operations that return Results
const parsed = flatMap(ok('42'), str => {
  const num = Number.parseInt(str, 10)
  return Number.isNaN(num) ? err('Invalid number') : ok(num)
})

// mapErr: Transform error values
const withContext = mapErr(err('not found'), e => new Error(`Resource ${e}`))

Extracting Values

// unwrap: Get value or throw (use sparingly)
const value = unwrap(ok(42))  // 42
const willThrow = unwrap(err('fail'))  // throws Error

// unwrapOr: Get value or default (preferred)
const withDefault = unwrapOr(err('fail'), 0)  // 0

Wrapping Throwing Code

import {fromPromise, fromThrowable} from '@bfra.me/es/result'

// Wrap synchronous throwing functions
const parsed = fromThrowable(() => JSON.parse(input))

// Wrap promises
const fetched = await fromPromise(fetch('/api/data'))

Functional Utilities (@bfra.me/es/functional)

import {compose, constant, curry, flip, identity, memoize, noop, partial, pipe, tap} from '@bfra.me/es/functional'

pipe

Composes functions left-to-right. Each function receives the result of the previous.

const addOne = (x: number) => x + 1
const double = (x: number) => x * 2
const toString = (x: number) => `Value: ${x}`

const transform = pipe(addOne, double, toString)
transform(5)  // "Value: 12" (5 + 1 = 6, 6 * 2 = 12)

compose

Composes functions right-to-left (mathematical composition order).

const addOneThenDouble = compose(double, addOne)
addOneThenDouble(5)  // 12 (addOne first, then double)

curry

Transforms a function to accept arguments one at a time.

const add = (a: number, b: number, c: number) => a + b + c
const curriedAdd = curry(add)

curriedAdd(1)(2)(3)     // 6
curriedAdd(1, 2)(3)     // 6
curriedAdd(1)(2, 3)     // 6
curriedAdd(1, 2, 3)     // 6

memoize

Caches function results based on arguments. Supports multiple cache strategies.

import {memoize} from '@bfra.me/es/functional'

// Simple memoization
const expensive = memoize((n: number) => {
  // Complex calculation...
  return n * 2
})

// With LRU cache (evicts least recently used)
const withLRU = memoize(fn, {
  strategy: 'lru',
  maxSize: 100
})

// With TTL (time-to-live expiration)
const withTTL = memoize(fn, {
  strategy: 'ttl',
  ttl: 60000  // 1 minute
})

// Cache statistics
console.log(expensive.getStats())  // { hits: 10, misses: 5, size: 5 }
expensive.clear()  // Clear all cached values

Other Utilities

// identity: Returns its argument unchanged
const x = identity(42)  // 42

// tap: Execute side effect, return original value
const logged = pipe(
  addOne,
  tap(x => console.log('After addOne:', x)),
  double
)

// partial: Pre-fill some arguments
const greet = (greeting: string, name: string) => `${greeting}, ${name}!`
const sayHello = partial(greet, 'Hello')
sayHello('World')  // "Hello, World!"

// flip: Swap first two arguments
const divide = (a: number, b: number) => a / b
const flipped = flip(divide)
flipped(2, 10)  // 5 (same as divide(10, 2))

// constant: Create function that always returns same value
const alwaysTrue = constant(true)
alwaysTrue()  // true

// noop: Do nothing function
element.addEventListener('click', noop)

Async Utilities (@bfra.me/es/async)

import {debounce, pAll, pLimit, retry, sleep, throttle, timeout} from '@bfra.me/es/async'

retry

Retries a function with exponential backoff.

const result = await retry(
  () => fetch('/api/data'),
  {
    maxAttempts: 3,      // Default: 3
    initialDelay: 100,   // Default: 100ms
    maxDelay: 10000,     // Default: 10000ms
    backoffFactor: 2,    // Default: 2
    shouldRetry: (error, attempt) => {
      // Custom retry logic
      return error.message !== 'Not Found'
    }
  }
)

if (isOk(result)) {
  console.log(result.data)
} else {
  console.error('All retries failed:', result.error)
}

timeout

Wraps a promise with a timeout.

const result = await timeout(fetch('/api/slow'), 5000)

if (isErr(result) && result.error instanceof TimeoutError) {
  console.log('Request timed out')
}

debounce

Creates a debounced function that delays invocation.

const saveInput = debounce((value: string) => {
  localStorage.setItem('draft', value)
}, 300)

input.addEventListener('input', e => saveInput(e.target.value))

// Cancel pending invocation
saveInput.cancel()

throttle

Limits function invocation frequency.

const handleScroll = throttle(() => {
  updateScrollPosition()
}, 100)

window.addEventListener('scroll', handleScroll)

Concurrency Control

// pLimit: Create a concurrency limiter
const limit = pLimit(5)  // Max 5 concurrent operations

const results = await Promise.all(
  urls.map(url => limit(() => fetch(url)))
)

// pAll: Run promises with concurrency control
const allResults = await pAll(
  urls.map(url => () => fetch(url)),
  {concurrency: 5}
)

Module Interop (@bfra.me/es/module)

import {dynamicImport, interopDefault, isESModule, isPackageInScope, resolveModule} from '@bfra.me/es/module'

interopDefault

Unwraps default exports from both ES and CommonJS modules.

// Handles both ESM and CJS exports
const lodash = await interopDefault(import('lodash'))

// Works with dynamic imports
const config = await interopDefault(import('./config.js'))

resolveModule

Safely resolves and imports a module with Result return type.

const result = await resolveModule<typeof import('lodash')>('lodash')

if (isOk(result)) {
  const _ = result.data
}

isPackageInScope

Checks if a package is available from a specific directory context.

if (isPackageInScope('typescript', {scopeUrl: import.meta.url})) {
  // TypeScript is available
}

Type Utilities (@bfra.me/es/types)

import type {Brand, NonEmptyString, Opaque, PositiveInteger} from '@bfra.me/es/types'
import {assertType, brand, hasProperty, isArray, isNonNullable, isNumber, isObject, isString, unbrand} from '@bfra.me/es/types'

Branded Types

Create nominal types in TypeScript's structural type system.

type UserId = Brand<string, 'UserId'>
type OrderId = Brand<string, 'OrderId'>

function getUser(id: UserId): User { /* ... */ }
function getOrder(id: OrderId): Order { /* ... */ }

const userId = brand<string, 'UserId'>('user-123')
const orderId = brand<string, 'OrderId'>('order-456')

getUser(userId)   // ✅ OK
getUser(orderId)  // ❌ Type error - can't use OrderId as UserId

Type Guards

function processValue(value: unknown) {
  if (isString(value)) {
    // value is string
    return value.toUpperCase()
  }

  if (isNumber(value)) {
    // value is number (excludes NaN)
    return value * 2
  }

  if (isObject(value)) {
    // value is Record<string, unknown> (excludes null and arrays)
    return Object.keys(value)
  }

  if (isArray(value)) {
    // value is unknown[]
    return value.length
  }
}

// Check for property existence
if (hasProperty(obj, 'name')) {
  // obj is Record<'name', unknown>
  console.log(obj.name)
}

// Filter nullish values
const values = [1, null, 2, undefined, 3].filter(isNonNullable)  // [1, 2, 3]

Type Assertions

// Assert and narrow type
assertType(value, isString)  // throws if not string
// value is now string

// Use with custom guards
const isUser = (v: unknown): v is User =>
  isObject(v) && hasProperty(v, 'name') && hasProperty(v, 'email')

assertType(data, isUser)
// data is now User

Validation (@bfra.me/es/validation)

import {createValidator, isWithinBoundary, sanitizeInput, validatePath} from '@bfra.me/es/validation'

Path Validation

Validates paths against traversal attacks.

const result = validatePath('../etc/passwd')
if (isErr(result)) {
  console.log(result.error.code)  // 'PATH_TRAVERSAL'
}

const valid = validatePath('src/index.ts')
if (isOk(valid)) {
  // valid.data is ValidPath branded type
}

// Check if path is within boundary
if (isWithinBoundary('/app/uploads/file.txt', '/app/uploads')) {
  // Safe to access
}

Input Sanitization

const userInput = '<script>alert("xss")</script>'
const safe = sanitizeInput(userInput)
// "&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;"

// With options
const cleaned = sanitizeInput(input, {
  escapeHtml: true,      // Default: true
  removeNullBytes: true, // Default: true
  trim: true             // Default: true
})

Error Utilities (@bfra.me/es/error)

import {BaseError, createError, formatError, NotFoundError, PermissionError, TimeoutError, ValidationError, withErrorContext} from '@bfra.me/es/error'

Structured Errors

// Create custom errors with code and context
const error = createError('User not found', {
  code: 'USER_NOT_FOUND',
  cause: originalError,
  context: {userId: 123}
})

// Use specialized error types
throw new ValidationError('Invalid email format', {
  field: 'email',
  value: 'not-an-email'
})

throw new NotFoundError('Resource not found', {
  resourceType: 'User',
  resourceId: '123'
})

Error Formatting

try {
  riskyOperation()
} catch (error) {
  // Format any error to string with cause chain
  console.log(formatError(error))
}

Environment Detection (@bfra.me/es/env)

import {getEnvironment, isBrowser, isDeno, isInCI, isInEditorEnv, isInGitLifecycle, isNode} from '@bfra.me/es/env'
if (isInCI()) {
  // Running in CI (GitHub Actions, Jenkins, etc.)
}

if (isInEditorEnv()) {
  // Running in VS Code, JetBrains, Vim, or Neovim
}

if (isInGitLifecycle()) {
  // Running during git hook (commit, push, etc.)
}

// Runtime detection
if (isNode()) { /* Node.js */ }
if (isBrowser()) { /* Browser */ }
if (isDeno()) { /* Deno */ }

// Get comprehensive environment info
const env = getEnvironment()
// { runtime: 'node', isCI: false, isEditor: true, ... }

File Watcher (@bfra.me/es/watcher)

Note: Requires chokidar as a peer dependency.

import {createChangeDetector, createDebouncer, createFileHasher, createFileWatcher} from '@bfra.me/es/watcher'

Basic File Watching

const watcher = createFileWatcher(['src/**/*.ts', 'test/**/*.ts'], {
  debounceMs: 100,
  ignored: ['**/node_modules/**'],
  usePolling: false
})

watcher.on('change', event => {
  console.log('Changes detected:', event.changes)
  // event.changes: Array<{path: string, type: 'add' | 'change' | 'unlink', timestamp: number}>
})

await watcher.start()

// Later: clean up
await watcher.close()

Change Detection with Hashing

const hasher = createFileHasher('sha256')
const detector = createChangeDetector()

// Hash file content
const hash = await hasher.hashFile('/path/to/file.ts')

// Detect changes
const {changed, added, removed} = await detector.detectChanges({
  'src/index.ts': 'abc123...',  // Previous hashes
})

TypeScript Configuration

This package requires TypeScript 5.0+ and works best with strict mode:

{
  "compilerOptions": {
    "strict": true,
    "exactOptionalPropertyTypes": true,
    "moduleResolution": "bundler"
  }
}

Bundle Size

  • Core utilities (excluding watcher): < 5KB minified
  • Full package with watcher: < 10KB minified
  • Tree-shaking supported via subpath exports

Requirements

  • Node.js 20+
  • TypeScript 5.0+ (for development)
  • ES2022+ compatible runtime

License

MIT © Marcus R. Brown

Keywords

bfra.me

FAQs

Package last updated on 02 Dec 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts