Tools: Why I use Typetify: A Type-Safe Alternative to Lodash

Tools: Why I use Typetify: A Type-Safe Alternative to Lodash

Source: Dev.to

Why I Built Typetify: A Type-Safe Alternative to Lodash ## The Problem with Lodash + TypeScript ## Example: The _.pick() Trap ## Example: The _.isString() Trap ## The Solution: TypeScript-First Design ## Type-Safe pick() ## Proper Type Narrowing ## 5 Real-World Use Cases ## 1. API Response Validation ## 2. Error Handling Without try/catch ## 3. Form Data Processing ## 4. Filtering Arrays ## 5. Safe JSON Parsing ## The Design Philosophy ## 1. Runtime First ## 2. No Magic ## 3. Tree-Shakable ## 4. Zero Dependencies ## Comparing to Alternatives ## The Complete Feature Set ## Getting Started ## What's Next? ## Try It Out ## Join the Discussion Runtime TypeScript helpers that actually protect you when it matters most. TL;DR: I built Typetify β€” a zero-dependency utility library for TypeScript that provides runtime safety, not just compile-time types. Think Lodash, but TypeScript-first. Lodash is great. I've used it for years. But when you add TypeScript to the mix, something feels... off. TypeScript can't catch this because Lodash's types are too permissive. The keys are typed as string[], not the actual keys of the object. Lodash's type guards don't narrow types properly. You get runtime safety, but TypeScript still doesn't trust the check. Typetify solves these issues by being designed for TypeScript, not retrofitted to it. The keys are actually type-checked. No typos, no silent failures. Type guards that actually work with TypeScript's control flow analysis. No more nested try/catch blocks. Clean, readable error handling. Parse external data safely with proper TypeScript types. Types are great, but they disappear at runtime. Typetify gives you both. Every function does exactly what it says. No hidden behavior, no gotchas. Simple composition. No magic. Only bundle what you use. Your bundler will only include what you import. No bloat. No supply chain risks. Just pure TypeScript. Typetify includes 18 modules covering: See full documentation I'd love to hear your feedback! Some areas I'm exploring: πŸ”— GitHub: github.com/CodeSenior/typetify πŸ“¦ npm: npmjs.com/package/typetify πŸ“š Docs: typetify.hosby.io Give it a try and let me know what you think! Stars ⭐ on GitHub are always appreciated. What utility functions do you wish existed in TypeScript? Drop a comment below! πŸ‘‡ P.S. We hit 477 downloads in the first 48 hours! πŸš€ Thank you to everyone who's trying it out. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse COMMAND_BLOCK: npm install typetify Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: npm install typetify COMMAND_BLOCK: npm install typetify CODE_BLOCK: import _ from 'lodash' const user = { id: 1, name: 'John', email: '[email protected]', password: 'secret123' } // TypeScript says this is fine 🀷 const safe = _.pick(user, ['id', 'name', 'emial']) // Typo! // ^^^^^^ // Runtime: { id: 1, name: 'John', emial: undefined } // No error, no warning, just silent failure Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: import _ from 'lodash' const user = { id: 1, name: 'John', email: '[email protected]', password: 'secret123' } // TypeScript says this is fine 🀷 const safe = _.pick(user, ['id', 'name', 'emial']) // Typo! // ^^^^^^ // Runtime: { id: 1, name: 'John', emial: undefined } // No error, no warning, just silent failure CODE_BLOCK: import _ from 'lodash' const user = { id: 1, name: 'John', email: '[email protected]', password: 'secret123' } // TypeScript says this is fine 🀷 const safe = _.pick(user, ['id', 'name', 'emial']) // Typo! // ^^^^^^ // Runtime: { id: 1, name: 'John', emial: undefined } // No error, no warning, just silent failure CODE_BLOCK: function processValue(value: unknown) { if (_.isString(value)) { return value.toUpperCase() // ^^^^^ TS Error: Object is of type 'unknown' } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: function processValue(value: unknown) { if (_.isString(value)) { return value.toUpperCase() // ^^^^^ TS Error: Object is of type 'unknown' } } CODE_BLOCK: function processValue(value: unknown) { if (_.isString(value)) { return value.toUpperCase() // ^^^^^ TS Error: Object is of type 'unknown' } } CODE_BLOCK: import { pick } from 'typetify/object' const user = { id: 1, name: 'John', email: '[email protected]', password: 'secret123' } const safe = pick(user, ['id', 'name']) // Type: { id: number; name: string } // This won't compile: const invalid = pick(user, ['id', 'emial']) // ^^^^^^^ TS Error: 'emial' is not a key of user Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: import { pick } from 'typetify/object' const user = { id: 1, name: 'John', email: '[email protected]', password: 'secret123' } const safe = pick(user, ['id', 'name']) // Type: { id: number; name: string } // This won't compile: const invalid = pick(user, ['id', 'emial']) // ^^^^^^^ TS Error: 'emial' is not a key of user CODE_BLOCK: import { pick } from 'typetify/object' const user = { id: 1, name: 'John', email: '[email protected]', password: 'secret123' } const safe = pick(user, ['id', 'name']) // Type: { id: number; name: string } // This won't compile: const invalid = pick(user, ['id', 'emial']) // ^^^^^^^ TS Error: 'emial' is not a key of user CODE_BLOCK: import { isString } from 'typetify/guards' function processValue(value: unknown) { if (isString(value)) { return value.toUpperCase() // βœ… TypeScript knows it's a string } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: import { isString } from 'typetify/guards' function processValue(value: unknown) { if (isString(value)) { return value.toUpperCase() // βœ… TypeScript knows it's a string } } CODE_BLOCK: import { isString } from 'typetify/guards' function processValue(value: unknown) { if (isString(value)) { return value.toUpperCase() // βœ… TypeScript knows it's a string } } CODE_BLOCK: import { hasKeys, 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 with type safety if (!hasKeys(data, ['id', 'name', 'email'])) { return { error: 'Invalid response' } } // TypeScript now knows data has these keys return { data } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: import { hasKeys, 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 with type safety if (!hasKeys(data, ['id', 'name', 'email'])) { return { error: 'Invalid response' } } // TypeScript now knows data has these keys return { data } } CODE_BLOCK: import { hasKeys, 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 with type safety if (!hasKeys(data, ['id', 'name', 'email'])) { return { error: 'Invalid response' } } // TypeScript now knows data has these keys return { data } } COMMAND_BLOCK: import { awaitTo, retry, withTimeout } from 'typetify/async' 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 } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: import { awaitTo, retry, withTimeout } from 'typetify/async' 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 } COMMAND_BLOCK: import { awaitTo, retry, withTimeout } from 'typetify/async' 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 } CODE_BLOCK: import { parseNumber, parseBoolean, compact, defaults } from 'typetify/input' function processFormData(formData: FormData) { return { age: parseNumber(formData.get('age')), // number | undefined newsletter: parseBoolean(formData.get('newsletter')), // boolean tags: compact(formData.getAll('tags')), // string[] bio: defaults(formData.get('bio'), 'No bio provided'), // string } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: import { parseNumber, parseBoolean, compact, defaults } from 'typetify/input' function processFormData(formData: FormData) { return { age: parseNumber(formData.get('age')), // number | undefined newsletter: parseBoolean(formData.get('newsletter')), // boolean tags: compact(formData.getAll('tags')), // string[] bio: defaults(formData.get('bio'), 'No bio provided'), // string } } CODE_BLOCK: import { parseNumber, parseBoolean, compact, defaults } from 'typetify/input' function processFormData(formData: FormData) { return { age: parseNumber(formData.get('age')), // number | undefined newsletter: parseBoolean(formData.get('newsletter')), // boolean tags: compact(formData.getAll('tags')), // string[] bio: defaults(formData.get('bio'), 'No bio provided'), // string } } COMMAND_BLOCK: import { isDefined } from 'typetify/core' // Problem: TypeScript doesn't narrow this const items = [1, null, 2, undefined, 3] const numbers = items.filter(x => x != null) // (number | null | undefined)[] // Solution: Proper type guard const numbers = items.filter(isDefined) // number[] βœ… Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: import { isDefined } from 'typetify/core' // Problem: TypeScript doesn't narrow this const items = [1, null, 2, undefined, 3] const numbers = items.filter(x => x != null) // (number | null | undefined)[] // Solution: Proper type guard const numbers = items.filter(isDefined) // number[] βœ… COMMAND_BLOCK: import { isDefined } from 'typetify/core' // Problem: TypeScript doesn't narrow this const items = [1, null, 2, undefined, 3] const numbers = items.filter(x => x != null) // (number | null | undefined)[] // Solution: Proper type guard const numbers = items.filter(isDefined) // number[] βœ… CODE_BLOCK: import { safeJsonParse } from 'typetify/input' const result = safeJsonParse(jsonString) if (result.ok) { console.log(result.data.name) // Type-safe access } else { console.error(result.error) // Handle error } // No try/catch needed Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: import { safeJsonParse } from 'typetify/input' const result = safeJsonParse(jsonString) if (result.ok) { console.log(result.data.name) // Type-safe access } else { console.error(result.error) // Handle error } // No try/catch needed CODE_BLOCK: import { safeJsonParse } from 'typetify/input' const result = safeJsonParse(jsonString) if (result.ok) { console.log(result.data.name) // Type-safe access } else { console.error(result.error) // Handle error } // No try/catch needed CODE_BLOCK: import { assert } from 'typetify/core' function getUser(id: string) { const user = findUser(id) // User | null assert(user, `User ${id} not found`) // TypeScript now knows user is User (not null) return user.name // Safe! } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: import { assert } from 'typetify/core' function getUser(id: string) { const user = findUser(id) // User | null assert(user, `User ${id} not found`) // TypeScript now knows user is User (not null) return user.name // Safe! } CODE_BLOCK: import { assert } from 'typetify/core' function getUser(id: string) { const user = findUser(id) // User | null assert(user, `User ${id} not found`) // TypeScript now knows user is User (not null) return user.name // Safe! } COMMAND_BLOCK: import { pipe } from 'typetify/flow' const result = pipe( 5, n => n * 2, // 10 n => n + 1, // 11 n => `Result: ${n}` // 'Result: 11' ) Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: import { pipe } from 'typetify/flow' const result = pipe( 5, n => n * 2, // 10 n => n + 1, // 11 n => `Result: ${n}` // 'Result: 11' ) COMMAND_BLOCK: import { pipe } from 'typetify/flow' const result = pipe( 5, n => n * 2, // 10 n => n + 1, // 11 n => `Result: ${n}` // 'Result: 11' ) CODE_BLOCK: // Import specific functions import { pick } from 'typetify/object' import { retry } from 'typetify/async' // Or import from specific modules import * as object from 'typetify/object' Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: // Import specific functions import { pick } from 'typetify/object' import { retry } from 'typetify/async' // Or import from specific modules import * as object from 'typetify/object' CODE_BLOCK: // Import specific functions import { pick } from 'typetify/object' import { retry } from 'typetify/async' // Or import from specific modules import * as object from 'typetify/object' COMMAND_BLOCK: $ npm ls typetify [email protected] └── (no dependencies) Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: $ npm ls typetify [email protected] └── (no dependencies) COMMAND_BLOCK: $ npm ls typetify [email protected] └── (no dependencies) COMMAND_BLOCK: npm install typetify Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: npm install typetify COMMAND_BLOCK: npm install typetify CODE_BLOCK: import { isDefined, pick, awaitTo } from 'typetify' // Or use the Lodash-style _ namespace import { _ } from 'typetify' const safe = _.pick(user, ['id', 'name']) Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: import { isDefined, pick, awaitTo } from 'typetify' // Or use the Lodash-style _ namespace import { _ } from 'typetify' const safe = _.pick(user, ['id', 'name']) CODE_BLOCK: import { isDefined, pick, awaitTo } from 'typetify' // Or use the Lodash-style _ namespace import { _ } from 'typetify' const safe = _.pick(user, ['id', 'name']) - Core - isDefined, assert, noop, identity - Guards - isString, isNumber, isObject, hasKey - Object - pick, omit, mapObject, get, set - Async - awaitTo, retry, debounce, throttle - Collection - unique, groupBy, partition, chunk - Input - safeJsonParse, parseNumber, parseBoolean - Flow - pipe, tap, match, tryCatch - String - String manipulation utilities - Math - Math utilities - Result - Result type pattern - Iterator - Iterator utilities - Decorator - TypeScript decorators - Logic - Logic utilities - Narrowing - Advanced type narrowing - Schema - Schema validation - DX - debug, invariant, measure, todo - Typed - Type utilities and branded types - Fn - Function utilities - πŸ” More schema validation utilities (like Zod-lite) - 🎯 Performance benchmarks vs Lodash/Ramda - πŸ“¦ Plugin system for custom utilities - πŸ§ͺ More real-world examples and recipes