@tienedev/datype
v0.1.0
Published
Modern TypeScript utility library with pragmatic typing and zero dependencies
Maintainers
Readme
datype
A modern, TypeScript-first utility library with perfect type inference and zero dependencies.
Why datype?
datype is built for the modern TypeScript ecosystem. Unlike traditional utility libraries, every function is designed with TypeScript-first principles, providing perfect type inference without manual type annotations.
import { deepMerge, pick, groupBy } from 'datype';
// ✨ Perfect type inference - no manual typing needed
const config = deepMerge(
{ api: { timeout: 5000 }, features: ['auth'] },
{ api: { retries: 3 }, debug: true }
);
// Type: { api: { timeout: number; retries: number }; features: string[]; debug: boolean }
const users = [
{ name: 'Alice', role: 'admin', age: 32 },
{ name: 'Bob', role: 'user', age: 28 }
];
// Type-safe property selection
const publicData = pick(users[0], ['name', 'role']);
// Type: { name: string; role: string }
// Smart grouping with automatic type inference
const byRole = groupBy(users, 'role');
// Type: Record<string, { name: string; role: string; age: number }[]>Features
- 🎯 TypeScript-first - Pragmatic typing that prioritizes usability
- 📦 Tree-shakable - Optimal bundle size by design
- 🔒 Immutable - All operations return new objects/arrays
- ⚡ Modern - Built for ES2020+ environments
- 🛡️ Zero dependencies - No external dependencies
- 📊 Lightweight - ~5KB gzipped for the full library
Installation
npm install datypeyarn add datypepnpm add datypeQuick Start
import {
deepMerge, pick, omit, get, set,
debounce, throttle, isEmpty, isEqual,
groupBy, mapValues, slugify
} from 'datype';
// 🏗️ Object manipulation
const merged = deepMerge(defaults, userConfig);
const subset = pick(user, ['id', 'name', 'email']);
const cleaned = omit(data, ['password', 'secret']);
// 🔍 Safe property access
const value = get(obj, 'nested.deep.property', 'default');
const updated = set(obj, 'nested.new.path', 'value');
// ⚡ Performance optimization
const debouncedSave = debounce(saveToAPI, 300);
const throttledScroll = throttle(onScroll, 16);
// ✅ Validation and comparison
if (isEmpty(formData.email)) { /* handle empty */ }
if (isEqual(prevState, newState)) { /* skip update */ }
// 📊 Data transformation
const grouped = groupBy(items, 'category');
const transformed = mapValues(obj, value => value.toString());
const slug = slugify('Hello World!'); // 'hello-world'API Reference
Object Utilities
deepMerge<T, U>(target: T, source: U, options?: DeepMergeOptions): DeepMergeResult<T, U>
Deep merge objects with intelligent type inference and configurable array handling.
const defaults = { api: { timeout: 5000 }, features: ['auth'] };
const config = { api: { retries: 3 }, features: ['logging'], debug: true };
// Merge with array concatenation (default)
const result = deepMerge(defaults, config);
// { api: { timeout: 5000, retries: 3 }, features: ['auth', 'logging'], debug: true }
// Merge with array replacement
const result2 = deepMerge(defaults, config, { arrayMerge: 'replace' });
// { api: { timeout: 5000, retries: 3 }, features: ['logging'], debug: true }pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>
Extract specific properties with perfect type safety.
const user = { id: 1, name: 'Alice', email: '[email protected]', password: 'secret' };
const safeUser = pick(user, ['id', 'name', 'email']);
// Type: { id: number; name: string; email: string }omit<T, K extends keyof T>(obj: T, keys: K[]): Omit<T, K>
Exclude specific properties with type safety.
const user = { id: 1, name: 'Alice', email: '[email protected]', password: 'secret' };
const publicUser = omit(user, ['password']);
// Type: { id: number; name: string; email: string }get<T>(obj: T, path: string, defaultValue?: any): any
Safe nested property access with dot notation.
const data = { user: { profile: { name: 'Alice' } } };
get(data, 'user.profile.name'); // 'Alice'
get(data, 'user.profile.age', 25); // 25 (default)
get(data, 'nonexistent.path'); // undefinedset<T>(obj: T, path: string, value: any): any
Immutably set nested properties with flexible return typing.
const obj = { a: { b: 1 } };
const updated = set(obj, 'a.c', 2);
// { a: { b: 1, c: 2 } }
// Original obj is unchangedmapValues<T, U>(obj: Record<string, T>, iteratee: (value: T, key: string) => U): Record<string, U>
Transform all object values.
const scores = { alice: '95', bob: '87', charlie: '92' };
const numeric = mapValues(scores, score => parseInt(score, 10));
// { alice: 95, bob: 87, charlie: 92 }mapKeys<T>(obj: Record<string, T>, iteratee: (key: string) => string): Record<string, T>
Transform all object keys with built-in transformers.
import { mapKeys, keyTransformers } from 'datype';
const apiData = { 'user_name': 'Alice', 'user_email': '[email protected]' };
const camelCased = mapKeys(apiData, keyTransformers.camelCase);
// { userName: 'Alice', userEmail: '[email protected]' }
// Available transformers: camelCase, kebabCase, snakeCase, pascalCase, constantCase, dotCaseArray Utilities
chunk<T>(array: T[], size: number): T[][]
Split array into chunks of specified size.
const numbers = [1, 2, 3, 4, 5, 6, 7, 8];
const chunked = chunk(numbers, 3);
// [[1, 2, 3], [4, 5, 6], [7, 8]]flatten(array: any[]): any[] / flattenDeep(array: any[]): any[]
Flatten nested arrays.
const nested = [1, [2, 3], [4, [5, 6]]];
flatten(nested); // [1, 2, 3, 4, [5, 6]]
flattenDeep(nested); // [1, 2, 3, 4, 5, 6]uniq<T>(array: T[]): T[] / uniqBy<T>(array: T[], iteratee: (item: T) => any): T[]
Remove duplicates.
const numbers = [1, 2, 2, 3, 3, 3];
uniq(numbers); // [1, 2, 3]
const users = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 1, name: 'Alice' }];
uniqBy(users, user => user.id); // [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]compact<T>(array: (T | null | undefined | false | 0 | '')[]): T[]
Remove falsy values.
const mixed = [1, null, 2, undefined, 3, false, 4, '', 5];
compact(mixed); // [1, 2, 3, 4, 5]Function Utilities
debounce<T extends (...args: any[]) => any>(func: T, delay: number, options?: DebounceOptions): DebouncedFunction<T>
Delay function execution until calls stop.
const searchAPI = debounce((query: string) => {
console.log('Searching:', query);
}, 300, {
leading: false, // Don't execute on leading edge
trailing: true, // Execute on trailing edge
maxWait: 1000 // Force execution after 1s
});
// Rapid calls - only last one executes
searchAPI('java');
searchAPI('javascript');
searchAPI('javascript tutorial'); // This will execute after 300ms
// Control methods
searchAPI.cancel(); // Cancel pending execution
searchAPI.flush(); // Execute immediatelythrottle<T extends (...args: any[]) => any>(func: T, delay: number, options?: ThrottleOptions): ThrottledFunction<T>
Limit function execution frequency.
const onScroll = throttle(() => {
console.log('Scroll position:', window.scrollY);
}, 100, {
leading: true, // Execute immediately on first call
trailing: true // Execute once more after calls stop
});
window.addEventListener('scroll', onScroll);once<T extends (...args: any[]) => any>(func: T): T
Ensure function executes only once.
const initialize = once(() => {
console.log('App initialized');
// expensive setup code
});
initialize(); // "App initialized"
initialize(); // Nothing happens
initialize(); // Nothing happensAdvanced Function Utilities
compose<T>(...functions: Function[]): (value: T) => any
Compose functions right-to-left.
const addOne = (x: number) => x + 1;
const double = (x: number) => x * 2;
const square = (x: number) => x * x;
const transform = compose(square, double, addOne);
transform(3); // square(double(addOne(3))) = square(8) = 64pipe<T>(...functions: Function[]): (value: T) => any
Compose functions left-to-right.
const transform = pipe(addOne, double, square);
transform(3); // square(double(addOne(3))) = 64curry<T extends (...args: any[]) => any>(func: T): T & ((...args: any[]) => any)
Transform function to support flexible partial application with pragmatic typing.
const add = (a: number, b: number, c: number) => a + b + c;
const curriedAdd = curry(add);
// All equivalent:
curriedAdd(1, 2, 3); // 6
curriedAdd(1)(2, 3); // 6
curriedAdd(1, 2)(3); // 6
curriedAdd(1)(2)(3); // 6
// Partial application
const add5 = curriedAdd(5);
add5(2, 3); // 10Validation Utilities
isEmpty(value: any): boolean
Check if value is empty.
isEmpty([]); // true
isEmpty({}); // true
isEmpty(''); // true
isEmpty(null); // true
isEmpty(undefined); // true
isEmpty(0); // false
isEmpty(false); // false
isEmpty([1]); // false
isEmpty({a: 1}); // falseisEqual(a: any, b: any): boolean
Deep equality comparison with circular reference support.
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { a: 1, b: { c: 2 } };
obj1 === obj2; // false
isEqual(obj1, obj2); // true
// Handles complex types
isEqual([1, 2, 3], [1, 2, 3]); // true
isEqual(new Date('2023-01-01'), new Date('2023-01-01')); // true
isEqual(/abc/g, /abc/g); // truecloneDeep<T>(value: T): T
Create deep clone with circular reference protection.
const original = {
user: { name: 'Alice' },
items: [1, 2, { nested: true }]
};
const clone = cloneDeep(original);
clone.user.name = 'Bob';
console.log(original.user.name); // 'Alice' (unchanged)String Utilities
slugify(text: string, options?: SlugifyOptions): string
Convert text to URL-friendly slug.
slugify('Hello World!'); // 'hello-world'
slugify('Café & Crème'); // 'cafe-and-creme'
slugify('TypeScript is Awesome!', {
separator: '_',
maxLength: 20
}); // 'typescript_is_awesome'String Case Transformers
import { camelCase, kebabCase, snakeCase, pascalCase, constantCase } from 'datype';
camelCase('hello-world'); // 'helloWorld'
kebabCase('HelloWorld'); // 'hello-world'
snakeCase('helloWorld'); // 'hello_world'
pascalCase('hello-world'); // 'HelloWorld'
constantCase('helloWorld'); // 'HELLO_WORLD'capitalize(text: string): string
Capitalize first letter of each word.
capitalize('hello world'); // 'Hello World'
capitalize('HELLO WORLD'); // 'Hello World'
capitalize('hello-world'); // 'Hello-World'Text Truncation
import { truncate, truncateWords, truncateMiddle } from 'datype';
truncate('This is a long text', 10); // 'This is a…'
truncateWords('One two three four five', 3); // 'One two three…'
truncateMiddle('very-long-filename.txt', 15); // 'very-lo…ame.txt'Data Organization
groupBy<T>(array: T[], iteratee: string | ((item: T) => any)): Record<string, T[]>
Group array elements.
const users = [
{ name: 'Alice', role: 'admin', age: 30 },
{ name: 'Bob', role: 'user', age: 25 },
{ name: 'Charlie', role: 'admin', age: 35 }
];
// Group by property
const byRole = groupBy(users, 'role');
// { admin: [Alice, Charlie], user: [Bob] }
// Group by function
const byAgeGroup = groupBy(users, user => user.age >= 30 ? 'senior' : 'junior');
// { senior: [Alice, Charlie], junior: [Bob] }Type Guards
import { isPlainObject, isArray, isFunction } from 'datype';
isPlainObject({}); // true
isPlainObject([]); // false
isPlainObject(new Date()); // false
isArray([]); // true
isArray('string'); // false
isFunction(() => {}); // true
isFunction('string'); // falseImport Strategies
datype is optimized for tree-shaking. Choose the import style that fits your needs:
// 1. Named imports (recommended for most projects)
import { deepMerge, pick, debounce } from 'datype';
// 2. Individual imports (maximum tree-shaking)
import { deepMerge } from 'datype/deepMerge';
// 3. Wildcard import (when using many functions)
import * as dt from 'datype';
const result = dt.deepMerge(obj1, obj2);TypeScript Configuration
datype uses pragmatic TypeScript types that prioritize developer experience. For optimal type inference, ensure your tsconfig.json includes:
{
"compilerOptions": {
"strict": true,
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true
}
}Philosophy: We believe TypeScript should enhance productivity, not hinder it. Our types are designed to be permissive where needed while maintaining essential type safety.
Bundle Size
datype is designed for optimal bundle size. Import only what you need:
| Import | Bundle Impact |
|--------|--------------|
| import { pick } | ~200 bytes |
| import { deepMerge } | ~800 bytes |
| import { debounce, throttle } | ~600 bytes |
| Full library | ~5KB gzipped |
Browser Support
- Modern browsers: Chrome 80+, Firefox 72+, Safari 13+, Edge 80+
- Node.js: 14.0+
- TypeScript: 4.5+
Contributing
We welcome contributions! Please read our Contributing Guide for:
- Development setup
- Code standards
- Testing requirements
- Pull request process
License
Built with ❤️ for the TypeScript community
