The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .all-contributorsrc
├── .github
    └── workflows
    │   ├── ci.yml
    │   └── codecov.yml
├── .gitignore
├── .wispbit
    └── rules
    │   ├── comprehensive-documentation.md
    │   ├── comprehensive-testing-standards.md
    │   ├── consistent-naming-conventions.md
    │   ├── documentation-examples-accuracy.md
    │   ├── edge-case-handling.md
    │   ├── object-key-transformation-pattern.md
    │   ├── project-configuration-standards.md
    │   └── type-safety-dual-implementation.md
├── LICENSE
├── README.md
├── SOW.md
├── biome.json
├── codecov.yml
├── docs
    └── string-ts-banner.png
├── package.json
├── pnpm-lock.yaml
├── scripts
    └── generate-entrypoints.mts
├── src
    ├── index.ts
    ├── internal
    │   ├── fixtures.ts
    │   ├── internals.test.ts
    │   ├── internals.ts
    │   ├── literals.test.ts
    │   ├── literals.ts
    │   ├── math.test.ts
    │   ├── math.ts
    │   └── types.d.ts
    ├── native
    │   ├── char-at.test.ts
    │   ├── char-at.ts
    │   ├── concat.test.ts
    │   ├── concat.ts
    │   ├── ends-with.test.ts
    │   ├── ends-with.ts
    │   ├── includes.test.ts
    │   ├── includes.ts
    │   ├── index.d.ts
    │   ├── join.test.ts
    │   ├── join.ts
    │   ├── length.test.ts
    │   ├── length.ts
    │   ├── native-overrides.test.ts
    │   ├── pad-end.test.ts
    │   ├── pad-end.ts
    │   ├── pad-start.test.ts
    │   ├── pad-start.ts
    │   ├── repeat.test.ts
    │   ├── repeat.ts
    │   ├── replace-all.test.ts
    │   ├── replace-all.ts
    │   ├── replace.test.ts
    │   ├── replace.ts
    │   ├── slice.test.ts
    │   ├── slice.ts
    │   ├── split.test.ts
    │   ├── split.ts
    │   ├── starts-with.test.ts
    │   ├── starts-with.ts
    │   ├── to-lower-case.test.ts
    │   ├── to-lower-case.ts
    │   ├── to-upper-case.test.ts
    │   ├── to-upper-case.ts
    │   ├── trim-end.test.ts
    │   ├── trim-end.ts
    │   ├── trim-start.test.ts
    │   ├── trim-start.ts
    │   ├── trim.test.ts
    │   └── trim.ts
    └── utils
    │   ├── characters
    │       ├── apostrophe.ts
    │       ├── letters.test.ts
    │       ├── letters.ts
    │       ├── numbers.test.ts
    │       ├── numbers.ts
    │       ├── separators.test.ts
    │       ├── separators.ts
    │       ├── special.test.ts
    │       └── special.ts
    │   ├── object-keys
    │       ├── camel-keys.test.ts
    │       ├── camel-keys.ts
    │       ├── constant-keys.test.ts
    │       ├── constant-keys.ts
    │       ├── deep-camel-keys.test.ts
    │       ├── deep-camel-keys.ts
    │       ├── deep-constant-keys.test.ts
    │       ├── deep-constant-keys.ts
    │       ├── deep-delimiter-keys.test.ts
    │       ├── deep-delimiter-keys.ts
    │       ├── deep-kebab-keys.test.ts
    │       ├── deep-kebab-keys.ts
    │       ├── deep-pascal-keys.test.ts
    │       ├── deep-pascal-keys.ts
    │       ├── deep-snake-keys.test.ts
    │       ├── deep-snake-keys.ts
    │       ├── deep-transform-keys.test.ts
    │       ├── deep-transform-keys.ts
    │       ├── delimiter-keys.test.ts
    │       ├── delimiter-keys.ts
    │       ├── kebab-keys.test.ts
    │       ├── kebab-keys.ts
    │       ├── pascal-keys.test.ts
    │       ├── pascal-keys.ts
    │       ├── replace-keys.test.ts
    │       ├── replace-keys.ts
    │       ├── snake-keys.test.ts
    │       ├── snake-keys.ts
    │       ├── transform-keys.test.ts
    │       └── transform-keys.ts
    │   ├── reverse.test.ts
    │   ├── reverse.ts
    │   ├── truncate.test.ts
    │   ├── truncate.ts
    │   ├── word-case
    │       ├── camel-case.test.ts
    │       ├── camel-case.ts
    │       ├── capitalize.test.ts
    │       ├── capitalize.ts
    │       ├── constant-case.test.ts
    │       ├── constant-case.ts
    │       ├── delimiter-case.test.ts
    │       ├── delimiter-case.ts
    │       ├── kebab-case.test.ts
    │       ├── kebab-case.ts
    │       ├── lower-case.test.ts
    │       ├── lower-case.ts
    │       ├── pascal-case.test.ts
    │       ├── pascal-case.ts
    │       ├── snake-case.test.ts
    │       ├── snake-case.ts
    │       ├── title-case.test.ts
    │       ├── title-case.ts
    │       ├── uncapitalize.test.ts
    │       ├── uncapitalize.ts
    │       ├── upper-case.test.ts
    │       └── upper-case.ts
    │   ├── words.test.ts
    │   └── words.ts
├── tsconfig.dist.json
├── tsconfig.json
└── vitest.config.ts


/.all-contributorsrc:
--------------------------------------------------------------------------------
 1 | {
 2 |   "files": ["README.md"],
 3 |   "imageSize": 100,
 4 |   "commit": false,
 5 |   "commitType": "docs",
 6 |   "commitConvention": "angular",
 7 |   "contributors": [
 8 |     {
 9 |       "login": "gustavoguichard",
10 |       "name": "Guga Guichard",
11 |       "avatar_url": "https://avatars.githubusercontent.com/u/566971?v=4",
12 |       "profile": "https://github.com/gustavoguichard",
13 |       "contributions": [
14 |         "code",
15 |         "projectManagement",
16 |         "promotion",
17 |         "maintenance",
18 |         "doc",
19 |         "bug",
20 |         "infra",
21 |         "question",
22 |         "research",
23 |         "review",
24 |         "ideas",
25 |         "example"
26 |       ]
27 |     },
28 |     {
29 |       "login": "jly36963",
30 |       "name": "Landon Yarrington",
31 |       "avatar_url": "https://avatars.githubusercontent.com/u/33426811?v=4",
32 |       "profile": "https://github.com/jly36963",
33 |       "contributions": [
34 |         "code",
35 |         "maintenance",
36 |         "doc",
37 |         "review",
38 |         "ideas",
39 |         "example",
40 |         "question",
41 |         "bug"
42 |       ]
43 |     },
44 |     {
45 |       "login": "p9f",
46 |       "name": "Guillaume",
47 |       "avatar_url": "https://avatars.githubusercontent.com/u/20539361?v=4",
48 |       "profile": "https://github.com/p9f",
49 |       "contributions": [
50 |         "code",
51 |         "maintenance",
52 |         "doc",
53 |         "bug",
54 |         "infra",
55 |         "question",
56 |         "ideas"
57 |       ]
58 |     },
59 |     {
60 |       "login": "mattpocock",
61 |       "name": "Matt Pocock",
62 |       "avatar_url": "https://avatars.githubusercontent.com/u/28293365?v=4",
63 |       "profile": "https://totaltypescript.com",
64 |       "contributions": ["doc", "code", "promotion"]
65 |     },
66 |     {
67 |       "login": "iamandrewluca",
68 |       "name": "Andrew Luca",
69 |       "avatar_url": "https://avatars.githubusercontent.com/u/1881266?v=4",
70 |       "profile": "https://luca.md",
71 |       "contributions": ["doc", "promotion"]
72 |     },
73 |     {
74 |       "login": "mjuksel",
75 |       "name": "Mjuksel",
76 |       "avatar_url": "https://avatars.githubusercontent.com/u/10691584?v=4",
77 |       "profile": "https://github.com/mjuksel",
78 |       "contributions": ["code", "ideas"]
79 |     },
80 |     {
81 |       "login": "hverlin",
82 |       "name": "hverlin",
83 |       "avatar_url": "https://avatars.githubusercontent.com/u/9151470?v=4",
84 |       "profile": "https://huguesverlin.fr",
85 |       "contributions": ["code"]
86 |     }
87 |   ],
88 |   "contributorsPerLine": 7,
89 |   "skipCi": true,
90 |   "repoType": "github",
91 |   "repoHost": "https://github.com",
92 |   "projectName": "string-ts",
93 |   "projectOwner": "gustavoguichard"
94 | }
95 | 


--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
 1 | on:
 2 |   push:
 3 |     branches: [main]
 4 |   pull_request:
 5 | 
 6 | jobs:
 7 |   typecheck-outputs:
 8 |     name: 🚚 Typecheck Outputs / ${{ matrix.typescript-version }}
 9 |     runs-on: ubuntu-latest
10 |     strategy:
11 |       fail-fast: false
12 |       matrix:
13 |         typescript-version:
14 |           - '~5.7.0'
15 |           - '~5.6.0'
16 |           - '~5.5.0'
17 |           - '~5.4.0'
18 |           - '~5.3.0'
19 |           - '~5.2.0'
20 |           - '~5.1.0'
21 |             # We use features that were added in v5.0 of typescript, so that is
22 |             # the lowest we can go here. This also means this is the lowest
23 |             # version we support. When this value changes in the future it needs
24 |             # to be communicated to the users.
25 |           - '~5.0.0'
26 | 
27 |     steps:
28 |       - name: ⬇️ Checkout repo
29 |         uses: actions/checkout@v4
30 | 
31 |       - name: 📦 Manually Install pnpm
32 |         run: npm install -g pnpm@10
33 |       
34 |       - name: 🔧 Ensure TSX is available
35 |         run: pnpm add -D tsx
36 | 
37 |       - name: 🪡 Install Node.js
38 |         uses: actions/setup-node@v4
39 |         with:
40 |           node-version: '18'
41 |           cache: 'pnpm'
42 | 
43 |       - name: 📦 Install Dependencies
44 |         run: pnpm install --frozen-lockfile
45 | 
46 |       # Order is important here, we build with the typescript version defined
47 |       # in package.json, before we overrite it for the tests.
48 |       - name: 🏗️ Build
49 |         run: pnpm run build
50 | 
51 |       - name: 📘 Install Typescript
52 |         run: pnpm add -D typescript@${{ matrix.typescript-version }}
53 | 
54 |       - name: 🔎 Type check
55 |         run: pnpm run tsc:dist
56 | 
57 | 
58 |   test:
59 |     name: Run tests
60 |     runs-on: ubuntu-latest
61 | 
62 |     steps:
63 |       - name: 🛑 Cancel Previous Runs
64 |         uses: styfle/cancel-workflow-action@0.12.1
65 | 
66 |       - name: ⬇️ Checkout repo
67 |         uses: actions/checkout@v4
68 | 
69 |       - name: 📦 Manually Install pnpm
70 |         run: npm install -g pnpm@10
71 | 
72 |       - name: 🪡 Install Node.js
73 |         uses: actions/setup-node@v4
74 |         with:
75 |           node-version: '18'
76 |           cache: 'pnpm'
77 | 
78 |       - name: 📦 Install Dependencies
79 |         run: pnpm install --frozen-lockfile
80 | 
81 |       - name: ⚡ Run tests
82 |         run: |
83 |           pnpm run test
84 |       - name: 🚦 Lint
85 |         run: |
86 |           pnpm run lint
87 |       - name: 🧙🏿‍♂️ TSC
88 |         run: |
89 |           pnpm run tsc
90 |       - name: 📥 Generate npm package
91 |         run: |
92 |           pnpm run build
93 |       - name: ✅ Verify native entrypoint files
94 |         run: |
95 |           test -f dist/native.d.ts && test -f dist/native.js && test -f dist/native.mjs
96 | 


--------------------------------------------------------------------------------
/.github/workflows/codecov.yml:
--------------------------------------------------------------------------------
 1 | name: Codecov
 2 | 
 3 | on:
 4 |   push:
 5 |     branches: [main]
 6 |   pull_request:
 7 | 
 8 | jobs:
 9 |   codecov:
10 |     runs-on: ubuntu-latest
11 |     steps:
12 |       - name: Cancel Previous Runs
13 |         uses: styfle/cancel-workflow-action@0.12.1
14 | 
15 |       - name: Checkout
16 |         uses: actions/checkout@v4
17 | 
18 |       - name: 📦 Manually Install pnpm
19 |         run: npm install -g pnpm@10
20 | 
21 |       - name: Setup Node.js
22 |         uses: actions/setup-node@v4
23 |         with:
24 |           node-version: 18
25 |           cache: 'pnpm'
26 | 
27 |       - name: Install dependencies
28 |         run: pnpm install --frozen-lockfile
29 | 
30 |       - name: Run tests
31 |         run: pnpm run test -- --coverage
32 | 
33 |       - name: Upload coverage to github actions artifacts
34 |         uses: actions/upload-artifact@v4
35 |         with:
36 |           name: code-coverage
37 |           path: coverage/
38 | 
39 |       - name: Upload coverage to codecov
40 |         uses: codecov/codecov-action@v5
41 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bun.lockb
2 | .vscode/
3 | node_modules
4 | dist/
5 | npm
6 | tsc/
7 | .DS_Store
8 | 


--------------------------------------------------------------------------------
/.wispbit/rules/comprehensive-documentation.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | include: *.ts, *.tsx
 3 | ---
 4 | 
 5 | # Comprehensive Documentation Standards
 6 | 
 7 | All exported functions and types must be thoroughly documented to ensure usability and maintainability.
 8 | 
 9 | ## Requirements
10 | 
11 | 1. Include JSDoc comments for all exported functions and types
12 | 2. Always include example usage in function documentation
13 | 3. Document parameters with descriptive names and types
14 | 4. Document return values
15 | 5. Include edge case handling in documentation when relevant
16 | 
17 | ## Examples
18 | 
19 | ### Good ✅
20 | ```typescript
21 | /**
22 |  * A strongly-typed version of `String.prototype.charAt`.
23 |  * @param str the string to get the character from.
24 |  * @param index the index of the character.
25 |  * @returns the character in both type level and runtime.
26 |  * @example charAt('hello world', 6) // 'w'
27 |  */
28 | export function charAt<T extends string, I extends number>(
29 |   str: T,
30 |   index: I
31 | ): CharAt<T, I> {
32 |   return str.charAt(index) as CharAt<T, I>
33 | }
34 | ```
35 | 
36 | ### Bad ❌
37 | ```typescript
38 | // Missing documentation
39 | export function charAt<T extends string, I extends number>(
40 |   str: T,
41 |   index: I
42 | ): CharAt<T, I> {
43 |   return str.charAt(index) as CharAt<T, I>
44 | }
45 | 
46 | // Or incomplete documentation
47 | /**
48 |  * Gets a character from a string.
49 |  */
50 | export function charAt(str, index) {
51 |   return str.charAt(index)
52 | }
53 | ```


--------------------------------------------------------------------------------
/.wispbit/rules/comprehensive-testing-standards.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | include: *.test.ts
 3 | ---
 4 | 
 5 | # Comprehensive Testing Standards
 6 | 
 7 | All string utility functions must have thorough tests covering both runtime behavior and type-level correctness.
 8 | 
 9 | ## Testing Requirements
10 | 
11 | 1. Test both runtime functionality and type-level behavior
12 | 2. Include tests for edge cases (empty strings, negative indices, non-literal types)
13 | 3. Organize tests in a structured, consistent manner
14 | 4. Extract reusable test values to variables
15 | 
16 | ## Test Organization
17 | 
18 | 1. Group type tests within namespaces that reflect the functionality being tested
19 | 2. Group runtime tests within describe blocks that match the structure of type tests
20 | 3. Organize test suites alphabetically for better navigation
21 | 
22 | ## Examples
23 | 
24 | ### Good ✅
25 | 
26 | ```typescript
27 | // Type-level tests in a namespace
28 | namespace TruncateTests {
29 |   type test1 = Expect<Equal<Truncate<'Hello, world', 9, '...'>, 'Hello,...'>>
30 |   type test2 = Expect<Equal<Truncate<'Hello', 10, '...'>, 'Hello'>>
31 |   // Edge case: negative length
32 |   type test3 = Expect<Equal<Truncate<'Hello', -1, '...'>, '...'>>
33 | }
34 | 
35 | // Runtime tests in a matching describe block
36 | describe('truncate', () => {
37 |   // Extract reusable test values
38 |   const longText = 'Hello, world';
39 |   const shortText = 'Hello';
40 |   
41 |   test('truncates a string that exceeds target length', () => {
42 |     expect(truncate(longText, 9, '...')).toEqual('Hello,...');
43 |   });
44 |   
45 |   test('does not truncate a string shorter than target length', () => {
46 |     expect(truncate(shortText, 10, '...')).toEqual('Hello');
47 |   });
48 |   
49 |   // Edge case test
50 |   test('handles negative length by returning just the omission', () => {
51 |     expect(truncate(longText, -1, '...')).toEqual('...');
52 |   });
53 | });
54 | ```
55 | 
56 | ### Bad ❌
57 | 
58 | ```typescript
59 | // Disorganized testing
60 | describe('string functions', () => {
61 |   test('truncate works', () => {
62 |     expect(truncate('Hello, world', 9, '...')).toEqual('Hello,...');
63 |     expect(truncate('Hello', 10, '...')).toEqual('Hello');
64 |     // Missing edge case tests
65 |   });
66 |   
67 |   // Missing type tests
68 | });
69 | ```


--------------------------------------------------------------------------------
/.wispbit/rules/consistent-naming-conventions.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | include: *.ts, *.tsx
 3 | ---
 4 | 
 5 | # Consistent Naming Conventions
 6 | 
 7 | Maintain consistent naming patterns throughout the codebase to improve readability and predictability.
 8 | 
 9 | ## Naming Guidelines
10 | 
11 | 1. **Functions**: Use `lowerCamelCase` for all function names (e.g., `camelCase`, `trimStart`)
12 | 2. **Types**: Use `PascalCase` for all type definitions (e.g., `CamelCase`, `TrimStart`)
13 | 3. **Files**: Use `kebab-case` for all filenames (e.g., `camel-case.ts`, `trim-start.ts`)
14 | 4. **Case Transformations**: Prefer direct naming (e.g., `camelCase`) over "to" prefix (e.g., `toCamelCase`)
15 | 
16 | ## Deprecated Functions
17 | 
18 | For backward compatibility, older naming patterns should be preserved but marked as deprecated:
19 | 
20 | ```typescript
21 | /**
22 |  * @deprecated
23 |  * Use `camelCase` instead.
24 |  */
25 | export const toCamelCase = camelCase
26 | ```
27 | 
28 | ## Examples
29 | 
30 | ### Good ✅
31 | ```typescript
32 | // Type definition in PascalCase
33 | export type KebabCase<T extends string> = Lowercase<
34 |   DelimiterCase<RemoveApostrophe<T>, '-'>
35 | >
36 | 
37 | // Function in lowerCamelCase
38 | export function kebabCase<T extends string>(str: T): KebabCase<T> {
39 |   return toLowerCase(delimiterCase(removeApostrophe(str), '-'))
40 | }
41 | 
42 | // Deprecated function with notice
43 | /**
44 |  * @deprecated
45 |  * Use `kebabCase` instead.
46 |  */
47 | export const toKebabCase = kebabCase
48 | ```
49 | 
50 | ### Bad ❌
51 | ```typescript
52 | // Inconsistent naming
53 | export type kebab_case<T extends string> = Lowercase<
54 |   DelimiterCase<RemoveApostrophe<T>, '-'>
55 | >
56 | 
57 | export function KebabCase<T extends string>(str: T) {
58 |   return toLowerCase(delimiterCase(removeApostrophe(str), '-'))
59 | }
60 | 
61 | // Missing deprecation notice
62 | export const toKebabCase = kebabCase
63 | ```


--------------------------------------------------------------------------------
/.wispbit/rules/documentation-examples-accuracy.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | include: *.md
 3 | ---
 4 | 
 5 | # Documentation Examples Accuracy
 6 | 
 7 | All code examples in documentation (README files, comments, JSDoc) must be accurate and demonstrate the actual behavior of the functions.
 8 | 
 9 | ## Requirements
10 | 
11 | 1. Examples must produce the exact outputs shown in comments
12 | 2. Examples should be simple and focused on demonstrating the feature
13 | 3. Include proper syntax highlighting for code blocks
14 | 4. Maintain alphabetical ordering of sections in documentation
15 | 5. Include badges for npm version, package size, and other project metrics when appropriate
16 | 
17 | ## Examples
18 | 
19 | ### Good ✅
20 | 
21 | ```markdown
22 | [![NPM](https://img.shields.io/npm/v/string-ts)](https://www.npmjs.org/package/string-ts)
23 | ![Library size](https://img.shields.io/bundlephobia/minzip/string-ts)
24 | 
25 | # string-ts
26 | 
27 | Strongly-typed string functions for all!
28 | 
29 | ## Usage
30 | 
31 | ```typescript
32 | import { camelCase, kebabCase } from 'string-ts';
33 | 
34 | // camelCase example
35 | const str1 = 'hello-world';
36 | const result1 = camelCase(str1);
37 | //    ^ 'helloWorld'
38 | 
39 | // kebabCase example
40 | const str2 = 'helloWorld';
41 | const result2 = kebabCase(str2);
42 | //    ^ 'hello-world'
43 | ```
44 | ```
45 | 
46 | ### Bad ❌
47 | 
48 | ```markdown
49 | # string-ts
50 | 
51 | Strongly-typed string functions.
52 | 
53 | ## Usage
54 | 
55 | ```typescript
56 | // Incorrect output
57 | const str = 'hello-world';
58 | const result = camelCase(str);
59 | //    ^ 'HelloWorld' // WRONG! camelCase would produce 'helloWorld'
60 | 
61 | // Inconsistent style
62 | const myString = 'hello-world';
63 | const output = camelCase(myString);
64 | ```
65 | ```


--------------------------------------------------------------------------------
/.wispbit/rules/edge-case-handling.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | include: *.ts, *.tsx
 3 | ---
 4 | 
 5 | # Edge Case Handling Requirements
 6 | 
 7 | String manipulation functions must properly handle edge cases to ensure consistent behavior. Always test and account for edge cases in both type-level and runtime implementations.
 8 | 
 9 | ## Critical Edge Cases
10 | 
11 | 1. Empty strings
12 | 2. Empty delimiters/separators 
13 | 3. Negative indexes for position-based operations
14 | 4. Cases where target length is shorter than input string
15 | 5. Non-literal types (e.g., `string` instead of string literals)
16 | 
17 | ## Examples
18 | 
19 | ### Good ✅
20 | 
21 | ```typescript
22 | // Properly handling empty strings and negative cases in types
23 | type TrimStart<T extends string> = T extends ` ${infer rest}`
24 |   ? TrimStart<rest>
25 |   : T
26 | 
27 | // Handling negative indexes in runtime
28 | function slice<T extends string, S extends number = 0, E extends number = Length<T>>(
29 |   str: T, 
30 |   start: S = 0 as S, 
31 |   end: E = str.length as E
32 | ): Slice<T, S, E> {
33 |   return str.slice(start, end) as Slice<T, S, E>
34 | }
35 | 
36 | // Handling non-literal types
37 | type CharAt<T extends string, index extends number> = All<
38 |   [IsStringLiteral<T>, IsNumberLiteral<index>]
39 | > extends true
40 |   ? Split<T>[index]
41 |   : string
42 | ```
43 | 
44 | ### Bad ❌
45 | 
46 | ```typescript
47 | // Not handling empty string inputs
48 | type Split<
49 |   T,
50 |   delimiter extends string,
51 | > = T extends `${infer first}${delimiter}${infer rest}`
52 |   ? [first, ...Split<rest, delimiter>]
53 |   : [T] // This could result in [''] for empty strings, which might be unexpected
54 | 
55 | // Not handling negative indexes
56 | function charAt<T extends string, I extends number>(
57 |   str: T,
58 |   index: I
59 | ): CharAt<T, I> {
60 |   // No handling for negative indexes
61 |   return str.charAt(index) as CharAt<T, I>
62 | }
63 | ```


--------------------------------------------------------------------------------
/.wispbit/rules/object-key-transformation-pattern.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | include: *.ts, *.tsx
 3 | ---
 4 | 
 5 | # Object Key Transformation Pattern
 6 | 
 7 | When implementing object key transformations, follow a consistent pattern and clearly distinguish between shallow and deep (recursive) transformations.
 8 | 
 9 | ## Requirements
10 | 
11 | 1. **Shallow vs Deep**: Clearly identify functions that transform only top-level keys versus those that recursively transform nested objects
12 | 2. **Naming Convention**: Use `camelKeys` for shallow transforms and `deepCamelKeys` for recursive transforms
13 | 3. **Type Definitions**: Provide corresponding type definitions (`CamelKeys<T>` and `DeepCamelKeys<T>`)
14 | 4. **Documentation**: Clearly document the transformation depth in JSDoc comments
15 | 
16 | ## Examples
17 | 
18 | ### Good ✅
19 | 
20 | ```typescript
21 | /**
22 |  * A strongly typed function that shallowly transforms the keys of an object to camelCase.
23 |  * The transformation is done both at runtime and type level.
24 |  */
25 | function camelKeys<T>(obj: T): CamelKeys<T> {
26 |   return transformKeys(obj, camelCase) as CamelKeys<T>
27 | }
28 | 
29 | /**
30 |  * A strongly typed function that recursively transforms the keys of an object to camelCase.
31 |  * The transformation is done both at runtime and type level.
32 |  */
33 | function deepCamelKeys<T>(obj: T): DeepCamelKeys<T> {
34 |   return deepTransformKeys(obj, camelCase) as DeepCamelKeys<T>
35 | }
36 | ```
37 | 
38 | ### Bad ❌
39 | 
40 | ```typescript
41 | /**
42 |  * Transforms the keys of an object to camelCase.
43 |  * [No indication if shallow or deep]
44 |  */
45 | function camelKeys<T>(obj: T): CamelKeys<T> {
46 |   return transformKeys(obj, camelCase) as CamelKeys<T>
47 | }
48 | 
49 | /**
50 |  * A strongly typed function that recursively transforms the keys of an object to camelCase.
51 |  * [Incorrect name - should be deepCamelKeys for recursive]
52 |  */
53 | function camelKeysRecursive<T>(obj: T): DeepCamelKeys<T> {
54 |   return deepTransformKeys(obj, camelCase) as DeepCamelKeys<T>
55 | }
56 | ```


--------------------------------------------------------------------------------
/.wispbit/rules/project-configuration-standards.md:
--------------------------------------------------------------------------------
  1 | ---
  2 | include: package.json, .github/**
  3 | ---
  4 | 
  5 | # Project Configuration Standards
  6 | 
  7 | Maintain consistent project configuration files for dependency management, CI workflows, and contributor recognition.
  8 | 
  9 | ## Requirements
 10 | 
 11 | 1. **Package.json**
 12 |    - Add `"sideEffects": false` to enable tree-shaking
 13 |    - Use consistent versioning syntax for dependencies (e.g., caret `^` or tilde `~`)
 14 |    - Update related packages together
 15 | 
 16 | 2. **Dependabot**
 17 |    - Maintain a proper `.github/dependabot.yml` configuration
 18 |    - Configure all used package ecosystems (npm, github-actions)
 19 |    - Use appropriate update schedule and settings
 20 | 
 21 | 3. **CI Workflow**
 22 |    - CI workflows should run on both push to main branch and pull requests
 23 |    - Include tests, linting, type checking, and build steps
 24 |    - Test against all supported TypeScript versions
 25 | 
 26 | 4. **All Contributors**
 27 |    - Use the All Contributors spec to recognize all types of contributions
 28 |    - Maintain the `.all-contributorsrc` file
 29 |    - Include the contributors section in the README
 30 | 
 31 | ## Examples
 32 | 
 33 | ### Good ✅
 34 | 
 35 | ```json
 36 | // package.json
 37 | {
 38 |   "name": "string-ts",
 39 |   "version": "1.0.0",
 40 |   "sideEffects": false,
 41 |   "devDependencies": {
 42 |     "@vitest/coverage-v8": "^1.5.2",
 43 |     "typescript": "^5.4.5"
 44 |   }
 45 | }
 46 | ```
 47 | 
 48 | ```yaml
 49 | # .github/dependabot.yml
 50 | version: 2
 51 | updates:
 52 |   - package-ecosystem: "npm"
 53 |     directory: "/"
 54 |     schedule:
 55 |       interval: "daily"
 56 | 
 57 |   - package-ecosystem: "github-actions"
 58 |     directory: "/"
 59 |     schedule:
 60 |       interval: "daily"
 61 | ```
 62 | 
 63 | ```yaml
 64 | # .github/workflows/main.yml
 65 | on:
 66 |   push:
 67 |     branches: [main]
 68 |   pull_request:
 69 | 
 70 | jobs:
 71 |   test:
 72 |     runs-on: ubuntu-latest
 73 |     steps:
 74 |       - uses: actions/checkout@v3
 75 |       - uses: actions/setup-node@v2
 76 |         with:
 77 |           node-version: '18'
 78 |       - run: npm install
 79 |       - run: npm run test
 80 |       - run: npm run lint
 81 |       - run: npm run tsc
 82 |       - run: npm run build
 83 | ```
 84 | 
 85 | ### Bad ❌
 86 | 
 87 | ```json
 88 | // package.json with inconsistent versioning
 89 | {
 90 |   "devDependencies": {
 91 |     "@vitest/coverage-v8": "^3.0.5", 
 92 |     "typescript": "~5.7.3",
 93 |     "vitest": "2.0.1" 
 94 |   }
 95 | }
 96 | ```
 97 | 
 98 | ```yaml
 99 | # Missing or incomplete dependabot.yml
100 | updates:
101 |   - package-ecosystem: "npm"
102 |     directory: "/"
103 |     # Missing version and schedule
104 | ```
105 | 
106 | ```yaml
107 | # Incomplete CI workflow
108 | on:
109 |   push:
110 |     branches: [main]
111 | # Missing pull_request trigger
112 | 
113 | jobs:
114 |   test:
115 |     runs-on: ubuntu-latest
116 |     steps:
117 |       - uses: actions/checkout@v3
118 |       - run: npm install
119 |       - run: npm run test
120 |       # Missing linting, type checking, and build steps
121 | ```


--------------------------------------------------------------------------------
/.wispbit/rules/type-safety-dual-implementation.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | include: *.ts, *.tsx
 3 | ---
 4 | 
 5 | # Type Safety and Dual Implementation Pattern
 6 | 
 7 | All string utilities in this codebase must follow the dual implementation pattern:
 8 | 
 9 | 1. Define a type-level implementation that transforms types using TypeScript's type system
10 | 2. Implement a corresponding runtime function that performs the same transformation
11 | 3. Ensure both implementations handle edge cases consistently
12 | 4. Preserve string literal types whenever possible rather than widening to `string`
13 | 
14 | ## Examples
15 | 
16 | ### Good ✅
17 | ```typescript
18 | /**
19 |  * Reverses a string in the type system.
20 |  */
21 | export type Reverse<
22 |   T extends string,
23 |   _acc extends string = '',
24 | > = T extends `${infer Head}${infer Tail}`
25 |   ? Reverse<Tail, `${Head}${_acc}`>
26 |   : _acc extends ''
27 |     ? T
28 |     : `${T}${_acc}`
29 | 
30 | /**
31 |  * A strongly-typed version that reverses a string.
32 |  * @param str the string to reverse.
33 |  * @returns the reversed string in both type level and runtime.
34 |  * @example reverse('hello') // 'olleh'
35 |  */
36 | export function reverse<T extends string>(str: T) {
37 |   return str.split('').reverse().join('') as Reverse<T>
38 | }
39 | ```
40 | 
41 | ### Bad ❌
42 | ```typescript
43 | // Missing type-level implementation
44 | export function reverse(str: string): string {
45 |   return str.split('').reverse().join('')
46 | }
47 | 
48 | // Or type and runtime implementations that don't match
49 | export type Reverse<T extends string> = string // Incorrect type implementation
50 | 
51 | export function reverse<T extends string>(str: T) {
52 |   return str.split('').reverse().join('')
53 | }
54 | ```


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
 1 | MIT License
 2 | 
 3 | Copyright (c) 2023 Gustavo Guichard
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 


--------------------------------------------------------------------------------
/SOW.md:
--------------------------------------------------------------------------------
 1 | # Statement of Work - string-ts
 2 | 
 3 | ## Purpose
 4 | 
 5 | `string-ts` provides strongly typed string manipulation utilities for TypeScript. Standard string methods operate purely at runtime and lose information about literal types, as noted in the README:
 6 | 
 7 | ```
 8 | const str = 'hello-world'
 9 | const result = str.replace('-', ' ') // you should use: as 'hello world'
10 | //    ^? string
11 | ```
12 | 
13 | The library addresses this limitation by offering counterparts that operate both at runtime and at the type level. Using `replace` from `string-ts` preserves literal types:
14 | 
15 | ```
16 | import { replace } from 'string-ts'
17 | const str = 'hello-world'
18 | const result = replace(str, '-', ' ')
19 | //    ^ 'hello world'
20 | ```
21 | 
22 | ## Why it matters
23 | 
24 | TypeScript produces the best static analysis when types are specific. By keeping literal and union types intact after transformations, `string-ts` prevents accidental widening of types.
25 | 
26 | ## Installation
27 | 
28 | ```
29 | npm install string-ts
30 | ```
31 | 
32 | The project is optimized for tree shaking and has been tested with Webpack, Vite and Rollup. It currently targets TypeScript v5+ and assumes ASCII-only input.
33 | 
34 | ## Project Layout
35 | 
36 | - **src/native** – strongly typed counterparts of native `String.prototype` methods (e.g. `charAt`, `replace`)
37 | - **src/utils** – additional features such as word casing (`camelCase`, `snakeCase`) and object key transformation
38 | - **src/internal** – helper types and runtime utilities used by the rest of the codebase
39 | - **src/index.ts** – main entry point exporting all public APIs
40 | - **tests** – each function has dedicated Vitest tests alongside the implementation
41 | 
42 | ## Design and Patterns
43 | 
44 | Functions are implemented with TypeScript generics so their return types reflect the result of the operation. For example `charAt` uses a conditional type that relies on literal checks:
45 | 
46 | ```ts
47 | export type CharAt<T extends string, index extends number> = All<[
48 |   IsStringLiteral<T>,
49 |   IsNumberLiteral<index>
50 | ]> extends true
51 |   ? Split<T>[index]
52 |   : string
53 | ```
54 | 
55 | Transformations that change object keys rely on small runtime helpers, such as `transformKeys` and `deepTransformKeys`, which check the input type and apply a provided transformation function.
56 | 
57 | ## Getting Started for Contributors
58 | 
59 | 1. Install dependencies with `npm install`.
60 | 2. Run the test suite using `npm test` to ensure everything passes.
61 | 3. Explore `src/index.ts` to view all exports and see how the modules are organized.
62 | 4. Review the tests located next to the implementation files for practical examples of every function.
63 | 
64 | Newcomers should read through the README for a deeper explanation of the problem domain and the available API surface.
65 | 
66 | ## Utilities and Tooling
67 | 
68 | - **Vitest** for testing (`npm test`)
69 | - **tsup** for bundling (`npm run build`)
70 | - **Biome** (`biome.json`) for code formatting and linting
71 | 
72 | With these tools, the project maintains a clean codebase and high test coverage.
73 | 
74 | 


--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
 3 |   "vcs": {
 4 |     "enabled": false,
 5 |     "clientKind": "git",
 6 |     "useIgnoreFile": false
 7 |   },
 8 |   "files": {
 9 |     "ignoreUnknown": false,
10 |     "ignore": ["dist"]
11 |   },
12 |   "formatter": {
13 |     "enabled": true,
14 |     "indentStyle": "space",
15 |     "indentWidth": 2
16 |   },
17 |   "organizeImports": {
18 |     "enabled": true
19 |   },
20 |   "linter": {
21 |     "enabled": true,
22 |     "rules": {
23 |       "recommended": true,
24 |       "correctness": {
25 |         "noUnusedImports": {
26 |           "level": "warn",
27 |           "fix": "safe"
28 |         }
29 |       },
30 |       "style": {
31 |         "noUnusedTemplateLiteral": {
32 |           "level": "error",
33 |           "fix": "safe"
34 |         },
35 |         "useTemplate": {
36 |           "level": "error",
37 |           "fix": "safe"
38 |         }
39 |       },
40 |       "suspicious": {
41 |         "noExplicitAny": "off",
42 |         "noShadowRestrictedNames": "off"
43 |       }
44 |     }
45 |   },
46 |   "javascript": {
47 |     "formatter": {
48 |       "quoteStyle": "single",
49 |       "semicolons": "asNeeded",
50 |       "trailingCommas": "es5"
51 |     }
52 |   }
53 | }
54 | 


--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | comment: false
2 | 


--------------------------------------------------------------------------------
/docs/string-ts-banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gustavoguichard/string-ts/0ce5f3d2e3fac98eeff9818be68a667fe1057ee9/docs/string-ts-banner.png


--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "string-ts",
 3 |   "version": "2.3.0-experimental.4",
 4 |   "description": "Strongly-typed string functions.",
 5 |   "author": "Gustavo Guichard <@gugaguichard>",
 6 |   "license": "MIT",
 7 |   "scripts": {
 8 |     "build": "tsup ./src/index.ts --format esm,cjs --dts --treeshake && tsx scripts/generate-entrypoints.mts",
 9 |     "dev": "tsup ./src/index.ts --format esm,cjs --watch --dts",
10 |     "lint": "node_modules/.bin/biome check --write --error-on-warnings",
11 |     "tsc": "tsc --noEmit",
12 |     "tsc:dist": "tsc --project tsconfig.dist.json",
13 |     "test": "vitest run"
14 |   },
15 |   "exports": {
16 |     ".": {
17 |       "import": "./dist/index.mjs",
18 |       "require": "./dist/index.js",
19 |       "default": "./dist/index.mjs",
20 |       "types": "./dist/index.d.ts"
21 |     },
22 |     "./native": {
23 |       "default": "./dist/native.js",
24 |       "types": "./dist/native.d.ts"
25 |     }
26 |   },
27 |   "main": "./dist/index.js",
28 |   "module": "./dist/index.mjs",
29 |   "types": "./dist/index.d.ts",
30 |   "typesVersions": {
31 |     "*": {
32 |       "native": [
33 |         "dist/native.d.ts"
34 |       ]
35 |     }
36 |   },
37 |   "devDependencies": {
38 |     "@biomejs/biome": "^1.9.4",
39 |     "@types/node": "^22.15.3",
40 |     "@vitest/coverage-v8": "^3.0.5",
41 |     "tsup": "latest",
42 |     "tsx": "^4.19.4",
43 |     "typescript": "^5.7.3",
44 |     "vitest": "latest"
45 |   },
46 |   "files": [
47 |     "README.md",
48 |     "./dist/*"
49 |   ],
50 |   "repository": {
51 |     "type": "git",
52 |     "url": "git+https://github.com/gustavoguichard/string-ts.git"
53 |   },
54 |   "bugs": {
55 |     "url": "https://github.com/gustavoguichard/string-ts/issues"
56 |   },
57 |   "sideEffects": false,
58 |   "pnpm": {
59 |     "onlyBuiltDependencies": [
60 |       "@biomejs/biome",
61 |       "esbuild"
62 |     ]
63 |   },
64 |   "packageManager": "pnpm@10.12.4+sha512.5ea8b0deed94ed68691c9bad4c955492705c5eeb8a87ef86bc62c74a26b037b08ff9570f108b2e4dbd1dd1a9186fea925e527f141c648e85af45631074680184"
65 | }
66 | 


--------------------------------------------------------------------------------
/scripts/generate-entrypoints.mts:
--------------------------------------------------------------------------------
 1 | import * as fs from 'node:fs/promises'
 2 | import * as path from 'node:path'
 3 | 
 4 | const entry = 'native'
 5 | const src = path.resolve('src', entry, 'index.d.ts')
 6 | const dist = path.resolve('dist', `${entry}.d.ts`)
 7 | 
 8 | let code = await fs.readFile(src, 'utf8')
 9 | code = code.replace(/(\.\.\/[^'"]+)\.ts/g, '$1') // strip ".ts"
10 | 
11 | await fs.writeFile(dist, code)
12 | await fs.writeFile(path.resolve('dist', `${entry}.js`), '')
13 | await fs.writeFile(path.resolve('dist', `${entry}.mjs`), '')
14 | 


--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
  1 | // Native
  2 | export type { CharAt } from './native/char-at.js'
  3 | export { charAt } from './native/char-at.js'
  4 | export type { Concat } from './native/concat.js'
  5 | export { concat } from './native/concat.js'
  6 | export type { EndsWith } from './native/ends-with.js'
  7 | export { endsWith } from './native/ends-with.js'
  8 | export type { Includes } from './native/includes.js'
  9 | export { includes } from './native/includes.js'
 10 | export type { Join } from './native/join.js'
 11 | export { join } from './native/join.js'
 12 | export type { Length } from './native/length.js'
 13 | export { length } from './native/length.js'
 14 | export type { PadEnd } from './native/pad-end.js'
 15 | export { padEnd } from './native/pad-end.js'
 16 | export type { PadStart } from './native/pad-start.js'
 17 | export { padStart } from './native/pad-start.js'
 18 | export type { Repeat } from './native/repeat.js'
 19 | export { repeat } from './native/repeat.js'
 20 | export type { Replace } from './native/replace.js'
 21 | export { replace } from './native/replace.js'
 22 | export type { ReplaceAll } from './native/replace-all.js'
 23 | export { replaceAll } from './native/replace-all.js'
 24 | export type { Slice } from './native/slice.js'
 25 | export { slice } from './native/slice.js'
 26 | export type { Split } from './native/split.js'
 27 | export { split } from './native/split.js'
 28 | export type { StartsWith } from './native/starts-with.js'
 29 | export { startsWith } from './native/starts-with.js'
 30 | export type { TrimStart } from './native/trim-start.js'
 31 | export { trimStart } from './native/trim-start.js'
 32 | export type { TrimEnd } from './native/trim-end.js'
 33 | export { trimEnd } from './native/trim-end.js'
 34 | export type { Trim } from './native/trim.js'
 35 | export { trim } from './native/trim.js'
 36 | 
 37 | export { toLowerCase } from './native/to-lower-case.js'
 38 | export { toUpperCase } from './native/to-upper-case.js'
 39 | 
 40 | // Utils
 41 | export type { Reverse } from './utils/reverse'
 42 | export { reverse } from './utils/reverse'
 43 | export type { Truncate } from './utils/truncate.js'
 44 | export { truncate } from './utils/truncate.js'
 45 | export type { Words } from './utils/words.js'
 46 | export { words } from './utils/words.js'
 47 | 
 48 | // Characters
 49 | export type { IsLetter, IsLower, IsUpper, LowerLetter, LowerLetters, UpperLetter, UpperLetters, Letter, Letters } from './utils/characters/letters.js'
 50 | export type { IsDigit, Digit, Digits } from './utils/characters/numbers.js'
 51 | export type { IsSpecial } from './utils/characters/special.js'
 52 | export type { IsSeparator, Separator, Separators } from './utils/characters/separators.js'
 53 | 
 54 | // Word casing
 55 | export type { CamelCase } from './utils/word-case/camel-case.js'
 56 | export { camelCase, toCamelCase } from './utils/word-case/camel-case.js'
 57 | export type { ConstantCase } from './utils/word-case/constant-case.js'
 58 | export {
 59 |   constantCase,
 60 |   toConstantCase,
 61 | } from './utils/word-case/constant-case.js'
 62 | export type { DelimiterCase } from './utils/word-case/delimiter-case.js'
 63 | export {
 64 |   delimiterCase,
 65 |   toDelimiterCase,
 66 | } from './utils/word-case/delimiter-case.js'
 67 | export type { KebabCase } from './utils/word-case/kebab-case.js'
 68 | export { kebabCase, toKebabCase } from './utils/word-case/kebab-case.js'
 69 | export type { PascalCase } from './utils/word-case/pascal-case.js'
 70 | export { pascalCase, toPascalCase } from './utils/word-case/pascal-case.js'
 71 | export type { SnakeCase } from './utils/word-case/snake-case.js'
 72 | export { snakeCase, toSnakeCase } from './utils/word-case/snake-case.js'
 73 | export type { TitleCase } from './utils/word-case/title-case.js'
 74 | export { titleCase, toTitleCase } from './utils/word-case/title-case.js'
 75 | 
 76 | export { capitalize } from './utils/word-case/capitalize.js'
 77 | export { lowerCase } from './utils/word-case/lower-case.js'
 78 | export { uncapitalize } from './utils/word-case/uncapitalize.js'
 79 | export { upperCase } from './utils/word-case/upper-case.js'
 80 | 
 81 | // Object keys word casing
 82 | export type { CamelKeys } from './utils/object-keys/camel-keys.js'
 83 | export { camelKeys } from './utils/object-keys/camel-keys.js'
 84 | export type { ConstantKeys } from './utils/object-keys/constant-keys.js'
 85 | export { constantKeys } from './utils/object-keys/constant-keys.js'
 86 | export type { DelimiterKeys } from './utils/object-keys/delimiter-keys.js'
 87 | export { delimiterKeys } from './utils/object-keys/delimiter-keys.js'
 88 | export type { KebabKeys } from './utils/object-keys/kebab-keys.js'
 89 | export { kebabKeys } from './utils/object-keys/kebab-keys.js'
 90 | export type { PascalKeys } from './utils/object-keys/pascal-keys.js'
 91 | export { pascalKeys } from './utils/object-keys/pascal-keys.js'
 92 | export type { SnakeKeys } from './utils/object-keys/snake-keys.js'
 93 | export { snakeKeys } from './utils/object-keys/snake-keys.js'
 94 | // Object keys transformation
 95 | export type { ReplaceKeys } from './utils/object-keys/replace-keys.js'
 96 | export { replaceKeys } from './utils/object-keys/replace-keys.js'
 97 | 
 98 | // Object keys word casing (deep)
 99 | export type { DeepCamelKeys } from './utils/object-keys/deep-camel-keys.js'
100 | export { deepCamelKeys } from './utils/object-keys/deep-camel-keys.js'
101 | export type { DeepConstantKeys } from './utils/object-keys/deep-constant-keys.js'
102 | export { deepConstantKeys } from './utils/object-keys/deep-constant-keys.js'
103 | export type { DeepDelimiterKeys } from './utils/object-keys/deep-delimiter-keys.js'
104 | export { deepDelimiterKeys } from './utils/object-keys/deep-delimiter-keys.js'
105 | export type { DeepKebabKeys } from './utils/object-keys/deep-kebab-keys.js'
106 | export { deepKebabKeys } from './utils/object-keys/deep-kebab-keys.js'
107 | export type { DeepPascalKeys } from './utils/object-keys/deep-pascal-keys.js'
108 | export { deepPascalKeys } from './utils/object-keys/deep-pascal-keys.js'
109 | export type { DeepSnakeKeys } from './utils/object-keys/deep-snake-keys.js'
110 | export { deepSnakeKeys } from './utils/object-keys/deep-snake-keys.js'
111 | 
112 | export { deepTransformKeys } from './utils/object-keys/deep-transform-keys.js'
113 | 


--------------------------------------------------------------------------------
/src/internal/fixtures.ts:
--------------------------------------------------------------------------------
 1 | export const SEPARATORS_TEXT =
 2 |   '[one] two-three/four.five(six){seven}|eight_nine\\ten' as const
 3 | 
 4 | export const WEIRD_TEXT =
 5 |   ' someWeird-cased$*String1986Foo [Bar] W_FOR_WUMBO...' as const
 6 | 
 7 | export type WeirdTextUnion =
 8 |   | typeof WEIRD_TEXT
 9 |   | "where's the leak ma'am"
10 |   | 'dont.distribute unions'
11 | 


--------------------------------------------------------------------------------
/src/internal/internals.test.ts:
--------------------------------------------------------------------------------
 1 | import type { DropSuffix, PascalCaseAll, Reject, TupleOf } from './internals.js'
 2 | import { pascalCaseAll, typeOf } from './internals.js'
 3 | 
 4 | namespace Internals {
 5 |   type testPascalCaseAll1 = Expect<
 6 |     Equal<PascalCaseAll<['one', 'two', 'three']>, ['One', 'Two', 'Three']>
 7 |   >
 8 |   type testPascalCaseAll2 = Expect<Equal<PascalCaseAll<string[]>, string[]>>
 9 | 
10 |   type testReject1 = Expect<
11 |     Equal<Reject<['one', '', 'two', '', 'three'], ''>, ['one', 'two', 'three']>
12 |   >
13 | 
14 |   type testDropSuffix1 = Expect<
15 |     Equal<DropSuffix<'helloWorld', 'World'>, 'hello'>
16 |   >
17 |   type testDropSuffix2 = Expect<Equal<DropSuffix<string, 'World'>, string>>
18 |   type testDropSuffix3 = Expect<Equal<DropSuffix<'helloWorld', string>, string>>
19 | 
20 |   type testTupleOf1 = Expect<Equal<TupleOf<3, ' '>, [' ', ' ', ' ']>>
21 | }
22 | 
23 | describe('typeOf', () => {
24 |   test('null', () => {
25 |     expect(typeOf(null)).toEqual('null')
26 |   })
27 |   test('object', () => {
28 |     expect(typeOf({})).toEqual('object')
29 |   })
30 |   test('object', () => {
31 |     expect(typeOf(['a', 'b', 'c'])).toEqual('array')
32 |   })
33 |   test('string', () => {
34 |     expect(typeOf('hello')).toEqual('string')
35 |   })
36 | })
37 | 
38 | describe('pascalCaseAll', () => {
39 |   test('simple', () => {
40 |     const result = pascalCaseAll(['one', 'two', 'three'])
41 |     const expected = ['One', 'Two', 'Three']
42 |     expect(result).toEqual(expected)
43 |   })
44 | })
45 | 


--------------------------------------------------------------------------------
/src/internal/internals.ts:
--------------------------------------------------------------------------------
 1 | import { toLowerCase } from '../native/to-lower-case.js'
 2 | import { capitalize } from '../utils/word-case/capitalize.js'
 3 | 
 4 | /**
 5 |  * This is an enhanced version of the typeof operator to check the type of more complex values.
 6 |  * In this case we just mind about arrays and objects. We can add more on demand.
 7 |  * @param t the value to be checked
 8 |  * @returns the type of the value
 9 |  */
10 | function typeOf(t: unknown) {
11 |   return Object.prototype.toString
12 |     .call(t)
13 |     .replace(/^\[object (.+)\]$/, '$1')
14 |     .toLowerCase() as 'array' | 'object' | (string & {})
15 | }
16 | 
17 | // MAP TYPES
18 | 
19 | /**
20 |  * PascalCases all the words in a tuple of strings
21 |  */
22 | type PascalCaseAll<T extends string[]> = T extends [
23 |   infer head extends string,
24 |   ...infer rest extends string[],
25 | ]
26 |   ? [Capitalize<Lowercase<head>>, ...PascalCaseAll<rest>]
27 |   : T
28 | 
29 | function pascalCaseAll<T extends string[]>(words: T) {
30 |   return words.map((v) => capitalize(toLowerCase(v))) as PascalCaseAll<T>
31 | }
32 | 
33 | /**
34 |  * Removes all the elements matching the given condition from a tuple.
35 |  */
36 | type Reject<tuple, cond, output extends any[] = []> = tuple extends [
37 |   infer first,
38 |   ...infer rest,
39 | ]
40 |   ? Reject<rest, cond, first extends cond ? output : [...output, first]>
41 |   : output
42 | 
43 | /**
44 |  * Removes the given suffix from a sentence.
45 |  */
46 | type DropSuffix<sentence extends string, suffix extends string> = string extends
47 |   | sentence
48 |   | suffix
49 |   ? string
50 |   : sentence extends `${infer rest}${suffix}`
51 |     ? rest
52 |     : sentence
53 | 
54 | /**
55 |  * Returns a tuple of the given length with the given type.
56 |  */
57 | type TupleOf<
58 |   L extends number,
59 |   T = unknown,
60 |   result extends any[] = [],
61 | > = result['length'] extends L ? result : TupleOf<L, T, [...result, T]>
62 | 
63 | export type { Reject, DropSuffix, PascalCaseAll, TupleOf }
64 | export { pascalCaseAll, typeOf }
65 | 


--------------------------------------------------------------------------------
/src/internal/literals.test.ts:
--------------------------------------------------------------------------------
 1 | import type {
 2 |   All,
 3 |   Any,
 4 |   IsBooleanLiteral,
 5 |   IsNumberLiteral,
 6 |   IsStringLiteral,
 7 |   IsStringLiteralArray,
 8 | } from './literals.js'
 9 | 
10 | namespace LiteralsTests {
11 |   // IsNumberLiteral
12 |   type testINL1 = Expect<Equal<true, IsNumberLiteral<5>>>
13 |   type testINL2 = Expect<Equal<false, IsNumberLiteral<number>>>
14 | 
15 |   // IsBooleanLiteral
16 |   type testIBL1 = Expect<Equal<IsBooleanLiteral<true>, true>>
17 |   type testIBL2 = Expect<Equal<IsBooleanLiteral<false>, true>>
18 |   type testIBL3 = Expect<Equal<IsBooleanLiteral<boolean>, false>>
19 | 
20 |   // Any
21 |   type testAny1 = Expect<Equal<Any<[true, false]>, true>>
22 |   type testAny2 = Expect<Equal<Any<[true, boolean]>, true>>
23 |   type testAny3 = Expect<Equal<Any<[false, boolean]>, false>>
24 |   type testAny4 = Expect<Equal<Any<[false, false]>, false>>
25 |   type testAny5 = Expect<Equal<Any<[]>, false>>
26 |   type testAny6 = Expect<Equal<Any<boolean[]>, false>>
27 | 
28 |   // All
29 |   type testAll1 = Expect<Equal<All<[true, true]>, true>>
30 |   type testAll2 = Expect<Equal<All<[true, false]>, false>>
31 |   type testAll3 = Expect<Equal<All<[true, boolean]>, false>>
32 |   type testAll4 = Expect<Equal<All<[false, boolean]>, false>>
33 |   type testAll5 = Expect<Equal<All<[false, false]>, false>>
34 |   type testAll6 = Expect<Equal<All<[]>, true>>
35 |   type testAll7 = Expect<Equal<All<boolean[]>, false>>
36 | 
37 |   // IsStringLiteral
38 |   type testISL1 = Expect<Equal<true, IsStringLiteral<'foo'>>>
39 |   type testISL2 = Expect<Equal<true, IsStringLiteral<Uppercase<'foo'>>>>
40 |   type testISL3 = Expect<
41 |     Equal<false, IsStringLiteral<Uppercase<`foo${string}`>>>
42 |   >
43 |   type testISL4 = Expect<Equal<false, IsStringLiteral<`foo${string}`>>>
44 |   type testISL5 = Expect<Equal<false, IsStringLiteral<`foo${number}`>>>
45 |   type testISL6 = Expect<Equal<false, IsStringLiteral<string>>>
46 |   type testISL7 = Expect<Equal<false, IsStringLiteral<Lowercase<string>>>>
47 |   type testISL8 = Expect<
48 |     Equal<false, IsStringLiteral<Uppercase<Lowercase<string>>>>
49 |   >
50 |   type testISL9 = Expect<Equal<true, IsStringLiteral<'abc' | 'def'>>>
51 |   type testISL10 = Expect<Equal<true, IsStringLiteral<Capitalize<'abc'>>>>
52 |   type testISL11 = Expect<Equal<true, IsStringLiteral<Uncapitalize<'abc'>>>>
53 |   type testISL12 = Expect<Equal<false, IsStringLiteral<`${string}abc`>>>
54 |   type testISL13 = Expect<
55 |     Equal<false, IsStringLiteral<'abc' | Uppercase<string>>>
56 |   >
57 |   type testISL14 = Expect<Equal<true, IsStringLiteral<`${boolean}bar`>>>
58 |   type testISL15 = Expect<Equal<true, IsStringLiteral<`${undefined}bar`>>>
59 |   type testISL16 = Expect<Equal<true, IsStringLiteral<`${null}bar`>>>
60 |   type testISL17 = Expect<Equal<false, IsStringLiteral<`${number}`>>>
61 | 
62 |   // IsStringLiteralArray
63 |   type testISLA1 = Expect<Equal<IsStringLiteralArray<['foo', 'bar']>, true>>
64 |   type testISLA2 = Expect<Equal<IsStringLiteralArray<string[]>, false>>
65 |   type testISLA3 = Expect<
66 |     Equal<IsStringLiteralArray<['abc', 'def', string]>, false>
67 |   >
68 | }
69 | 
70 | test('dummy test', () => expect(true).toBe(true))
71 | 


--------------------------------------------------------------------------------
/src/internal/literals.ts:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * Returns true if input number type is a literal
 3 |  */
 4 | type IsNumberLiteral<T extends number> = [T] extends [number]
 5 |   ? [number] extends [T]
 6 |     ? false
 7 |     : true
 8 |   : false
 9 | 
10 | type IsBooleanLiteral<T extends boolean> = [T] extends [boolean]
11 |   ? [boolean] extends [T]
12 |     ? false
13 |     : true
14 |   : false
15 | 
16 | /**
17 |  * Returns true if any elements in boolean array are the literal true (not false or boolean)
18 |  */
19 | type Any<Arr extends boolean[]> = Arr extends [
20 |   infer Head extends boolean,
21 |   ...infer Rest extends boolean[],
22 | ]
23 |   ? IsBooleanLiteral<Head> extends true
24 |     ? Head extends true
25 |       ? true
26 |       : Any<Rest>
27 |     : Any<Rest>
28 |   : false
29 | 
30 | /**
31 |  * Returns true if every element in boolean array is the literal true (not false or boolean)
32 |  */
33 | type All<Arr extends boolean[]> = IsBooleanLiteral<Arr[number]> extends true
34 |   ? Arr extends [infer Head extends boolean, ...infer Rest extends boolean[]]
35 |     ? Head extends true
36 |       ? Any<Rest>
37 |       : false // Found `false` in array
38 |     : true // Empty array (or all elements have already passed test)
39 |   : false // Array/Tuple contains `boolean` type
40 | 
41 | /**
42 |  * Returns true if string input type is a literal
43 |  */
44 | type IsStringLiteral<T extends string> = [T] extends [string]
45 |   ? [string] extends [T]
46 |     ? false
47 |     : Uppercase<T> extends Uppercase<Lowercase<T>>
48 |       ? Lowercase<T> extends Lowercase<Uppercase<T>>
49 |         ? true
50 |         : false
51 |       : false
52 |   : false
53 | 
54 | type IsStringLiteralArray<Arr extends string[] | readonly string[]> =
55 |   IsStringLiteral<Arr[number]> extends true ? true : false
56 | 
57 | export type {
58 |   IsNumberLiteral,
59 |   IsBooleanLiteral,
60 |   Any,
61 |   All,
62 |   IsStringLiteral,
63 |   IsStringLiteralArray,
64 | }
65 | 


--------------------------------------------------------------------------------
/src/internal/math.test.ts:
--------------------------------------------------------------------------------
 1 | import type { Math } from '../internal/math.js'
 2 | 
 3 | namespace MathTest {
 4 |   // NOTE: `Subtract` only supports non-negative integers
 5 |   type testSubtract1 = Expect<Equal<Math.Subtract<2, 1>, 1>>
 6 |   type testSubtract2 = Expect<Equal<Math.Subtract<2, 2>, 0>>
 7 |   type testSubtract3 = Expect<Equal<Math.Subtract<number, 2>, number>>
 8 |   type testSubtract4 = Expect<Equal<Math.Subtract<2, number>, number>>
 9 | 
10 |   type testIsNegative1 = Expect<Equal<Math.IsNegative<2>, false>>
11 |   type testIsNegative2 = Expect<Equal<Math.IsNegative<0>, false>>
12 |   type testIsNegative3 = Expect<Equal<Math.IsNegative<-1>, true>>
13 | 
14 |   type testAbs1 = Expect<Equal<Math.Abs<-1>, 1>>
15 |   type testAbs2 = Expect<Equal<Math.Abs<1>, 1>>
16 |   type testAbs3 = Expect<Equal<Math.Abs<0>, 0>>
17 |   type testAbs4 = Expect<Equal<Math.Abs<-0>, 0>>
18 |   type testAbs5 = Expect<Equal<Math.Abs<number>, number>>
19 | 
20 |   type testGetPositiveIndex1 = Expect<
21 |     Equal<Math.GetPositiveIndex<'abc', -1>, 2>
22 |   >
23 |   type testGetPositiveIndex2 = Expect<
24 |     Equal<Math.GetPositiveIndex<string, -1>, number>
25 |   >
26 | }
27 | 
28 | test('dummy test', () => expect(true).toBe(true))
29 | 


--------------------------------------------------------------------------------
/src/internal/math.ts:
--------------------------------------------------------------------------------
 1 | import type { Length } from '../native/length.js'
 2 | import type { TupleOf } from './internals.js'
 3 | 
 4 | namespace Math {
 5 |   export type Subtract<A extends number, B extends number> = number extends
 6 |     | A
 7 |     | B
 8 |     ? number
 9 |     : TupleOf<A> extends [...infer U, ...TupleOf<B>]
10 |       ? U['length']
11 |       : 0
12 | 
13 |   export type IsNegative<T extends number> = number extends T
14 |     ? boolean
15 |     : `${T}` extends `-${number}`
16 |       ? true
17 |       : false
18 | 
19 |   export type Abs<T extends number> = `${T}` extends `-${infer U extends
20 |     number}`
21 |     ? U
22 |     : T
23 | 
24 |   export type GetPositiveIndex<
25 |     T extends string,
26 |     I extends number,
27 |   > = IsNegative<I> extends false ? I : Subtract<Length<T>, Abs<I>>
28 | }
29 | 
30 | export type { Math }
31 | 


--------------------------------------------------------------------------------
/src/internal/types.d.ts:
--------------------------------------------------------------------------------
1 | // TEST UTILITIES
2 | type Expect<T extends true> = T
3 | type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B
4 |   ? 1
5 |   : 2
6 |   ? true
7 |   : [A, 'should equal', B]
8 | 


--------------------------------------------------------------------------------
/src/native/char-at.test.ts:
--------------------------------------------------------------------------------
 1 | import { type CharAt, charAt } from './char-at.js'
 2 | 
 3 | namespace TypeTests {
 4 |   type test1 = Expect<Equal<CharAt<'some nice string', 5>, 'n'>>
 5 |   type test2 = Expect<Equal<CharAt<string, 5>, string>>
 6 |   type test3 = Expect<Equal<CharAt<Uppercase<string>, 5>, string>>
 7 |   type test4 = Expect<Equal<CharAt<'some nice string', number>, string>>
 8 | 
 9 |   // TODO: index greater than Length<T>
10 |   // type test4 = Expect<Equal<CharAt<'some nice string', 100>, ''>>
11 | }
12 | 
13 | describe('charAt', () => {
14 |   test('should get the character of a string at the given index in both type and runtime level', () => {
15 |     const data = 'some nice string'
16 |     const result = charAt(data, 5)
17 |     expect(result).toEqual('n')
18 |     type test = Expect<Equal<typeof result, 'n'>>
19 |   })
20 | })
21 | 


--------------------------------------------------------------------------------
/src/native/char-at.ts:
--------------------------------------------------------------------------------
 1 | import type {
 2 |   All,
 3 |   IsNumberLiteral,
 4 |   IsStringLiteral,
 5 | } from '../internal/literals.js'
 6 | import type { Split } from './split.js'
 7 | 
 8 | /**
 9 |  * Gets the character at the given index.
10 |  * T: The string to get the character from.
11 |  * index: The index of the character.
12 |  */
13 | export type CharAt<T extends string, index extends number> = All<
14 |   [IsStringLiteral<T>, IsNumberLiteral<index>]
15 | > extends true
16 |   ? Split<T>[index]
17 |   : string
18 | 
19 | /**
20 |  * A strongly-typed version of `String.prototype.charAt`.
21 |  * @param str the string to get the character from.
22 |  * @param index the index of the character.
23 |  * @returns the character in both type level and runtime.
24 |  * @example charAt('hello world', 6) // 'w'
25 |  */
26 | export function charAt<T extends string, I extends number>(
27 |   str: T,
28 |   index: I
29 | ): CharAt<T, I> {
30 |   return str.charAt(index) as CharAt<T, I>
31 | }
32 | 


--------------------------------------------------------------------------------
/src/native/concat.test.ts:
--------------------------------------------------------------------------------
 1 | import { type Concat, concat } from './concat.js'
 2 | 
 3 | namespace TypeTests {
 4 |   type test1 = Expect<Equal<Concat<['a', 'bc', 'def']>, 'abcdef'>>
 5 |   type test2 = Expect<
 6 |     Equal<Concat<['a', 'bc', 'def'] | ['1', '23', '456']>, 'abcdef' | '123456'>
 7 |   >
 8 |   type test3 = Expect<Equal<Concat<string[]>, string>>
 9 | }
10 | 
11 | describe('concat', () => {
12 |   test('concatenates', () => {
13 |     const result = concat('one', 'two', 'three')
14 |     const expected = 'onetwothree'
15 |     expect(result).toEqual(expected)
16 |     type test = Expect<Equal<typeof result, typeof expected>>
17 |   })
18 | })
19 | 


--------------------------------------------------------------------------------
/src/native/concat.ts:
--------------------------------------------------------------------------------
 1 | import { join } from './join.js'
 2 | import type { Join } from './join.js'
 3 | 
 4 | /**
 5 |  * Concatenates a tuple of strings.
 6 |  * T: The tuple of strings to concatenate.
 7 |  */
 8 | export type Concat<T extends string[]> = Join<T>
 9 | 
10 | /**
11 |  * A strongly-typed version of `String.prototype.concat`.
12 |  * @param strings the tuple of strings to concatenate.
13 |  * @returns the concatenated string in both type level and runtime.
14 |  * @example concat('a', 'bc', 'def') // 'abcdef'
15 |  */
16 | export function concat<T extends string[]>(...strings: T): Concat<T> {
17 |   return join(strings)
18 | }
19 | 


--------------------------------------------------------------------------------
/src/native/ends-with.test.ts:
--------------------------------------------------------------------------------
 1 | import { type EndsWith, endsWith } from './ends-with.js'
 2 | 
 3 | namespace TypeTests {
 4 |   type test1 = Expect<Equal<EndsWith<'abc', 'c'>, true>>
 5 |   type test2 = Expect<Equal<EndsWith<string, 'c'>, boolean>>
 6 |   type test3 = Expect<Equal<EndsWith<Uppercase<string>, 'c'>, boolean>>
 7 |   type test4 = Expect<Equal<EndsWith<'abc', string>, boolean>>
 8 |   type test6 = Expect<Equal<EndsWith<'abcde', 'd', 4>, true>>
 9 |   type test7 = Expect<Equal<EndsWith<'abcde', 'e', 4>, false>>
10 |   type test8 = Expect<Equal<EndsWith<'abcde', 'e', 6>, true>>
11 |   type test9 = Expect<Equal<EndsWith<'abcde', 'e', -1>, false>>
12 | 
13 |   // Template strings
14 |   type testTS1 = Expect<Equal<EndsWith<`${string}cba`, 'a'>, true>>
15 |   type testTS2 = Expect<Equal<EndsWith<`${string}abc`, 'a'>, false>>
16 |   type testTS3 = Expect<Equal<EndsWith<`zyx${string}cba`, 'a'>, true>>
17 |   type testTS4 = Expect<Equal<EndsWith<`xyz${string}abc`, 'a'>, false>>
18 |   type testTS5 = Expect<Equal<EndsWith<`abc${string}xyz`, 'c', 3>, true>>
19 |   type testTS6 = Expect<Equal<EndsWith<`abc${string}xyz`, 'c', 4>, boolean>>
20 | }
21 | 
22 | describe('endsWith', () => {
23 |   const text = 'abc'
24 | 
25 |   describe('without offset', () => {
26 |     test('should return true when text ends with search', () => {
27 |       const result = endsWith(text, 'c')
28 |       expect(result).toEqual(true)
29 |       type test = Expect<Equal<typeof result, true>>
30 |     })
31 |     test('should return false when text does not end with search', () => {
32 |       const result = endsWith(text, 'b')
33 |       expect(result).toEqual(false)
34 |       type test = Expect<Equal<typeof result, false>>
35 |     })
36 |   })
37 | 
38 |   describe('with offset', () => {
39 |     test('should return true when offset text ends with search', () => {
40 |       const result = endsWith(text, 'b', 2)
41 |       expect(result).toEqual(true)
42 |       type test = Expect<Equal<typeof result, true>>
43 |     })
44 |     test('should return true when offset text ends with search (multi-char)', () => {
45 |       const result = endsWith(text, 'bc', 3)
46 |       expect(result).toEqual(true)
47 |       type test = Expect<Equal<typeof result, true>>
48 |     })
49 |     test('should return false when offset string does not end with search', () => {
50 |       const result = endsWith(text, 'c', 1)
51 |       expect(result).toEqual(false)
52 |       type test = Expect<Equal<typeof result, false>>
53 |     })
54 |   })
55 | 
56 |   describe('with bad offset', () => {
57 |     test('should return false when the offset is negative', () => {
58 |       const result = endsWith(text, 'a', -1)
59 |       expect(result).toEqual(false)
60 |       type test = Expect<Equal<typeof result, false>>
61 |     })
62 |     test('should return true when the end matches and offset is greater than text length', () => {
63 |       const result = endsWith(text, 'c', 10)
64 |       expect(result).toEqual(true)
65 |       type test = Expect<Equal<typeof result, true>>
66 |     })
67 |   })
68 | })
69 | 


--------------------------------------------------------------------------------
/src/native/ends-with.ts:
--------------------------------------------------------------------------------
 1 | import type {
 2 |   All,
 3 |   IsNumberLiteral,
 4 |   IsStringLiteral,
 5 | } from '../internal/literals.js'
 6 | import type { Math } from '../internal/math.js'
 7 | import type { Reverse } from '../utils/reverse.js'
 8 | import type { Length } from './length.js'
 9 | import type { Slice } from './slice.js'
10 | import type { StartsWith } from './starts-with.js'
11 | 
12 | /**
13 |  * Checks if a string ends with another string.
14 |  * T: The string to check.
15 |  * S: The string to check against.
16 |  * P: The position the search should end.
17 |  */
18 | export type EndsWith<
19 |   T extends string,
20 |   S extends string,
21 |   P extends number | undefined = undefined,
22 | > = P extends number ? _EndsWith<T, S, P> : _EndsWithNoPosition<T, S>
23 | 
24 | type _EndsWith<T extends string, S extends string, P extends number> = All<
25 |   [IsStringLiteral<S>, IsNumberLiteral<P>]
26 | > extends true
27 |   ? Math.IsNegative<P> extends false
28 |     ? P extends Length<T>
29 |       ? IsStringLiteral<T> extends true
30 |         ? S extends Slice<T, Math.Subtract<Length<T>, Length<S>>, Length<T>>
31 |           ? true
32 |           : false
33 |         : _EndsWithNoPosition<Slice<T, 0, P>, S> // Eg: EndsWith<`abc${string}xyz`, 'c', 3>
34 |       : _EndsWithNoPosition<Slice<T, 0, P>, S> // P !== T.length, slice
35 |     : false // P is negative, false
36 |   : boolean
37 | 
38 | /** Overload of EndsWith without P */
39 | type _EndsWithNoPosition<T extends string, S extends string> = StartsWith<
40 |   Reverse<T>,
41 |   Reverse<S>
42 | >
43 | 
44 | /**
45 |  * A strongly-typed version of `String.prototype.endsWith`.
46 |  * @param text the string to search.
47 |  * @param search the string to search with.
48 |  * @param position the index the search should end at.
49 |  * @returns boolean, whether or not the text string ends with the search string.
50 |  * @example endsWith('abc', 'c') // true
51 |  */
52 | export function endsWith<
53 |   T extends string,
54 |   S extends string,
55 |   P extends number = Length<T>,
56 | >(text: T, search: S, position = text.length as P) {
57 |   return text.endsWith(search, position) as EndsWith<T, S, P>
58 | }
59 | 


--------------------------------------------------------------------------------
/src/native/includes.test.ts:
--------------------------------------------------------------------------------
 1 | import { type Includes, includes } from './includes.js'
 2 | 
 3 | namespace TypeTests {
 4 |   type test1 = Expect<Equal<Includes<'abcde', 'bcd'>, true>>
 5 |   type test2 = Expect<Equal<Includes<string, 'bcd'>, boolean>>
 6 |   type test3 = Expect<Equal<Includes<'abcde', string>, boolean>>
 7 | }
 8 | 
 9 | describe('includes', () => {
10 |   const text = 'abcde'
11 | 
12 |   describe('without offset', () => {
13 |     test('should return true when text contains search', () => {
14 |       const result = includes(text, 'bcd')
15 |       expect(result).toEqual(true)
16 |       type test = Expect<Equal<typeof result, true>>
17 |     })
18 |     test('should return false when text does not end with search', () => {
19 |       const result = includes(text, 'hello')
20 |       expect(result).toEqual(false)
21 |       type test = Expect<Equal<typeof result, false>>
22 |     })
23 |   })
24 | 
25 |   describe('with offset', () => {
26 |     test('should return true when offset text does contain search', () => {
27 |       const result = includes(text, 'c', 1)
28 |       expect(result).toEqual(true)
29 |       type test = Expect<Equal<typeof result, true>>
30 |     })
31 |     test('should return true when offset text does contain search (multi-char)', () => {
32 |       const result = includes(text, 'bcd', 1)
33 |       expect(result).toEqual(true)
34 |       type test = Expect<Equal<typeof result, true>>
35 |     })
36 |     test('should return false when offset string does not contain search', () => {
37 |       const result = includes(text, 'abc', 3)
38 |       expect(result).toEqual(false)
39 |       type test = Expect<Equal<typeof result, false>>
40 |     })
41 |   })
42 | 
43 |   describe('with bad offset', () => {
44 |     test('should ignore offset when the offset is negative', () => {
45 |       const result = includes(text, 'a', -100)
46 |       expect(result).toEqual(true)
47 |       type test = Expect<Equal<typeof result, true>>
48 |     })
49 |     test('should return false when text contains search but offset is greater than text length', () => {
50 |       const result = includes(text, 'c', 10)
51 |       expect(result).toEqual(false)
52 |       type test = Expect<Equal<typeof result, false>>
53 |     })
54 |   })
55 | })
56 | 


--------------------------------------------------------------------------------
/src/native/includes.ts:
--------------------------------------------------------------------------------
 1 | import type { Math } from '../internal/math.js'
 2 | import type { Slice } from './slice.js'
 3 | 
 4 | /**
 5 |  * Checks if a string includes another string.
 6 |  * T: The string to check.
 7 |  * S: The string to check against.
 8 |  * P: The position to start the search.
 9 |  */
10 | export type Includes<
11 |   T extends string,
12 |   S extends string,
13 |   P extends number = 0,
14 | > = string extends T | S
15 |   ? boolean
16 |   : Math.IsNegative<P> extends false
17 |     ? P extends 0
18 |       ? T extends `${string}${S}${string}`
19 |         ? true
20 |         : false
21 |       : Includes<Slice<T, P>, S, 0> // P is >0, slice
22 |     : Includes<T, S, 0> // P is negative, ignore it
23 | 
24 | /**
25 |  * A strongly-typed version of `String.prototype.includes`.
26 |  * @param text the string to search
27 |  * @param search the string to search with
28 |  * @param position the index to start search at
29 |  * @returns boolean, whether or not the text contains the search string.
30 |  * @example includes('abcde', 'bcd') // true
31 |  */
32 | export function includes<
33 |   T extends string,
34 |   S extends string,
35 |   P extends number = 0,
36 | >(text: T, search: S, position = 0 as P) {
37 |   return text.includes(search, position) as Includes<T, S, P>
38 | }
39 | 


--------------------------------------------------------------------------------
/src/native/index.d.ts:
--------------------------------------------------------------------------------
  1 | import type {
  2 |   CharAt,
  3 |   Concat,
  4 |   EndsWith,
  5 |   Includes,
  6 |   Join,
  7 |   PadEnd,
  8 |   PadStart,
  9 |   Repeat,
 10 |   ReplaceAll,
 11 |   Replace,
 12 |   Slice,
 13 |   Split,
 14 |   StartsWith,
 15 |   TrimEnd,
 16 |   TrimStart,
 17 |   Trim,
 18 | } from '..'
 19 | 
 20 | // biome-ignore lint/complexity/noUselessEmptyExport: <explanation>
 21 | export {}
 22 | 
 23 | declare global {
 24 |   interface ReadonlyArray<T> {
 25 |     join<const A extends readonly string[], D extends string = ''>(
 26 |       this: A,
 27 |       separator?: D
 28 |     ): Join<A, D>
 29 |   }
 30 | 
 31 |   interface String {
 32 |     charAt<const S extends string, I extends number>(
 33 |       this: S,
 34 |       index: I
 35 |     ): CharAt<S, I>
 36 | 
 37 |     concat<const S extends string, const T extends readonly string[]>(
 38 |       this: S,
 39 |       ...args: T
 40 |     ): Concat<[S, ...T]>
 41 | 
 42 |     endsWith<
 43 |       const S extends string,
 44 |       const T extends string,
 45 |       P extends number | undefined = undefined,
 46 |     >(this: S, searchString: T, index?: P): EndsWith<S, T, P>
 47 | 
 48 |     includes<
 49 |       const S extends string,
 50 |       const T extends string,
 51 |       P extends number = 0,
 52 |     >(this: S, searchString: T, position?: P): Includes<S, T, P>
 53 | 
 54 |     padEnd<
 55 |       const S extends string,
 56 |       const T extends number,
 57 |       const U extends string = ' ',
 58 |     >(this: S, maxLength: T, fillString?: U): PadEnd<S, T, U>
 59 | 
 60 |     padStart<
 61 |       const S extends string,
 62 |       const T extends number,
 63 |       const U extends string = ' ',
 64 |     >(this: S, maxLength: T, fillString?: U): PadStart<S, T, U>
 65 | 
 66 |     repeat<const S extends string, const T extends number>(
 67 |       this: S,
 68 |       count: T
 69 |     ): Repeat<S, T>
 70 | 
 71 |     replace<
 72 |       const S extends string,
 73 |       const T extends string,
 74 |       const U extends string,
 75 |     >(this: S, searchValue: T, replaceValue: U): Replace<S, T, U>
 76 | 
 77 |     replaceAll<
 78 |       const S extends string,
 79 |       const T extends string,
 80 |       const U extends string,
 81 |     >(this: S, searchValue: T, replaceValue: U): ReplaceAll<S, T, U>
 82 | 
 83 |     slice<const S extends string>(this: S): Slice<S>
 84 | 
 85 |     slice<const S extends string, const T extends number>(
 86 |       this: S,
 87 |       start?: T
 88 |     ): Slice<S, T>
 89 | 
 90 |     slice<
 91 |       const S extends string,
 92 |       const T extends number,
 93 |       const U extends number,
 94 |     >(this: S, start?: T, end?: U): Slice<S, T, U>
 95 | 
 96 |     split<const S extends string, D extends string>(
 97 |       this: S,
 98 |       delimiter: D
 99 |     ): Split<S, D>
100 | 
101 |     startsWith<
102 |       const S extends string,
103 |       const T extends string,
104 |       P extends number = 0,
105 |     >(this: S, searchString: T, position?: P): StartsWith<S, T, P>
106 | 
107 |     toLowerCase<const S extends string>(this: S): Lowercase<S>
108 |     toUpperCase<const S extends string>(this: S): Uppercase<S>
109 | 
110 |     trim<const S extends string>(this: S): Trim<S>
111 | 
112 |     trimEnd<const S extends string>(this: S): TrimEnd<S>
113 | 
114 |     trimStart<const S extends string>(this: S): TrimStart<S>
115 |   }
116 | }
117 | 


--------------------------------------------------------------------------------
/src/native/join.test.ts:
--------------------------------------------------------------------------------
 1 | import { type Join, join } from './join.js'
 2 | 
 3 | namespace TypeTests {
 4 |   type test1 = Expect<
 5 |     Equal<Join<['some', 'nice', 'string'], ' '>, 'some nice string'>
 6 |   >
 7 |   type test2 = Expect<Equal<Join<string[], ' '>, string>>
 8 |   type test3 = Expect<Equal<Join<Uppercase<string>[], ' '>, string>>
 9 |   type test4 = Expect<Equal<Join<['some', 'nice', 'string'], string>, string>>
10 | }
11 | 
12 | describe('join', () => {
13 |   test('should join words in both type level and runtime level', () => {
14 |     const result = join(['a', 'b', 'c'], '-')
15 |     expect(result).toEqual('a-b-c')
16 |     type test = Expect<Equal<typeof result, 'a-b-c'>>
17 |   })
18 | 
19 |   test('should join only at runtime level when type is wide', () => {
20 |     const data = ['a', 'b', 'c']
21 |     const result = join(data, '-')
22 |     expect(result).toEqual('a-b-c')
23 |     type test = Expect<Equal<typeof result, string>>
24 |   })
25 | })
26 | 


--------------------------------------------------------------------------------
/src/native/join.ts:
--------------------------------------------------------------------------------
 1 | import type {
 2 |   All,
 3 |   IsStringLiteral,
 4 |   IsStringLiteralArray,
 5 | } from '../internal/literals.js'
 6 | 
 7 | /**
 8 |  * Joins a tuple of strings with the given delimiter.
 9 |  * T: The tuple of strings to join.
10 |  * delimiter: The delimiter.
11 |  */
12 | export type Join<
13 |   T extends readonly string[],
14 |   delimiter extends string = '',
15 | > = All<[IsStringLiteralArray<T>, IsStringLiteral<delimiter>]> extends true
16 |   ? T extends readonly [
17 |       infer first extends string,
18 |       ...infer rest extends string[],
19 |     ]
20 |     ? rest extends []
21 |       ? first
22 |       : `${first}${delimiter}${Join<rest, delimiter>}`
23 |     : ''
24 |   : string
25 | 
26 | /**
27 |  * A strongly-typed version of `Array.prototype.join`.
28 |  * @param tuple the tuple of strings to join.
29 |  * @param delimiter the delimiter.
30 |  * @returns the joined string in both type level and runtime.
31 |  * @example join(['hello', 'world'], '-') // 'hello-world'
32 |  */
33 | export function join<const T extends readonly string[], D extends string = ''>(
34 |   tuple: T,
35 |   delimiter: D = '' as D
36 | ) {
37 |   return tuple.join(delimiter) as Join<T, D>
38 | }
39 | 


--------------------------------------------------------------------------------
/src/native/length.test.ts:
--------------------------------------------------------------------------------
 1 | import { type Length, length } from './length.js'
 2 | 
 3 | namespace TypeTests {
 4 |   type test1 = Expect<Equal<Length<'some nice string'>, 16>>
 5 |   type test2 = Expect<Equal<Length<Uppercase<string>>, number>>
 6 |   type test3 = Expect<Equal<Length<string>, number>>
 7 | }
 8 | 
 9 | describe('length', () => {
10 |   test('should return the lenght of a string at both type level and runtime level', () => {
11 |     const data = 'some nice string'
12 |     const result = length(data)
13 |     expect(result).toEqual(16)
14 |     type test = Expect<Equal<typeof result, 16>>
15 |   })
16 | })
17 | 


--------------------------------------------------------------------------------
/src/native/length.ts:
--------------------------------------------------------------------------------
 1 | import type { IsStringLiteral } from '../internal/literals.js'
 2 | import type { Split } from './split.js'
 3 | 
 4 | /**
 5 |  * Gets the length of a string.
 6 |  */
 7 | export type Length<T extends string> = IsStringLiteral<T> extends true
 8 |   ? Split<T>['length']
 9 |   : number
10 | /**
11 |  * A strongly-typed version of `String.prototype.length`.
12 |  * @param str the string to get the length from.
13 |  * @returns the length of the string in both type level and runtime.
14 |  * @example length('hello world') // 11
15 |  */
16 | export function length<T extends string>(str: T) {
17 |   return str.length as Length<T>
18 | }
19 | 


--------------------------------------------------------------------------------
/src/native/native-overrides.test.ts:
--------------------------------------------------------------------------------
  1 | describe('Array.prototype.join', () => {
  2 |   test('test 1', () => {
  3 |     const testArray = ['a', 'b', 'c'] as const
  4 |     const result = testArray.join(', ')
  5 |     const expected = 'a, b, c'
  6 |     expect(result).toEqual(expected)
  7 |     type test = Expect<Equal<typeof result, typeof expected>>
  8 |   })
  9 | })
 10 | 
 11 | describe('String.prototype.charAt', () => {
 12 |   test('test 1', () => {
 13 |     const result = 'abcdef'.charAt(2)
 14 |     const expected = 'c'
 15 |     expect(result).toEqual(expected)
 16 |     type test = Expect<Equal<typeof result, typeof expected>>
 17 |   })
 18 | })
 19 | 
 20 | describe('String.prototype.concat', () => {
 21 |   test('test 1', () => {
 22 |     const result = 'abc'.concat('d')
 23 |     const expected = 'abcd'
 24 |     expect(result).toEqual(expected)
 25 |     type test = Expect<Equal<typeof result, typeof expected>>
 26 |   })
 27 | })
 28 | 
 29 | describe('String.prototype.endsWith', () => {
 30 |   test('test 1', () => {
 31 |     const result = 'abc'.endsWith('c')
 32 |     const expected = true
 33 |     expect(result).toEqual(expected)
 34 |     type test = Expect<Equal<typeof result, typeof expected>>
 35 |   })
 36 | 
 37 |   test('test 2', () => {
 38 |     const result = 'abc'.endsWith('d')
 39 |     const expected = false
 40 |     expect(result).toEqual(expected)
 41 |     type test = Expect<Equal<typeof result, typeof expected>>
 42 |   })
 43 | 
 44 |   test('test 3', () => {
 45 |     const result = 'a,b,c'.endsWith('b,c', 5)
 46 |     const expected = true
 47 |     expect(result).toEqual(expected)
 48 |     type test = Expect<Equal<typeof result, typeof expected>>
 49 |   })
 50 | 
 51 |   test('test 4', () => {
 52 |     const result = 'a,b,c'.endsWith('b,c', 4)
 53 |     const expected = false
 54 |     expect(result).toEqual(expected)
 55 |     type test = Expect<Equal<typeof result, typeof expected>>
 56 |   })
 57 | })
 58 | 
 59 | describe('String.prototype.includes', () => {
 60 |   test('test 1', () => {
 61 |     const result = 'abc'.includes('b')
 62 |     const expected = true
 63 |     expect(result).toEqual(expected)
 64 |     type test = Expect<Equal<typeof result, typeof expected>>
 65 |   })
 66 | 
 67 |   test('test 2', () => {
 68 |     const result = 'abc'.includes('d')
 69 |     const expected = false
 70 |     expect(result).toEqual(expected)
 71 |     type test = Expect<Equal<typeof result, typeof expected>>
 72 |   })
 73 | 
 74 |   test('test 3', () => {
 75 |     const result = 'abc'.includes('b', 1)
 76 |     const expected = true
 77 |     expect(result).toEqual(expected)
 78 |     type test = Expect<Equal<typeof result, typeof expected>>
 79 |   })
 80 | 
 81 |   test('test 4', () => {
 82 |     const result = 'abc'.includes('b', 2)
 83 |     const expected = false
 84 |     expect(result).toEqual(expected)
 85 |     type test = Expect<Equal<typeof result, typeof expected>>
 86 |   })
 87 | })
 88 | 
 89 | describe('String.prototype.padEnd', () => {
 90 |   test('test 1', () => {
 91 |     const result = 'abc'.padEnd(10, '-')
 92 |     const expected = 'abc-------'
 93 |     expect(result).toEqual(expected)
 94 |     type test = Expect<Equal<typeof result, typeof expected>>
 95 |   })
 96 | 
 97 |   test('test 2', () => {
 98 |     const result = 'abc'.padEnd(10)
 99 |     const expected = 'abc       '
100 |     expect(result).toEqual(expected)
101 |     type test = Expect<Equal<typeof result, typeof expected>>
102 |   })
103 | })
104 | 
105 | describe('String.prototype.padStart', () => {
106 |   test('test 1', () => {
107 |     const result = 'abc'.padStart(10, '-')
108 |     const expected = '-------abc'
109 |     expect(result).toEqual(expected)
110 |     type test = Expect<Equal<typeof result, typeof expected>>
111 |   })
112 | 
113 |   test('test 2', () => {
114 |     const result = 'abc'.padStart(10)
115 |     const expected = '       abc'
116 |     expect(result).toEqual(expected)
117 |     type test = Expect<Equal<typeof result, typeof expected>>
118 |   })
119 | })
120 | 
121 | describe('String.prototype.repeat', () => {
122 |   test('test 1', () => {
123 |     const result = 'abc'.repeat(3)
124 |     const expected = 'abcabcabc'
125 |     expect(result).toEqual(expected)
126 |     type test = Expect<Equal<typeof result, typeof expected>>
127 |   })
128 | 
129 |   test('test 2', () => {
130 |     const result = 'abc'.repeat(0)
131 |     const expected = ''
132 |     expect(result).toEqual(expected)
133 |     type test = Expect<Equal<typeof result, typeof expected>>
134 |   })
135 | })
136 | 
137 | describe('String.prototype.slice', () => {
138 |   test('test 1', () => {
139 |     const result = 'abcdef'.slice(2, 4)
140 |     const expected = 'cd'
141 |     expect(result).toEqual(expected)
142 |     type test = Expect<Equal<typeof result, typeof expected>>
143 |   })
144 | 
145 |   test('test 2', () => {
146 |     const result = 'abcdef'.slice(3)
147 |     const expected = 'def'
148 |     expect(result).toEqual(expected)
149 |     type test = Expect<Equal<typeof result, typeof expected>>
150 |   })
151 | 
152 |   test('test 3', () => {
153 |     const result = 'abcdef'.slice()
154 |     const expected = 'abcdef'
155 |     expect(result).toEqual(expected)
156 |     type test = Expect<Equal<typeof result, typeof expected>>
157 |   })
158 | })
159 | 
160 | describe('String.prototype.replace', () => {
161 |   test('test 1', () => {
162 |     const result = 'a.b.c'.replace('.', ',')
163 |     const expected = 'a,b.c'
164 |     expect(result).toEqual(expected)
165 |     type test = Expect<Equal<typeof result, typeof expected>>
166 |   })
167 | })
168 | 
169 | describe('String.prototype.replaceAll', () => {
170 |   test('test 1', () => {
171 |     const result = 'a.b.c'.replaceAll('.', ',')
172 |     const expected = 'a,b,c'
173 |     expect(result).toEqual(expected)
174 |     type test = Expect<Equal<typeof result, typeof expected>>
175 |   })
176 | })
177 | 
178 | describe('String.prototype.slice', () => {
179 |   test('test 1', () => {
180 |     const result = 'abcdef'.slice(2, 4)
181 |     const expected = 'cd'
182 |     expect(result).toEqual(expected)
183 |     type test = Expect<Equal<typeof result, typeof expected>>
184 |   })
185 | 
186 |   test('test 2', () => {
187 |     const result = 'abcdef'.slice(3)
188 |     const expected = 'def'
189 |     expect(result).toEqual(expected)
190 |     type test = Expect<Equal<typeof result, typeof expected>>
191 |   })
192 | 
193 |   test('test 3', () => {
194 |     const result = 'abcdef'.slice()
195 |     const expected = 'abcdef'
196 |     expect(result).toEqual(expected)
197 |     type test = Expect<Equal<typeof result, typeof expected>>
198 |   })
199 | })
200 | 
201 | describe('String.prototype.split', () => {
202 |   test('test 1', () => {
203 |     const result = 'a,b,c'.split(',')
204 |     const expected = ['a', 'b', 'c'] as const
205 |     type Mutable<T extends readonly unknown[]> = [...T]
206 |     type Expected = Mutable<typeof expected>
207 |     expect(result).toEqual(expected)
208 |     type test = Expect<Equal<typeof result, Expected>>
209 |   })
210 | })
211 | 
212 | describe('String.prototype.startsWith', () => {
213 |   test('test 1', () => {
214 |     const result = 'abc'.startsWith('a')
215 |     const expected = true
216 |     expect(result).toEqual(expected)
217 |     type test = Expect<Equal<typeof result, typeof expected>>
218 |   })
219 | 
220 |   test('test 2', () => {
221 |     const result = 'abc'.startsWith('b')
222 |     const expected = false
223 |     expect(result).toEqual(expected)
224 |     type test = Expect<Equal<typeof result, typeof expected>>
225 |   })
226 | 
227 |   test('test 3', () => {
228 |     const result = 'abc'.startsWith('b', 1)
229 |     const expected = true
230 |     expect(result).toEqual(expected)
231 |     type test = Expect<Equal<typeof result, typeof expected>>
232 |   })
233 | 
234 |   test('test 4', () => {
235 |     const result = 'abc'.startsWith('b', 2)
236 |     const expected = false
237 |     expect(result).toEqual(expected)
238 |     type test = Expect<Equal<typeof result, typeof expected>>
239 |   })
240 | })
241 | 
242 | describe('String.prototype.toLowerCase', () => {
243 |   test('test 1', () => {
244 |     const result = 'ABC'.toLowerCase()
245 |     const expected = 'abc'
246 |     expect(result).toEqual(expected)
247 |     type test = Expect<Equal<typeof result, typeof expected>>
248 |   })
249 | })
250 | 
251 | describe('String.prototype.toUpperCase', () => {
252 |   test('test 1', () => {
253 |     const result = 'abc'.toUpperCase()
254 |     const expected = 'ABC'
255 |     expect(result).toEqual(expected)
256 |     type test = Expect<Equal<typeof result, typeof expected>>
257 |   })
258 | })
259 | 
260 | describe('String.prototype.trim', () => {
261 |   test('test 1', () => {
262 |     const result = '  foo  '.trim()
263 |     const expected = 'foo'
264 |     expect(result).toEqual(expected)
265 |     type test = Expect<Equal<typeof result, typeof expected>>
266 |   })
267 | })
268 | 
269 | describe('String.prototype.trimEnd', () => {
270 |   test('test 1', () => {
271 |     const result = '  foo  '.trimEnd()
272 |     const expected = '  foo'
273 |     expect(result).toEqual(expected)
274 |     type test = Expect<Equal<typeof result, typeof expected>>
275 |   })
276 | })
277 | 
278 | describe('String.prototype.trimStart', () => {
279 |   test('test 1', () => {
280 |     const result = '  foo  '.trimStart()
281 |     const expected = 'foo  '
282 |     expect(result).toEqual(expected)
283 |     type test = Expect<Equal<typeof result, typeof expected>>
284 |   })
285 | })
286 | 


--------------------------------------------------------------------------------
/src/native/pad-end.test.ts:
--------------------------------------------------------------------------------
 1 | import { type PadEnd, padEnd } from './pad-end.js'
 2 | 
 3 | namespace TypeTests {
 4 |   type test1 = Expect<Equal<PadEnd<'hello', 10, ' '>, 'hello     '>>
 5 |   type test2 = Expect<Equal<PadEnd<string, 10, ' '>, string>>
 6 |   type test3 = Expect<Equal<PadEnd<Uppercase<string>, 10, ' '>, string>>
 7 |   type test4 = Expect<Equal<PadEnd<'hello', number, ' '>, string>>
 8 |   type test5 = Expect<Equal<PadEnd<'hello', 10, string>, string>>
 9 | }
10 | 
11 | describe('padEnd', () => {
12 |   test('should pad a string at the end', () => {
13 |     const data = 'hello'
14 |     const result = padEnd(data, 10)
15 |     expect(result).toEqual('hello     ')
16 |     type test = Expect<Equal<typeof result, 'hello     '>>
17 |   })
18 | 
19 |   test('should pad with a given string', () => {
20 |     const data = 'hello'
21 |     const result = padEnd(data, 10, '=>')
22 |     expect(result).toEqual('hello=>=>=')
23 |     type test = Expect<Equal<typeof result, 'hello=>=>='>>
24 |   })
25 | 
26 |   test('should not pad if no arguments are given', () => {
27 |     const data = 'hello'
28 |     const result = padEnd(data)
29 |     expect(result).toEqual('hello')
30 |     type test = Expect<Equal<typeof result, 'hello'>>
31 |   })
32 | 
33 |   test('should not pad or truncate if length is shorter than string', () => {
34 |     const data = 'hello'
35 |     const result = padEnd(data, 3, '=')
36 |     expect(result).toEqual('hello')
37 |     type test = Expect<Equal<typeof result, 'hello'>>
38 |   })
39 | 
40 |   test('should not pad for negative numbers', () => {
41 |     const data = 'hello'
42 |     const result = padEnd(data, -1, '=')
43 |     expect(result).toEqual('hello')
44 |     type test = Expect<Equal<typeof result, 'hello'>>
45 |   })
46 | })
47 | 


--------------------------------------------------------------------------------
/src/native/pad-end.ts:
--------------------------------------------------------------------------------
 1 | import type {
 2 |   All,
 3 |   IsNumberLiteral,
 4 |   IsStringLiteral,
 5 | } from '../internal/literals.js'
 6 | import type { Math } from '../internal/math.js'
 7 | import type { Length } from './length.js'
 8 | import type { Repeat } from './repeat.js'
 9 | import type { Slice } from './slice.js'
10 | 
11 | /**
12 |  * Pads a string at the end with another string.
13 |  * T: The string to pad.
14 |  * times: The number of times to pad.
15 |  * pad: The string to pad with.
16 |  */
17 | export type PadEnd<
18 |   T extends string,
19 |   times extends number = 0,
20 |   pad extends string = ' ',
21 | > = All<[IsStringLiteral<T | pad>, IsNumberLiteral<times>]> extends true
22 |   ? Math.IsNegative<times> extends false
23 |     ? Math.Subtract<times, Length<T>> extends infer missing extends number
24 |       ? `${T}${Slice<Repeat<pad, missing>, 0, missing>}`
25 |       : never
26 |     : T
27 |   : string
28 | 
29 | /**
30 |  * A strongly-typed version of `String.prototype.padEnd`.
31 |  * @param str the string to pad.
32 |  * @param length the length to pad.
33 |  * @param pad the string to pad with.
34 |  * @returns the padded string in both type level and runtime.
35 |  * @example padEnd('hello', 10, '=') // 'hello====='
36 |  */
37 | export function padEnd<
38 |   T extends string,
39 |   N extends number = 0,
40 |   U extends string = ' ',
41 | >(str: T, length: N = 0 as N, pad: U = ' ' as U) {
42 |   return str.padEnd(length, pad) as PadEnd<T, N, U>
43 | }
44 | 


--------------------------------------------------------------------------------
/src/native/pad-start.test.ts:
--------------------------------------------------------------------------------
 1 | import { type PadStart, padStart } from './pad-start.js'
 2 | 
 3 | namespace TypeTests {
 4 |   type test1 = Expect<Equal<PadStart<'hello', 10, ' '>, '     hello'>>
 5 |   type test2 = Expect<Equal<PadStart<string, 10, ' '>, string>>
 6 |   type test3 = Expect<Equal<PadStart<Uppercase<string>, 10, ' '>, string>>
 7 |   type test4 = Expect<Equal<PadStart<'hello', number, ' '>, string>>
 8 |   type test5 = Expect<Equal<PadStart<'hello', 10, string>, string>>
 9 | }
10 | 
11 | describe('padStart', () => {
12 |   test('should pad a string at the start', () => {
13 |     const data = 'hello'
14 |     const result = padStart(data, 10)
15 |     expect(result).toEqual('     hello')
16 |     type test = Expect<Equal<typeof result, '     hello'>>
17 |   })
18 | 
19 |   test('should pad with a given string', () => {
20 |     const data = 'hello'
21 |     const result = padStart(data, 10, '=>')
22 |     expect(result).toEqual('=>=>=hello')
23 |     type test = Expect<Equal<typeof result, '=>=>=hello'>>
24 |   })
25 | 
26 |   test('should not pad if no arguments are given', () => {
27 |     const data = 'hello'
28 |     const result = padStart(data)
29 |     expect(result).toEqual('hello')
30 |     type test = Expect<Equal<typeof result, 'hello'>>
31 |   })
32 | 
33 |   test('should not pad or truncate if length is shorter than string', () => {
34 |     const data = 'hello'
35 |     const result = padStart(data, 3, '=')
36 |     expect(result).toEqual('hello')
37 |     type test = Expect<Equal<typeof result, 'hello'>>
38 |   })
39 | 
40 |   test('should not pad for negative numbers', () => {
41 |     const data = 'hello'
42 |     const result = padStart(data, -1, '=')
43 |     expect(result).toEqual('hello')
44 |     type test = Expect<Equal<typeof result, 'hello'>>
45 |   })
46 | })
47 | 


--------------------------------------------------------------------------------
/src/native/pad-start.ts:
--------------------------------------------------------------------------------
 1 | import type {
 2 |   All,
 3 |   IsNumberLiteral,
 4 |   IsStringLiteral,
 5 | } from '../internal/literals.js'
 6 | import type { Math } from '../internal/math.js'
 7 | import type { Length } from './length.js'
 8 | import type { Repeat } from './repeat.js'
 9 | import type { Slice } from './slice.js'
10 | 
11 | /**
12 |  * Pads a string at the start with another string.
13 |  * T: The string to pad.
14 |  * times: The number of times to pad.
15 |  * pad: The string to pad with.
16 |  */
17 | export type PadStart<
18 |   T extends string,
19 |   times extends number = 0,
20 |   pad extends string = ' ',
21 | > = All<[IsStringLiteral<T | pad>, IsNumberLiteral<times>]> extends true
22 |   ? Math.IsNegative<times> extends false
23 |     ? Math.Subtract<times, Length<T>> extends infer missing extends number
24 |       ? `${Slice<Repeat<pad, missing>, 0, missing>}${T}`
25 |       : never
26 |     : T
27 |   : string
28 | /**
29 |  * A strongly-typed version of `String.prototype.padStart`.
30 |  * @param str the string to pad.
31 |  * @param length the length to pad.
32 |  * @param pad the string to pad with.
33 |  * @returns the padded string in both type level and runtime.
34 |  * @example padStart('hello', 10, '=') // '=====hello'
35 |  */
36 | export function padStart<
37 |   T extends string,
38 |   N extends number = 0,
39 |   U extends string = ' ',
40 | >(str: T, length: N = 0 as N, pad: U = ' ' as U) {
41 |   return str.padStart(length, pad) as PadStart<T, N, U>
42 | }
43 | 


--------------------------------------------------------------------------------
/src/native/repeat.test.ts:
--------------------------------------------------------------------------------
 1 | import { type Repeat, repeat } from './repeat.js'
 2 | 
 3 | namespace TypeTests {
 4 |   type test1 = Expect<Equal<Repeat<' ', 3>, '   '>>
 5 |   type test2 = Expect<Equal<Repeat<string, 3>, string>>
 6 |   type test3 = Expect<Equal<Repeat<Uppercase<string>, 3>, string>>
 7 |   type test4 = Expect<Equal<Repeat<' ', number>, string>>
 8 | }
 9 | 
10 | describe('repeat', () => {
11 |   test('should repeat the string by a given number of times', () => {
12 |     const data = 'abc'
13 |     const result = repeat(data, 3)
14 |     expect(result).toEqual('abcabcabc')
15 |     type test = Expect<Equal<typeof result, 'abcabcabc'>>
16 |   })
17 | 
18 |   test('should be empty when repeating 0 times', () => {
19 |     const data = 'abc'
20 |     const result = repeat(data)
21 |     expect(result).toEqual('')
22 |     type test = Expect<Equal<typeof result, ''>>
23 |   })
24 | 
25 |   test('should throw when trying to repeat with negative number', () => {
26 |     const data = 'abc'
27 |     expect(() => repeat(data, -1)).toThrow()
28 |     type test = Expect<Equal<Repeat<'a', -1>, never>>
29 |   })
30 | })
31 | 


--------------------------------------------------------------------------------
/src/native/repeat.ts:
--------------------------------------------------------------------------------
 1 | import type { TupleOf } from '../internal/internals.js'
 2 | import type { Math } from '../internal/math.js'
 3 | import type { Join } from './join.js'
 4 | 
 5 | import type {
 6 |   All,
 7 |   IsNumberLiteral,
 8 |   IsStringLiteral,
 9 | } from '../internal/literals.js'
10 | 
11 | /**
12 |  * Repeats a string N times.
13 |  * T: The string to repeat.
14 |  * N: The number of times to repeat.
15 |  */
16 | export type Repeat<T extends string, times extends number = 0> = All<
17 |   [IsStringLiteral<T>, IsNumberLiteral<times>]
18 | > extends true
19 |   ? times extends 0
20 |     ? ''
21 |     : Math.IsNegative<times> extends false
22 |       ? Join<TupleOf<times, T>>
23 |       : never
24 |   : string
25 | 
26 | /**
27 |  * A strongly-typed version of `String.prototype.repeat`.
28 |  * @param str the string to repeat.
29 |  * @param times the number of times to repeat.
30 |  * @returns the repeated string in both type level and runtime.
31 |  * @example repeat('hello', 3) // 'hellohellohello'
32 |  */
33 | export function repeat<T extends string, N extends number = 0>(
34 |   str: T,
35 |   times: N = 0 as N
36 | ) {
37 |   return str.repeat(times) as Repeat<T, N>
38 | }
39 | 


--------------------------------------------------------------------------------
/src/native/replace-all.test.ts:
--------------------------------------------------------------------------------
 1 | import { type ReplaceAll, replaceAll } from './replace-all.js'
 2 | 
 3 | namespace TypeTests {
 4 |   type test1 = Expect<
 5 |     Equal<ReplaceAll<'some nice string', ' ', '-'>, 'some-nice-string'>
 6 |   >
 7 |   type test2 = Expect<
 8 |     Equal<ReplaceAll<'some nice string', RegExp, '-'>, string>
 9 |   >
10 |   type test3 = Expect<Equal<ReplaceAll<string, ' ', '-'>, string>>
11 |   type test4 = Expect<Equal<ReplaceAll<Uppercase<string>, ' ', '-'>, string>>
12 |   type test5 = Expect<
13 |     Equal<ReplaceAll<'some nice string', string, '-'>, string>
14 |   >
15 |   type test6 = Expect<
16 |     Equal<ReplaceAll<'some nice string', ' ', string>, string>
17 |   >
18 | }
19 | 
20 | beforeEach(() => {
21 |   vi.clearAllMocks()
22 | })
23 | 
24 | describe('replaceAll', () => {
25 |   test('should replace all chars in a string at both type level and runtime level once', () => {
26 |     const data = 'some nice string'
27 |     const result = replaceAll(data, ' ')
28 |     expect(result).toEqual('somenicestring')
29 |     type test = Expect<Equal<typeof result, 'somenicestring'>>
30 |   })
31 | 
32 |   test('accepts an argument for the replacement', () => {
33 |     const data = 'some nice string'
34 |     const result = replaceAll(data, ' ', '@')
35 |     expect(result).toEqual('some@nice@string')
36 |     type test = Expect<Equal<typeof result, 'some@nice@string'>>
37 |   })
38 | 
39 |   test('should replace chars but not at type level when using RegExp', () => {
40 |     const data = 'some nice string'
41 |     const result = replaceAll(data, / /g, '-')
42 |     expect(result).toEqual('some-nice-string')
43 |     // Note: `string` instead of `some-nice-string`
44 |     type test = Expect<Equal<typeof result, string>>
45 |   })
46 | })
47 | 
48 | describe('replaceAll polyfill', () => {
49 |   const replaceAllPlaceholder = String.prototype.replaceAll
50 | 
51 |   beforeAll(() => {
52 |     // @ts-ignore
53 |     String.prototype.replaceAll = undefined
54 |   })
55 | 
56 |   afterAll(() => {
57 |     String.prototype.replaceAll = replaceAllPlaceholder
58 |   })
59 | 
60 |   test('it works through a polyfill', () => {
61 |     const spy = vi.spyOn(String.prototype, 'replace')
62 |     const data = 'some nice string'
63 |     const result = replaceAll(data, ' ', '@')
64 |     expect(result).toEqual('some@nice@string')
65 |     expect(spy).toHaveBeenCalledWith(/ /g, '@')
66 |   })
67 | })
68 | 


--------------------------------------------------------------------------------
/src/native/replace-all.ts:
--------------------------------------------------------------------------------
 1 | import type { IsStringLiteral } from '../internal/literals.js'
 2 | 
 3 | /**
 4 |  * Replaces all the occurrences of a string with another string.
 5 |  * sentence: The sentence to replace.
 6 |  * lookup: The lookup string to be replaced.
 7 |  * replacement: The replacement string.
 8 |  */
 9 | export type ReplaceAll<
10 |   sentence extends string,
11 |   lookup extends string | RegExp,
12 |   replacement extends string = '',
13 | > = lookup extends string
14 |   ? IsStringLiteral<lookup | sentence | replacement> extends true
15 |     ? sentence extends `${infer rest}${lookup}${infer rest2}`
16 |       ? `${rest}${replacement}${ReplaceAll<rest2, lookup, replacement>}`
17 |       : sentence
18 |     : string
19 |   : string // Regex used, can't preserve literal
20 | 
21 | /**
22 |  * A strongly-typed version of `String.prototype.replaceAll`.
23 |  * @param sentence the sentence to replace.
24 |  * @param lookup the lookup string to be replaced.
25 |  * @param replacement the replacement string.
26 |  * @returns the replaced string in both type level and runtime.
27 |  * @example replaceAll('hello world', 'l', '1') // 'he11o wor1d'
28 |  */
29 | export function replaceAll<
30 |   T extends string,
31 |   S extends string | RegExp,
32 |   R extends string = '',
33 | >(sentence: T, lookup: S, replacement: R = '' as R) {
34 |   // Only supported in ES2021+
35 |   if (typeof sentence.replaceAll === 'function') {
36 |     return sentence.replaceAll(lookup, replacement) as ReplaceAll<T, S, R>
37 |   }
38 | 
39 |   const regex = new RegExp(lookup, 'g')
40 |   return sentence.replace(regex, replacement) as ReplaceAll<T, S, R>
41 | }
42 | 


--------------------------------------------------------------------------------
/src/native/replace.test.ts:
--------------------------------------------------------------------------------
 1 | import { type Replace, replace } from './replace.js'
 2 | 
 3 | namespace TypeTests {
 4 |   type test1 = Expect<
 5 |     Equal<Replace<'some nice string', ' ', '-'>, 'some-nice string'>
 6 |   >
 7 |   type test2 = Expect<Equal<Replace<'some nice string', RegExp, '-'>, string>>
 8 |   type test3 = Expect<Equal<Replace<string, ' ', '-'>, string>>
 9 |   type test4 = Expect<Equal<Replace<Uppercase<string>, ' ', '-'>, string>>
10 |   type test5 = Expect<Equal<Replace<'some nice string', string, '-'>, string>>
11 |   type test6 = Expect<Equal<Replace<'some nice string', ' ', string>, string>>
12 | }
13 | 
14 | describe('replace', () => {
15 |   test('should replace chars in a string at both type level and runtime level once', () => {
16 |     const data = 'some nice string'
17 |     const result = replace(data, ' ')
18 |     expect(result).toEqual('somenice string')
19 |     type test = Expect<Equal<typeof result, 'somenice string'>>
20 |   })
21 |   test('should replace chars but not at type level when using RegExp', () => {
22 |     const data = 'some nice string'
23 |     const result = replace(data, /nice /)
24 |     expect(result).toEqual('some string')
25 |     type test = Expect<Equal<typeof result, string>>
26 |   })
27 | })
28 | 


--------------------------------------------------------------------------------
/src/native/replace.ts:
--------------------------------------------------------------------------------
 1 | import type { IsStringLiteral } from '../internal/literals.js'
 2 | 
 3 | /**
 4 |  * Replaces the first occurrence of a string with another string.
 5 |  * sentence: The sentence to replace.
 6 |  * lookup: The lookup string to be replaced.
 7 |  * replacement: The replacement string.
 8 |  */
 9 | export type Replace<
10 |   sentence extends string,
11 |   lookup extends string | RegExp,
12 |   replacement extends string = '',
13 | > = lookup extends string
14 |   ? IsStringLiteral<lookup | sentence | replacement> extends true
15 |     ? sentence extends `${infer rest}${lookup}${infer rest2}`
16 |       ? `${rest}${replacement}${rest2}`
17 |       : sentence
18 |     : string
19 |   : string // Regex used, can't preserve literal
20 | /**
21 |  * A strongly-typed version of `String.prototype.replace`.
22 |  * @param sentence the sentence to replace.
23 |  * @param lookup the lookup string to be replaced.
24 |  * @param replacement the replacement string.
25 |  * @returns the replaced string in both type level and runtime.
26 |  * @example replace('hello world', 'l', '1') // 'he1lo world'
27 |  */
28 | export function replace<
29 |   T extends string,
30 |   S extends string | RegExp,
31 |   R extends string = '',
32 | >(sentence: T, lookup: S, replacement: R = '' as R) {
33 |   return sentence.replace(lookup, replacement) as Replace<T, S, R>
34 | }
35 | 


--------------------------------------------------------------------------------
/src/native/slice.test.ts:
--------------------------------------------------------------------------------
 1 | import { type Slice, slice } from './slice.js'
 2 | 
 3 | namespace TypeTests {
 4 |   type test1 = Expect<Equal<Slice<'some nice string', 5>, 'nice string'>>
 5 |   type test2 = Expect<Equal<Slice<'some nice string', 5, 9>, 'nice'>>
 6 |   type test3 = Expect<Equal<Slice<string, 5, 9>, string>>
 7 |   type test4 = Expect<Equal<Slice<Uppercase<string>, 5, 9>, string>>
 8 |   type test5 = Expect<Equal<Slice<'some nice string', number, 9>, string>>
 9 |   type test6 = Expect<Equal<Slice<'some nice string', 5, number>, string>>
10 |   type test7 = Expect<Equal<Slice<'abcde', -3>, 'cde'>>
11 |   type test8 = Expect<Equal<Slice<'abc', 10>, ''>>
12 |   type test9 = Expect<Equal<Slice<'abc', 10, 12>, ''>>
13 | 
14 |   // Template literals
15 |   type testTS1 = Expect<Equal<Slice<`abc${string}`, 1, 3>, 'bc'>>
16 |   type testTS2 = Expect<Equal<Slice<`abcd${string}`, 1, 3>, 'bc'>>
17 |   type testTS3 = Expect<Equal<Slice<`abc${string}xyz`, 0, 3>, 'abc'>>
18 |   type testTS4 = Expect<Equal<Slice<`${string}abcd`, 1, 3>, string>>
19 |   type testTS5 = Expect<Equal<Slice<`abc${string}`, 1>, `bc${string}`>>
20 | }
21 | 
22 | describe('slice', () => {
23 |   const str = 'The quick brown fox jumps over the lazy dog.'
24 |   test('should slice a string from a startIndex position', () => {
25 |     const result = slice(str, 31)
26 |     expect(result).toEqual('the lazy dog.')
27 |     type test = Expect<Equal<typeof result, 'the lazy dog.'>>
28 |   })
29 | 
30 |   test('should slice a string from a startIndex to an endIndex position', () => {
31 |     const result = slice(str, 4, 19)
32 |     expect(result).toEqual('quick brown fox')
33 |     type test = Expect<Equal<typeof result, 'quick brown fox'>>
34 |   })
35 | 
36 |   test('should slice a string from the end with a negative startIndex', () => {
37 |     const result = slice(str, -4)
38 |     expect(result).toEqual('dog.')
39 |     type test = Expect<Equal<typeof result, 'dog.'>>
40 |   })
41 | 
42 |   test('should allow a negative endIndex', () => {
43 |     const result = slice(str, 0, -5)
44 |     expect(result).toEqual('The quick brown fox jumps over the lazy')
45 |     type test = Expect<
46 |       Equal<typeof result, 'The quick brown fox jumps over the lazy'>
47 |     >
48 |   })
49 | 
50 |   test('should slice a string from the end with a negative startIndex to a negative endIndex', () => {
51 |     const result = slice(str, -9, -5)
52 |     expect(result).toEqual('lazy')
53 |     type test = Expect<Equal<typeof result, 'lazy'>>
54 |   })
55 | 
56 |   test('should return an empty string if endIndex is lower than startIndex', () => {
57 |     const result = slice(str, -9, -10)
58 |     expect(result).toEqual('')
59 |     type test = Expect<Equal<typeof result, ''>>
60 | 
61 |     const result2 = slice(str, 9, 1)
62 |     expect(result2).toEqual('')
63 |     type test2 = Expect<Equal<typeof result2, ''>>
64 |   })
65 | })
66 | 


--------------------------------------------------------------------------------
/src/native/slice.ts:
--------------------------------------------------------------------------------
 1 | import type { IsNumberLiteral, IsStringLiteral } from '../internal/literals.js'
 2 | import type { Math } from '../internal/math.js'
 3 | 
 4 | /**
 5 |  * Slices a string from a startIndex to an endIndex.
 6 |  * T: The string to slice.
 7 |  * startIndex: The start index.
 8 |  * endIndex: The end index.
 9 |  */
10 | export type Slice<
11 |   T extends string,
12 |   startIndex extends number = 0,
13 |   endIndex extends number | undefined = undefined,
14 | > = endIndex extends number
15 |   ? _Slice<T, startIndex, endIndex>
16 |   : _SliceStart<T, startIndex>
17 | 
18 | /** Slice with startIndex and endIndex */
19 | type _Slice<
20 |   T extends string,
21 |   startIndex extends number,
22 |   endIndex extends number,
23 |   _result extends string = '',
24 | > = IsNumberLiteral<startIndex | endIndex> extends true
25 |   ? T extends `${infer head}${infer rest}`
26 |     ? IsStringLiteral<head> extends true
27 |       ? startIndex extends 0
28 |         ? endIndex extends 0
29 |           ? _result
30 |           : _Slice<
31 |               rest,
32 |               0,
33 |               Math.Subtract<Math.GetPositiveIndex<T, endIndex>, 1>,
34 |               `${_result}${head}`
35 |             >
36 |         : _Slice<
37 |             rest,
38 |             Math.Subtract<Math.GetPositiveIndex<T, startIndex>, 1>,
39 |             Math.Subtract<Math.GetPositiveIndex<T, endIndex>, 1>,
40 |             _result
41 |           >
42 |       : startIndex | endIndex extends 0
43 |         ? _result
44 |         : string // Head is non-literal
45 |     : IsStringLiteral<T> extends true // Couldn't be split into head/tail
46 |       ? _result // T ran out
47 |       : startIndex | endIndex extends 0
48 |         ? _result // Eg: Slice<`abc${string}`, 1, 3> -> 'bc'
49 |         : string // Head is non-literal
50 |   : string
51 | 
52 | /** Slice with startIndex only */
53 | type _SliceStart<
54 |   T extends string,
55 |   startIndex extends number,
56 |   _result extends string = '',
57 | > = IsNumberLiteral<startIndex> extends true
58 |   ? T extends `${infer head}${infer rest}`
59 |     ? IsStringLiteral<head> extends true
60 |       ? startIndex extends 0
61 |         ? T
62 |         : _SliceStart<
63 |             rest,
64 |             Math.Subtract<Math.GetPositiveIndex<T, startIndex>, 1>,
65 |             _result
66 |           >
67 |       : string
68 |     : IsStringLiteral<T> extends true
69 |       ? _result
70 |       : startIndex extends 0
71 |         ? _result
72 |         : string
73 |   : string
74 | 
75 | /**
76 |  * A strongly-typed version of `String.prototype.slice`.
77 |  * @param str the string to slice.
78 |  * @param start the start index.
79 |  * @param end the end index.
80 |  * @returns the sliced string in both type level and runtime.
81 |  * @example slice('hello world', 6) // 'world'
82 |  */
83 | export function slice<
84 |   T extends string,
85 |   S extends number = 0,
86 |   E extends number | undefined = undefined,
87 | >(str: T, start: S = 0 as S, end: E = undefined as E) {
88 |   return str.slice(start, end) as Slice<T, S, E>
89 | }
90 | 


--------------------------------------------------------------------------------
/src/native/split.test.ts:
--------------------------------------------------------------------------------
 1 | import { type Split, split } from './split.js'
 2 | 
 3 | namespace TypeTests {
 4 |   type test1 = Expect<
 5 |     Equal<Split<'some nice string', ' '>, ['some', 'nice', 'string']>
 6 |   >
 7 |   type test2 = Expect<Equal<Split<string, ' '>, string[]>>
 8 |   type test3 = Expect<Equal<Split<Uppercase<string>, ' '>, string[]>>
 9 |   type test4 = Expect<Equal<Split<'some nice string', string>, string[]>>
10 | }
11 | 
12 | describe('split', () => {
13 |   test('should split a string by a delimiter into an array of substrings', () => {
14 |     const data = 'some nice string'
15 |     const result = split(data, ' ')
16 |     expect(result).toEqual(['some', 'nice', 'string'])
17 |     type test = Expect<Equal<typeof result, ['some', 'nice', 'string']>>
18 |   })
19 | 
20 |   test('should no add extra characters when splitting by empty string', () => {
21 |     const data = 'hello'
22 |     const result = split(data, '')
23 |     expect(result).toEqual(['h', 'e', 'l', 'l', 'o'])
24 |     type test = Expect<Equal<typeof result, ['h', 'e', 'l', 'l', 'o']>>
25 | 
26 |     const result2 = split('', '')
27 |     expect(result2).toEqual([])
28 |     type test2 = Expect<Equal<typeof result2, []>>
29 |   })
30 | })
31 | 


--------------------------------------------------------------------------------
/src/native/split.ts:
--------------------------------------------------------------------------------
 1 | import type { IsStringLiteral } from '../internal/literals.js'
 2 | 
 3 | /**
 4 |  * Splits a string into an array of substrings.
 5 |  * T: The string to split.
 6 |  * delimiter: The delimiter.
 7 |  */
 8 | export type Split<
 9 |   T extends string,
10 |   delimiter extends string = '',
11 | > = IsStringLiteral<T | delimiter> extends true
12 |   ? T extends `${infer first}${delimiter}${infer rest}`
13 |     ? [first, ...Split<rest, delimiter>]
14 |     : T extends ''
15 |       ? []
16 |       : [T]
17 |   : string[]
18 | /**
19 |  * A strongly-typed version of `String.prototype.split`.
20 |  * @param str the string to split.
21 |  * @param delimiter the delimiter.
22 |  * @returns the splitted string in both type level and runtime.
23 |  * @example split('hello world', ' ') // ['hello', 'world']
24 |  */
25 | export function split<T extends string, D extends string = ''>(
26 |   str: T,
27 |   delimiter: D = '' as D
28 | ) {
29 |   return str.split(delimiter) as Split<T, D>
30 | }
31 | 


--------------------------------------------------------------------------------
/src/native/starts-with.test.ts:
--------------------------------------------------------------------------------
 1 | import { type StartsWith, startsWith } from './starts-with.js'
 2 | 
 3 | namespace TypeTests {
 4 |   type test1 = Expect<Equal<StartsWith<'abc', 'a'>, true>>
 5 |   type test2 = Expect<Equal<StartsWith<'abc', 'b', 1>, true>>
 6 |   type test3 = Expect<Equal<StartsWith<Uppercase<string>, 'a'>, boolean>>
 7 |   type test4 = Expect<Equal<StartsWith<string, 'a'>, boolean>>
 8 |   type test5 = Expect<Equal<StartsWith<'abc', string>, boolean>>
 9 |   type test6 = Expect<Equal<StartsWith<'abc', 'a', number>, boolean>>
10 |   type test7 = Expect<Equal<StartsWith<`abc${string}`, 'a'>, true>>
11 |   type test8 = Expect<Equal<StartsWith<`cba${string}`, 'a'>, false>>
12 |   type test9 = Expect<Equal<StartsWith<`abc${string}`, 'abc'>, true>>
13 |   type test10 = Expect<Equal<StartsWith<`abc${string}`, 'b', 1>, true>>
14 | }
15 | 
16 | describe('startsWith', () => {
17 |   const text = 'abc'
18 | 
19 |   describe('without offset', () => {
20 |     test('should return true when text starts with search', () => {
21 |       const result = startsWith(text, 'a')
22 |       expect(result).toEqual(true)
23 |       type test = Expect<Equal<typeof result, true>>
24 |     })
25 |     test('should return false when text does not start with search', () => {
26 |       const result = startsWith(text, 'b')
27 |       expect(result).toEqual(false)
28 |       type test = Expect<Equal<typeof result, false>>
29 |     })
30 |   })
31 | 
32 |   describe('with offset', () => {
33 |     test('should return true when offset text starts with search', () => {
34 |       const result = startsWith(text, 'b', 1)
35 |       expect(result).toEqual(true)
36 |       type test = Expect<Equal<typeof result, true>>
37 |     })
38 |     test('should return false when offset string does not start with search', () => {
39 |       const result = startsWith(text, 'a', 1)
40 |       expect(result).toEqual(false)
41 |       type test = Expect<Equal<typeof result, false>>
42 |     })
43 |   })
44 | 
45 |   describe('with bad offset', () => {
46 |     test('should return true when text starts with search and offset is negative', () => {
47 |       const result = startsWith(text, 'a', -1)
48 |       expect(result).toEqual(true)
49 |       type test = Expect<Equal<typeof result, true>>
50 |     })
51 |     test('should return false when offset is greater than text length', () => {
52 |       const result = startsWith(text, 'a', 10)
53 |       expect(result).toEqual(false)
54 |       type test = Expect<Equal<typeof result, false>>
55 |     })
56 |   })
57 | })
58 | 


--------------------------------------------------------------------------------
/src/native/starts-with.ts:
--------------------------------------------------------------------------------
 1 | import type {
 2 |   All,
 3 |   IsNumberLiteral,
 4 |   IsStringLiteral,
 5 | } from '../internal/literals.js'
 6 | import type { Math } from '../internal/math.js'
 7 | import type { Slice } from './slice.js'
 8 | 
 9 | /**
10 |  * Checks if a string starts with another string.
11 |  * T: The string to check.
12 |  * S: The string to check against.
13 |  * P: The position to start the search.
14 |  */
15 | export type StartsWith<
16 |   T extends string,
17 |   S extends string,
18 |   P extends number = 0,
19 | > = All<[IsStringLiteral<S>, IsNumberLiteral<P>]> extends true
20 |   ? Math.IsNegative<P> extends false
21 |     ? P extends 0
22 |       ? S extends `${infer SHead}${infer SRest}`
23 |         ? T extends `${infer THead}${infer TRest}`
24 |           ? IsStringLiteral<THead | SHead> extends true
25 |             ? THead extends SHead
26 |               ? StartsWith<TRest, SRest>
27 |               : false // Heads weren't equal
28 |             : boolean // THead is non-literal
29 |           : IsStringLiteral<T> extends true // Couldn't split T
30 |             ? false // T ran out, but we still have S
31 |             : boolean // T (or TRest) is not a literal
32 |         : true // Couldn't split S, we've already ruled out non-literal
33 |       : StartsWith<Slice<T, P>, S, 0> // P is >0, slice
34 |     : StartsWith<T, S, 0> // P is negative, ignore it
35 |   : boolean
36 | 
37 | /**
38 |  * A strongly-typed version of `String.prototype.startsWith`.
39 |  * @param text the string to search.
40 |  * @param search the string to search with.
41 |  * @param position the index to start search at.
42 |  * @returns boolean, whether or not the text string starts with the search string.
43 |  * @example startsWith('abc', 'a') // true
44 |  */
45 | export function startsWith<
46 |   T extends string,
47 |   S extends string,
48 |   P extends number = 0,
49 | >(text: T, search: S, position = 0 as P) {
50 |   return text.startsWith(search, position) as StartsWith<T, S, P>
51 | }
52 | 


--------------------------------------------------------------------------------
/src/native/to-lower-case.test.ts:
--------------------------------------------------------------------------------
 1 | import { SEPARATORS_TEXT, WEIRD_TEXT } from '../internal/fixtures.js'
 2 | import { toLowerCase } from './to-lower-case.js'
 3 | 
 4 | describe('toLowerCase', () => {
 5 |   test('casing functions', () => {
 6 |     const expected =
 7 |       ' someweird-cased$*string1986foo [bar] w_for_wumbo...' as const
 8 |     const result = toLowerCase(WEIRD_TEXT)
 9 |     expect(result).toEqual(expected)
10 |     type test = Expect<Equal<typeof result, typeof expected>>
11 |   })
12 |   test('with various separators', () => {
13 |     const result = toLowerCase(SEPARATORS_TEXT)
14 |     const expected = '[one] two-three/four.five(six){seven}|eight_nine\\ten'
15 |     expect(result).toEqual(expected)
16 |     type test = Expect<Equal<typeof result, typeof expected>>
17 |   })
18 | })
19 | 


--------------------------------------------------------------------------------
/src/native/to-lower-case.ts:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * This function is a strongly-typed counterpart of String.prototype.toLowerCase.
 3 |  * @param str the string to make lowercase.
 4 |  * @returns the lowercased string.
 5 |  * @example toLowerCase('HELLO WORLD') // 'hello world'
 6 |  */
 7 | export function toLowerCase<T extends string>(str: T) {
 8 |   return str.toLowerCase() as Lowercase<T>
 9 | }
10 | 


--------------------------------------------------------------------------------
/src/native/to-upper-case.test.ts:
--------------------------------------------------------------------------------
 1 | import { SEPARATORS_TEXT, WEIRD_TEXT } from '../internal/fixtures.js'
 2 | import { toUpperCase } from './to-upper-case.js'
 3 | 
 4 | describe('toUpperCase', () => {
 5 |   test('casing functions', () => {
 6 |     const expected =
 7 |       ' SOMEWEIRD-CASED$*STRING1986FOO [BAR] W_FOR_WUMBO...' as const
 8 |     const result = toUpperCase(WEIRD_TEXT)
 9 |     expect(result).toEqual(expected)
10 |     type test = Expect<Equal<typeof result, typeof expected>>
11 |   })
12 |   test('with various separators', () => {
13 |     const result = toUpperCase(SEPARATORS_TEXT)
14 |     const expected = '[ONE] TWO-THREE/FOUR.FIVE(SIX){SEVEN}|EIGHT_NINE\\TEN'
15 |     expect(result).toEqual(expected)
16 |     type test = Expect<Equal<typeof result, typeof expected>>
17 |   })
18 | })
19 | 


--------------------------------------------------------------------------------
/src/native/to-upper-case.ts:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * This function is a strongly-typed counterpart of String.prototype.toUpperCase.
 3 |  * @param str the string to make uppercase.
 4 |  * @returns the uppercased string.
 5 |  * @example toUpperCase('hello world') // 'HELLO WORLD'
 6 |  */
 7 | export function toUpperCase<T extends string>(str: T) {
 8 |   return str.toUpperCase() as Uppercase<T>
 9 | }
10 | 


--------------------------------------------------------------------------------
/src/native/trim-end.test.ts:
--------------------------------------------------------------------------------
 1 | import { type TrimEnd, trimEnd } from './trim-end.js'
 2 | 
 3 | namespace TypeTests {
 4 |   type test1 = Expect<Equal<TrimEnd<' some nice string '>, ' some nice string'>>
 5 |   type test2 = Expect<Equal<TrimEnd<string>, string>>
 6 |   type test3 = Expect<Equal<TrimEnd<Uppercase<string>>, Uppercase<string>>>
 7 |   type test4 = Expect<
 8 |     Equal<TrimEnd<`on${Capitalize<string>} `>, `on${Capitalize<string>}`>
 9 |   >
10 |   type test5 = Expect<Equal<TrimEnd<`hey, ${string}  `>, `hey, ${string}`>>
11 | }
12 | 
13 | describe('trimEnd', () => {
14 |   test('should trim the end of a string at both type level and runtime level', () => {
15 |     const data = ' some nice string '
16 |     const result = trimEnd(data)
17 |     expect(result).toEqual(' some nice string')
18 |     type test = Expect<Equal<typeof result, ' some nice string'>>
19 |   })
20 | })
21 | 


--------------------------------------------------------------------------------
/src/native/trim-end.ts:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * Trims all whitespaces at the end of a string.
 3 |  * T: The string to trim.
 4 |  */
 5 | export type TrimEnd<T extends string> = T extends `${infer rest} `
 6 |   ? TrimEnd<rest>
 7 |   : T
 8 | /**
 9 |  * A strongly-typed version of `String.prototype.trimEnd`.
10 |  * @param str the string to trim.
11 |  * @returns the trimmed string in both type level and runtime.
12 |  * @example trimEnd(' hello world ') // ' hello world'
13 |  */
14 | export function trimEnd<T extends string>(str: T) {
15 |   return str.trimEnd() as TrimEnd<T>
16 | }
17 | 


--------------------------------------------------------------------------------
/src/native/trim-start.test.ts:
--------------------------------------------------------------------------------
 1 | import { type TrimStart, trimStart } from './trim-start.js'
 2 | 
 3 | namespace TypeTests {
 4 |   type test1 = Expect<
 5 |     Equal<TrimStart<' some nice string '>, 'some nice string '>
 6 |   >
 7 |   type test2 = Expect<Equal<TrimStart<string>, string>>
 8 |   type test3 = Expect<Equal<TrimStart<Uppercase<string>>, Uppercase<string>>>
 9 |   type test4 = Expect<
10 |     Equal<TrimStart<` on${Capitalize<string>}`>, `on${Capitalize<string>}`>
11 |   >
12 |   type test5 = Expect<Equal<TrimStart<`  hey, ${string}`>, `hey, ${string}`>>
13 | }
14 | 
15 | describe('trimStart', () => {
16 |   test('should trim the start of a string at both type level and runtime level', () => {
17 |     const data = ' some nice string '
18 |     const result = trimStart(data)
19 |     expect(result).toEqual('some nice string ')
20 |     type test = Expect<Equal<typeof result, 'some nice string '>>
21 |   })
22 | })
23 | 


--------------------------------------------------------------------------------
/src/native/trim-start.ts:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * Trims all whitespaces at the start of a string.
 3 |  * T: The string to trim.
 4 |  */
 5 | export type TrimStart<T extends string> = T extends ` ${infer rest}`
 6 |   ? TrimStart<rest>
 7 |   : T
 8 | /**
 9 |  * A strongly-typed version of `String.prototype.trimStart`.
10 |  * @param str the string to trim.
11 |  * @returns the trimmed string in both type level and runtime.
12 |  * @example trimStart(' hello world ') // 'hello world '
13 |  */
14 | export function trimStart<T extends string>(str: T) {
15 |   return str.trimStart() as TrimStart<T>
16 | }
17 | 


--------------------------------------------------------------------------------
/src/native/trim.test.ts:
--------------------------------------------------------------------------------
 1 | import { type Trim, trim } from './trim.js'
 2 | 
 3 | namespace TypeTests {
 4 |   type test1 = Expect<Equal<Trim<' some nice string '>, 'some nice string'>>
 5 |   type test2 = Expect<Equal<Trim<string>, string>>
 6 |   type test3 = Expect<Equal<Trim<Uppercase<string>>, Uppercase<string>>>
 7 |   type test4 = Expect<
 8 |     Equal<Trim<` on${Capitalize<string>} `>, `on${Capitalize<string>}`>
 9 |   >
10 | }
11 | 
12 | describe('trim', () => {
13 |   test('should trim a string at both type level and runtime level', () => {
14 |     const data = ' some nice string '
15 |     const result = trim(data)
16 |     expect(result).toEqual('some nice string')
17 |     type test = Expect<Equal<typeof result, 'some nice string'>>
18 |   })
19 | })
20 | 


--------------------------------------------------------------------------------
/src/native/trim.ts:
--------------------------------------------------------------------------------
 1 | import type { TrimEnd } from './trim-end.js'
 2 | import type { TrimStart } from './trim-start.js'
 3 | 
 4 | /**
 5 |  * Trims all whitespaces at the start and end of a string.
 6 |  * T: The string to trim.
 7 |  */
 8 | export type Trim<T extends string> = TrimEnd<TrimStart<T>>
 9 | 
10 | /**
11 |  * A strongly-typed version of `String.prototype.trim`.
12 |  * @param str the string to trim.
13 |  * @returns the trimmed string in both type level and runtime.
14 |  * @example trim(' hello world ') // 'hello world'
15 |  */
16 | export function trim<T extends string>(str: T) {
17 |   return str.trim() as Trim<T>
18 | }
19 | 


--------------------------------------------------------------------------------
/src/utils/characters/apostrophe.ts:
--------------------------------------------------------------------------------
 1 | import type { IsStringLiteral } from '../../internal/literals.js'
 2 | import { type ReplaceAll, replaceAll } from '../../native/replace-all.js'
 3 | 
 4 | type Apostrophe = "'"
 5 | 
 6 | /**
 7 |  * Checks if the given character is an apostrophe
 8 |  */
 9 | export type IsApostrophe<T extends string> = IsStringLiteral<T> extends true
10 |   ? T extends Apostrophe
11 |     ? true
12 |     : false
13 |   : boolean
14 | 
15 | export type RemoveApostrophe<T extends string> = ReplaceAll<T, "'", ''>
16 | 
17 | export function removeApostrophe<T extends string>(
18 |   str: T
19 | ): RemoveApostrophe<T> {
20 |   return replaceAll(str, "'", '')
21 | }
22 | 


--------------------------------------------------------------------------------
/src/utils/characters/letters.test.ts:
--------------------------------------------------------------------------------
 1 | import type { IsLetter, IsLower, IsUpper } from './letters.js'
 2 | 
 3 | namespace TypeChecks {
 4 |   type testIsLower1 = Expect<Equal<IsLower<'1'>, false>>
 5 |   type testIsLower2 = Expect<Equal<IsLower<'a'>, true>>
 6 |   type testIsLower3 = Expect<Equal<IsLower<'A'>, false>>
 7 |   type testIsLower4 = Expect<Equal<IsLower<'
#39;>, false>>
 8 |   type testIsLower5 = Expect<Equal<IsLower<string>, boolean>>
 9 | 
10 |   type testIsUpper1 = Expect<Equal<IsUpper<'1'>, false>>
11 |   type testIsUpper2 = Expect<Equal<IsUpper<'a'>, false>>
12 |   type testIsUpper3 = Expect<Equal<IsUpper<'A'>, true>>
13 |   type testIsUpper4 = Expect<Equal<IsUpper<'
#39;>, false>>
14 |   type testIsUpper5 = Expect<Equal<IsUpper<string>, boolean>>
15 | 
16 |   type testIsLetter1 = Expect<Equal<IsLetter<'1'>, false>>
17 |   type testIsLetter2 = Expect<Equal<IsLetter<'a'>, true>>
18 |   type testIsLetter3 = Expect<Equal<IsLetter<'A'>, true>>
19 |   type testIsLetter4 = Expect<Equal<IsLetter<'
#39;>, false>>
20 |   type testIsLetter5 = Expect<Equal<IsLetter<string>, boolean>>
21 | }
22 | 
23 | test('dummy test', () => expect(true).toBe(true))
24 | 


--------------------------------------------------------------------------------
/src/utils/characters/letters.ts:
--------------------------------------------------------------------------------
 1 | import type { IsStringLiteral } from '../../internal/literals.js'
 2 | 
 3 | export type UpperLetters = [
 4 |     'A'
 5 |   , 'B'
 6 |   , 'C'
 7 |   , 'D'
 8 |   , 'E'
 9 |   , 'F'
10 |   , 'G'
11 |   , 'H'
12 |   , 'I'
13 |   , 'J'
14 |   , 'K'
15 |   , 'L'
16 |   , 'M'
17 |   , 'N'
18 |   , 'O'
19 |   , 'P'
20 |   , 'Q'
21 |   , 'R'
22 |   , 'S'
23 |   , 'T'
24 |   , 'U'
25 |   , 'V'
26 |   , 'W'
27 |   , 'X'
28 |   , 'Y'
29 |   , 'Z'
30 | ]
31 | export type UpperLetter = UpperLetters[number]
32 | export type LowerLetters = [
33 |     'a'
34 |   , 'b'
35 |   , 'c'
36 |   , 'd'
37 |   , 'e'
38 |   , 'f'
39 |   , 'g'
40 |   , 'h'
41 |   , 'i'
42 |   , 'j'
43 |   , 'k'
44 |   , 'l'
45 |   , 'm'
46 |   , 'n'
47 |   , 'o'
48 |   , 'p'
49 |   , 'q'
50 |   , 'r'
51 |   , 's'
52 |   , 't'
53 |   , 'u'
54 |   , 'v'
55 |   , 'w'
56 |   , 'x'
57 |   , 'y'
58 |   , 'z'
59 | ]
60 | export type LowerLetter = LowerLetters[number]
61 | 
62 | export type Letters = [...LowerLetters, ...UpperLetters]
63 | export type Letter = LowerLetter | UpperLetter
64 | 
65 | // UTILITIES FOR DETECTING CHARS
66 | /**
67 |  * Checks if the given character is an upper case letter.
68 |  */
69 | export type IsUpper<T extends string> = IsStringLiteral<T> extends true
70 |   ? T extends UpperLetter
71 |     ? true
72 |     : false
73 |   : boolean
74 | 
75 | /**
76 |  * Checks if the given character is a lower case letter.
77 |  */
78 | export type IsLower<T extends string> = IsStringLiteral<T> extends true
79 |   ? T extends LowerLetter
80 |     ? true
81 |     : false
82 |   : boolean
83 | 
84 | /**
85 |  * Checks if the given character is a letter.
86 |  */
87 | export type IsLetter<T extends string> = IsStringLiteral<T> extends true
88 |   ? T extends Letter
89 |     ? true
90 |     : false
91 |   : boolean
92 | 


--------------------------------------------------------------------------------
/src/utils/characters/numbers.test.ts:
--------------------------------------------------------------------------------
 1 | import type { IsDigit } from './numbers.js'
 2 | 
 3 | namespace TypeChecks {
 4 |   type test1 = Expect<Equal<IsDigit<'1'>, true>>
 5 |   type test2 = Expect<Equal<IsDigit<'a'>, false>>
 6 |   type test3 = Expect<Equal<IsDigit<'A'>, false>>
 7 |   type test4 = Expect<Equal<IsDigit<'
#39;>, false>>
 8 |   type test5 = Expect<Equal<IsDigit<string>, boolean>>
 9 | }
10 | 
11 | test('dummy test', () => expect(true).toBe(true))
12 | 


--------------------------------------------------------------------------------
/src/utils/characters/numbers.ts:
--------------------------------------------------------------------------------
 1 | import type { IsStringLiteral } from '../../internal/literals.js'
 2 | 
 3 | export type Digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
 4 | export type Digit = Digits[number];
 5 | 
 6 | /**
 7 |  * Checks if the given character is a number.
 8 |  */
 9 | export type IsDigit<T extends string> = IsStringLiteral<T> extends true
10 |   ? T extends Digit
11 |     ? true
12 |     : false
13 |   : boolean
14 | 


--------------------------------------------------------------------------------
/src/utils/characters/separators.test.ts:
--------------------------------------------------------------------------------
 1 | import { SEPARATOR_REGEX } from './separators.js'
 2 | import type { IsSeparator } from './separators.js'
 3 | 
 4 | namespace TypeChecks {
 5 |   type test1 = Expect<Equal<IsSeparator<'1'>, false>>
 6 |   type test2 = Expect<Equal<IsSeparator<'a'>, false>>
 7 |   type test3 = Expect<Equal<IsSeparator<'A'>, false>>
 8 |   type test4 = Expect<Equal<IsSeparator<'
#39;>, false>>
 9 |   type test5 = Expect<Equal<IsSeparator<' '>, true>>
10 |   type test6 = Expect<Equal<IsSeparator<'-'>, true>>
11 |   type test7 = Expect<Equal<IsSeparator<'/'>, true>>
12 |   type test8 = Expect<Equal<IsSeparator<'_'>, true>>
13 |   type test9 = Expect<Equal<IsSeparator<'.'>, true>>
14 |   type test10 = Expect<Equal<IsSeparator<string>, boolean>>
15 |   type test11 = Expect<Equal<IsSeparator<Uppercase<string>>, boolean>>
16 | }
17 | 
18 | describe('SEPARATOR_REGEX', () => {
19 |   test('dummy regex test', () => {
20 |     expect(SEPARATOR_REGEX.test('[test]')).toEqual(true)
21 |     expect(SEPARATOR_REGEX.test('te.st')).toEqual(true)
22 |     expect(SEPARATOR_REGEX.test('te$st')).toEqual(false)
23 |     expect(SEPARATOR_REGEX.test('test')).toEqual(false)
24 |   })
25 | })
26 | 


--------------------------------------------------------------------------------
/src/utils/characters/separators.ts:
--------------------------------------------------------------------------------
 1 | import type { IsStringLiteral } from '../../internal/literals.js'
 2 | 
 3 | const UNESCAPED_SEPARATORS = [
 4 |   '[',
 5 |   ']',
 6 |   '{',
 7 |   '}',
 8 |   '(',
 9 |   ')',
10 |   '|',
11 |   '/',
12 |   '-',
13 |   '\\',
14 | ] as const
15 | const SEPARATORS = [...UNESCAPED_SEPARATORS, ' ', '_', '.'] as const
16 | 
17 | /** Escape characters with special significance in regular expressions */
18 | function escapeChar(char: string): string {
19 |   return (UNESCAPED_SEPARATORS as readonly string[]).includes(char)
20 |     ? `\\${char}`
21 |     : char
22 | }
23 | 
24 | export const SEPARATOR_REGEX = new RegExp(
25 |   `[${SEPARATORS.map(escapeChar).join('')}]`,
26 |   'g'
27 | )
28 | 
29 | export type Separators = typeof SEPARATORS
30 | export type Separator = Separators[number]
31 | 
32 | /**
33 |  * Checks if the given character is a separator.
34 |  * E.g. space, underscore, dash, dot, slash.
35 |  */
36 | export type IsSeparator<T extends string> = IsStringLiteral<T> extends true
37 |   ? T extends Separator
38 |     ? true
39 |     : false
40 |   : boolean
41 | 


--------------------------------------------------------------------------------
/src/utils/characters/special.test.ts:
--------------------------------------------------------------------------------
 1 | import type * as Subject from './special.js'
 2 | 
 3 | namespace TypeChecks {
 4 |   type test1 = Expect<Equal<Subject.IsSpecial<'1'>, false>>
 5 |   type test2 = Expect<Equal<Subject.IsSpecial<'a'>, false>>
 6 |   type test3 = Expect<Equal<Subject.IsSpecial<'A'>, false>>
 7 |   type test4 = Expect<Equal<Subject.IsSpecial<'
#39;>, true>>
 8 |   type test5 = Expect<Equal<Subject.IsSpecial<' '>, false>>
 9 |   type test6 = Expect<Equal<Subject.IsSpecial<'*'>, true>>
10 |   type test7 = Expect<Equal<Subject.IsSpecial<'_'>, false>>
11 |   type test8 = Expect<Equal<Subject.IsSpecial<string>, boolean>>
12 |   type test9 = Expect<Equal<Subject.IsSpecial<Uppercase<string>>, boolean>>
13 | }
14 | test('dummy test', () => expect(true).toBe(true))
15 | 


--------------------------------------------------------------------------------
/src/utils/characters/special.ts:
--------------------------------------------------------------------------------
 1 | import type { IsStringLiteral } from '../../internal/literals.js'
 2 | import type { IsApostrophe } from './apostrophe.js'
 3 | import type { IsLetter } from './letters.js'
 4 | import type { IsDigit } from './numbers.js'
 5 | import type { IsSeparator } from './separators.js'
 6 | 
 7 | /**
 8 |  * Checks if the given character is a special character.
 9 |  * E.g. not a letter, number, or separator.
10 |  */
11 | export type IsSpecial<T extends string> = IsStringLiteral<T> extends true
12 |   ? IsLetter<T> extends true
13 |     ? false
14 |     : IsDigit<T> extends true
15 |       ? false
16 |       : IsSeparator<T> extends true
17 |         ? false
18 |         : IsApostrophe<T> extends true
19 |           ? false
20 |           : true
21 |   : boolean
22 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/camel-keys.test.ts:
--------------------------------------------------------------------------------
 1 | import { type CamelKeys, camelKeys } from './camel-keys.js'
 2 | 
 3 | namespace TypeTransforms {
 4 |   type test = Expect<
 5 |     Equal<
 6 |       CamelKeys<{
 7 |         'some-value': { 'deep-nested': true }
 8 |         'other-value': true
 9 |       }>,
10 |       { someValue: { 'deep-nested': true }; otherValue: true }
11 |     >
12 |   >
13 | }
14 | 
15 | test('camelKeys', () => {
16 |   const expected = {
17 |     some: { 'deep-nested': { value: true } },
18 |     otherValue: true,
19 |   }
20 |   const result = camelKeys({
21 |     some: { 'deep-nested': { value: true } },
22 |     'other-value': true,
23 |   })
24 |   expect(result).toEqual(expected)
25 |   type test = Expect<Equal<typeof result, typeof expected>>
26 | })
27 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/camel-keys.ts:
--------------------------------------------------------------------------------
 1 | import { type CamelCase, camelCase } from '../word-case/camel-case.js'
 2 | import { transformKeys } from './transform-keys.js'
 3 | 
 4 | /**
 5 |  * Shallowly transforms the keys of a Record to camelCase.
 6 |  * T: the type of the Record to transform.
 7 |  */
 8 | export type CamelKeys<T> = T extends []
 9 |   ? T
10 |   : { [K in keyof T as CamelCase<Extract<K, string>>]: T[K] }
11 | /**
12 |  * A strongly typed function that shallowly transforms the keys of an object to camelCase. The transformation is done both at runtime and type level.
13 |  * @param obj the object to transform.
14 |  * @returns the transformed object.
15 |  * @example camelKeys({ 'foo-bar': { 'fizz-buzz': true } }) // { fooBar: { 'fizz-buz': true } }
16 |  */
17 | export function camelKeys<T>(obj: T): CamelKeys<T> {
18 |   return transformKeys(obj, camelCase) as never
19 | }
20 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/constant-keys.test.ts:
--------------------------------------------------------------------------------
 1 | import { type ConstantKeys, constantKeys } from './constant-keys.js'
 2 | 
 3 | namespace TypeTransforms {
 4 |   type test = Expect<
 5 |     Equal<
 6 |       ConstantKeys<{
 7 |         someValue: { deepNested: true }
 8 |         otherValue: true
 9 |       }>,
10 |       { SOME_VALUE: { deepNested: true }; OTHER_VALUE: true }
11 |     >
12 |   >
13 | }
14 | 
15 | describe('constantKeys', () => {
16 |   test('should shollowly transform object keys to constant case', () => {
17 |     const expected = {
18 |       SOME: { deepNested: { value: true } },
19 |       OTHER_VALUE: true,
20 |     }
21 |     const result = constantKeys({
22 |       some: { deepNested: { value: true } },
23 |       otherValue: true,
24 |     })
25 |     expect(result).toEqual(expected)
26 |     type test = Expect<Equal<typeof result, typeof expected>>
27 |   })
28 | 
29 |   test('should handle null properly', () => {
30 |     const expected = null
31 |     const result = constantKeys(null)
32 |     expect(result).toEqual(expected)
33 |     type test = Expect<Equal<typeof result, typeof expected>>
34 |   })
35 | })
36 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/constant-keys.ts:
--------------------------------------------------------------------------------
 1 | import { type ConstantCase, constantCase } from '../word-case/constant-case.js'
 2 | import { transformKeys } from './transform-keys.js'
 3 | 
 4 | /**
 5 |  * Shallowly transforms the keys of a Record to CONSTANT_CASE.
 6 |  * T: the type of the Record to transform.
 7 |  */
 8 | export type ConstantKeys<T> = T extends []
 9 |   ? T
10 |   : { [K in keyof T as ConstantCase<Extract<K, string>>]: T[K] }
11 | /**
12 |  * A strongly typed function that shallowly transforms the keys of an object to CONSTANT_CASE. The transformation is done both at runtime and type level.
13 |  * @param obj the object to transform.
14 |  * @returns the transformed object.
15 |  * @example constantKeys({ 'foo-bar': { 'fizz-buzz': true } }) // { FOO_BAR: { 'fizz-buzz': true } }
16 |  */
17 | export function constantKeys<T>(obj: T): ConstantKeys<T> {
18 |   return transformKeys(obj, constantCase) as never
19 | }
20 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/deep-camel-keys.test.ts:
--------------------------------------------------------------------------------
 1 | import { type DeepCamelKeys, deepCamelKeys } from './deep-camel-keys.js'
 2 | 
 3 | namespace TypeTransforms {
 4 |   type test = Expect<
 5 |     Equal<
 6 |       DeepCamelKeys<{
 7 |         some: { 'deep-nested': { value: true } }
 8 |         'other-value': true
 9 |       }>,
10 |       { some: { deepNested: { value: true } }; otherValue: true }
11 |     >
12 |   >
13 | }
14 | 
15 | describe('deepCamelKeys', () => {
16 |   test('should camelize the object', () => {
17 |     const expected = {
18 |       some: { deepNested: { value: true } },
19 |       otherValue: true,
20 |     }
21 |     const result = deepCamelKeys({
22 |       some: { 'deep-nested': { value: true } },
23 |       'other-value': true,
24 |     })
25 |     expect(result).toEqual(expected)
26 |     type test = Expect<Equal<typeof result, typeof expected>>
27 |   })
28 | 
29 |   test('should camelize from SCREAMING_SNAKE_CASE', () => {
30 |     const obj = {
31 |       NODE_ENV: 'development',
32 |     }
33 |     const expected = {
34 |       nodeEnv: 'development',
35 |     }
36 |     const result = deepCamelKeys(obj)
37 |     expect(result).toEqual(expected)
38 |     type test = Expect<Equal<typeof result, typeof expected>>
39 |   })
40 | })
41 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/deep-camel-keys.ts:
--------------------------------------------------------------------------------
 1 | import { type CamelCase, camelCase } from '../word-case/camel-case.js'
 2 | import { deepTransformKeys } from './deep-transform-keys.js'
 3 | 
 4 | /**
 5 |  * Recursively transforms the keys of a Record to camelCase.
 6 |  * T: the type of the Record to transform.
 7 |  */
 8 | export type DeepCamelKeys<T> = T extends [any, ...any]
 9 |   ? { [I in keyof T]: DeepCamelKeys<T[I]> }
10 |   : T extends (infer V)[]
11 |     ? DeepCamelKeys<V>[]
12 |     : {
13 |         [K in keyof T as CamelCase<Extract<K, string>>]: DeepCamelKeys<T[K]>
14 |       }
15 | /**
16 |  * A strongly typed function that recursively transforms the keys of an object to camelCase. The transformation is done both at runtime and type level.
17 |  * @param obj the object to transform.
18 |  * @returns the transformed object.
19 |  * @example deepCamelKeys({ 'foo-bar': { 'fizz-buzz': true } }) // { fooBar: { fizzBuzz: true } }
20 |  */
21 | export function deepCamelKeys<T>(obj: T): DeepCamelKeys<T> {
22 |   return deepTransformKeys(obj, camelCase) as never
23 | }
24 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/deep-constant-keys.test.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   type DeepConstantKeys,
 3 |   deepConstantKeys,
 4 | } from './deep-constant-keys.js'
 5 | 
 6 | namespace TypeTransforms {
 7 |   type test5 = Expect<
 8 |     Equal<
 9 |       DeepConstantKeys<{
10 |         some: { 'deep-nested': { value: true } }
11 |         'other-value': true
12 |       }>,
13 |       { SOME: { DEEP_NESTED: { VALUE: true } }; OTHER_VALUE: true }
14 |     >
15 |   >
16 | }
17 | 
18 | describe('deepConstantKeys', () => {
19 |   test('should deeply transform object keys to constant case', () => {
20 |     const expected = {
21 |       SOME: { DEEP_NESTED: { VALUE: true } },
22 |       OTHER_VALUE: true,
23 |     }
24 |     const result = deepConstantKeys({
25 |       some: { deepNested: { value: true } },
26 |       otherValue: true,
27 |     })
28 |     expect(result).toEqual(expected)
29 |     type test = Expect<Equal<typeof result, typeof expected>>
30 |   })
31 | 
32 |   test('should handle null properly', () => {
33 |     const expected = null
34 |     const result = deepConstantKeys(null)
35 |     expect(result).toEqual(expected)
36 |     type test = Expect<Equal<typeof result, typeof expected>>
37 |   })
38 | })
39 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/deep-constant-keys.ts:
--------------------------------------------------------------------------------
 1 | import { type ConstantCase, constantCase } from '../word-case/constant-case.js'
 2 | import { deepTransformKeys } from './deep-transform-keys.js'
 3 | 
 4 | /**
 5 |  * Recursively transforms the keys of a Record to CONSTANT_CASE.
 6 |  * T: the type of the Record to transform.
 7 |  */
 8 | export type DeepConstantKeys<T> = T extends [any, ...any]
 9 |   ? { [I in keyof T]: DeepConstantKeys<T[I]> }
10 |   : T extends (infer V)[]
11 |     ? DeepConstantKeys<V>[]
12 |     : {
13 |         [K in keyof T as ConstantCase<Extract<K, string>>]: DeepConstantKeys<
14 |           T[K]
15 |         >
16 |       }
17 | /**
18 |  * A strongly typed function that recursively transforms the keys of an object to CONSTANT_CASE. The transformation is done both at runtime and type level.
19 |  * @param obj the object to transform.
20 |  * @returns the transformed object.
21 |  * @example deepConstantKeys({ 'foo-bar': { 'fizz-buzz': true } }) // { FOO_BAR: { FIZZ_BUZZ: true } }
22 |  */
23 | export function deepConstantKeys<T>(obj: T): DeepConstantKeys<T> {
24 |   return deepTransformKeys(obj, constantCase) as never
25 | }
26 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/deep-delimiter-keys.test.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   type DeepDelimiterKeys,
 3 |   deepDelimiterKeys,
 4 | } from './deep-delimiter-keys.js'
 5 | 
 6 | namespace TypeTransforms {
 7 |   type test = Expect<
 8 |     Equal<
 9 |       DeepDelimiterKeys<
10 |         {
11 |           some: { 'deep-nested': { value: true } }
12 |           'other-value': true
13 |         },
14 |         '@'
15 |       >,
16 |       { some: { 'deep@nested': { value: true } }; 'other@value': true }
17 |     >
18 |   >
19 | }
20 | 
21 | test('deepDelimiterKeys', () => {
22 |   const expected = {
23 |     some: { 'deep@nested': { value: true } },
24 |     'other@value': true,
25 |   }
26 |   const result = deepDelimiterKeys(
27 |     {
28 |       some: { 'deep-nested': { value: true } },
29 |       'other-value': true,
30 |     },
31 |     '@'
32 |   )
33 |   expect(result).toEqual(expected)
34 |   type test = Expect<Equal<typeof result, typeof expected>>
35 | })
36 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/deep-delimiter-keys.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   type DelimiterCase,
 3 |   delimiterCase,
 4 | } from '../word-case/delimiter-case.js'
 5 | import { deepTransformKeys } from './deep-transform-keys.js'
 6 | 
 7 | /**
 8 |  * Recursively transforms the keys of a Record to a custom delimiter case.
 9 |  * T: the type of the Record to transform.
10 |  * D: the delimiter to use.
11 |  */
12 | export type DeepDelimiterKeys<T, D extends string> = T extends [any, ...any]
13 |   ? { [I in keyof T]: DeepDelimiterKeys<T[I], D> }
14 |   : T extends (infer V)[]
15 |     ? DeepDelimiterKeys<V, D>[]
16 |     : {
17 |         [K in keyof T as DelimiterCase<
18 |           Extract<K, string>,
19 |           D
20 |         >]: DeepDelimiterKeys<T[K], D>
21 |       }
22 | /**
23 |  * A strongly typed function that recursively transforms the keys of an object to a custom delimiter case. The transformation is done both at runtime and type level.
24 |  * @param obj the object to transform.
25 |  * @param delimiter the delimiter to use.
26 |  * @returns the transformed object.
27 |  * @example deepDelimiterKeys({ 'foo-bar': { 'fizz-buzz': true } }, '.') // { 'foo.bar': { 'fizz.buzz': true } }
28 |  */
29 | export function deepDelimiterKeys<T, D extends string>(
30 |   obj: T,
31 |   delimiter: D
32 | ): DeepDelimiterKeys<T, D> {
33 |   return deepTransformKeys(obj, (str) => delimiterCase(str, delimiter)) as never
34 | }
35 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/deep-kebab-keys.test.ts:
--------------------------------------------------------------------------------
 1 | import { type DeepKebabKeys, deepKebabKeys } from './deep-kebab-keys.js'
 2 | 
 3 | namespace TypeTransforms {
 4 |   type test = Expect<
 5 |     Equal<
 6 |       DeepKebabKeys<{
 7 |         some: { deepNested: { value: true } }
 8 |         otherValue: true
 9 |       }>,
10 |       { some: { 'deep-nested': { value: true } }; 'other-value': true }
11 |     >
12 |   >
13 | }
14 | 
15 | test('deepKebabKeys', () => {
16 |   const expected = {
17 |     some: { 'deep-nested': { value: true } },
18 |     'other-value': true,
19 |   }
20 |   const result = deepKebabKeys({
21 |     some: { deepNested: { value: true } },
22 |     otherValue: true,
23 |   })
24 |   expect(result).toEqual(expected)
25 |   type test = Expect<Equal<typeof result, typeof expected>>
26 | })
27 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/deep-kebab-keys.ts:
--------------------------------------------------------------------------------
 1 | import { type KebabCase, kebabCase } from '../word-case/kebab-case.js'
 2 | import { deepTransformKeys } from './deep-transform-keys.js'
 3 | 
 4 | /**
 5 |  * Recursively transforms the keys of a Record to kebab-case.
 6 |  * T: the type of the Record to transform.
 7 |  */
 8 | export type DeepKebabKeys<T> = T extends [any, ...any]
 9 |   ? { [I in keyof T]: DeepKebabKeys<T[I]> }
10 |   : T extends (infer V)[]
11 |     ? DeepKebabKeys<V>[]
12 |     : {
13 |         [K in keyof T as KebabCase<Extract<K, string>>]: DeepKebabKeys<T[K]>
14 |       }
15 | /**
16 |  * A strongly typed function that recursively transforms the keys of an object to kebab-case. The transformation is done both at runtime and type level.
17 |  * @param obj the object to transform.
18 |  * @returns the transformed object.
19 |  * @example deepKebabKeys({ 'foo-bar': { 'fizz-buzz': true } }) // { 'foo-bar': { 'fizz-buzz': true } }
20 |  */
21 | export function deepKebabKeys<T>(obj: T): DeepKebabKeys<T> {
22 |   return deepTransformKeys(obj, kebabCase) as never
23 | }
24 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/deep-pascal-keys.test.ts:
--------------------------------------------------------------------------------
 1 | import { type DeepPascalKeys, deepPascalKeys } from './deep-pascal-keys.js'
 2 | 
 3 | namespace TypeTransforms {
 4 |   type test = Expect<
 5 |     Equal<
 6 |       DeepPascalKeys<{
 7 |         some: { 'deep-nested': { value: true } }
 8 |         'other-value': true
 9 |       }>,
10 |       { Some: { DeepNested: { Value: true } }; OtherValue: true }
11 |     >
12 |   >
13 | }
14 | 
15 | test('deepPascalKeys', () => {
16 |   const expected = {
17 |     Some: { DeepNested: { Value: true } },
18 |     OtherValue: true,
19 |   }
20 |   const result = deepPascalKeys({
21 |     some: { deepNested: { value: true } },
22 |     otherValue: true,
23 |   })
24 |   expect(result).toEqual(expected)
25 |   type test = Expect<Equal<typeof result, typeof expected>>
26 | })
27 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/deep-pascal-keys.ts:
--------------------------------------------------------------------------------
 1 | import { type PascalCase, pascalCase } from '../word-case/pascal-case.js'
 2 | import { deepTransformKeys } from './deep-transform-keys.js'
 3 | 
 4 | /**
 5 |  * Recursively transforms the keys of a Record to PascalCase.
 6 |  * T: the type of the Record to transform.
 7 |  */
 8 | export type DeepPascalKeys<T> = T extends [any, ...any]
 9 |   ? { [I in keyof T]: DeepPascalKeys<T[I]> }
10 |   : T extends (infer V)[]
11 |     ? DeepPascalKeys<V>[]
12 |     : {
13 |         [K in keyof T as PascalCase<Extract<K, string>>]: DeepPascalKeys<T[K]>
14 |       }
15 | /**
16 |  * A strongly typed function that recursively transforms the keys of an object to pascal case. The transformation is done both at runtime and type level.
17 |  * @param obj the object to transform.
18 |  * @returns the transformed object.
19 |  * @example deepPascalKeys({ 'foo-bar': { 'fizz-buzz': true } }) // { FooBar: { FizzBuzz: true } }
20 |  */
21 | export function deepPascalKeys<T>(obj: T): DeepPascalKeys<T> {
22 |   return deepTransformKeys(obj, pascalCase) as never
23 | }
24 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/deep-snake-keys.test.ts:
--------------------------------------------------------------------------------
 1 | import { type DeepSnakeKeys, deepSnakeKeys } from './deep-snake-keys.js'
 2 | 
 3 | namespace TypeTransforms {
 4 |   type test = Expect<
 5 |     Equal<
 6 |       DeepSnakeKeys<{
 7 |         some: { 'deep-nested': { value: true } }
 8 |         'other-value': true
 9 |       }>,
10 |       { some: { deep_nested: { value: true } }; other_value: true }
11 |     >
12 |   >
13 | }
14 | 
15 | test('deepSnakeKeys', () => {
16 |   const expected = {
17 |     some: { deep_nested: { value: true } },
18 |     other_value: true,
19 |   }
20 |   const result = deepSnakeKeys({
21 |     some: { deepNested: { value: true } },
22 |     otherValue: true,
23 |   })
24 |   expect(result).toEqual(expected)
25 |   type test = Expect<Equal<typeof result, typeof expected>>
26 | })
27 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/deep-snake-keys.ts:
--------------------------------------------------------------------------------
 1 | import { type SnakeCase, snakeCase } from '../word-case/snake-case.js'
 2 | import { deepTransformKeys } from './deep-transform-keys.js'
 3 | 
 4 | /**
 5 |  * Recursively transforms the keys of a Record to snake_case.
 6 |  * T: the type of the Record to transform.
 7 |  */
 8 | export type DeepSnakeKeys<T> = T extends [any, ...any]
 9 |   ? { [I in keyof T]: DeepSnakeKeys<T[I]> }
10 |   : T extends (infer V)[]
11 |     ? DeepSnakeKeys<V>[]
12 |     : {
13 |         [K in keyof T as SnakeCase<Extract<K, string>>]: DeepSnakeKeys<T[K]>
14 |       }
15 | /**
16 |  * A strongly typed function that recursively transforms the keys of an object to snake_case. The transformation is done both at runtime and type level.
17 |  * @param obj the object to transform.
18 |  * @returns the transformed object.
19 |  * @example deepSnakeKeys({ 'foo-bar': { 'fizz-buzz': true } }) // { 'foo_bar': { 'fizz_buzz': true } }
20 |  */
21 | export function deepSnakeKeys<T>(obj: T): DeepSnakeKeys<T> {
22 |   return deepTransformKeys(obj, snakeCase) as never
23 | }
24 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/deep-transform-keys.test.ts:
--------------------------------------------------------------------------------
 1 | import { deepTransformKeys } from './deep-transform-keys.js'
 2 | 
 3 | describe('deepTransformKeys', () => {
 4 |   test('should deeply transform the keys of an object', () => {
 5 |     const expected = {
 6 |       SOME: [{ 'DEEP-NESTED': { VALUE: true } }],
 7 |       'OTHER-VALUE': true,
 8 |     }
 9 |     const result = deepTransformKeys(
10 |       {
11 |         some: [{ 'deep-nested': { value: true } }],
12 |         'other-value': true,
13 |       },
14 |       (key) => key.toUpperCase()
15 |     )
16 |     expect(result).toEqual(expected)
17 |   })
18 | })
19 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/deep-transform-keys.ts:
--------------------------------------------------------------------------------
 1 | import { typeOf } from '../../internal/internals.js'
 2 | 
 3 | /**
 4 |  * This function is used to transform the keys of an object deeply.
 5 |  * It will only be transformed at runtime, so it's not type safe.
 6 |  * @param obj the object to transform.
 7 |  * @param transform the function to transform the keys from string to string.
 8 |  * @returns the transformed object.
 9 |  * @example deepTransformKeys({ 'foo-bar': { 'fizz-buzz': true } }, camelCase)
10 |  * // { fooBar: { fizzBuzz: true } }
11 |  */
12 | export function deepTransformKeys<T>(
13 |   obj: T,
14 |   transform: (s: string) => string
15 | ): T {
16 |   if (!['object', 'array'].includes(typeOf(obj))) return obj
17 | 
18 |   if (Array.isArray(obj)) {
19 |     return obj.map((x) => deepTransformKeys(x, transform)) as T
20 |   }
21 |   const res = {} as T
22 |   for (const key in obj) {
23 |     res[transform(key) as keyof T] = deepTransformKeys(obj[key], transform)
24 |   }
25 |   return res
26 | }
27 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/delimiter-keys.test.ts:
--------------------------------------------------------------------------------
 1 | import { type DelimiterKeys, delimiterKeys } from './delimiter-keys.js'
 2 | 
 3 | namespace TypeTransforms {
 4 |   type test = Expect<
 5 |     Equal<
 6 |       DelimiterKeys<
 7 |         {
 8 |           'some-value': { 'nested-value': true }
 9 |           'other-value': true
10 |         },
11 |         '@'
12 |       >,
13 |       { 'some@value': { 'nested-value': true }; 'other@value': true }
14 |     >
15 |   >
16 | }
17 | 
18 | test('delimiterKeys', () => {
19 |   const expected = {
20 |     some: { 'deep-nested': { value: true } },
21 |     'other@value': true,
22 |   }
23 |   const result = delimiterKeys(
24 |     {
25 |       some: { 'deep-nested': { value: true } },
26 |       'other-value': true,
27 |     },
28 |     '@'
29 |   )
30 |   expect(result).toEqual(expected)
31 |   type test = Expect<Equal<typeof result, typeof expected>>
32 | })
33 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/delimiter-keys.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   type DelimiterCase,
 3 |   delimiterCase,
 4 | } from '../word-case/delimiter-case.js'
 5 | import { transformKeys } from './transform-keys.js'
 6 | 
 7 | /**
 8 |  * Shallowly transforms the keys of a Record to a custom delimiter case.
 9 |  * T: the type of the Record to transform.
10 |  * D: the delimiter to use.
11 |  */
12 | export type DelimiterKeys<T, D extends string> = T extends []
13 |   ? T
14 |   : { [K in keyof T as DelimiterCase<Extract<K, string>, D>]: T[K] }
15 | /**
16 |  * A strongly typed function that shallowly transforms the keys of an object to a custom delimiter case. The transformation is done both at runtime and type level.
17 |  * @param obj the object to transform.
18 |  * @param delimiter the delimiter to use.
19 |  * @returns the transformed object.
20 |  * @example delimiterKeys({ 'foo-bar': { 'fizz-buzz': true } }, '.') // { 'foo.bar': { 'fizz.buzz': true } }
21 |  */
22 | export function delimiterKeys<T, D extends string>(
23 |   obj: T,
24 |   delimiter: D
25 | ): DelimiterKeys<T, D> {
26 |   return transformKeys(obj, (str) => delimiterCase(str, delimiter)) as never
27 | }
28 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/kebab-keys.test.ts:
--------------------------------------------------------------------------------
 1 | import { type KebabKeys, kebabKeys } from './kebab-keys.js'
 2 | 
 3 | namespace TypeTransforms {
 4 |   type test = Expect<
 5 |     Equal<
 6 |       KebabKeys<{
 7 |         someValue: { deepNested: true }
 8 |         otherValue: true
 9 |       }>,
10 |       { 'some-value': { deepNested: true }; 'other-value': true }
11 |     >
12 |   >
13 | }
14 | 
15 | test('kebabKeys', () => {
16 |   const expected = {
17 |     some: { deepNested: { value: true } },
18 |     'other-value': true,
19 |   }
20 |   const result = kebabKeys({
21 |     some: { deepNested: { value: true } },
22 |     otherValue: true,
23 |   })
24 |   expect(result).toEqual(expected)
25 |   type test = Expect<Equal<typeof result, typeof expected>>
26 | })
27 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/kebab-keys.ts:
--------------------------------------------------------------------------------
 1 | import { type KebabCase, kebabCase } from '../word-case/kebab-case.js'
 2 | import { transformKeys } from './transform-keys.js'
 3 | 
 4 | /**
 5 |  * Shallowly transforms the keys of a Record to kebab-case.
 6 |  * T: the type of the Record to transform.
 7 |  */
 8 | export type KebabKeys<T> = T extends []
 9 |   ? T
10 |   : {
11 |       [K in keyof T as KebabCase<Extract<K, string>>]: T[K]
12 |     }
13 | /**
14 |  * A strongly typed function that shallowly transforms the keys of an object to kebab-case. The transformation is done both at runtime and type level.
15 |  * @param obj the object to transform.
16 |  * @returns the transformed object.
17 |  * @example kebabKeys({ fooBar: { fizzBuzz: true } }) // { 'foo-bar': { fizzBuzz: true } }
18 |  */
19 | export function kebabKeys<T>(obj: T): KebabKeys<T> {
20 |   return transformKeys(obj, kebabCase) as never
21 | }
22 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/pascal-keys.test.ts:
--------------------------------------------------------------------------------
 1 | import { type PascalKeys, pascalKeys } from './pascal-keys.js'
 2 | 
 3 | namespace TypeTransforms {
 4 |   type test = Expect<
 5 |     Equal<
 6 |       PascalKeys<{
 7 |         someValue: { deepNested: true }
 8 |         otherValue: true
 9 |       }>,
10 |       { SomeValue: { deepNested: true }; OtherValue: true }
11 |     >
12 |   >
13 | }
14 | 
15 | test('pascalKeys', () => {
16 |   const expected = {
17 |     Some: { deepNested: { value: true } },
18 |     OtherValue: true,
19 |   }
20 |   const result = pascalKeys({
21 |     some: { deepNested: { value: true } },
22 |     otherValue: true,
23 |   })
24 |   expect(result).toEqual(expected)
25 |   type test = Expect<Equal<typeof result, typeof expected>>
26 | })
27 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/pascal-keys.ts:
--------------------------------------------------------------------------------
 1 | import { type PascalCase, pascalCase } from '../word-case/pascal-case.js'
 2 | import { transformKeys } from './transform-keys.js'
 3 | 
 4 | /**
 5 |  * Shallowly transforms the keys of a Record to PascalCase.
 6 |  * T: the type of the Record to transform.
 7 |  */
 8 | export type PascalKeys<T> = T extends []
 9 |   ? T
10 |   : { [K in keyof T as PascalCase<Extract<K, string>>]: T[K] }
11 | /**
12 |  * A strongly typed function that shallowly transforms the keys of an object to pascal case. The transformation is done both at runtime and type level.
13 |  * @param obj the object to transform.
14 |  * @returns the transformed object.
15 |  * @example pascalKeys({ 'foo-bar': { 'fizz-buzz': true } }) // { FooBar: { 'fizz-buzz': true } }
16 |  */
17 | export function pascalKeys<T>(obj: T): PascalKeys<T> {
18 |   return transformKeys(obj, pascalCase) as never
19 | }
20 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/replace-keys.test.ts:
--------------------------------------------------------------------------------
 1 | import { type ReplaceKeys, replaceKeys } from './replace-keys.js'
 2 | 
 3 | namespace TypeTransforms {
 4 |   type test = Expect<
 5 |     Equal<
 6 |       ReplaceKeys<
 7 |         {
 8 |           'some-value': { 'deep-nested': true }
 9 |           'other-value': true
10 |         },
11 |         'some-',
12 |         ''
13 |       >,
14 |       { value: { 'deep-nested': true }; 'other-value': true }
15 |     >
16 |   >
17 |   type testWithUnion = Expect<
18 |     Equal<
19 |       ReplaceKeys<Record<'foo' | 'bar', string>, 'oo', 'izz'>,
20 |       Record<'fizz' | 'bar', string>
21 |     >
22 |   >
23 |   type test2 = Expect<
24 |     Equal<
25 |       ReplaceKeys<Record<'some nice string', string>, RegExp, '-'>,
26 |       Record<string, string>
27 |     >
28 |   >
29 |   type test3 = Expect<
30 |     Equal<ReplaceKeys<Record<string, string>, ' ', '-'>, Record<string, string>>
31 |   >
32 |   type test4 = Expect<
33 |     Equal<
34 |       ReplaceKeys<Record<Uppercase<string>, string>, ' ', '-'>,
35 |       Record<string, string>
36 |     >
37 |   >
38 |   type test5 = Expect<
39 |     Equal<
40 |       ReplaceKeys<Record<'some nice string', string>, string, '-'>,
41 |       Record<string, string>
42 |     >
43 |   >
44 |   type test6 = Expect<
45 |     Equal<
46 |       ReplaceKeys<Record<'some nice string', string>, ' ', string>,
47 |       Record<string, string>
48 |     >
49 |   >
50 | }
51 | 
52 | test('replaceKeys', () => {
53 |   const expected = {
54 |     some: { deepNested: { value: true } },
55 |     value: true,
56 |   }
57 |   const result = replaceKeys(
58 |     {
59 |       some: { deepNested: { value: true } },
60 |       other_value: true,
61 |     },
62 |     'other_',
63 |     ''
64 |   )
65 |   expect(result).toEqual(expected)
66 |   type test = Expect<Equal<typeof result, typeof expected>>
67 | })
68 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/replace-keys.ts:
--------------------------------------------------------------------------------
 1 | import { type Replace, replace } from '../../native/replace.js'
 2 | import { transformKeys } from './transform-keys.js'
 3 | 
 4 | /**
 5 |  * Shallowly transforms the keys of a Record with `replace`.
 6 |  * T: the type of the Record to transform.
 7 |  */
 8 | export type ReplaceKeys<
 9 |   T,
10 |   lookup extends string | RegExp,
11 |   replacement extends string = '',
12 | > = T extends []
13 |   ? T
14 |   : {
15 |       [K in keyof T as Replace<Extract<K, string>, lookup, replacement>]: T[K]
16 |     }
17 | /**
18 |  * A strongly typed function that shallowly transforms the keys of an object by running the `replace` method in every key. The transformation is done both at runtime and type level.
19 |  * @param obj the object to transform.
20 |  * @param lookup the lookup string to be replaced.
21 |  * @param replacement the replacement string.
22 |  * @returns the transformed object.
23 |  * @example replaceKeys({ 'foo-bar': { 'fizz-buzz': true } }, 'f', 'b') // { booBar: { 'fizz-buz': true } }
24 |  */
25 | export function replaceKeys<
26 |   T,
27 |   S extends string | RegExp,
28 |   R extends string = '',
29 | >(obj: T, lookup: S, replacement: R = '' as R): ReplaceKeys<T, S, R> {
30 |   return transformKeys(obj, (s) => replace(s, lookup, replacement)) as never
31 | }
32 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/snake-keys.test.ts:
--------------------------------------------------------------------------------
 1 | import { type SnakeKeys, snakeKeys } from './snake-keys.js'
 2 | 
 3 | namespace TypeTransforms {
 4 |   type test = Expect<
 5 |     Equal<
 6 |       SnakeKeys<{
 7 |         'some-value': { 'deep-nested': true }
 8 |         'other-value': true
 9 |       }>,
10 |       { some_value: { 'deep-nested': true }; other_value: true }
11 |     >
12 |   >
13 | }
14 | 
15 | test('snakeKeys', () => {
16 |   const expected = {
17 |     some: { deepNested: { value: true } },
18 |     other_value: true,
19 |   }
20 |   const result = snakeKeys({
21 |     some: { deepNested: { value: true } },
22 |     otherValue: true,
23 |   })
24 |   expect(result).toEqual(expected)
25 |   type test = Expect<Equal<typeof result, typeof expected>>
26 | })
27 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/snake-keys.ts:
--------------------------------------------------------------------------------
 1 | import { type SnakeCase, snakeCase } from '../word-case/snake-case.js'
 2 | import { transformKeys } from './transform-keys.js'
 3 | 
 4 | /**
 5 |  * Shallowly transforms the keys of a Record to snake_case.
 6 |  * T: the type of the Record to transform.
 7 |  */
 8 | export type SnakeKeys<T> = T extends []
 9 |   ? T
10 |   : { [K in keyof T as SnakeCase<Extract<K, string>>]: T[K] }
11 | /**
12 |  * A strongly typed function that shallowly the keys of an object to snake_case. The transformation is done both at runtime and type level.
13 |  * @param obj the object to transform.
14 |  * @returns the transformed object.
15 |  * @example snakeKeys({ 'foo-bar': { 'fizz-buzz': true } }) // { 'foo_bar': { 'fizz-buzz': true } }
16 |  */
17 | export function snakeKeys<T>(obj: T): SnakeKeys<T> {
18 |   return transformKeys(obj, snakeCase) as never
19 | }
20 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/transform-keys.test.ts:
--------------------------------------------------------------------------------
 1 | import { transformKeys } from './transform-keys.js'
 2 | 
 3 | describe('transformKeys', () => {
 4 |   test('should shallowly transform the keys of an object', () => {
 5 |     const expected = {
 6 |       SOME: { 'deep-nested': { value: true } },
 7 |       'OTHER-VALUE': true,
 8 |     }
 9 |     const result = transformKeys(
10 |       {
11 |         some: { 'deep-nested': { value: true } },
12 |         'other-value': true,
13 |       },
14 |       (key) => key.toUpperCase()
15 |     )
16 |     expect(result).toEqual(expected)
17 |   })
18 | })
19 | 


--------------------------------------------------------------------------------
/src/utils/object-keys/transform-keys.ts:
--------------------------------------------------------------------------------
 1 | import { typeOf } from '../../internal/internals.js'
 2 | 
 3 | /**
 4 |  * This function is used to shallowly transform the keys of an object.
 5 |  * It will only be transformed at runtime, so it's not type safe.
 6 |  * @param obj the object to transform.
 7 |  * @param transform the function to transform the keys from string to string.
 8 |  * @returns the transformed object.
 9 |  * @example transformKeys({ 'foo-bar': { 'fizz-buzz': true } }, camelCase)
10 |  * // { fooBar: { 'fizz-buzz': true } }
11 |  */
12 | export function transformKeys<T>(obj: T, transform: (s: string) => string): T {
13 |   if (typeOf(obj) !== 'object') return obj
14 | 
15 |   const res = {} as T
16 |   for (const key in obj) {
17 |     res[transform(key) as keyof T] = obj[key]
18 |   }
19 |   return res
20 | }
21 | 


--------------------------------------------------------------------------------
/src/utils/reverse.test.ts:
--------------------------------------------------------------------------------
 1 | import { type Reverse, reverse } from './reverse'
 2 | 
 3 | namespace ReverseTests {
 4 |   type test1 = Expect<Equal<Reverse<'hello'>, 'olleh'>>
 5 |   type test2 = Expect<Equal<Reverse<'123'>, '321'>>
 6 |   type test3 = Expect<
 7 |     Equal<Reverse<'I love TypeScript!'>, '!tpircSepyT evol I'>
 8 |   >
 9 |   type test4 = Expect<Equal<Reverse<string>, string>>
10 |   type test5 = Expect<Equal<Reverse<Uppercase<string>>, Uppercase<string>>>
11 | 
12 |   // Template strings
13 |   type testTS1 = Expect<Equal<Reverse<`abc${string}`>, `${string}cba`>>
14 |   type testTS2 = Expect<Equal<Reverse<`abc${string}xyz`>, `zyx${string}cba`>>
15 |   type testTS3 = Expect<Equal<Reverse<`${string}xyz`>, `zyx${string}`>>
16 | }
17 | 
18 | describe('reverse', () => {
19 |   test('should reverse a string', () => {
20 |     const expected = '!desrever eb ot eraperp ,dlrow lufituaeb olleH'
21 |     const data = 'Hello beautiful world, prepare to be reversed!'
22 |     const result = reverse(data)
23 |     expect(result).toEqual(expected)
24 |     type test = Expect<Equal<typeof result, typeof expected>>
25 |   })
26 | 
27 |   test('should reverse a long string', () => {
28 |     const expected =
29 |       'murobal tse di mina tillom tnuresed aiciffo iuq apluc ni tnus ,tnediorp non tatadipuc taceacco tnis ruetpecxE .rutairap allun taiguf ue erolod mullic esse tilev etatpulov ni tiredneherper ni rolod eruri etua siuD .tauqesnoc odommoc ae xe piuqila tu isin sirobal ocmallu noitaticrexe durtson siuq ,mainev minim da mine tU .auqila angam erolod te erobal tu tnudidicni ropmet domsuie od des ,tile gnicsipida rutetcesnoc ,tema tis rolod muspi meroL'
30 |     const data =
31 |       'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum'
32 |     const result = reverse(data)
33 |     expect(result).toEqual(expected)
34 |     type test = Expect<Equal<typeof result, typeof expected>>
35 |   })
36 | })
37 | 


--------------------------------------------------------------------------------
/src/utils/reverse.ts:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * Reverses a string.
 3 |  * - `T` The string to reverse.
 4 |  */
 5 | type Reverse<
 6 |   T extends string,
 7 |   _acc extends string = '',
 8 | > = T extends `${infer Head}${infer Tail}`
 9 |   ? Reverse<Tail, `${Head}${_acc}`>
10 |   : _acc extends ''
11 |     ? T
12 |     : `${T}${_acc}`
13 | 
14 | /**
15 |  * A strongly-typed function to reverse a string.
16 |  * @param str the string to reverse.
17 |  * @returns the reversed string in both type level and runtime.
18 |  * @example reverse('hello world') // 'dlrow olleh'
19 |  */
20 | function reverse<T extends string>(str: T) {
21 |   return str.split('').reverse().join('') as Reverse<T>
22 | }
23 | 
24 | export type { Reverse }
25 | export { reverse }
26 | 


--------------------------------------------------------------------------------
/src/utils/truncate.test.ts:
--------------------------------------------------------------------------------
 1 | import { type Truncate, truncate } from './truncate.js'
 2 | 
 3 | namespace TruncateTests {
 4 |   type test1 = Expect<Equal<Truncate<'Hello, world', 9>, 'Hello,...'>>
 5 |   type test2 = Expect<Equal<Truncate<'Hello, world', 12>, 'Hello, world'>>
 6 |   type test3 = Expect<Equal<Truncate<'Hello, world', 2>, '...'>>
 7 |   type test4 = Expect<Equal<Truncate<'Hello, world', 9, '[...]'>, 'Hell[...]'>>
 8 |   type test5 = Expect<Equal<Truncate<'Hello, world', -1>, '...'>>
 9 |   type test6 = Expect<Equal<Truncate<'Hello, world', 0, '[...]'>, '[...]'>>
10 |   type test7 = Expect<Equal<Truncate<string, 0, '[...]'>, string>>
11 |   type test8 = Expect<Equal<Truncate<Uppercase<string>, 0, '[...]'>, string>>
12 |   type test9 = Expect<Equal<Truncate<'Hello, world', number, '[...]'>, string>>
13 |   type test10 = Expect<Equal<Truncate<'Hello, world', 0, string>, string>>
14 | }
15 | 
16 | describe('truncate', () => {
17 |   test('truncate small sentence does nothing', () => {
18 |     const expected = 'Hello' as const
19 |     const result = truncate('Hello', 9)
20 |     expect(result).toEqual(expected)
21 |     type test = Expect<Equal<typeof result, typeof expected>>
22 |   })
23 | 
24 |   test('truncate big sentence truncate', () => {
25 |     const expected = 'Hello ...' as const
26 |     const result = truncate('Hello world', 9)
27 |     expect(result).toEqual(expected)
28 |     type test = Expect<Equal<typeof result, typeof expected>>
29 |   })
30 | 
31 |   test('truncate with negative integer does truncate', () => {
32 |     const expected = '...' as const
33 |     const result = truncate('Hello world', -1)
34 |     expect(result).toEqual(expected)
35 |     type test = Expect<Equal<typeof result, typeof expected>>
36 |   })
37 | 
38 |   test('truncate big sentence with specified omission', () => {
39 |     const expected = 'Hello[...]' as const
40 |     const result = truncate('Hello world', 10, '[...]')
41 |     expect(result).toEqual(expected)
42 |     type test = Expect<Equal<typeof result, typeof expected>>
43 |   })
44 | 
45 |   test('truncate small sentence with specified omission', () => {
46 |     const expected = 'Hello' as const
47 |     const result = truncate('Hello', 10, '[...]')
48 |     expect(result).toEqual(expected)
49 |     type test = Expect<Equal<typeof result, typeof expected>>
50 |   })
51 | })
52 | 


--------------------------------------------------------------------------------
/src/utils/truncate.ts:
--------------------------------------------------------------------------------
 1 | import type {
 2 |   All,
 3 |   IsNumberLiteral,
 4 |   IsStringLiteral,
 5 | } from '../internal/literals.js'
 6 | import type { Math } from '../internal/math.js'
 7 | import { type Join, join } from '../native/join.js'
 8 | import type { Length } from '../native/length.js'
 9 | import type { Slice } from '../native/slice.js'
10 | 
11 | // STRING FUNCTIONS
12 | 
13 | /**
14 |  * Truncate a string if it's longer than the given maximum length.
15 |  * The last characters of the truncated string are replaced with the omission string which defaults to "...".
16 |  */
17 | export type Truncate<
18 |   T extends string,
19 |   Size extends number,
20 |   Omission extends string = '...',
21 | > = All<[IsStringLiteral<T | Omission>, IsNumberLiteral<Size>]> extends true
22 |   ? Math.IsNegative<Size> extends true
23 |     ? Omission
24 |     : Math.Subtract<Length<T>, Size> extends 0
25 |       ? T
26 |       : Join<[Slice<T, 0, Math.Subtract<Size, Length<Omission>>>, Omission]>
27 |   : string
28 | 
29 | /**
30 |  * A strongly typed function to truncate a string if it's longer than the given maximum string length.
31 |  * The last characters of the truncated string are replaced with the omission string which defaults to "...".
32 |  * @param sentence the sentence to extract the words from.
33 |  * @param length the maximum length of the string.
34 |  * @param omission the string to append to the end of the truncated string.
35 |  * @returns the truncated string
36 |  * @example truncate('Hello, World', 8) // 'Hello...'
37 |  */
38 | export function truncate<
39 |   T extends string,
40 |   S extends number,
41 |   P extends string = '...',
42 | >(sentence: T, length: S, omission = '...' as P) {
43 |   if (length < 0) return omission as Truncate<T, S, P>
44 |   if (sentence.length <= length) return sentence as Truncate<T, S, P>
45 |   return join([
46 |     sentence.slice(0, length - omission.length),
47 |     omission,
48 |   ]) as Truncate<T, S, P>
49 | }
50 | 


--------------------------------------------------------------------------------
/src/utils/word-case/camel-case.test.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   SEPARATORS_TEXT,
 3 |   WEIRD_TEXT,
 4 |   type WeirdTextUnion,
 5 | } from '../../internal/fixtures.js'
 6 | import { type CamelCase, camelCase } from './camel-case.js'
 7 | 
 8 | namespace TypeTransforms {
 9 |   type test = Expect<
10 |     Equal<
11 |       CamelCase<WeirdTextUnion>,
12 |       | 'someWeirdCased$*String1986FooBarWForWumbo'
13 |       | 'wheresTheLeakMaam'
14 |       | 'dontDistributeUnions'
15 |     >
16 |   >
17 | }
18 | 
19 | describe('camelCase', () => {
20 |   test('casing functions', () => {
21 |     const expected = 'someWeirdCased$*String1986FooBarWForWumbo' as const
22 |     const result = camelCase(WEIRD_TEXT)
23 |     expect(result).toEqual(expected)
24 |     type test = Expect<Equal<typeof result, typeof expected>>
25 |   })
26 |   test('with various separators', () => {
27 |     const result = camelCase(SEPARATORS_TEXT)
28 |     const expected = 'oneTwoThreeFourFiveSixSevenEightNineTen'
29 |     expect(result).toEqual(expected)
30 |     type test = Expect<Equal<typeof result, typeof expected>>
31 |   })
32 | })
33 | 


--------------------------------------------------------------------------------
/src/utils/word-case/camel-case.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   type RemoveApostrophe,
 3 |   removeApostrophe,
 4 | } from '../characters/apostrophe.js'
 5 | import { type PascalCase, pascalCase } from './pascal-case.js'
 6 | import { uncapitalize } from './uncapitalize.js'
 7 | 
 8 | /**
 9 |  * Transforms a string to camelCase.
10 |  */
11 | export type CamelCase<T extends string> = Uncapitalize<
12 |   PascalCase<RemoveApostrophe<T>>
13 | >
14 | 
15 | /**
16 |  * A strongly typed version of `camelCase` that works in both runtime and type level.
17 |  * @param str the string to convert to camel case.
18 |  * @returns the camel cased string.
19 |  * @example camelCase('hello world') // 'helloWorld'
20 |  */
21 | export function camelCase<T extends string>(str: T): CamelCase<T> {
22 |   return uncapitalize(pascalCase(removeApostrophe(str)))
23 | }
24 | 
25 | /**
26 |  * @deprecated
27 |  * Use `camelCase` instead.
28 |  * Read more about the deprecation [here](https://github.com/gustavoguichard/string-ts/issues/44).
29 |  */
30 | export const toCamelCase = camelCase
31 | 


--------------------------------------------------------------------------------
/src/utils/word-case/capitalize.test.ts:
--------------------------------------------------------------------------------
 1 | import { WEIRD_TEXT } from '../../internal/fixtures.js'
 2 | import { capitalize } from './capitalize.js'
 3 | 
 4 | describe('capitalize', () => {
 5 |   test('it does nothing with a string that has no char at the beginning', () => {
 6 |     const expected = WEIRD_TEXT
 7 |     const result = capitalize(WEIRD_TEXT)
 8 |     expect(result).toEqual(expected)
 9 |     type test = Expect<Equal<typeof result, typeof expected>>
10 |   })
11 | 
12 |   test('it capitalizes the first char of a string', () => {
13 |     const expected = 'SomeWeird-casedString' as const
14 |     const result = capitalize('someWeird-casedString')
15 |     expect(result).toEqual(expected)
16 |     type test = Expect<Equal<typeof result, typeof expected>>
17 |   })
18 | })
19 | 


--------------------------------------------------------------------------------
/src/utils/word-case/capitalize.ts:
--------------------------------------------------------------------------------
 1 | import { charAt } from '../../native/char-at.js'
 2 | import { join } from '../../native/join.js'
 3 | import { slice } from '../../native/slice.js'
 4 | import { toUpperCase } from '../../native/to-upper-case.js'
 5 | 
 6 | /**
 7 |  * Capitalizes the first letter of a string. This is a runtime counterpart of `Capitalize<T>` from `src/types.d.ts`.
 8 |  * @param str the string to capitalize.
 9 |  * @returns the capitalized string.
10 |  * @example capitalize('hello world') // 'Hello world'
11 |  */
12 | export function capitalize<T extends string>(str: T) {
13 |   return join([
14 |     toUpperCase(charAt(str, 0) ?? ''),
15 |     slice(str, 1),
16 |   ]) as Capitalize<T>
17 | }
18 | 


--------------------------------------------------------------------------------
/src/utils/word-case/constant-case.test.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   SEPARATORS_TEXT,
 3 |   type WeirdTextUnion,
 4 | } from '../../internal/fixtures.js'
 5 | import { type ConstantCase, constantCase } from './constant-case.js'
 6 | 
 7 | namespace TypeTransforms {
 8 |   type test = Expect<
 9 |     Equal<
10 |       ConstantCase<WeirdTextUnion>,
11 |       | 'SOME_WEIRD_CASED_$*_STRING_1986_FOO_BAR_W_FOR_WUMBO'
12 |       | 'WHERES_THE_LEAK_MAAM'
13 |       | 'DONT_DISTRIBUTE_UNIONS'
14 |     >
15 |   >
16 | }
17 | 
18 | describe('constantCase', () => {
19 |   test('casing functions', () => {
20 |     const expected =
21 |       'SOME_WEIRD_CASED_$*_STRING_1986_FOO_BAR_W_FOR_WUMBO' as const
22 |     const result = constantCase(
23 |       ' someWeird-cased$*String1986Foo Bar W_FOR_WUMBO'
24 |     )
25 |     expect(result).toEqual(expected)
26 |     type test = Expect<Equal<typeof result, typeof expected>>
27 |   })
28 | 
29 |   test('with various separators', () => {
30 |     const result = constantCase(SEPARATORS_TEXT)
31 |     const expected = 'ONE_TWO_THREE_FOUR_FIVE_SIX_SEVEN_EIGHT_NINE_TEN'
32 |     expect(result).toEqual(expected)
33 |     type test = Expect<Equal<typeof result, typeof expected>>
34 |   })
35 | })
36 | 


--------------------------------------------------------------------------------
/src/utils/word-case/constant-case.ts:
--------------------------------------------------------------------------------
 1 | import { toUpperCase } from '../../native/to-upper-case.js'
 2 | import {
 3 |   type RemoveApostrophe,
 4 |   removeApostrophe,
 5 | } from '../characters/apostrophe.js'
 6 | import { type DelimiterCase, delimiterCase } from './delimiter-case.js'
 7 | 
 8 | /**
 9 |  * Transforms a string to CONSTANT_CASE.
10 |  */
11 | export type ConstantCase<T extends string> = Uppercase<
12 |   DelimiterCase<RemoveApostrophe<T>, '_'>
13 | >
14 | /**
15 |  * A strongly typed version of `constantCase` that works in both runtime and type level.
16 |  * @param str the string to convert to constant case.
17 |  * @returns the constant cased string.
18 |  * @example constantCase('hello world') // 'HELLO_WORLD'
19 |  */
20 | export function constantCase<T extends string>(str: T): ConstantCase<T> {
21 |   return toUpperCase(delimiterCase(removeApostrophe(str), '_'))
22 | }
23 | 
24 | /**
25 |  * @deprecated
26 |  * Use `constantCase` instead.
27 |  * Read more about the deprecation [here](https://github.com/gustavoguichard/string-ts/issues/44).
28 |  */
29 | export const toConstantCase = constantCase
30 | 


--------------------------------------------------------------------------------
/src/utils/word-case/delimiter-case.test.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   SEPARATORS_TEXT,
 3 |   WEIRD_TEXT,
 4 |   type WeirdTextUnion,
 5 | } from '../../internal/fixtures.js'
 6 | import { type DelimiterCase, delimiterCase } from './delimiter-case.js'
 7 | 
 8 | namespace TypeTransforms {
 9 |   type test = Expect<
10 |     Equal<
11 |       DelimiterCase<WeirdTextUnion, '%'>,
12 |       | 'some%Weird%cased%$*%String%1986%Foo%Bar%W%FOR%WUMBO'
13 |       | 'wheres%the%leak%maam'
14 |       | 'dont%distribute%unions'
15 |     >
16 |   >
17 | }
18 | 
19 | describe('delimiterCase', () => {
20 |   test('casing functions', () => {
21 |     const expected =
22 |       'some@Weird@cased@$*@String@1986@Foo@Bar@W@FOR@WUMBO' as const
23 |     const result = delimiterCase(WEIRD_TEXT, '@')
24 |     expect(result).toEqual(expected)
25 |     type test = Expect<Equal<typeof result, typeof expected>>
26 |   })
27 |   test('with various separators', () => {
28 |     const result = delimiterCase(SEPARATORS_TEXT, '.')
29 |     const expected = 'one.two.three.four.five.six.seven.eight.nine.ten'
30 |     expect(result).toEqual(expected)
31 |     type test = Expect<Equal<typeof result, typeof expected>>
32 |   })
33 | })
34 | 


--------------------------------------------------------------------------------
/src/utils/word-case/delimiter-case.ts:
--------------------------------------------------------------------------------
 1 | import { type Join, join } from '../../native/join.js'
 2 | import {
 3 |   type RemoveApostrophe,
 4 |   removeApostrophe,
 5 | } from '../characters/apostrophe.js'
 6 | import { type Words, words } from '../words.js'
 7 | 
 8 | /**
 9 |  * Transforms a string with the specified separator (delimiter).
10 |  */
11 | export type DelimiterCase<T extends string, D extends string> = Join<
12 |   Words<RemoveApostrophe<T>>,
13 |   D
14 | >
15 | /**
16 |  * A function that transforms a string by splitting it into words and joining them with the specified delimiter.
17 |  * @param str the string to transform.
18 |  * @param delimiter the delimiter to use.
19 |  * @returns the transformed string.
20 |  * @example delimiterCase('hello world', '.') // 'hello.world'
21 |  */
22 | export function delimiterCase<T extends string, D extends string>(
23 |   str: T,
24 |   delimiter: D
25 | ): DelimiterCase<T, D> {
26 |   return join(words(removeApostrophe(str)), delimiter)
27 | }
28 | 
29 | /**
30 |  * @deprecated
31 |  * Use `delimiterCase` instead.
32 |  * Read more about the deprecation [here](https://github.com/gustavoguichard/string-ts/issues/44).
33 |  */
34 | export const toDelimiterCase = delimiterCase
35 | 


--------------------------------------------------------------------------------
/src/utils/word-case/kebab-case.test.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   SEPARATORS_TEXT,
 3 |   WEIRD_TEXT,
 4 |   type WeirdTextUnion,
 5 | } from '../../internal/fixtures.js'
 6 | import { type KebabCase, kebabCase } from './kebab-case.js'
 7 | 
 8 | namespace TypeTransforms {
 9 |   type test = Expect<
10 |     Equal<
11 |       KebabCase<WeirdTextUnion>,
12 |       | 'some-weird-cased-$*-string-1986-foo-bar-w-for-wumbo'
13 |       | 'wheres-the-leak-maam'
14 |       | 'dont-distribute-unions'
15 |     >
16 |   >
17 | }
18 | 
19 | describe('kebabCase', () => {
20 |   test('casing functions', () => {
21 |     const expected =
22 |       'some-weird-cased-$*-string-1986-foo-bar-w-for-wumbo' as const
23 |     const result = kebabCase(WEIRD_TEXT)
24 |     expect(result).toEqual(expected)
25 |     type test = Expect<Equal<typeof result, typeof expected>>
26 |   })
27 |   test('with various separators', () => {
28 |     const result = kebabCase(SEPARATORS_TEXT)
29 |     const expected = 'one-two-three-four-five-six-seven-eight-nine-ten'
30 |     expect(result).toEqual(expected)
31 |     type test = Expect<Equal<typeof result, typeof expected>>
32 |   })
33 | })
34 | 


--------------------------------------------------------------------------------
/src/utils/word-case/kebab-case.ts:
--------------------------------------------------------------------------------
 1 | import { toLowerCase } from '../../native/to-lower-case.js'
 2 | import {
 3 |   type RemoveApostrophe,
 4 |   removeApostrophe,
 5 | } from '../characters/apostrophe.js'
 6 | import { type DelimiterCase, delimiterCase } from './delimiter-case.js'
 7 | 
 8 | /**
 9 |  * Transforms a string to kebab-case.
10 |  */
11 | export type KebabCase<T extends string> = Lowercase<
12 |   DelimiterCase<RemoveApostrophe<T>, '-'>
13 | >
14 | /**
15 |  * A strongly typed version of `kebabCase` that works in both runtime and type level.
16 |  * @param str the string to convert to kebab case.
17 |  * @returns the kebab cased string.
18 |  * @example kebabCase('hello world') // 'hello-world'
19 |  */
20 | export function kebabCase<T extends string>(str: T): KebabCase<T> {
21 |   return toLowerCase(delimiterCase(removeApostrophe(str), '-'))
22 | }
23 | 
24 | /**
25 |  * @deprecated
26 |  * Use `kebabCase` instead.
27 |  * Read more about the deprecation [here](https://github.com/gustavoguichard/string-ts/issues/44).
28 |  */
29 | export const toKebabCase = kebabCase
30 | 


--------------------------------------------------------------------------------
/src/utils/word-case/lower-case.test.ts:
--------------------------------------------------------------------------------
 1 | import { SEPARATORS_TEXT, WEIRD_TEXT } from '../../internal/fixtures.js'
 2 | import { lowerCase } from './lower-case.js'
 3 | 
 4 | describe('lowerCase', () => {
 5 |   test('casing functions', () => {
 6 |     const expected =
 7 |       'some weird cased $* string 1986 foo bar w for wumbo' as const
 8 |     const result = lowerCase(WEIRD_TEXT)
 9 |     expect(result).toEqual(expected)
10 |     type test = Expect<Equal<typeof result, typeof expected>>
11 |   })
12 | 
13 |   test('lowerCase', () => {
14 |     const result = lowerCase(SEPARATORS_TEXT)
15 |     const expected = 'one two three four five six seven eight nine ten'
16 |     expect(result).toEqual(expected)
17 |     type test = Expect<Equal<typeof result, typeof expected>>
18 |   })
19 | })
20 | 


--------------------------------------------------------------------------------
/src/utils/word-case/lower-case.ts:
--------------------------------------------------------------------------------
 1 | import { toLowerCase } from '../../native/to-lower-case.js'
 2 | import { type DelimiterCase, delimiterCase } from './delimiter-case.js'
 3 | 
 4 | /**
 5 |  * A strongly-typed version of `lowerCase` that works in both runtime and type level.
 6 |  * @param str the string to convert to lower case.
 7 |  * @returns the lowercased string.
 8 |  * @example lowerCase('HELLO-WORLD') // 'hello world'
 9 |  */
10 | export function lowerCase<T extends string>(
11 |   str: T
12 | ): Lowercase<DelimiterCase<T, ' '>> {
13 |   return toLowerCase(delimiterCase(str, ' '))
14 | }
15 | 


--------------------------------------------------------------------------------
/src/utils/word-case/pascal-case.test.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   SEPARATORS_TEXT,
 3 |   WEIRD_TEXT,
 4 |   type WeirdTextUnion,
 5 | } from '../../internal/fixtures.js'
 6 | import { type PascalCase, pascalCase } from './pascal-case.js'
 7 | 
 8 | namespace TypeTransforms {
 9 |   type test = Expect<
10 |     Equal<
11 |       PascalCase<WeirdTextUnion>,
12 |       | 'SomeWeirdCased$*String1986FooBarWForWumbo'
13 |       | 'WheresTheLeakMaam'
14 |       | 'DontDistributeUnions'
15 |     >
16 |   >
17 | }
18 | 
19 | describe('pascalCase', () => {
20 |   test('casing functions', () => {
21 |     const expected = 'SomeWeirdCased$*String1986FooBarWForWumbo' as const
22 |     const result = pascalCase(WEIRD_TEXT)
23 |     expect(result).toEqual(expected)
24 |     type test = Expect<Equal<typeof result, typeof expected>>
25 |   })
26 |   test('with various separators', () => {
27 |     const result = pascalCase(SEPARATORS_TEXT)
28 |     const expected = 'OneTwoThreeFourFiveSixSevenEightNineTen'
29 |     expect(result).toEqual(expected)
30 |     type test = Expect<Equal<typeof result, typeof expected>>
31 |   })
32 | })
33 | 


--------------------------------------------------------------------------------
/src/utils/word-case/pascal-case.ts:
--------------------------------------------------------------------------------
 1 | import { type PascalCaseAll, pascalCaseAll } from '../../internal/internals.js'
 2 | import { type Join, join } from '../../native/join.js'
 3 | import {
 4 |   type RemoveApostrophe,
 5 |   removeApostrophe,
 6 | } from '../characters/apostrophe.js'
 7 | import { type Words, words } from '../words.js'
 8 | 
 9 | /**
10 |  * Transforms a string to PascalCase.
11 |  */
12 | export type PascalCase<T extends string> = Join<
13 |   PascalCaseAll<Words<RemoveApostrophe<T>>>
14 | >
15 | /**
16 |  * A strongly typed version of `pascalCase` that works in both runtime and type level.
17 |  * @param str the string to convert to pascal case.
18 |  * @returns the pascal cased string.
19 |  * @example pascalCase('hello world') // 'HelloWorld'
20 |  */
21 | export function pascalCase<T extends string>(str: T): PascalCase<T> {
22 |   return join(pascalCaseAll(words(removeApostrophe(str))))
23 | }
24 | 
25 | /**
26 |  * @deprecated
27 |  * Use `pascalCase` instead.
28 |  * Read more about the deprecation [here](https://github.com/gustavoguichard/string-ts/issues/44).
29 |  */
30 | export const toPascalCase = pascalCase
31 | 


--------------------------------------------------------------------------------
/src/utils/word-case/snake-case.test.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   SEPARATORS_TEXT,
 3 |   WEIRD_TEXT,
 4 |   type WeirdTextUnion,
 5 | } from '../../internal/fixtures.js'
 6 | import { type SnakeCase, snakeCase } from './snake-case.js'
 7 | 
 8 | namespace TypeTransforms {
 9 |   type test = Expect<
10 |     Equal<
11 |       SnakeCase<WeirdTextUnion>,
12 |       | 'some_weird_cased_$*_string_1986_foo_bar_w_for_wumbo'
13 |       | 'wheres_the_leak_maam'
14 |       | 'dont_distribute_unions'
15 |     >
16 |   >
17 | }
18 | 
19 | describe('snakeCase', () => {
20 |   test('casing functions', () => {
21 |     const expected =
22 |       'some_weird_cased_$*_string_1986_foo_bar_w_for_wumbo' as const
23 |     const result = snakeCase(WEIRD_TEXT)
24 |     expect(result).toEqual(expected)
25 |     type test = Expect<Equal<typeof result, typeof expected>>
26 |   })
27 |   test('with various separators', () => {
28 |     const result = snakeCase(SEPARATORS_TEXT)
29 |     const expected = 'one_two_three_four_five_six_seven_eight_nine_ten'
30 |     expect(result).toEqual(expected)
31 |     type test = Expect<Equal<typeof result, typeof expected>>
32 |   })
33 | })
34 | 


--------------------------------------------------------------------------------
/src/utils/word-case/snake-case.ts:
--------------------------------------------------------------------------------
 1 | import { toLowerCase } from '../../native/to-lower-case.js'
 2 | import {
 3 |   type RemoveApostrophe,
 4 |   removeApostrophe,
 5 | } from '../characters/apostrophe.js'
 6 | import { type DelimiterCase, delimiterCase } from './delimiter-case.js'
 7 | 
 8 | /**
 9 |  * Transforms a string to snake_case.
10 |  */
11 | export type SnakeCase<T extends string> = Lowercase<
12 |   DelimiterCase<RemoveApostrophe<T>, '_'>
13 | >
14 | /**
15 |  * A strongly typed version of `snakeCase` that works in both runtime and type level.
16 |  * @param str the string to convert to snake case.
17 |  * @returns the snake cased string.
18 |  * @example snakeCase('hello world') // 'hello_world'
19 |  */
20 | export function snakeCase<T extends string>(str: T): SnakeCase<T> {
21 |   return toLowerCase(delimiterCase(removeApostrophe(str), '_'))
22 | }
23 | 
24 | /**
25 |  * @deprecated
26 |  * Use `snakeCase` instead.
27 |  * Read more about the deprecation [here](https://github.com/gustavoguichard/string-ts/issues/44).
28 |  */
29 | export const toSnakeCase = snakeCase
30 | 


--------------------------------------------------------------------------------
/src/utils/word-case/title-case.test.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   SEPARATORS_TEXT,
 3 |   WEIRD_TEXT,
 4 |   type WeirdTextUnion,
 5 | } from '../../internal/fixtures.js'
 6 | import { type TitleCase, titleCase } from './title-case.js'
 7 | 
 8 | namespace TypeTransforms {
 9 |   type test = Expect<
10 |     Equal<
11 |       TitleCase<WeirdTextUnion>,
12 |       | 'Some Weird Cased $* String 1986 Foo Bar W For Wumbo'
13 |       | 'Wheres The Leak Maam'
14 |       | 'Dont Distribute Unions'
15 |     >
16 |   >
17 | }
18 | 
19 | describe('titleCase', () => {
20 |   test('casing functions', () => {
21 |     const expected =
22 |       'Some Weird Cased $* String 1986 Foo Bar W For Wumbo' as const
23 |     const result = titleCase(WEIRD_TEXT)
24 |     expect(result).toEqual(expected)
25 |     type test = Expect<Equal<typeof result, typeof expected>>
26 |   })
27 |   test('with various separators', () => {
28 |     const result = titleCase(SEPARATORS_TEXT)
29 |     const expected = 'One Two Three Four Five Six Seven Eight Nine Ten'
30 |     expect(result).toEqual(expected)
31 |     type test = Expect<Equal<typeof result, typeof expected>>
32 |   })
33 | })
34 | 


--------------------------------------------------------------------------------
/src/utils/word-case/title-case.ts:
--------------------------------------------------------------------------------
 1 | import { type DelimiterCase, delimiterCase } from './delimiter-case.js'
 2 | import { type PascalCase, pascalCase } from './pascal-case.js'
 3 | 
 4 | /**
 5 |  * Transforms a string to "Title Case".
 6 |  */
 7 | export type TitleCase<T extends string> = DelimiterCase<PascalCase<T>, ' '>
 8 | /**
 9 |  * A strongly typed version of `titleCase` that works in both runtime and type level.
10 |  * @param str the string to convert to title case.
11 |  * @returns the title cased string.
12 |  * @example titleCase('hello world') // 'Hello World'
13 |  */
14 | export function titleCase<T extends string>(str: T): TitleCase<T> {
15 |   return delimiterCase(pascalCase(str), ' ')
16 | }
17 | 
18 | /**
19 |  * @deprecated
20 |  * Use `titleCase` instead.
21 |  * Read more about the deprecation [here](https://github.com/gustavoguichard/string-ts/issues/44).
22 |  */
23 | export const toTitleCase = titleCase
24 | 


--------------------------------------------------------------------------------
/src/utils/word-case/uncapitalize.test.ts:
--------------------------------------------------------------------------------
 1 | import { WEIRD_TEXT } from '../../internal/fixtures.js'
 2 | import { uncapitalize } from './uncapitalize.js'
 3 | 
 4 | describe('uncapitalize', () => {
 5 |   test('it does nothing with a string that has no char at the beginning', () => {
 6 |     const expected = WEIRD_TEXT
 7 |     const result = uncapitalize(WEIRD_TEXT)
 8 |     expect(result).toEqual(expected)
 9 |     type test = Expect<Equal<typeof result, typeof expected>>
10 |   })
11 | 
12 |   test('it uncapitalizes the first char of a string', () => {
13 |     const expected = 'someWeird-casedString' as const
14 |     const result = uncapitalize('SomeWeird-casedString')
15 |     expect(result).toEqual(expected)
16 |     type test = Expect<Equal<typeof result, typeof expected>>
17 |   })
18 | })
19 | 


--------------------------------------------------------------------------------
/src/utils/word-case/uncapitalize.ts:
--------------------------------------------------------------------------------
 1 | import { charAt } from '../../native/char-at.js'
 2 | import { join } from '../../native/join.js'
 3 | import { slice } from '../../native/slice.js'
 4 | import { toLowerCase } from '../../native/to-lower-case.js'
 5 | 
 6 | /**
 7 |  * Uncapitalizes the first letter of a string. This is a runtime counterpart of `Uncapitalize<T>` from `src/types.d.ts`.
 8 |  * @param str the string to uncapitalize.
 9 |  * @returns the uncapitalized string.
10 |  * @example uncapitalize('Hello world') // 'hello world'
11 |  */
12 | export function uncapitalize<T extends string>(str: T) {
13 |   return join([
14 |     toLowerCase(charAt(str, 0) ?? ''),
15 |     slice(str, 1),
16 |   ]) as Uncapitalize<T>
17 | }
18 | 


--------------------------------------------------------------------------------
/src/utils/word-case/upper-case.test.ts:
--------------------------------------------------------------------------------
 1 | import { SEPARATORS_TEXT, WEIRD_TEXT } from '../../internal/fixtures.js'
 2 | import { upperCase } from './upper-case.js'
 3 | 
 4 | describe('upperCase', () => {
 5 |   test('casing functions', () => {
 6 |     const expected =
 7 |       'SOME WEIRD CASED $* STRING 1986 FOO BAR W FOR WUMBO' as const
 8 |     const result = upperCase(WEIRD_TEXT)
 9 |     expect(result).toEqual(expected)
10 |     type test = Expect<Equal<typeof result, typeof expected>>
11 |   })
12 |   test('upperCase', () => {
13 |     const result = upperCase(SEPARATORS_TEXT)
14 |     const expected = 'ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE TEN'
15 |     expect(result).toEqual(expected)
16 |     type test = Expect<Equal<typeof result, typeof expected>>
17 |   })
18 | })
19 | 


--------------------------------------------------------------------------------
/src/utils/word-case/upper-case.ts:
--------------------------------------------------------------------------------
 1 | import { toUpperCase } from '../../native/to-upper-case.js'
 2 | import { type DelimiterCase, delimiterCase } from './delimiter-case.js'
 3 | 
 4 | /**
 5 |  * A strongly-typed version of `upperCase` that works in both runtime and type level.
 6 |  * @param str the string to convert to upper case.
 7 |  * @returns the uppercased string.
 8 |  * @example upperCase('hello-world') // 'HELLO WORLD'
 9 |  */
10 | export function upperCase<T extends string>(
11 |   str: T
12 | ): Uppercase<DelimiterCase<T, ' '>> {
13 |   return toUpperCase(delimiterCase(str, ' '))
14 | }
15 | 


--------------------------------------------------------------------------------
/src/utils/words.test.ts:
--------------------------------------------------------------------------------
  1 | import type { Words } from './words.js'
  2 | import { words } from './words.js'
  3 | 
  4 | namespace WordsTests {
  5 |   type test1 = Expect<
  6 |     Equal<
  7 |       Words<' someWeird-cased$*String1986Foo Bar obj.items[0]'>,
  8 |       [
  9 |         'some',
 10 |         'Weird',
 11 |         'cased',
 12 |         '$*',
 13 |         'String',
 14 |         '1986',
 15 |         'Foo',
 16 |         'Bar',
 17 |         'obj',
 18 |         'items',
 19 |         '0',
 20 |       ]
 21 |     >
 22 |   >
 23 |   type test2 = Expect<Equal<Words<string>, string[]>>
 24 |   type test3 = Expect<Equal<Words<'abc def', string>, string[]>>
 25 |   type test4 = Expect<Equal<Words<'abc def', ' ', string>, string[]>>
 26 |   type test5 = Expect<
 27 |     Equal<Words<"Where's the leak ma'am">, ["Where's", 'the', 'leak', "ma'am"]>
 28 |   >
 29 | }
 30 | 
 31 | type Mutable<Type> = {
 32 |   -readonly [Key in keyof Type]: Type[Key]
 33 | }
 34 | 
 35 | describe('words', () => {
 36 |   test('it splits words at separators', () => {
 37 |     const expected = [
 38 |       'one',
 39 |       'two',
 40 |       'three',
 41 |       'four',
 42 |       'five',
 43 |       'six',
 44 |       'seven',
 45 |       'eight',
 46 |       'nine',
 47 |       'ten',
 48 |     ] as const
 49 |     const result = words(
 50 |       '[one] two-three/four.five(six){seven}|eight_nine\\ten'
 51 |     )
 52 |     expect(result).toEqual(expected)
 53 |     type test = Expect<Equal<typeof result, Mutable<typeof expected>>>
 54 |   })
 55 | 
 56 |   test('it splits words at digits', () => {
 57 |     const expected = ['2', 'Weird', 'Cased', '1986', 'Foo'] as const
 58 |     const result = words('2WeirdCased1986Foo')
 59 |     expect(result).toEqual(expected)
 60 |     type test = Expect<Equal<typeof result, Mutable<typeof expected>>>
 61 |   })
 62 | 
 63 |   test('it splits words at special chars', () => {
 64 |     const expected = ['
#39;, '2', 'Weird', 'Cased', '@@', 'Foo'] as const
 65 |     const result = words('$2WeirdCased@@Foo')
 66 |     expect(result).toEqual(expected)
 67 |     type test = Expect<Equal<typeof result, Mutable<typeof expected>>>
 68 |   })
 69 | 
 70 |   test('it splits words at casing', () => {
 71 |     const expected = ['some', 'Weird', 'Cased', 'STRING', 'Foo'] as const
 72 |     const result = words('someWeirdCasedSTRINGFoo')
 73 |     expect(result).toEqual(expected)
 74 |     type test = Expect<Equal<typeof result, Mutable<typeof expected>>>
 75 |   })
 76 | 
 77 |   test('it preserves apostrophes', () => {
 78 |     const expected = ["Where's", 'the', 'leak', "ma'am"] as const
 79 |     const result = words("Where's the leak ma'am")
 80 |     expect(result).toEqual(expected)
 81 |     type test = Expect<Equal<typeof result, Mutable<typeof expected>>>
 82 |   })
 83 | 
 84 |   test('it combines all of the rules above and trims the word', () => {
 85 |     const expected = [
 86 |       'some',
 87 |       'Weird',
 88 |       'cased',
 89 |       '$*',
 90 |       'String',
 91 |       '1986',
 92 |       'Foo',
 93 |       'Bar',
 94 |     ] as const
 95 |     const result = words(' someWeird-cased$*String1986Foo Bar ')
 96 |     expect(result).toEqual(expected)
 97 |     type test = Expect<Equal<typeof result, Mutable<typeof expected>>>
 98 |   })
 99 | })
100 | 


--------------------------------------------------------------------------------
/src/utils/words.ts:
--------------------------------------------------------------------------------
 1 | import type { DropSuffix, Reject } from '../internal/internals.js'
 2 | import type { IsStringLiteral } from '../internal/literals.js'
 3 | import type { IsLower, IsUpper } from './characters/letters.js'
 4 | import type { IsDigit } from './characters/numbers.js'
 5 | import type { IsSeparator } from './characters/separators.js'
 6 | import { SEPARATOR_REGEX } from './characters/separators.js'
 7 | import type { IsSpecial } from './characters/special.js'
 8 | 
 9 | /**
10 |  * Splits a string into words.
11 |  * sentence: The current string to split.
12 |  * word: The current word.
13 |  * prev: The previous character.
14 |  */
15 | export type Words<
16 |   sentence extends string,
17 |   word extends string = '',
18 |   prev extends string = '',
19 | > = IsStringLiteral<sentence | word | prev> extends true
20 |   ? sentence extends `${infer curr}${infer rest}`
21 |     ? IsSeparator<curr> extends true
22 |       ? // Step 1: Remove separators
23 |         Reject<[word, ...Words<rest>], ''>
24 |       : prev extends ''
25 |         ? // Start of sentence, start a new word
26 |           Reject<Words<rest, curr, curr>, ''>
27 |         : [false, true] extends [IsDigit<prev>, IsDigit<curr>]
28 |           ? // Step 2: From non-digit to digit
29 |             [word, ...Words<rest, curr, curr>]
30 |           : [true, false] extends [IsDigit<prev>, IsDigit<curr>]
31 |             ? // Step 3: From digit to non-digit
32 |               [word, ...Words<rest, curr, curr>]
33 |             : [false, true] extends [IsSpecial<prev>, IsSpecial<curr>]
34 |               ? // Step 4: From non-special to special
35 |                 [word, ...Words<rest, curr, curr>]
36 |               : [true, false] extends [IsSpecial<prev>, IsSpecial<curr>]
37 |                 ? // Step 5: From special to non-special
38 |                   [word, ...Words<rest, curr, curr>]
39 |                 : [true, true] extends [IsDigit<prev>, IsDigit<curr>]
40 |                   ? // If both are digit, continue with the sentence
41 |                     Reject<Words<rest, `${word}${curr}`, curr>, ''>
42 |                   : [true, true] extends [IsLower<prev>, IsUpper<curr>]
43 |                     ? // Step 6: From lower to upper
44 |                       [word, ...Words<rest, curr, curr>]
45 |                     : [true, true] extends [IsUpper<prev>, IsLower<curr>]
46 |                       ? // Step 7: From upper to upper and lower
47 |                         // Remove the last character from the current word and start a new word with it
48 |                         [
49 |                           DropSuffix<word, prev>,
50 |                           ...Words<rest, `${prev}${curr}`, curr>,
51 |                         ]
52 |                       : Reject<Words<rest, `${word}${curr}`, curr>, ''> // Otherwise continue with the sentence
53 |     : // Step 8: Trim the last word
54 |       Reject<[word], ''>
55 |   : string[] // Avoid spending resources on a wide type
56 | 
57 | /**
58 |  * A strongly-typed function to extract the words from a sentence.
59 |  * @param sentence the sentence to extract the words from.
60 |  * @returns an array of words in both type level and runtime.
61 |  * @example words('helloWorld') // ['hello', 'World']
62 |  */
63 | export function words<T extends string>(sentence: T): Words<T> {
64 |   return sentence
65 |     .replace(SEPARATOR_REGEX, ' ') // Step 1: Remove separators
66 |     .replace(/([a-zA-Z])([0-9])/g, '$1 $2') // Step 2: From non-digit to digit
67 |     .replace(/([0-9])([a-zA-Z])/g, '$1 $2') // Step 3: From digit to non-digit
68 |     .replace(/([a-zA-Z0-9_\-./])([^a-zA-Z0-9_\-./'])/g, '$1 $2') // Step 4: From non-special to special
69 |     .replace(/([^a-zA-Z0-9_\-./'])([a-zA-Z0-9_\-./])/g, '$1 $2') // Step 5: From special to non-special
70 |     .replace(/([a-z])([A-Z])/g, '$1 $2') // Step 6: From lower to upper
71 |     .replace(/([A-Z])([A-Z][a-z])/g, '$1 $2') // Step 7: From upper to upper and lower
72 |     .trim() // Step 8: Trim the last word
73 |     .split(/\s+/g) as Words<T>
74 | }
75 | 


--------------------------------------------------------------------------------
/tsconfig.dist.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "$schema": "http://json.schemastore.org/tsconfig",
 3 |   "include": ["dist"],
 4 |   "compilerOptions": {
 5 |     "strict": true,
 6 |     "noEmit": true,
 7 |     "skipDefaultLibCheck": false,
 8 |     "skipLibCheck": false
 9 |   }
10 | }
11 | 


--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "include": ["src/**/*.ts"],
 3 |   "compilerOptions": {
 4 |     "declaration": true,
 5 |     "esModuleInterop": true,
 6 |     "forceConsistentCasingInFileNames": true,
 7 |     "module": "commonjs",
 8 |     "outDir": "./tsc/",
 9 |     "skipLibCheck": true,
10 |     "strict": true,
11 |     "target": "ES2021",
12 |     "types": ["vitest/globals", "node"]
13 |   }
14 | }
15 | 


--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
 1 | /// <reference types="vitest" />
 2 | import { defineConfig } from 'vitest/config'
 3 | 
 4 | export default defineConfig(() => ({
 5 |   test: {
 6 |     globals: true,
 7 |     maxConcurrency: 1,
 8 |     minThreads: 0,
 9 |     maxThreads: 1,
10 |     exclude: ['tsc', 'node_modules'],
11 |   },
12 | }))
13 | 


--------------------------------------------------------------------------------