{"id":20401011,"url":"https://github.com/sohanemon/utils","last_synced_at":"2026-02-19T10:10:28.334Z","repository":{"id":260036330,"uuid":"685508745","full_name":"sohanemon/utils","owner":"sohanemon","description":"Swiss army knife for React/Nextjs","archived":false,"fork":false,"pushed_at":"2026-02-15T17:34:18.000Z","size":365,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-16T00:40:40.205Z","etag":null,"topics":["cn","dom","nextjs","react","typescript","utilities"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@sohanemon/utils","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sohanemon.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-08-31T11:49:16.000Z","updated_at":"2026-02-15T17:34:22.000Z","dependencies_parsed_at":null,"dependency_job_id":"980c586b-7d38-41b2-9acd-f5884d8c2b6a","html_url":"https://github.com/sohanemon/utils","commit_stats":null,"previous_names":["sohanemon/utils"],"tags_count":27,"template":false,"template_full_name":null,"purl":"pkg:github/sohanemon/utils","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sohanemon%2Futils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sohanemon%2Futils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sohanemon%2Futils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sohanemon%2Futils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sohanemon","download_url":"https://codeload.github.com/sohanemon/utils/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sohanemon%2Futils/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29609532,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-19T06:47:36.664Z","status":"ssl_error","status_checked_at":"2026-02-19T06:45:47.551Z","response_time":117,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cn","dom","nextjs","react","typescript","utilities"],"created_at":"2024-11-15T04:47:31.730Z","updated_at":"2026-02-19T10:10:28.313Z","avatar_url":"https://github.com/sohanemon.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# sohanemon/utils\n\n![npm version](https://img.shields.io/npm/v/@sohanemon/utils)\n![npm downloads](https://img.shields.io/npm/dm/@sohanemon/utils)\n![License](https://img.shields.io/npm/l/@sohanemon/utils)\n![Tests](https://github.com/sohanemon/sohanemon-utils/actions/workflows/test.yml/badge.svg)\n\n## Description\n\n`sohanemon/utils` is a comprehensive collection of utility functions, hooks, components, and types designed to simplify common tasks in modern web development. It includes utilities for object manipulation, async operations, scheduling, data transformation, React hooks for state management and effects, UI components, and advanced TypeScript types. The library is built with TypeScript and is fully typed, ensuring a smooth and error-free development experience.\n\n## Features\n\n- **Object Utilities**: Functions to access and manipulate nested object properties, extend objects, and more.\n- **Data Transformation**: Deep merging, null-to-undefined conversion, slug generation, text normalization, and more.\n- **Async Operations**: Polling, scheduling, debouncing, throttling, and safe async execution.\n- **Cookie Management**: Functions to set, get, delete, and check for cookies.\n- **Class Name Merging**: A utility to merge class names with Tailwind CSS and custom logic.\n- **React Hooks**: Hooks for media queries, effects, state management (local/session storage, URL params), DOM calculations, async operations, scheduling, and more.\n- **UI Components**: React components for HTML injection, media wrapping, responsive indicators, scrollable markers, and Iconify icons.\n- **TypeScript Types**: Advanced utility types for deep partials, requireds, readonly, guards, and type-level logic gates.\n- **Browser Utilities**: Clipboard operations, scroll management, SSR detection, and more.\n\n## Installation\n\nYou can install `sohanemon/utils` using npm or yarn:\n\n```bash\nnpm install @sohanemon/utils\n```\n\nor\n\n```bash\nyarn add @sohanemon/utils\n```\n\n### Peer Dependencies\n\nThis package has several peer dependencies that you must install separately:\n\n```bash\nnpm install react react-dom @iconify/react clsx tailwind-merge @radix-ui/react-slot\n```\n\nThese dependencies are expected to be provided by your application and are not bundled with this package.\n\n## Usage\n\n### Importing Utilities\n\nYou can import individual utilities, hooks, components, or types as needed:\n\n```typescript\nimport { cn, getObjectValue, setClientSideCookie, hydrate, poll } from '@sohanemon/utils';\nimport { useAsync, useLocalStorage } from '@sohanemon/utils';\nimport { HtmlInjector, ResponsiveIndicator } from '@sohanemon/utils';\nimport type { DeepPartial, Primitive } from '@sohanemon/utils';\n```\n\n### Examples\n\n#### Class Name Merging\n\n```typescript\nimport { cn } from '@sohanemon/utils';\n\nconst className = cn('bg-blue-500', 'text-white', 'p-4', 'rounded-lg');\n```\n\n#### Object Utilities\n\n```typescript\nimport { getObjectValue, extendProps } from '@sohanemon/utils';\n\nconst obj = { a: { b: { c: 1 } } };\nconst value = getObjectValue(obj, 'a.b.c'); // 1\n\nconst extended = extendProps({ a: 1 }, { b: 'hello' }); // { a: 1, b: 'hello' }\n```\n\n#### Data Transformation\n\n```typescript\nimport { hydrate, deepmerge, convertToSlug, normalizeText } from '@sohanemon/utils';\n\nconst cleaned = hydrate({ a: null, b: { c: null } }); // { a: undefined, b: { c: undefined } }\n\n// Deep merge objects\nconst merged = deepmerge({ user: { name: 'John' } }, { user: { age: 30 } });\n// { user: { name: 'John', age: 30 } }\n\n// Compose functions automatically\nconst composed = deepmerge(\n  { onFinish() { console.log('first') } },\n  { onFinish() { console.log('second') } },\n  { functionMerge: 'compose' }\n);\n// composed.onFinish() logs 'first' then 'second'\n\n// Merge functions with custom logic\nconst combined = deepmerge(\n  { onFinish() { console.log('first') } },\n  { onFinish(v) { console.log('second', v) } },\n  {\n    customMerge: (key, target, source) =\u003e {\n      if (typeof target === 'function' \u0026\u0026 typeof source === 'function') {\n        return (...args) =\u003e { target(...args); source(...args); };\n      }\n      return source;\n    }\n  }\n);\n// combined.onFinish('done') logs 'first' then 'second done'\n\nconst slug = convertToSlug('Hello World!'); // 'hello-world'\nconst normalized = normalizeText('Café', { removeAccents: true }); // 'cafe'\n```\n\n#### Async Operations\n\n```typescript\nimport { poll, shield, sleep } from '@sohanemon/utils';\n\nconst result = await poll(async () =\u003e {\n  const status = await checkStatus();\n  return status === 'ready' ? status : null;\n}, { interval: 2000, timeout: 30000 });\n\nconst [error, data] = await shield(fetchData());\n```\n\n#### Cookie Management\n\n```typescript\nimport { setClientSideCookie, getClientSideCookie } from '@sohanemon/utils';\n\nsetClientSideCookie('username', 'sohanemon', 7);\nconst { value } = getClientSideCookie('username'); // 'sohanemon'\n```\n\n#### Debounce and Throttle\n\n```typescript\nimport { debounce, throttle } from '@sohanemon/utils';\n\nconst debouncedFunction = debounce(() =\u003e console.log('Debounced!'), 300);\nconst throttledFunction = throttle(() =\u003e console.log('Throttled!'), 300);\n```\n\n#### React Hooks\n\n```typescript\nimport { useAsync, useLocalStorage, useMediaQuery, useCopyToClipboard, useScrollTracker } from '@sohanemon/utils';\n\nconst { data, isLoading } = useAsync(async (signal) =\u003e {\n  return await fetchData(signal);\n}, { mode: 'auto' });\n\nconst [value, setValue] = useLocalStorage('key', { count: 0 });\n\nconst isMobile = useMediaQuery('sm');\n\nconst { isCopied, copy } = useCopyToClipboard();\n\nconst { scrolledPast, direction } = useScrollTracker({ threshold: 300 });\n```\n\n#### UI Components\n\n```tsx\nimport { HtmlInjector, ResponsiveIndicator, Iconify } from '@sohanemon/utils';\n\n\u003cHtmlInjector html=\"\u003cp\u003eInjected HTML\u003c/p\u003e\" /\u003e\n\u003cResponsiveIndicator /\u003e\n\u003cIconify icon=\"mdi:home\" /\u003e\n```\n\n#### TypeScript Types\n\n```typescript\nimport type { DeepPartial, Nullable, KeysOfType } from '@sohanemon/utils';\n\ntype PartialUser = DeepPartial\u003cUser\u003e;\ntype NullableUser = Nullable\u003cUser\u003e;\ntype StringKeys = KeysOfType\u003cUser, string\u003e;\n```\n\n## API Documentation\n\n### Functions\n\n#### Class Name Merging\n```typescript\ncn(...inputs: ClassValue[]): string\n```\n\n#### Object Utilities\n```typescript\ngetObjectValue\u003cT, K extends Array\u003cstring | number\u003e, D\u003e(\n  obj: T,\n  path: K,\n  defaultValue: D\n): Exclude\u003cGetValue\u003cT, K\u003e, undefined\u003e | D;\n\ngetObjectValue\u003cT, K extends Array\u003cstring | number\u003e\u003e(\n  obj: T,\n  path: K\n): GetValue\u003cT, K\u003e | undefined;\n\ngetObjectValue\u003cT, S extends string, D\u003e(\n  obj: T,\n  path: S,\n  defaultValue: D\n): Exclude\u003cGetValue\u003cT, SplitPath\u003cS\u003e\u003e, undefined\u003e | D;\n\ngetObjectValue\u003cT, S extends string\u003e(\n  obj: T,\n  path: S\n): GetValue\u003cT, SplitPath\u003cS\u003e\u003e | undefined;\n\nextendProps\u003cT, P extends object\u003e(\n  base: T,\n  props: P\n): T extends null | undefined ? T : T \u0026 P;\n```\n\n#### Data Transformation\n```typescript\nhydrate\u003cT\u003e(data: T): Hydrate\u003cT\u003e\n\ndeepmerge\u003cT extends Record\u003cstring, any\u003e, S extends Record\u003cstring, any\u003e[]\u003e(\n  target: T,\n  ...sources: S\n): TMerged\u003cT | S[number]\u003e\n\ndeepmerge\u003cT extends Record\u003cstring, any\u003e, S extends Record\u003cstring, any\u003e[]\u003e(\n  target: T,\n  sources: S,\n  options?: DeepMergeOptions\n): TMerged\u003cT | S[number]\u003e\n\nconvertToSlug(str: string): string\n\nnormalizeText(\n  str?: string | null,\n  options?: {\n    lowercase?: boolean;\n    removeAccents?: boolean;\n    removeNonAlphanumeric?: boolean;\n  }\n): string\n\nconvertToNormalCase(inputString: string): string\n\nescapeRegExp(str: string): string\n\nprintf(format: string, ...args: unknown[]): string\n```\n\n#### Async \u0026 Scheduling\n```typescript\npoll\u003cT\u003e(\n  cond: () =\u003e Promise\u003cT | null | false | undefined\u003e,\n  options?: {\n    interval?: number;\n    timeout?: number;\n    signal?: AbortSignal;\n    jitter?: boolean;\n  }\n): Promise\u003cT\u003e\n\nschedule(task: Task, options?: ScheduleOpts): void\n\nshield\u003cT, E = Error\u003e(operation: Promise\u003cT\u003e): Promise\u003c[E | null, T | null]\u003e\nshield\u003cT, E = Error\u003e(operation: () =\u003e T): [E | null, T | null]\n\nsleep(time?: number, signal?: AbortSignal): Promise\u003cvoid\u003e\n\ndebounce\u003cF extends (...args: any[]) =\u003e any\u003e(\n  function_: F,\n  wait?: number,\n  options?: { immediate?: boolean }\n): DebouncedFunction\u003cF\u003e\n\nthrottle\u003cF extends (...args: any[]) =\u003e any\u003e(\n  function_: F,\n  wait?: number,\n  options?: { leading?: boolean; trailing?: boolean }\n): ThrottledFunction\u003cF\u003e\n```\n\n#### Cookie Management\n```typescript\nsetClientSideCookie(name: string, value: string, days?: number, path?: string): void\ndeleteClientSideCookie(name: string, path?: string): void\nhasClientSideCookie(name: string): boolean\ngetClientSideCookie(name: string): { value: string | undefined }\n```\n\n#### Browser Utilities\n```typescript\ncopyToClipboard(value: string, onSuccess?: () =\u003e void): void\n\nscrollTo(\n  containerSelector: string | React.RefObject\u003cHTMLDivElement\u003e,\n  to: 'top' | 'bottom'\n): void\n\ngoToClientSideHash(id: string, opts?: ScrollIntoViewOptions): void\n\nisSSR: boolean\n\nsvgToBase64(str: string): string\n\nisLinkActive(options: {\n  path: string;\n  currentPath: string;\n  locales?: string[];\n  exact?: boolean;\n}): boolean\n\nisNavActive(href: string, path: string): boolean // deprecated\n\ncleanSrc(src: string): string\n```\n\n### React Hooks\n\n#### State \u0026 Effects\n```typescript\nuseAction\u003cInput, Result\u003e(\n  action: ActionType\u003cInput, Result\u003e,\n  options?: UseActionOptions\u003cInput, Result\u003e\n): {\n  execute: (input: Input) =\u003e void;\n  executeAsync: (input: Input) =\u003e Promise\u003cResult\u003e;\n  reset: () =\u003e void;\n  useExecute: (input: Input) =\u003e void;\n  data: Result | null;\n  error: Error | null;\n  input: Input | undefined;\n  isIdle: boolean;\n  isLoading: boolean;\n  isSuccess: boolean;\n  isError: boolean;\n}\n\nuseAsync\u003cTData, TError extends Error = Error\u003e(\n  asyncFn: (signal: AbortSignal) =\u003e Promise\u003cTData\u003e,\n  options?: UseAsyncOptions\u003cTData, TError\u003e\n): UseAsyncReturn\u003cTData, TError\u003e\n\nuseLocalStorage\u003cT extends Record\u003cstring, any\u003e\u003e(\n  key: string,\n  defaultValue: T\n): [T, React.Dispatch\u003cReact.SetStateAction\u003cT\u003e\u003e]\n\nuseSessionStorage\u003cT extends Record\u003cstring, any\u003e\u003e(\n  key: string,\n  defaultValue: T\n): [T, React.Dispatch\u003cReact.SetStateAction\u003cT\u003e\u003e]\n\nuseUrlParams\u003cT extends string | number | boolean\u003e(\n  key: string,\n  defaultValue: T\n): [T, (value: T) =\u003e void]\n\nuseDebounce\u003cT\u003e(state: T, delay?: number): T\n\nuseTimeout(callback: () =\u003e void, delay?: number | null): void\n\nuseEffectOnce(effect: React.EffectCallback): void\n\nuseUpdateEffect(effect: React.EffectCallback, deps: React.DependencyList): void\n\nuseIsomorphicEffect: typeof React.useLayoutEffect | typeof React.useEffect\n```\n\n#### UI \u0026 Interaction\n```typescript\nuseMediaQuery(tailwindBreakpoint: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | `(${string})`): boolean\n\nuseClickOutside(callback: () =\u003e void): React.RefObject\u003cHTMLDivElement\u003e\n\nuseCopyToClipboard(options?: { timeout?: number }): {\n  isCopied: boolean;\n  copy: (value: string) =\u003e void;\n}\n\nuseWindowEvent\u003cK extends keyof WindowEventMap\u003e(\n  type: K,\n  listener: (this: Window, ev: WindowEventMap[K]) =\u003e void,\n  options?: boolean | AddEventListenerOptions\n): void\n\nuseQuerySelector\u003cT extends Element\u003e(selector: string | React.RefObject\u003cT | null\u003e): T | null\n\nuseIsClient(): boolean\n\nuseLockScroll(): void\n\nuseIntersection(options?: UseIntersectionOptions): {\n  ref: React.RefObject\u003cElement\u003e;\n  isIntersecting: boolean;\n}\n\nuseIsScrolling(): {\n  isScrolling: boolean;\n  scrollableContainerRef: React.RefObject\u003cHTMLElement\u003e;\n}\n\nuseIsAtTop(options?: { offset?: number }): {\n  scrollableContainerRef: React.RefObject\u003cHTMLElement\u003e;\n  isAtTop: boolean;\n}\n\nuseScrollTracker(options?: {\n  threshold?: number;\n  container?: string | React.RefObject\u003cHTMLElement | null\u003e;\n}): {\n  scrolledPast: boolean;\n  direction: 'forward' | 'backward';\n}\n```\n\n#### DOM \u0026 Layout\n```typescript\nuseDomCalculation(options: CalculationProps): { height: number; width: number }\n\nuseHeightCalculation(options: CalculationProps2): number\n```\n\n#### Scheduling\n```typescript\nuseSchedule(options?: ScheduleOpts): (task: Task) =\u003e void\n\nuseScheduledEffect(\n  effect: () =\u003e void | (() =\u003e void),\n  deps?: React.DependencyList,\n  options?: ScheduleOpts\n): void\n```\n\n### Components\n\n```typescript\nIconify: React.Component (from @iconify/react)\n\nHtmlInjector: React.Component\u003c{ html: string; className?: string }\u003e\n\nMediaWrapper: React.Component\u003c{\n  src: string;\n  alt?: string;\n  className?: string;\n  lazy?: boolean;\n}\u003e\n\nResponsiveIndicator: React.Component\u003c{\n  className?: string;\n  showText?: boolean;\n}\u003e\n\nScrollableMarker: React.Component\u003c{\n  className?: string;\n  children?: React.ReactNode;\n}\u003e\n```\n\n### Types\n\n#### Utility Types\n```typescript\nKeys\u003cT extends object\u003e: keyof T\nValues\u003cT extends object\u003e: T[keyof T]\nDeepPartial\u003cT\u003e: T with all properties optional recursively\nSelectivePartial\u003cT, K\u003e: T with selected keys optional\nDeepRequired\u003cT\u003e: T with all properties required recursively\nSelectiveRequired\u003cT, K\u003e: T with selected keys required\nNever\u003cT\u003e: Object with never values\nNullable\u003cT\u003e: T with null added to primitives\nOptional\u003cT\u003e: T with undefined added to primitives\nNullish\u003cT\u003e: T with null|undefined added to primitives\nMaybe\u003cT\u003e: T with all properties optional and nullish\nDeepReadonly\u003cT\u003e: T with all properties readonly recursively\nMutable\u003cT\u003e: T with readonly removed\nKeysOfType\u003cT, U\u003e: Keys of T where value is U\nOmitByType\u003cT, U\u003e: T without properties of type U\nRequiredKeys\u003cT, K\u003e: T with selected keys required\nDiff\u003cT, U\u003e: Properties in T or U but not both\nIntersection\u003cT, U\u003e: Common properties\nMerge\u003cT, U\u003e: Merged object\nSubstract\u003cT, U\u003e: T without U properties\nAllOrNone\u003cT\u003e: T or empty object\nOneOf\u003cT\u003e: Union of single property objects\nTwoOf\u003cT\u003e: Union of two property objects\nPrettify\u003cT\u003e: Clean type representation\nNestedKeyOf\u003cT\u003e: All nested keys as strings\nWithout\u003cT, U\u003e: T without U keys\nDeepMergeOptions: {\n  arrayMerge?: 'replace' | 'concat' | 'merge' | ((target: any[], source: any[]) =\u003e any[]);\n  clone?: boolean;\n  customMerge?: (key: string | symbol, targetValue: any, sourceValue: any) =\u003e any;\n  functionMerge?: 'replace' | 'compose';\n  maxDepth?: number;\n}\n```\n\n#### Type Guards \u0026 Primitives\n```typescript\nPrimitive: string | number | bigint | boolean | symbol | null | undefined\nFalsy: false | '' | 0 | null | undefined\n\nisFalsy(val: unknown): val is Falsy\nisNullish(val: unknown): val is null | undefined\nisPrimitive(val: unknown): val is Primitive\nisPlainObject(value: unknown): value is Record\u003cstring, any\u003e\n```\n\n#### Logic Gates\n```typescript\nBUFFER\u003cT\u003e: T\nIMPLIES\u003cT, U\u003e: true if T extends U\nXOR_Binary\u003cT, U\u003e: T | U with exclusions\nXNOR_Binary\u003cT, U\u003e: T \u0026 U | neither\nAND\u003cT extends any[]\u003e: All true\nOR\u003cT extends any[]\u003e: At least one true\nXOR\u003cT extends any[]\u003e: Odd number true\nXNOR\u003cT extends any[]\u003e: Even number true\nNOT\u003cT\u003e: Never properties\nNAND\u003cT extends any[]\u003e: NOT AND\nNOR\u003cT extends any[]\u003e: NOT OR\n```\n\n## Contributing\n\nContributions are welcome! Please read the [CONTRIBUTING.md](CONTRIBUTING.md) file for guidelines on how to contribute to the project.\n\n## License\n\nThis project is licensed under the ISC License - see the [LICENSE](LICENSE) file for details.\n\n## Contact\n\n- **Sohan Emon**: [sohanemon@outlook.com](mailto:sohanemon@outlook.com)\n- **GitHub**: [sohanemon](https://github.com/sohanemon)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsohanemon%2Futils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsohanemon%2Futils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsohanemon%2Futils/lists"}