{"id":48081932,"url":"https://github.com/wiesnerbernard/fragmen","last_synced_at":"2026-04-04T14:55:05.680Z","repository":{"id":313894374,"uuid":"1052950057","full_name":"wiesnerbernard/fragmen","owner":"wiesnerbernard","description":"ShadCN like helper function for TS","archived":false,"fork":false,"pushed_at":"2025-12-21T14:56:26.000Z","size":725,"stargazers_count":6,"open_issues_count":1,"forks_count":2,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-23T05:06:49.987Z","etag":null,"topics":["typescript","utility-function"],"latest_commit_sha":null,"homepage":"https://fragmen-web.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wiesnerbernard.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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":"2025-09-08T19:19:04.000Z","updated_at":"2025-12-14T12:48:32.000Z","dependencies_parsed_at":"2026-03-03T16:01:52.699Z","dependency_job_id":null,"html_url":"https://github.com/wiesnerbernard/fragmen","commit_stats":null,"previous_names":["wiesnerbernard/fragmen"],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/wiesnerbernard/fragmen","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wiesnerbernard%2Ffragmen","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wiesnerbernard%2Ffragmen/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wiesnerbernard%2Ffragmen/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wiesnerbernard%2Ffragmen/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wiesnerbernard","download_url":"https://codeload.github.com/wiesnerbernard/fragmen/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wiesnerbernard%2Ffragmen/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31403836,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T10:20:44.708Z","status":"ssl_error","status_checked_at":"2026-04-04T10:20:06.846Z","response_time":60,"last_error":"SSL_read: 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":["typescript","utility-function"],"created_at":"2026-04-04T14:55:05.128Z","updated_at":"2026-04-04T14:55:05.671Z","avatar_url":"https://github.com/wiesnerbernard.png","language":"TypeScript","readme":"# Fragmen 🧩\n\n\u003c!-- COVERAGE-BADGES:START --\u003e\n\n![Coverage](https://img.shields.io/badge/coverage-97%25-brightgreen)\n![Lines](https://img.shields.io/badge/lines-96.25%25-brightgreen)\n![Branches](https://img.shields.io/badge/branches-95.55%25-brightgreen)\n![Functions](https://img.shields.io/badge/functions-98.26%25-brightgreen)\n![Statements](https://img.shields.io/badge/statements-96.27%25-brightgreen)\n\n\u003c!-- COVERAGE-BADGES:END --\u003e\n\n[![npm version](https://img.shields.io/npm/v/fragmen.svg)](https://www.npmjs.com/package/fragmen)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n**\"ShadCN but for helper functions.\"**\n\nFragmen is a CLI tool that lets you add high-quality, standalone TypeScript utility functions directly into your project. Instead of adding another dependency to your `package.json`, you get the source code. You own it, you can change it, and you won't have to worry about bundle size or breaking changes from a third-party library.\n\n🌐 **[Browse utilities on the web →](https://fragmen.vercel.app)**\n\n## Table of Contents\n\n- [Core Philosophy](#core-philosophy)\n- [Getting Started](#getting-started)\n  - [1. Initialize Project](#1-initialize-project)\n  - [2. Add a Fragment](#2-add-a-fragment)\n- [Available Fragments](#available-fragments)\n  - [Array Utilities](#array-utilities)\n  - [Boolean Utilities](#boolean-utilities)\n  - [Date Utilities](#date-utilities)\n  - [Function Utilities](#function-utilities)\n  - [JSON Utilities](#json-utilities)\n  - [Number Utilities](#number-utilities)\n  - [Object Utilities](#object-utilities)\n  - [Promise Utilities](#promise-utilities)\n  - [String Utilities](#string-utilities)\n  - [URL Utilities](#url-utilities)\n- [CLI Commands](#cli-commands)\n- [Testing \u0026 Coverage](#testing--coverage)\n- [Contributing](#contributing)\n- [License](#license)\n\n---\n\n## Core Philosophy\n\n- **Own Your Code**: Utilities are copied directly into your codebase under a `lib/utils` directory. This gives you full control to inspect, adapt, and learn from them.\n- **Zero Dependencies**: Each fragment is self-contained. Adding a utility doesn't add to your `node_modules` folder.\n- **TypeScript First**: All fragments are written in TypeScript with excellent type safety and JSDoc annotations.\n- **Incremental Adoption**: Add only what you need, when you need it.\n\n---\n\n## Getting Started\n\nGet started in three simple steps. Or **[browse all utilities on the website](https://fragmen.vercel.app)** to see what's available.\n\n### 1. Initialize Project\n\nRun the `init` command in the root of your project. This will create a `fragmen.json` file to configure where your utilities will be stored.\n\n```bash\nnpx fragmen init\n```\n\nYou'll be asked a few questions to set up your project:\n\n```\n✔ Where should utility functions be copied? › lib/utils\n✔ Which language are you using? › TypeScript\n✔ Which module system? › ESM (import/export)\n```\n\nThis creates your `fragmen.json` configuration file.\n\n### 2. Discover Utilities\n\nThere are three ways to discover and explore the 50+ available utilities:\n\n#### Option A: Interactive Browse (Recommended)\n\nUse the interactive `browse` command to visually explore utilities by category and add multiple at once:\n\n```bash\nnpx fragmen browse\n```\n\nThis opens an interactive menu where you can:\n\n1. Select a category (with utility count shown)\n2. Multi-select utilities using spacebar\n3. Confirm and add all selected utilities at once\n\n```\n🔍 Browse Fragmen Utilities\n\n? Select a category: (Use arrow keys)\n❯ array (10 utilities)\n  boolean (2 utilities)\n  function (3 utilities)\n  promise (3 utilities)\n  string (11 utilities)\n  ...\n\n? Select utilities from promise: (Space to select, Enter to confirm)\n❯◉ delay\n ◯ retry\n ◉ timeout\n\n? Add 2 utilities? Yes\n\n✓ promise/delay\n✓ promise/timeout\n\n✓ Successfully added 2 utilities\n```\n\n#### Option B: Preview Details\n\nUse the `show` command to preview a utility's documentation, examples, and source before adding:\n\n```bash\nnpx fragmen show promise/delay\n```\n\nThis displays:\n\n```\n📖 promise/delay\n\nReturns a promise that resolves after a given number of milliseconds.\n\nUsage:\n  fragmen add promise/delay\n\nExample:\nawait delay(1000);\nconsole.log('This runs after 1 second');\n\nSource:\n  registry/promise/delay/index.ts\n\nTo add this utility: fragmen add promise/delay\n```\n\n#### Option C: List All Utilities\n\nUse the `list` command to see all available utilities organized by category:\n\n```bash\n# List all utilities\nnpx fragmen list\n\n# Filter by category\nnpx fragmen list promise\nnpx fragmen list string\n```\n\nOutput:\n\n```\n📦 Available Utilities\n\npromise/ (3)\n  • promise/delay\n  • promise/retry\n  • promise/timeout\n\nstring/ (11)\n  • string/camel-case\n  • string/capitalize\n  ...\n\nTotal: 50 utilities\n```\n\n### 3. Add Utilities\n\nOnce you've discovered utilities you want, add them to your project:\n\n#### Add Single Utility\n\nUse the `add` command with the full path (category/utility-name):\n\n```bash\nnpx fragmen add promise/delay\n```\n\n#### Add Multiple Utilities\n\nAdd multiple utilities in a single command for efficiency:\n\n```bash\nnpx fragmen add promise/delay promise/retry string/capitalize\n```\n\nThis will process all utilities with progress feedback:\n\n```\nAdding 3 utilities...\n\n✓ promise/delay\n✓ promise/retry\n✓ string/capitalize\n\n✓ Successfully added 3 utilities\n  Location: lib/utils/\n```\n\nThe utilities will be copied to your project:\n\n```\nyour-project/\n└── lib/\n    └── utils/\n        ├── promise-delay.ts\n        ├── promise-retry.ts\n        └── string-capitalize.ts\n```\n\nNow you can import and use them anywhere in your project:\n\n```typescript\nimport { delay } from '@/lib/utils/promise-delay';\nimport { retry } from '@/lib/utils/promise-retry';\nimport { capitalize } from '@/lib/utils/string-capitalize';\n\n// Use delay\nawait delay(1000);\nconsole.log('This runs after 1 second');\n\n// Use retry with fetch\nconst data = await retry(() =\u003e fetch('https://api.example.com'), {\n  retries: 3,\n});\n\n// Use capitalize\nconst title = capitalize('hello world'); // 'Hello world'\n```\n\n---\n\n## Available Fragments\n\nThis is the registry of available utility functions organized by category. Each utility is thoroughly tested and documented.\n\n### Array Utilities\n\n#### `array/group-by`\n\nGroups the elements of an array based on the result of a callback function.\n\n```typescript\nimport { groupBy } from '@/lib/utils/array-group-by';\n\nconst users = [\n  { name: 'Alice', department: 'Engineering' },\n  { name: 'Bob', department: 'Marketing' },\n  { name: 'Charlie', department: 'Engineering' },\n];\n\nconst byDepartment = groupBy(users, user =\u003e user.department);\n// Result: { Engineering: [Alice, Charlie], Marketing: [Bob] }\n```\n\n#### `array/unique`\n\nReturns a new array with only unique elements from the input array.\n\n```typescript\nimport { unique } from '@/lib/utils/array-unique';\n\nconst numbers = [1, 2, 2, 3, 1, 4];\nconst uniqueNumbers = unique(numbers);\n// Result: [1, 2, 3, 4]\n```\n\n#### `array/chunk`\n\nSplits an array into chunks of a specified size.\n\n```typescript\nimport { chunk } from '@/lib/utils/array-chunk';\n\nconst numbers = [1, 2, 3, 4, 5, 6, 7];\nconst chunks = chunk(numbers, 3);\n// Result: [[1, 2, 3], [4, 5, 6], [7]]\n```\n\n#### `array/flatten`\n\nFlattens nested arrays to a specified depth.\n\n```typescript\nimport { flatten } from '@/lib/utils/array-flatten';\n\nconst nested = [1, [2, 3], [4, [5, 6]]];\nconst flat = flatten(nested);\n// Result: [1, 2, 3, 4, [5, 6]]\n\nconst deepFlat = flatten(nested, Infinity);\n// Result: [1, 2, 3, 4, 5, 6]\n```\n\n#### `array/intersection`\n\nFinds the intersection of two or more arrays.\n\n```typescript\nimport { intersection } from '@/lib/utils/array-intersection';\n\nconst arr1 = [1, 2, 3, 4];\nconst arr2 = [2, 3, 4, 5];\nconst common = intersection(arr1, arr2);\n// Result: [2, 3, 4]\n```\n\n#### `array/compact`\n\nRemoves falsy values from an array.\n\n```typescript\nimport { compact } from '@/lib/utils/array-compact';\n\nconst mixed = [0, 1, false, 2, '', 3, null, 4, undefined, 5, NaN];\nconst clean = compact(mixed);\n// Result: [1, 2, 3, 4, 5]\n```\n\n#### `array/difference`\n\nCreates an array of values from the first array that are not present in the other arrays.\n\n```typescript\nimport { difference } from '@/lib/utils/array-difference';\n\nconst numbers = [1, 2, 3, 4, 5];\nconst toExclude = [2, 4];\nconst result = difference(numbers, toExclude);\n// Result: [1, 3, 5]\n\nconst arr1 = ['a', 'b', 'c', 'd'];\nconst arr2 = ['b', 'd'];\nconst arr3 = ['a'];\nconst remaining = difference(arr1, arr2, arr3);\n// Result: ['c']\n```\n\n#### `array/sort-by`\n\nSorts an array of objects by a property or using a custom function.\n\n```typescript\nimport { sortBy } from '@/lib/utils/array-sort-by';\n\nconst users = [\n  { name: 'Alice', age: 30 },\n  { name: 'Bob', age: 25 },\n  { name: 'Charlie', age: 35 },\n];\n\n// Sort by property\nconst byAge = sortBy(users, 'age');\n// Result: [Bob (25), Alice (30), Charlie (35)]\n\n// Sort descending\nconst byAgeDesc = sortBy(users, 'age', 'desc');\n// Result: [Charlie (35), Alice (30), Bob (25)]\n\n// Sort by custom function\nconst byNameLength = sortBy(users, u =\u003e u.name.length);\n// Result: [Bob, Alice, Charlie]\n```\n\n#### `array/union`\n\nCreates an array of unique values from all provided arrays.\n\n```typescript\nimport { union } from '@/lib/utils/array-union';\n\nconst arr1 = [1, 2, 3];\nconst arr2 = [2, 3, 4];\nconst arr3 = [3, 4, 5];\nconst combined = union(arr1, arr2, arr3);\n// Result: [1, 2, 3, 4, 5]\n```\n\n#### `array/zip`\n\nCreates an array of grouped elements from multiple arrays.\n\n```typescript\nimport { zip } from '@/lib/utils/array-zip';\n\nconst names = ['Alice', 'Bob', 'Charlie'];\nconst ages = [25, 30, 35];\nconst cities = ['NYC', 'LA', 'Chicago'];\nconst combined = zip(names, ages, cities);\n// Result: [['Alice', 25, 'NYC'], ['Bob', 30, 'LA'], ['Charlie', 35, 'Chicago']]\n```\n\n### Boolean Utilities\n\n#### `boolean/is-falsy`\n\nChecks if a value is falsy (false, 0, \"\", null, undefined, NaN).\n\n```typescript\nimport { isFalsy } from '@/lib/utils/boolean-is-falsy';\n\nisFalsy(''); // true\nisFalsy(0); // true\nisFalsy('hello'); // false\n```\n\n#### `boolean/is-truthy`\n\nChecks if a value is truthy (anything that is not falsy).\n\n```typescript\nimport { isTruthy } from '@/lib/utils/boolean-is-truthy';\n\nisTruthy('hello'); // true\nisTruthy([]); // true\nisTruthy(0); // false\n```\n\n### Function Utilities\n\n#### `function/debounce`\n\nCreates a debounced function that delays invoking until after wait milliseconds have elapsed.\n\n```typescript\nimport { debounce } from '@/lib/utils/function-debounce';\n\nconst handleSearch = (query: string) =\u003e console.log('Searching:', query);\nconst debouncedSearch = debounce(handleSearch, 300);\n\ndebouncedSearch('a'); // Canceled\ndebouncedSearch('ap'); // Canceled\ndebouncedSearch('app'); // Executes after 300ms\n```\n\n#### `function/once`\n\nCreates a function that can only be invoked once. Subsequent calls return the result of the first invocation.\n\n```typescript\nimport { once } from '@/lib/utils/function-once';\n\nconst initialize = () =\u003e {\n  console.log('Initializing...');\n  return { status: 'initialized' };\n};\n\nconst initOnce = once(initialize);\n\nconst result1 = initOnce(); // Logs: 'Initializing...', returns { status: 'initialized' }\nconst result2 = initOnce(); // Returns cached result, no log\nconsole.log(result1 === result2); // true\n```\n\n#### `function/throttle`\n\nCreates a throttled function that only invokes the provided function at most once per specified time period.\n\n```typescript\nimport { throttle } from '@/lib/utils/function-throttle';\n\nconst handleScroll = () =\u003e console.log('Scrolling...');\nconst throttledScroll = throttle(handleScroll, 200);\n\n// Attach to scroll event\nwindow.addEventListener('scroll', throttledScroll);\n// Will execute immediately, then at most once every 200ms\n```\n\n### JSON Utilities\n\n#### `json/safe-parse`\n\nSafely parses a JSON string, returning undefined if parsing fails.\n\n```typescript\nimport { safeParse } from '@/lib/utils/json-safe-parse';\n\nconst validJson = '{\"name\": \"John\"}';\nconst result = safeParse\u003c{ name: string }\u003e(validJson);\n// Result: { name: \"John\" }\n\nconst invalidJson = '{name: \"John\"}';\nconst failed = safeParse(invalidJson);\n// Result: undefined\n```\n\n### Object Utilities\n\n#### `object/pick`\n\nCreates a new object composed of the picked object properties.\n\n```typescript\nimport { pick } from '@/lib/utils/object-pick';\n\nconst user = { id: 1, name: 'John', email: 'john@example.com', age: 30 };\nconst publicInfo = pick(user, ['id', 'name']);\n// Result: { id: 1, name: 'John' }\n```\n\n#### `object/omit`\n\nCreates a new object by omitting specified keys from the source object.\n\n```typescript\nimport { omit } from '@/lib/utils/object-omit';\n\nconst user = {\n  id: 1,\n  name: 'John',\n  email: 'john@example.com',\n  password: 'secret123',\n};\nconst publicUser = omit(user, ['password', 'email']);\n// Result: { id: 1, name: 'John' }\n```\n\n#### `object/merge`\n\nDeep merges multiple objects into a single object.\n\n```typescript\nimport { merge } from '@/lib/utils/object-merge';\n\nconst obj1 = { a: 1, b: { x: 1, y: 2 } };\nconst obj2 = { b: { z: 3 }, c: 4 };\nconst merged = merge(obj1, obj2);\n// Result: { a: 1, b: { x: 1, y: 2, z: 3 }, c: 4 }\n```\n\n#### `object/clone`\n\nCreates a deep copy of an object.\n\n```typescript\nimport { clone } from '@/lib/utils/object-clone';\n\nconst original = { name: 'John', address: { city: 'NYC' } };\nconst cloned = clone(original);\ncloned.address.city = 'LA';\nconsole.log(original.address.city); // 'NYC' (unchanged)\n```\n\n#### `object/has-path`\n\nChecks if a nested property path exists in an object.\n\n```typescript\nimport { hasPath } from '@/lib/utils/object-has-path';\n\nconst user = { profile: { settings: { theme: 'dark' } } };\nhasPath(user, 'profile.settings.theme'); // true\nhasPath(user, 'profile.settings.language'); // false\n```\n\n### Promise Utilities\n\n#### `promise/delay`\n\nReturns a promise that resolves after a given number of milliseconds.\n\n```typescript\nimport { delay } from '@/lib/utils/promise-delay';\n\n// Simple delay\nawait delay(1000); // Wait 1 second\nconsole.log('This runs after 1 second');\n\n// Rate limiting\nfor (const item of items) {\n  await processItem(item);\n  await delay(100); // 100ms between each item\n}\n```\n\n#### `promise/retry`\n\nRetries a promise-returning function a specified number of times with exponential backoff.\n\n```typescript\nimport { retry } from '@/lib/utils/promise-retry';\n\n// Retry a fetch request\nconst fetchData = () =\u003e fetch('https://api.example.com/data');\nconst result = await retry(fetchData, { retries: 3 });\n\n// Custom retry configuration\nconst result = await retry(fetchData, {\n  retries: 5,\n  delay: 500, // Start with 500ms delay\n  backoff: 2, // Double delay each retry\n});\n```\n\n#### `promise/timeout`\n\nWraps a promise with a timeout, rejecting if it doesn't resolve within the specified time.\n\n```typescript\nimport { timeout } from '@/lib/utils/promise-timeout';\n\n// Fetch with timeout\nconst fetchData = fetch('https://api.example.com/data');\nconst result = await timeout(fetchData, 5000);\n// Rejects if fetch takes longer than 5 seconds\n\n// Custom timeout message\nconst result = await timeout(slowOperation(), 3000, 'Operation took too long');\n```\n\n### String Utilities\n\n#### `string/capitalize`\n\nCapitalizes the first letter of a string while leaving the rest unchanged.\n\n```typescript\nimport { capitalize } from '@/lib/utils/string-capitalize';\n\ncapitalize('hello world'); // 'Hello world'\ncapitalize('javaScript'); // 'JavaScript'\ncapitalize(''); // ''\n```\n\n#### `string/kebab-case`\n\nConverts a string to kebab-case.\n\n```typescript\nimport { kebabCase } from '@/lib/utils/string-kebab-case';\n\nkebabCase('Hello World'); // 'hello-world'\nkebabCase('firstName'); // 'first-name'\nkebabCase('XMLHttpRequest'); // 'xml-http-request'\nkebabCase('snake_case_string'); // 'snake-case-string'\n```\n\n#### `string/snake-case`\n\nConverts a string to snake_case.\n\n```typescript\nimport { snakeCase } from '@/lib/utils/string-snake-case';\n\nsnakeCase('Hello World'); // 'hello_world'\nsnakeCase('firstName'); // 'first_name'\nsnakeCase('XMLHttpRequest'); // 'xml_http_request'\nsnakeCase('kebab-case-string'); // 'kebab_case_string'\n```\n\n#### `string/camel-case`\n\nConverts a string to camelCase.\n\n```typescript\nimport { camelCase } from '@/lib/utils/string-camel-case';\n\ncamelCase('Hello World'); // 'helloWorld'\ncamelCase('first_name'); // 'firstName'\ncamelCase('kebab-case-string'); // 'kebabCaseString'\ncamelCase('PascalCase'); // 'pascalCase'\n```\n\n#### `string/pascal-case`\n\nConverts a string to PascalCase.\n\n```typescript\nimport { pascalCase } from '@/lib/utils/string-pascal-case';\n\npascalCase('Hello World'); // 'HelloWorld'\npascalCase('first_name'); // 'FirstName'\npascalCase('kebab-case-string'); // 'KebabCaseString'\npascalCase('camelCase'); // 'CamelCase'\n```\n\n#### `string/pad-end`\n\nPads the end of a string with another string until it reaches the target length.\n\n```typescript\nimport { padEnd } from '@/lib/utils/string-pad-end';\n\npadEnd('abc', 5, '0'); // 'abc00'\npadEnd('hello', 10, '.'); // 'hello.....'\npadEnd('test', 8); // 'test    ' (pads with spaces by default)\n```\n\n#### `string/pad-start`\n\nPads the start of a string with another string until it reaches the target length.\n\n```typescript\nimport { padStart } from '@/lib/utils/string-pad-start';\n\npadStart('abc', 5, '0'); // '00abc'\npadStart('5', 3, '0'); // '005'\npadStart('test', 8); // '    test' (pads with spaces by default)\n```\n\n#### `string/reverse`\n\nReverses the characters of a string.\n\n```typescript\nimport { reverse } from '@/lib/utils/string-reverse';\n\nreverse('hello'); // 'olleh'\nreverse('JavaScript'); // 'tpircSavaJ'\nreverse('12345'); // '54321'\n```\n\n#### `string/slugify`\n\nConverts a string into a URL-friendly slug.\n\n```typescript\nimport { slugify } from '@/lib/utils/string-slugify';\n\nslugify('Hello World!'); // 'hello-world'\nslugify('  Hello World!  -- 123  '); // 'hello-world-123'\nslugify('français café'); // 'francais-cafe'\nslugify('Hello World', { separator: '_' }); // 'hello_world'\n```\n\n#### `string/truncate`\n\nTruncates a string if it's longer than the given maximum length.\n\n```typescript\nimport { truncate } from '@/lib/utils/string-truncate';\n\ntruncate('Hello world, this is a long string', { length: 20 });\n// 'Hello world, this...'\n\ntruncate('Short', { length: 20 }); // 'Short'\n\ntruncate('Hello world!', { length: 10, omission: '…' });\n// 'Hello wor…'\n```\n\n#### `string/escape-html`\n\nEscapes HTML special characters in a string to prevent XSS attacks.\n\n```typescript\nimport { escapeHtml } from '@/lib/utils/string-escape-html';\n\nescapeHtml('\u003cscript\u003ealert(\"XSS\")\u003c/script\u003e');\n// '\u0026lt;script\u0026gt;alert(\u0026quot;XSS\u0026quot;)\u0026lt;/script\u0026gt;'\n\nescapeHtml('5 \u003c 10 \u0026 10 \u003e 5');\n// '5 \u0026lt; 10 \u0026amp; 10 \u0026gt; 5'\n\nescapeHtml(\"It's a \u003ctest\u003e \u0026 'example'\");\n// 'It\u0026#x27;s a \u0026lt;test\u0026gt; \u0026amp; \u0026#x27;example\u0026#x27;'\n```\n\n### Number Utilities\n\n#### `number/clamp`\n\nConstrains a number to be within a specified range.\n\n```typescript\nimport { clamp } from '@/lib/utils/number-clamp';\n\nclamp(15, 10, 20); // 15\nclamp(5, 10, 20); // 10\nclamp(25, 10, 20); // 20\n```\n\n#### `number/random`\n\nGenerates a random number within a specified range.\n\n```typescript\nimport { random } from '@/lib/utils/number-random';\n\nrandom(1, 10); // Random float between 1 and 10\nrandom(1, 10, { integer: true }); // Random integer between 1 and 10\nrandom(0, 1); // Random float between 0 and 1\n```\n\n#### `number/round`\n\nRounds a number to a specified number of decimal places.\n\n```typescript\nimport { round } from '@/lib/utils/number-round';\n\nround(4.006, 2); // 4.01\nround(4.006, 0); // 4\nround(4.006); // 4\n```\n\n#### `number/format-number`\n\nFormats a number with locale-specific formatting.\n\n```typescript\nimport { formatNumber } from '@/lib/utils/number-format-number';\n\nformatNumber(1234.56); // '1,234.56'\nformatNumber(1234.56, { locale: 'de-DE' }); // '1.234,56'\nformatNumber(1234.56, { minimumFractionDigits: 3 }); // '1,234.560'\n```\n\n#### `number/sum`\n\nCalculates the sum of an array of numbers.\n\n```typescript\nimport { sum } from '@/lib/utils/number-sum';\n\nsum([1, 2, 3, 4, 5]); // 15\nsum([10, -5, 3]); // 8\nsum([2.5, 3.7, 1.8]); // 8\nsum([]); // 0\n\nconst prices = [19.99, 29.99, 9.99];\nconst total = sum(prices); // 59.97\n```\n\n#### `number/average`\n\nCalculates the average (arithmetic mean) of an array of numbers.\n\n```typescript\nimport { average } from '@/lib/utils/number-average';\n\naverage([1, 2, 3, 4, 5]); // 3\naverage([10, 20, 30]); // 20\naverage([2.5, 3.5, 4.5]); // 3.5\naverage([]); // 0\n\nconst grades = [85, 92, 78, 95, 88];\nconst avgGrade = average(grades); // 87.6\n```\n\n### Date Utilities\n\n#### `date/add-days`\n\nAdds a specified number of days to a date.\n\n```typescript\nimport { addDays } from '@/lib/utils/date-add-days';\n\nconst date = new Date('2024-01-15');\nconst future = addDays(date, 7); // 2024-01-22\nconst past = addDays(date, -7); // 2024-01-08\n```\n\n#### `date/format-date`\n\nFormats a date using a flexible format string.\n\n```typescript\nimport { formatDate } from '@/lib/utils/date-format-date';\n\nconst date = new Date('2024-03-15T14:30:00');\n\nformatDate(date, 'YYYY-MM-DD'); // '2024-03-15'\nformatDate(date, 'MM/DD/YYYY'); // '03/15/2024'\nformatDate(date, 'DD MMM YYYY'); // '15 Mar 2024'\nformatDate(date, 'HH:mm:ss'); // '14:30:00'\n```\n\n#### `date/is-weekend`\n\nChecks if a date falls on a weekend (Saturday or Sunday).\n\n```typescript\nimport { isWeekend } from '@/lib/utils/date-is-weekend';\n\nconst saturday = new Date('2024-03-16');\nconst monday = new Date('2024-03-18');\n\nisWeekend(saturday); // true\nisWeekend(monday); // false\n```\n\n#### `date/time-ago`\n\nFormats a date as a relative time string (e.g., \"2 hours ago\").\n\n```typescript\nimport { timeAgo } from '@/lib/utils/date-time-ago';\n\nconst now = new Date();\nconst past = new Date(now.getTime() - 2 * 60 * 60 * 1000);\n\ntimeAgo(past); // '2 hours ago'\ntimeAgo(new Date(now.getTime() - 30 * 1000)); // '30 seconds ago'\ntimeAgo(new Date(now.getTime() - 24 * 60 * 60 * 1000)); // '1 day ago'\n```\n\n### Object Utilities\n\n#### `url/parse-url`\n\nParses a URL string into its component parts.\n\n```typescript\nimport { parseUrl } from '@/lib/utils/url-parse-url';\n\nparseUrl('https://example.com:8080/path?query=value#hash');\n// {\n//   protocol: 'https:',\n//   host: 'example.com:8080',\n//   hostname: 'example.com',\n//   port: '8080',\n//   pathname: '/path',\n//   search: '?query=value',\n//   hash: '#hash',\n//   origin: 'https://example.com:8080'\n// }\n```\n\n#### `url/build-query`\n\nBuilds a URL query string from an object of parameters.\n\n```typescript\nimport { buildQuery } from '@/lib/utils/url-build-query';\n\nbuildQuery({ name: 'John Doe', age: 30 });\n// 'name=John%20Doe\u0026age=30'\n\nbuildQuery({ tags: ['red', 'blue'], active: true });\n// 'tags=red\u0026tags=blue\u0026active=true'\n\nbuildQuery({ search: 'hello world' }, { prefix: true });\n// '?search=hello%20world'\n```\n\n#### `url/is-valid-url`\n\nChecks if a string is a valid URL.\n\n```typescript\nimport { isValidUrl } from '@/lib/utils/url-is-valid-url';\n\nisValidUrl('https://example.com'); // true\nisValidUrl('not-a-url'); // false\nisValidUrl('https://example.com', { protocols: ['https'] }); // true\nisValidUrl('example.com', { requireProtocol: false }); // true\n```\n\n#### `url/sanitize-url`\n\nSanitizes a URL by removing or encoding potentially dangerous elements.\n\n```typescript\nimport { sanitizeUrl } from '@/lib/utils/url-sanitize-url';\n\nsanitizeUrl('https://example.com/path?query=value');\n// 'https://example.com/path?query=value'\n\nsanitizeUrl('javascript:alert(\"xss\")'); // null\nsanitizeUrl('//example.com/path', { defaultProtocol: 'https' });\n// 'https://example.com/path'\n```\n\n#### `url/resolve-url`\n\nResolves a relative URL against a base URL.\n\n```typescript\nimport { resolveUrl } from '@/lib/utils/url-resolve-url';\n\nresolveUrl('page.html', 'https://example.com/base/');\n// 'https://example.com/base/page.html'\n\nresolveUrl('../other.html', 'https://example.com/base/page.html');\n// 'https://example.com/other.html'\n\nresolveUrl('/absolute/path', 'https://example.com/base/');\n// 'https://example.com/absolute/path'\n```\n\n---\n\n## CLI Commands\n\nFragmen provides a comprehensive CLI with 6 commands to help you discover, preview, and add utilities to your project.\n\n### `init`\n\nInitialize your project with a `fragmen.json` configuration file.\n\n```bash\nnpx fragmen init\n```\n\nThis command will prompt you for:\n\n- **Base directory**: Where utilities should be copied (default: `lib/utils`)\n- **Language**: TypeScript or JavaScript\n- **Module system**: ESM or CommonJS\n\nCreates a `fragmen.json` file in your project root with your preferences.\n\n---\n\n### `browse`\n\n**Interactive menu for discovering and adding utilities.** This is the recommended way to explore the registry.\n\n```bash\nnpx fragmen browse\n```\n\nFeatures:\n\n- **Category selection**: Browse utilities organized by category\n- **Multi-select**: Use spacebar to select multiple utilities\n- **Utility counts**: See how many utilities are in each category\n- **Bulk add**: Add all selected utilities at once with progress feedback\n- **Keyboard navigation**: Arrow keys to navigate, Enter to confirm, Ctrl+C to cancel\n\nExample workflow:\n\n```\n🔍 Browse Fragmen Utilities\n\n? Select a category: promise (3 utilities)\n? Select utilities from promise:\n  ◉ delay\n  ◉ retry\n  ◯ timeout\n\n? Add 2 utilities? Yes\n\nAdding 2 utilities...\n\n✓ promise/delay\n✓ promise/retry\n\n✓ Successfully added 2 utilities\n  Location: lib/utils/\n```\n\n---\n\n### `show`\n\nPreview detailed information about a utility before adding it.\n\n```bash\nnpx fragmen show \u003ccategory/utility-name\u003e\n```\n\n**Examples:**\n\n```bash\nnpx fragmen show promise/delay\nnpx fragmen show string/slugify\nnpx fragmen show array/chunk\n```\n\nDisplays:\n\n- **Description**: What the utility does (from JSDoc)\n- **Usage command**: How to add it to your project\n- **Code example**: How to use the utility\n- **Source location**: Where to find the source code\n\nExample output:\n\n```\n📖 promise/delay\n\nReturns a promise that resolves after a given number of milliseconds.\n\nUsage:\n  fragmen add promise/delay\n\nExample:\nawait delay(1000);\nconsole.log('This runs after 1 second');\n\nSource:\n  registry/promise/delay/index.ts\n```\n\n---\n\n### `list [category]`\n\nDisplay all available utilities, optionally filtered by category.\n\n```bash\n# List all utilities (50+ total)\nnpx fragmen list\n\n# List utilities in a specific category\nnpx fragmen list promise\nnpx fragmen list string\nnpx fragmen list array\nnpx fragmen list function\n```\n\n**Examples:**\n\n```bash\n# See all categories and utilities\n$ npx fragmen list\n\n📦 Available Utilities\n\narray/ (10)\n  • array/chunk\n  • array/compact\n  • array/difference\n  ...\n\npromise/ (3)\n  • promise/delay\n  • promise/retry\n  • promise/timeout\n\nstring/ (11)\n  • string/camel-case\n  • string/capitalize\n  ...\n\nTotal: 50 utilities\n\n# Filter by category\n$ npx fragmen list promise\n\n📦 promise Utilities\n\npromise/ (3)\n  • promise/delay\n  • promise/retry\n  • promise/timeout\n\n3 utilities in promise\n```\n\n---\n\n### `add`\n\nCopy one or more utilities from the registry into your project.\n\n```bash\n# Add a single utility\nnpx fragmen add \u003ccategory/utility-name\u003e\n\n# Add multiple utilities at once\nnpx fragmen add \u003cutility1\u003e \u003cutility2\u003e \u003cutility3\u003e\n```\n\n**Examples:**\n\n```bash\n# Add a single utility\nnpx fragmen add promise/delay\n\n# Add multiple utilities at once (bulk add)\nnpx fragmen add promise/delay promise/retry string/capitalize\n\n# Add multiple utilities from different categories\nnpx fragmen add array/chunk array/unique number/clamp string/slugify\n```\n\n**Single utility output:**\n\n```\nAdding 1 utility...\n\n✓ promise/delay\n\n✓ Successfully added 1 utility\n  Location: lib/utils/\n```\n\n**Bulk add output:**\n\n```\nAdding 3 utilities...\n\n✓ promise/delay\n✓ promise/retry\n✓ string/capitalize\n\n✓ Successfully added 3 utilities\n  Location: lib/utils/\n```\n\nIf any utilities fail to add (e.g., not found or incorrect path), the command will show which ones succeeded and which failed:\n\n```\nAdding 3 utilities...\n\n✓ promise/delay\n✗ promise/invalid - not found\n✓ string/capitalize\n\n✓ Successfully added 2 utilities\n  Location: lib/utils/\n✗ Failed to add 1:\n  • promise/invalid\n\nRun \"fragmen list\" to see available utilities\n```\n\n**Important:** You must specify the full path including the category (e.g., `promise/delay`, not just `delay`).\n\n---\n\n### `release`\n\nBump version and publish to npm (for maintainers only).\n\n```bash\nnpx fragmen release [type]\n```\n\nOptions:\n\n- `patch` (default): 1.0.0 → 1.0.1\n- `minor`: 1.0.0 → 1.1.0\n- `major`: 1.0.0 → 2.0.0\n\nFlags:\n\n- `--no-push`: Skip pushing commit and tags\n- `--no-publish`: Skip npm publish\n- `--dry-run`: Show commands without executing\n- `--tag \u003cdistTag\u003e`: Publish under given npm dist-tag\n\n---\n\n## Quick Start Examples\n\n### Example 1: Setting up a new project\n\n```bash\n# 1. Initialize configuration\nnpx fragmen init\n\n# 2. Browse and add utilities interactively\nnpx fragmen browse\n  # Select 'promise' category\n  # Select 'delay' and 'retry'\n  # Confirm\n\n# 3. Start using them\nimport { delay, retry } from '@/lib/utils/promise-delay';\n```\n\n### Example 2: Adding specific utilities\n\n```bash\n# Preview what a utility does\nnpx fragmen show string/slugify\n\n# Add it if you like it\nnpx fragmen add string/slugify\n\n# Or add multiple at once\nnpx fragmen add string/slugify string/capitalize string/truncate\n```\n\n### Example 3: Exploring a category\n\n```bash\n# See what's available in the array category\nnpx fragmen list array\n\n# Add the ones you need\nnpx fragmen add array/chunk array/unique array/flatten\n```\n\n---\n\n## Testing \u0026 Coverage\n\nThis project uses Vitest for testing and includes comprehensive coverage reporting.\n\n### Running Tests\n\n```bash\n# Run tests once\nnpm run test:run\n\n# Run tests in watch mode\nnpm test\n\n# Run tests with coverage\nnpm run test:coverage\n\n# Run tests with coverage in watch mode\nnpm run test:coverage:watch\n\n# Generate coverage and open HTML report (macOS)\nnpm run test:coverage:open\n\n# Check coverage thresholds\nnpm run test:coverage:check\n```\n\n### Coverage Configuration\n\nThe project is configured with coverage thresholds of 80% for:\n\n- Lines\n- Functions\n- Branches\n- Statements\n\nCoverage reports are generated in multiple formats:\n\n- **Text**: Displayed in terminal\n- **JSON**: `coverage/coverage-final.json`\n- **HTML**: `coverage/index.html` - Interactive report\n\n### Coverage Scripts\n\nFor advanced coverage operations, use the coverage utility script:\n\n```bash\n# Generate coverage report\nnode scripts/coverage.js generate\n\n# Run coverage in watch mode\nnode scripts/coverage.js watch\n\n# Check if coverage meets thresholds\nnode scripts/coverage.js check\n```\n\n### Automatic Badge Updates\n\nThe coverage badges in this README are automatically updated by GitHub Actions whenever code is pushed to the main branch. This ensures the badges always reflect the current test coverage.\n\n---\n\n## Contributing\n\nThis project is open-source and contributions are welcome! Feel free to open an issue to suggest a new fragment or submit a pull request to add one.\n\n## License\n\nLicensed under the **MIT License**.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwiesnerbernard%2Ffragmen","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwiesnerbernard%2Ffragmen","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwiesnerbernard%2Ffragmen/lists"}