npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

typetify

v4.4.1

Published

Runtime TypeScript helpers - like Lodash, but TS-first

Readme

Typetify

npm version license TypeScript npm downloads bundle size

Runtime TypeScript helpers — like Lodash, but TS-first

TypeScript is powerful, but it doesn't protect you at runtime. Typetify fills this gap with type-safe utilities that work when it matters most.

Documentation


Features

Runtime Safety

Guards and assertions that protect you when TypeScript can't — at runtime, where it matters.

Perfect Types

IntelliSense that actually helps. Every function is designed for maximum type inference.

Zero Dependencies

Lightweight and tree-shakable. Only bundle what you use.

No Magic

Boring, predictable API. No config, no setup, just functions that work.

Why Typetify?

| Feature | Lodash | Ramda | Typetify | |---------|--------|-------|----------| | TypeScript-first | ❌ | ❌ | ✅ | | Runtime safety | ❌ | ❌ | ✅ | | Type narrowing | ❌ | ❌ | ✅ | | Zero dependencies | ❌ | ✅ | ✅ | | Tree-shakable | ⚠️ | ✅ | ✅ | | Modern syntax | ❌ | ⚠️ | ✅ | | Frontend utilities | ❌ | ❌ | ✅ | | Backend utilities | ❌ | ❌ | ✅ |

Installation

# npm
npm install typetify

# pnpm (faster)
pnpm add typetify

# yarn
yarn add typetify

# bun (fastest)
bun add typetify

Requirements:

  • Node.js >= 18
  • TypeScript >= 5.0 (recommended)

Quick Start

import { isDefined, pick, awaitTo, safeJsonParse } from 'typetify'

// Filter null/undefined with proper types
const items = [1, null, 2, undefined, 3]
const defined = items.filter(isDefined) // number[]

// Pick object keys (type-safe)
const user = { id: 1, name: 'John', password: 'secret' }
const safe = pick(user, ['id', 'name']) // { id: number, name: string }

// Handle async errors without try/catch
const [error, data] = await awaitTo(fetchUser(id))
if (error) {
  console.error('Failed:', error)
  return
}
console.log(data.name)

// Parse JSON safely
const result = safeJsonParse<User>(jsonString)
if (result.ok) {
  console.log(result.data.name)
} else {
  console.error(result.error)
}

Migrating from Lodash

Typetify is designed to be a drop-in replacement for common Lodash patterns:

| Lodash | Typetify | Benefit | |--------|----------|--------| | _.isNil(x) | isNil(x) | Same API, better types | | _.pick(obj, keys) | pick(obj, keys) | Type-safe keys | | _.omit(obj, keys) | omit(obj, keys) | Type-safe keys | | _.uniq(arr) | unique(arr) | Same behavior | | _.groupBy(arr, fn) | groupBy(arr, fn) | Better inference | | _.chunk(arr, n) | chunk(arr, n) | Same API | | _.debounce(fn, ms) | debounce(fn, ms) | Simpler API |

Bonus: Use the _ namespace to avoid migration conflicts:

import { _ } from 'typetify'

// Works exactly like Lodash
const result = _.pick(user, ['id', 'name'])

Using Underscore _ (Lodash-style)

If you have naming conflicts with existing functions, use the underscore _ namespace:

// Import with underscore _ (just like Lodash!)
import { _ } from 'typetify'

// Use _.methodName() to avoid conflicts
const defined = items.filter(_.isDefined)
const safe = _.pick(user, ['id', 'name'])
const [error, data] = await _.awaitTo(fetchUser(id))

// Also works with default import
import _ from 'typetify'

// Or import * as
import * as _ from 'typetify'

Modules

Core — Foundations

import { isDefined, isNil, assert, assertDefined, fail, noop, identity, unreachable } from 'typetify/core'

// isDefined — Filter null/undefined
const items = [1, null, 2].filter(isDefined) // number[]

// assert — Fail fast with type narrowing
const user: User | null = getUser()
assert(user, 'User not found')
// user is now User

// unreachable — Exhaustive switch statements
switch (status) {
  case 'pending': return handlePending()
  case 'done': return handleDone()
  default: unreachable(status)
}

Guards — Type Guards

import { isObject, isString, isNumber, hasKey, hasKeys, isEmpty } from 'typetify/guards'

// isObject — Check for objects (not arrays, not null)
if (isObject(value)) {
  // value is Record<string, unknown>
}

// hasKey — Safe property access
if (hasKey(response, 'data')) {
  console.log(response.data)
}

// isEmpty — Check for empty values
isEmpty('') // true
isEmpty([]) // true
isEmpty({}) // true
isEmpty(null) // true

Object — Object Manipulation

import { pick, omit, keysTyped, mapObject, get, set } from 'typetify/object'

// pick/omit — Type-safe object manipulation
const user = { id: 1, name: 'John', password: 'secret' }
pick(user, ['id', 'name']) // { id: 1, name: 'John' }
omit(user, ['password']) // { id: 1, name: 'John' }

// keysTyped — Object.keys with proper types
const keys = keysTyped(user) // ('id' | 'name' | 'password')[]

// mapObject — Map over object values
const prices = { apple: 1, banana: 2 }
mapObject(prices, v => v * 2) // { apple: 2, banana: 4 }

// get/set — Safe nested access (immutable)
get(user, ['profile', 'name'])
set(user, ['profile', 'age'], 30)

Async — Async Utilities

import { awaitTo, retry, sleep, withTimeout, debounce, throttle, parallel, sequence } from 'typetify/async'

// awaitTo — No more try/catch
const [error, user] = await awaitTo(fetchUser(id))

// sequence — Execute async operations in sequence (cleaner than multiple awaitTo)
const result = await sequence([
  () => fetchUser(id),
  (user) => fetchProfile(user.id),
  (profile) => fetchOrders(profile.userId)
])

if (!result.ok) {
  console.error('Failed at step:', result.step, result.error)
  console.log('Partial results:', result.partial)
  return
}

const [user, profile, orders] = result.data

// retry — Retry with backoff
const data = await retry(() => fetchData(), {
  attempts: 3,
  delay: 1000,
  backoff: 2,
})

// withTimeout — Add timeout to any promise
const result = await withTimeout(fetchData(), 5000)

// parallel — Concurrent execution with limit
const results = await parallel(
  urls.map(url => () => fetch(url)),
  { concurrency: 3 }
)

// debounce/throttle
const debouncedSearch = debounce(search, 300)
const throttledScroll = throttle(onScroll, 100)

// allResults — Collect all Promise<Result> outcomes
const results = await allResults([fetchUser(1), fetchUser(2), fetchUser(3)])
if (results.ok) {
  const [user1, user2, user3] = results.value
}

// anyResult — First successful Result (fallback pattern)
const config = await anyResult([loadFromEnv(), loadFromFile(), loadDefaults()])

Collection — Array Utilities

import { unique, groupBy, partition, chunk, compact, sortBy, range } from 'typetify/collection'

// unique — Remove duplicates
unique([1, 2, 2, 3]) // [1, 2, 3]
unique(users, u => u.id) // Unique by key

// groupBy — Group by key
groupBy(users, u => u.role)
// { admin: [...], user: [...] }

// partition — Split by predicate
const [evens, odds] = partition([1, 2, 3, 4], n => n % 2 === 0)

// chunk — Split into chunks
chunk([1, 2, 3, 4, 5], 2) // [[1, 2], [3, 4], [5]]

// compact — Remove null/undefined
compact([1, null, 2, undefined]) // [1, 2]

// sortBy — Sort by key
sortBy(users, u => u.name)

// range — Generate number ranges
range(0, 5) // [0, 1, 2, 3, 4]

Input — Parse External Data

import { safeJsonParse, parseNumber, parseBoolean, parseDate, coerceArray, defaults } from 'typetify/input'

// safeJsonParse — No more try/catch for JSON
const result = safeJsonParse<User>(json)
if (result.ok) {
  console.log(result.data)
}

// parseNumber/parseBoolean/parseDate — Safe parsing
parseNumber('42') // 42
parseNumber('abc') // undefined
parseBoolean('yes') // true
parseDate('2024-01-15') // Date

// coerceArray — Ensure array
coerceArray('hello') // ['hello']
coerceArray(['a', 'b']) // ['a', 'b']
coerceArray(null) // []

// defaults — With empty string handling
defaults(null, 'fallback') // 'fallback'
defaults('', 'fallback') // 'fallback'

Flow — Functional Utilities

import { pipe, tap, when, match, tryCatch, ifElse } from 'typetify/flow'

// pipe — Chain transformations
const result = pipe(
  5,
  n => n * 2,
  n => n + 1,
  n => `Result: ${n}`
) // 'Result: 11'

// tap — Side effects in a chain
pipe(
  data,
  tap(console.log),
  transform,
)

// match — Pattern matching
const getDiscount = match<number, string>()
  .with(n => n >= 100, () => '20% off')
  .with(n => n >= 50, () => '10% off')
  .otherwise(() => 'No discount')

// tryCatch — Safe function execution
const result = tryCatch(() => JSON.parse(input))
if (result.ok) {
  console.log(result.value)
}

DX — Developer Experience

import { debug, invariant, assertNever, todo, measure } from 'typetify/dx'

// debug — Log in a pipe chain
pipe(data, debug('step 1'), transform, debug('step 2'))

// invariant — Assert with descriptive errors
invariant(user.id > 0, 'User ID must be positive')
// Throws: Invariant violation: User ID must be positive

// assertNever — Exhaustive checks
function handle(action: Action) {
  switch (action.type) {
    case 'add': return handleAdd()
    case 'remove': return handleRemove()
    default: assertNever(action)
  }
}

// todo — Mark unimplemented code
function processPayment() {
  todo('Implement payment processing')
}

// measure — Performance measurement
const { result, duration } = measure(() => heavyComputation())
console.log(`Took ${duration}ms`)

🎨 Typed — Type Utilities

import { defineConst, defineEnum, brand, type DeepPartial, type Merge } from 'typetify/typed'

// defineConst — Frozen constants with literal types
const STATUS = defineConst({
  PENDING: 'pending',
  ACTIVE: 'active',
})
// typeof STATUS.PENDING = 'pending' (not string)

// defineEnum — Enum-like objects
const Role = defineEnum(['admin', 'user', 'guest'] as const)
// Role.admin = 'admin'

// brand — Branded types for type safety
type UserId = Brand<number, 'UserId'>
type PostId = Brand<number, 'PostId'>

function getUser(id: UserId) { ... }
getUser(1 as UserId) // OK
getUser(1 as PostId) // Error!

// Type utilities
type PartialUser = DeepPartial<User>
type MergedConfig = Merge<DefaultConfig, UserConfig>

Real-World Examples

🎨 Frontend Focus

import { 
  // DOM
  querySelector, classNames, addEventListener, isInViewport,
  // Storage
  localStorageTyped, withExpiry, getCookie,
  // Color
  lighten, darken, getContrastColor, opacity
} from 'typetify'

// Type-safe DOM manipulation
const button = querySelector<HTMLButtonElement>('#submit')
if (button) {
  button.className = classNames('btn', { 'btn-primary': isPrimary, 'btn-disabled': disabled })
}

// Event handling with automatic cleanup
const cleanup = addEventListener(button, 'click', () => {
  console.log('Clicked!')
})
// Later: cleanup()

// Lazy loading with viewport detection
const images = querySelectorAll<HTMLImageElement>('img[data-src]')
images.forEach(img => {
  if (isInViewport(img)) {
    img.src = img.dataset.src!
  }
})

// Type-safe localStorage
interface UserPrefs {
  theme: 'light' | 'dark'
  language: string
}
const prefs = localStorageTyped<UserPrefs>('user-prefs')
prefs.set({ theme: 'dark', language: 'en' })
const theme = prefs.get()?.theme // 'dark' | 'light' | undefined

// Storage with expiration
const cache = withExpiry<ApiResponse>('api-cache', {
  storage: sessionStorage,
  ttl: 5 * 60 * 1000 // 5 minutes
})

// Dynamic theming with color utilities
const primaryColor = '#3b82f6'
const hoverColor = lighten(primaryColor, 10)
const activeColor = darken(primaryColor, 10)
const textColor = getContrastColor(primaryColor) // '#ffffff' or '#000000'
const overlay = opacity('#000000', 0.5) // 'rgba(0, 0, 0, 0.5)'

🔧 Backend Focus

import {
  // HTTP
  createHttpClient, requestWithRetry, bearerAuth, buildUrl,
  // DateTime
  formatDate, addTime, timeAgo, isBetween, parseDuration,
  // Path
  joinPath, parsePath, normalizePath, relativePath,
  // Crypto
  sha256, hmac, uuid, generateToken, base64Encode
} from 'typetify'

// Type-safe HTTP client with interceptors
const api = createHttpClient({
  baseUrl: 'https://api.example.com',
  timeout: 5000,
  headers: { Authorization: bearerAuth(token) },
  interceptors: {
    request: (opts) => {
      opts.headers = { ...opts.headers, 'X-Request-Id': uuid() }
      return opts
    }
  }
})

const users = await api.get<User[]>('/users')
const user = await api.post<User>('/users', { body: { name: 'John' } })

// Retry with exponential backoff
const data = await requestWithRetry<Data>('/api/data', {
  maxRetries: 3,
  delay: 1000,
  backoff: 'exponential'
})

// Date manipulation
const now = new Date()
const nextWeek = addTime(now, 7, 'days')
const formatted = formatDate(now, 'YYYY-MM-DD HH:mm')
console.log(timeAgo(lastLogin)) // '2 hours ago'

// Check booking availability
if (isBetween(requestedDate, bookingStart, bookingEnd)) {
  allowBooking()
}

// Parse duration strings
const ttl = parseDuration('2h 30m') // 9000000 (ms)

// Cross-platform path handling
const configPath = joinPath('/etc', 'app', 'config.json')
const { name, ext } = parsePath('/var/log/app.log') // { name: 'app', ext: '.log' }
const normalized = normalizePath('/home/user/../admin/./file.txt') // '/home/admin/file.txt'

// Cryptographic utilities
const hashedPassword = await sha256(password + salt)
const signature = await hmac(payload, webhookSecret)
const sessionId = uuid()
const apiKey = generateToken(32)
const encoded = base64Encode(JSON.stringify(data))

API Response Validation

import { hasKeys, isDefined, awaitTo } from 'typetify'

async function fetchUser(id: string) {
  const [error, response] = await awaitTo(fetch(`/api/users/${id}`))
  if (error) return { error: 'Network error' }

  const data = await response.json()
  
  // Runtime validation
  if (!hasKeys(data, ['id', 'name', 'email'])) {
    return { error: 'Invalid response format' }
  }

  return { data }
}

Form Data Processing

import { parseNumber, parseBoolean, compact, defaults } from 'typetify'

function processFormData(formData: FormData) {
  return {
    age: parseNumber(formData.get('age')),
    newsletter: parseBoolean(formData.get('newsletter')),
    tags: compact(formData.getAll('tags')),
    bio: defaults(formData.get('bio'), 'No bio provided'),
  }
}

Safe Error Handling

import { awaitTo, retry, withTimeout } from 'typetify'

async function fetchWithRetry(url: string) {
  const [error, data] = await awaitTo(
    retry(
      () => withTimeout(fetch(url), 5000),
      { attempts: 3, delay: 1000 }
    )
  )

  if (error) {
    console.error('Failed after retries:', error)
    return null
  }

  return data
}

Tree Shaking

Import only what you need:

// Import specific functions
import { isDefined } from 'typetify/core'
import { pick } from 'typetify/object'

// Or import everything
import { isDefined, pick, awaitTo } from 'typetify'

Philosophy

  1. Runtime first — Types are great, but runtime safety matters more
  2. No magic — Every function does exactly what it says
  3. Composable — Small functions that work together
  4. TypeScript-native — Built for TS, not ported from JS

Contributing

Contributions are welcome! Please read our contributing guidelines first.

License

MIT © typetify