@bfra.me/es (英语)
用于ES开发的高质量可再使用类型和公用事业(JavaScript和TypeScript). 核心公用事业的零运行时间依赖性,通过亚路径出口可动摇树木.
特征
- 🎯 结果类型 - 无一例外地对类型安全的错误处理实行歧视性结合
- 🔧 公用事业 —— 说
pipe, (中文(简体) ),compose, (中文(简体) ),curry, (中文(简体) ),memoize带有完整类型脚本推论 - 📦 模块接口 ——ES/CommonJS互操作性助手
- ⏱️ 同步公用事业 —— 说
retry, (中文(简体) ),timeout, (中文(简体) ),debounce, (中文(简体) ),throttle,货币控制 - 🏷️ 品牌类型 ——编译时间类型安全,运行时间成本为零
- ✅ 验证 ——路径验证,输入消毒,常见验证器
- 🔍 环境检测 ——CI,编辑,git生命周期检测
- 📁 文件监视器 – 用变化检测器( 可选的 chokidar 端口) 忽略文件监视
- ⚠️ 错误公用事业 - 有代码、上下文和起因链的结构错误
安装
# pnpm (recommended)
pnpm add @bfra.me/es
# npm
npm install @bfra.me/es
# yarn
yarn add @bfra.me/es
可选同伴依赖关系
# For file watcher functionality
pnpm add chokidar
快速启动
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})
子路径导出
所有公用设施都组成了可树动的亚道出口。 只导入您需要的 :
| 导出 | 说明 |
|---|---|
@bfra.me/es/result | 处理错误的结果类型 |
@bfra.me/es/functional | 职能方案拟订公用事业 |
@bfra.me/es/async | Async 公用设施(重复、超时、停用) |
@bfra.me/es/module | ES模块互通性 |
@bfra.me/es/types | 品牌类型和类型警卫 |
@bfra.me/es/validation | 路径验证和消毒 |
@bfra.me/es/error | 结构错误公用设施 |
@bfra.me/es/env | 环境检测 |
@bfra.me/es/watcher | 文件监视器抽象 |
API 参考
结果类型( E)@bfra.me/es/result(中文(简体) )
歧视工会类型,无一例外地处理错误。 受到Rust的启发 Result 类型.
import type {Err, Ok, Result} from '@bfra.me/es/result'
import {err, flatMap, isErr, isOk, map, ok, unwrap, unwrapOr} from '@bfra.me/es/result'
创造结果
// Create success result
const success = ok(42) // Ok<number>
// Create error result
const failure = err(new Error('failed')) // Err<Error>
结果类型守护
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)
}
转变结果
// 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}`))
提取值
// 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
环绕掷出代码
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'))
公用事业@bfra.me/es/functional(中文(简体) )
import {compose, constant, curry, flip, identity, memoize, noop, partial, pipe, tap} from '@bfra.me/es/functional'
烟斗
编程函数从左到右。 每个函数都收到前一个函数的结果.
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)
编曲
由右到左组成函数(数学组成顺序).
const addOneThenDouble = compose(double, addOne)
addOneThenDouble(5) // 12 (addOne first, then double)
咖喱
转换函数以接受一个参数.
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
记住
Caches 函数结果基于参数. 支持多个缓存策略.
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
其他公用事业
// 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)
公用事业@bfra.me/es/async(中文(简体) )
import {debounce, pAll, pLimit, retry, sleep, throttle, timeout} from '@bfra.me/es/async'
重试
以指数后退重置函数 .
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)
}
超时
{\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}我答应你暂停.
const result = await timeout(fetch('/api/slow'), 5000)
if (isErr(result) && result.error instanceof TimeoutError) {
console.log('Request timed out')
}
弹跳
创建延迟引用的已跳出函数.
const saveInput = debounce((value: string) => {
localStorage.setItem('draft', value)
}, 300)
input.addEventListener('input', e => saveInput(e.target.value))
// Cancel pending invocation
saveInput.cancel()
节流阀
限制函数引用频率 .
const handleScroll = throttle(() => {
updateScrollPosition()
}, 100)
window.addEventListener('scroll', handleScroll)
货币控制
// 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}
)
模块 Interop (@bfra.me/es/module(中文(简体) )
import {dynamicImport, interopDefault, isESModule, isPackageInScope, resolveModule} from '@bfra.me/es/module'
互换默认
解开ES和CommonJS模块的默认导出.
// Handles both ESM and CJS exports
const lodash = await interopDefault(import('lodash'))
// Works with dynamic imports
const config = await interopDefault(import('./config.js'))
解析器
安全地解决并导入带有结果返回类型的模块.
const result = await resolveModule<typeof import('lodash')>('lodash')
if (isOk(result)) {
const _ = result.data
}
isPackage InScope 组件
检查是否有来自特定目录上下文的软件包.
if (isPackageInScope('typescript', {scopeUrl: import.meta.url})) {
// TypeScript is available
}
类型公用事业@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'
品牌类型
在TypeScript的结构类型系统中创建标称类型.
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
类型警卫
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]
类型
// 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
审定@bfra.me/es/validation(中文(简体) )
import {createValidator, isWithinBoundary, sanitizeInput, validatePath} from '@bfra.me/es/validation'
路径验证
验证路径 防止逆行攻击.
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
}
输入保密
const userInput = '<script>alert("xss")</script>'
const safe = sanitizeInput(userInput)
// "<script>alert("xss")</script>"
// With options
const cleaned = sanitizeInput(input, {
escapeHtml: true, // Default: true
removeNullBytes: true, // Default: true
trim: true // Default: true
})
水电费( U)@bfra.me/es/error(中文(简体) )
import {BaseError, createError, formatError, NotFoundError, PermissionError, TimeoutError, ValidationError, withErrorContext} from '@bfra.me/es/error'
结构错误
// 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'
})
格式化出错
try {
riskyOperation()
} catch (error) {
// Format any error to string with cause chain
console.log(formatError(error))
}
环境探测@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, ... }
文件监视器( E)@bfra.me/es/watcher(中文(简体) )
说明: 要求数
chokidar作为同伴的依赖.
import {createChangeDetector, createDebouncer, createFileHasher, createFileWatcher} from '@bfra.me/es/watcher'
基本文件监视
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()
用 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
})
类型脚本配置
这个软件包需要 5.0+ 类型脚本,并且最能用严格的模式工作:
{
"compilerOptions": {
"strict": true,
"exactOptionalPropertyTypes": true,
"moduleResolution": "bundler"
}
}
套装大小
- 核心公用事业(不包括监视器):小于5KB
- 配备监视器的完整软件包: < 10KB 已修改
- 通过子路径导出支持树摇动
所需资源
- 节点.js 20+
- TypeScript 5.0+ (用于开发)
- ES2022+ 兼容运行时间
相关软件包
@bfra.me/eslint-config- 使用这些公用事业的ESLint配置@bfra.me/tsconfig- 类型脚本配置
许可证
麻省理工学院 马库斯·布朗