JSPM

  • Created
  • Published
  • Downloads 389432
  • Score
    100M100P100Q164717F
  • License MIT

Strongly-typed string functions.

Package Exports

    This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (string-ts) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

    Readme

    NPM Library size Code Coverage All Contributors

    string-ts

    Strongly-typed string functions for all!

    A demonstration of string-ts

    😬 The problem

    When you are working with literal strings, the string manipulation functions only work at the runtime level and the types don't follow those transformations. You end up losing type information and possibly having to cast the result.

    const str = 'hello-world'
    const result = str.replace('-', ' ') // you should use: as 'hello world'
    //    ^? string

    🤓 The solution

    This library aims to solve this problem by providing a set of common functions that work with literal strings at both type and runtime level.

    import { replace } from 'string-ts'
    
    const str = 'hello-world'
    const result = replace(str, '-', ' ')
    //    ^ 'hello world'

    🔍 Why this matters

    TypeScript yields the best static analysis when types are highly specific. Literals are more specific than type string. This library preserves literals (and unions of literals) after transformations, unlike most existing utility libraries (and built-in string methods.)

    In-depth example

    In the below example, I want to get a strongly-typed, camel-case version of process.env. One flow results in a loose type, and the other results in a more precise type. This example should illustrate the highly-specific and flexible nature of string-ts.

    import { deepCamelKeys } from 'string-ts'
    import { camelCase, mapKeys } from 'lodash-es'
    import z from 'zod'
    
    const EnvSchema = z.object({
      NODE_ENV: z.string(),
    })
    
    function getEnvLoose() {
      const rawEnv = EnvSchema.parse(process.env)
      const env = mapKeys(rawEnv, (_v, k) => camelCase(k))
      //    ^? Dictionary<string>
    
      // `Dictionary<string>` is too loose
      // TypeScript is okay with this, 'abc' is expected to be of type `string`
      // This will have unexpected behavior at runtime
      console.log(env.abc)
    }
    
    function getEnvPrecise() {
      const rawEnv = EnvSchema.parse(process.env)
      const env = deepCamelKeys(rawEnv)
      //    ^? { nodeEnv: string }
    
      // Error: Property 'abc' does not exist on type '{ nodeEnv: string; }'
      // Our type is more specific, so TypeScript catches this error.
      // This mistake will be caught at compile time
      console.log(env.abc)
    }
    
    function main() {
      getEnvLoose()
      getEnvPrecise()
    }
    
    main()

    📦 Installation

    npm install string-ts

    🌳 Tree shaking

    string-ts has been designed with tree shaking in mind. We have tested it with build tools like Webpack, Vite, Rollup, etc.

    👌 Supported TypeScript versions

    string-ts currently only works on TypeScript v5+.

    It also only work with common ASCII characters characters. We don't plan to support international characters or emojis.


    📖 API


    Runtime counterparts of native type utilities

    capitalize

    Capitalizes the first letter of a string. This is a runtime counterpart of Capitalize<T> from src/types.d.ts.

    import { capitalize } from 'string-ts'
    
    const str = 'hello world'
    const result = capitalize(str)
    //    ^ 'Hello world'

    uncapitalize

    Uncapitalizes the first letter of a string. This is a runtime counterpart of Uncapitalize<T> from src/types.d.ts.

    import { uncapitalize } from 'string-ts'
    
    const str = 'Hello world'
    const result = uncapitalize(str)
    //    ^ 'hello world'

    Strongly-typed alternatives to native runtime utilities

    charAt

    This function is a strongly-typed counterpart of String.prototype.charAt.

    import { charAt } from 'string-ts'
    
    const str = 'hello world'
    const result = charAt(str, 6)
    //    ^ 'w'

    concat

    This function is a strongly-typed counterpart of String.prototype.concat.

    import { concat } from 'string-ts'
    
    const result = concat('a', 'bc', 'def')
    //    ^ 'abcdef'

    endsWith

    This function is a strongly-typed counterpart of String.prototype.endsWith.

    import { endsWith } from 'string-ts'
    
    const result = endsWith('abc', 'c')
    //    ^ true

    includes

    This function is a strongly-typed counterpart of String.prototype.includes.

    import { includes } from 'string-ts'
    
    const result = includes('abcde', 'bcd')
    //    ^ true

    join

    This function is a strongly-typed counterpart of Array.prototype.join.

    import { join } from 'string-ts'
    
    const str = ['hello', 'world']
    const result = join(str, ' ')
    //    ^ 'hello world'

    length

    This function is a strongly-typed counterpart of String.prototype.length.

    import { length } from 'string-ts'
    
    const str = 'hello'
    const result = length(str)
    //    ^ 5

    padEnd

    This function is a strongly-typed counterpart of String.prototype.padEnd.

    import { padEnd } from 'string-ts'
    
    const str = 'hello'
    const result = padEnd(str, 10, '=')
    //    ^ 'hello====='

    padStart

    This function is a strongly-typed counterpart of String.prototype.padStart.

    import { padStart } from 'string-ts'
    
    const str = 'hello'
    const result = padStart(str, 10, '=')
    //    ^ '=====hello'

    repeat

    This function is a strongly-typed counterpart of String.prototype.repeat.

    import { repeat } from 'string-ts'
    
    const str = 'abc'
    const result = repeat(str, 3)
    //    ^ 'abcabcabc'

    replace

    This function is a strongly-typed counterpart of String.prototype.replace.

    Warning: this is a partial implementation, as we don't fully support Regex. Using a RegExp lookup will result in a loose typing.

    import { replace } from 'string-ts'
    
    const str = 'hello-world-'
    const result = replace(str, '-', ' ')
    //    ^ 'hello world-'
    const looselyTypedResult = replace(str, /-/, ' ')
    //    ^ string

    replaceAll

    This function is a strongly-typed counterpart of String.prototype.replaceAll. It also has a polyfill for runtimes older than ES2021.

    Warning: this is a partial implementation, as we don't fully support Regex. Using a RegExp lookup will result in a loose typing.

    import { replaceAll } from 'string-ts'
    
    const str = 'hello-world-'
    const result = replaceAll(str, '-', ' ')
    //    ^ 'hello world '
    const looselyTypedResult = replaceAll(str, /-/g, ' ')
    //    ^ string

    slice

    This function is a strongly-typed counterpart of String.prototype.slice.

    import { slice } from 'string-ts'
    
    const str = 'hello-world'
    const result = slice(str, 6)
    //    ^ 'world'
    const result2 = slice(str, 1, 5)
    //    ^ 'ello'
    const result3 = slice(str, -5)
    //    ^ 'world'

    split

    This function is a strongly-typed counterpart of String.prototype.split.

    import { split } from 'string-ts'
    
    const str = 'hello-world'
    const result = split(str, '-')
    //    ^ ['hello', 'world']

    startsWith

    This function is a strongly-typed counterpart of String.prototype.startsWith.

    import { startsWith } from 'string-ts'
    
    const result = startsWith('abc', 'a')
    //    ^ true

    toLowerCase

    This function is a strongly-typed counterpart of String.prototype.toLowerCase.

    import { toLowerCase } from 'string-ts'
    
    const str = 'HELLO WORLD'
    const result = toLowerCase(str)
    //    ^ 'hello world'

    toUpperCase

    This function is a strongly-typed counterpart of String.prototype.toUpperCase.

    import { toUpperCase } from 'string-ts'
    
    const str = 'hello world'
    const result = toUpperCase(str)
    //    ^ 'HELLO WORLD'

    trim

    This function is a strongly-typed counterpart of String.prototype.trim.

    import { trim } from 'string-ts'
    
    const str = '  hello world  '
    const result = trim(str)
    //    ^ 'hello world'

    trimEnd

    This function is a strongly-typed counterpart of String.prototype.trimEnd.

    import { trimEnd } from 'string-ts'
    
    const str = '  hello world  '
    const result = trimEnd(str)
    //    ^ '  hello world'

    trimStart

    This function is a strongly-typed counterpart of String.prototype.trimStart.

    import { trimStart } from 'string-ts'
    
    const str = '  hello world  '
    const result = trimStart(str)
    //    ^ 'hello world  '

    Strongly-typed alternatives to common loosely-typed functions

    lowerCase

    This function converts a string to lower case at both runtime and type levels. NOTE: this function will split by words and join them with " ", unlike toLowerCase.

    import { lowerCase } from 'string-ts'
    
    const str = 'HELLO-WORLD'
    const result = lowerCase(str)
    //    ^ 'hello world'

    camelCase

    This function converts a string to camelCase at both runtime and type levels.

    import { camelCase } from 'string-ts'
    
    const str = 'hello-world'
    const result = camelCase(str)
    //    ^ 'helloWorld'

    constantCase

    This function converts a string to CONSTANT_CASE at both runtime and type levels.

    import { constantCase } from 'string-ts'
    
    const str = 'helloWorld'
    const result = constantCase(str)
    //    ^ 'HELLO_WORLD'

    delimiterCase

    This function converts a string to a new case with a custom delimiter at both runtime and type levels.

    import { delimiterCase } from 'string-ts'
    
    const str = 'helloWorld'
    const result = delimiterCase(str, '.')
    //    ^ 'hello.World'

    kebabCase

    This function converts a string to kebab-case at both runtime and type levels.

    import { kebabCase } from 'string-ts'
    
    const str = 'helloWorld'
    const result = kebabCase(str)
    //    ^ 'hello-world'

    pascalCase

    This function converts a string to PascalCase at both runtime and type levels.

    import { pascalCase } from 'string-ts'
    
    const str = 'hello-world'
    const result = pascalCase(str)
    //    ^ 'HelloWorld'

    snakeCase

    This function converts a string to snake_case at both runtime and type levels.

    import { snakeCase } from 'string-ts'
    
    const str = 'helloWorld'
    const result = snakeCase(str)
    //    ^ 'hello_world'

    titleCase

    This function converts a string to Title Case at both runtime and type levels.

    import { titleCase } from 'string-ts'
    
    const str = 'helloWorld'
    const result = titleCase(str)
    //    ^ 'Hello World'

    upperCase

    This function converts a string to UPPER CASE at both runtime and type levels. NOTE: this function will split by words and join them with " ", unlike toUpperCase.

    import { upperCase } from 'string-ts'
    
    const str = 'hello-world'
    const result = upperCase(str)
    //    ^ 'HELLO WORLD'

    reverse

    This function reverses a string.

    import { reverse } from 'string-ts'
    
    const str = 'Hello StringTS!'
    const result = reverse(str)
    //    ^ '!TSgnirtS olleH'

    truncate

    This function truncates string if it's longer than the given maximum string length. The last characters of the truncated string are replaced with the omission string which defaults to "...".

    import { truncate } from 'string-ts'
    
    const str = '-20someVery-weird String'
    const result = truncate(str, 8)
    //    ^ '-20so...'

    words

    This function identifies the words in a string and returns a tuple of words split by separators, differences in casing, numbers, and etc.

    import { words } from 'string-ts'
    
    const str = '-20someVery-weird String'
    const result = words(str)
    //    ^ ['20', 'some', 'Very', 'weird', 'String']

    Strongly-typed shallow transformation of objects

    camelKeys

    This function shallowly converts the keys of an object to camelCase at both runtime and type levels.

    import { camelKeys } from 'string-ts'
    
    const data = {
      'hello-world': {
        'foo-bar': 'baz',
      },
    } as const
    const result = camelKeys(data)
    //    ^ { helloWorld: { 'foo-bar': 'baz' } }

    constantKeys

    This function shallowly converts the keys of an object to CONSTANT_CASE at both runtime and type levels.

    import { constantKeys } from 'string-ts'
    
    const data = {
      helloWorld: {
        fooBar: 'baz',
      },
    } as const
    const result = constantKeys(data)
    //    ^ { 'HELLO_WORLD': { 'fooBar': 'baz' } }

    delimiterKeys

    This function shallowly converts the keys of an object to a new case with a custom delimiter at both runtime and type levels.

    import { delimiterKeys } from 'string-ts'
    
    const data = {
      'hello-world': {
        'foo-bar': 'baz',
      },
    } as const
    const result = delimiterKeys(data, '.')
    //    ^ { 'hello.world': { 'foo-bar': 'baz' } }

    kebabKeys

    This function shallowly converts the keys of an object to kebab-case at both runtime and type levels.

    import { kebabKeys } from 'string-ts'
    
    const data = {
      helloWorld: {
        fooBar: 'baz',
      },
    } as const
    const result = kebabKeys(data)
    //    ^ { 'hello-world': { fooBar: 'baz' } }

    pascalKeys

    This function shallowly converts the keys of an object to PascalCase at both runtime and type levels.

    import { pascalKeys } from 'string-ts'
    
    const data = {
      'hello-world': {
        'foo-bar': 'baz',
      },
    } as const
    const result = pascalKeys(data)
    //    ^ { HelloWorld: { FooBar: 'baz' } }

    snakeKeys

    This function shallowly converts the keys of an object to snake_case at both runtime and type levels.

    import { snakeKeys } from 'string-ts'
    
    const data = {
      helloWorld: {
        fooBar: 'baz',
      },
    } as const
    const result = snakeKeys(data)
    //    ^ { 'hello_world': { 'fooBar': 'baz' } }

    Strongly-typed deep transformation of objects

    deepCamelKeys

    This function recursively converts the keys of an object to camelCase at both runtime and type levels.

    import { deepCamelKeys } from 'string-ts'
    
    const data = {
      'hello-world': {
        'foo-bar': 'baz',
      },
    } as const
    const result = deepCamelKeys(data)
    //    ^ { helloWorld: { fooBar: 'baz' } }

    deepConstantKeys

    This function recursively converts the keys of an object to CONSTANT_CASE at both runtime and type levels.

    import { deepConstantKeys } from 'string-ts'
    
    const data = {
      helloWorld: {
        fooBar: 'baz',
      },
    } as const
    const result = deepConstantKeys(data)
    //    ^ { 'HELLO_WORLD': { 'FOO_BAR': 'baz' } }

    deepDelimiterKeys

    This function recursively converts the keys of an object to a new case with a custom delimiter at both runtime and type levels.

    import { deepDelimiterKeys } from 'string-ts'
    
    const data = {
      'hello-world': {
        'foo-bar': 'baz',
      },
    } as const
    const result = deepDelimiterKeys(data, '.')
    //    ^ { 'hello.world': { 'foo.bar': 'baz' } }

    deepKebabKeys

    This function recursively converts the keys of an object to kebab-case at both runtime and type levels.

    import { deepKebabKeys } from 'string-ts'
    
    const data = {
      helloWorld: {
        fooBar: 'baz',
      },
    } as const
    const result = deepKebabKeys(data)
    //    ^ { 'hello-world': { 'foo-bar': 'baz' } }

    deepPascalKeys

    This function recursively converts the keys of an object to PascalCase at both runtime and type levels.

    import { deepPascalKeys } from 'string-ts'
    
    const data = {
      'hello-world': {
        'foo-bar': 'baz',
      },
    } as const
    const result = deepPascalKeys(data)
    //    ^ { HelloWorld: { FooBar: 'baz' } }

    deepSnakeKeys

    This function recursively converts the keys of an object to snake_case at both runtime and type levels.

    import { deepSnakeKeys } from 'string-ts'
    
    const data = {
      helloWorld: {
        fooBar: 'baz',
      },
    } as const
    const result = deepSnakeKeys(data)
    //    ^ { 'hello_world': { 'foo_bar': 'baz' } }

    Type utilities

    All the functions presented in this API have associated type counterparts.

    import type * as St from 'string-ts'

    Native TS type utilities

    Capitalize<'hello world'> // 'Hello world'
    Lowercase<'HELLO WORLD'> // 'hello world'
    Uppercase<'hello world'> // 'HELLO WORLD'

    General type utilities from this library

    St.CharAt<'hello world', 6> // 'w'
    St.Concat<['a', 'bc', 'def']> // 'abcdef'
    St.EndsWith<'abc', 'c'> // true
    St.Includes<'abcde', 'bcd'> // true
    St.Join<['hello', 'world'], '-'> // 'hello-world'
    St.Length<'hello'> // 5
    St.PadEnd<'hello', 10, '='> // 'hello====='
    St.PadStart<'hello', 10, '='> // '=====hello'
    St.Repeat<'abc', 3> // 'abcabcabc'
    St.Replace<'hello-world', 'l', '1'> // 'he1lo-world'
    St.ReplaceAll<'hello-world', 'l', '1'> // 'he11o-wor1d'
    St.Reverse<'Hello World!'> // '!dlroW olleH'
    St.Slice<'hello-world', -5> // 'world'
    St.Split<'hello-world', '-'> // ['hello', 'world']
    St.Trim<' hello world '> // 'hello world'
    St.StartsWith<'abc', 'a'> // true
    St.TrimEnd<' hello world '> // ' hello world'
    St.TrimStart<' hello world '> // 'hello world '
    St.Truncate<'hello world', 9, '[...]'> // 'hello[...]
    St.Words<'hello-world'> // ['hello', 'world']

    Casing type utilities

    Core

    St.CamelCase<'hello-world'> // 'helloWorld'
    St.ConstantCase<'helloWorld'> // 'HELLO_WORLD'
    St.DelimiterCase<'hello world', '.'> // 'hello.world'
    St.KebabCase<'helloWorld'> // 'hello-world'
    St.PascalCase<'hello-world'> // 'HelloWorld'
    St.SnakeCase<'helloWorld'> // 'hello_world'
    St.TitleCase<'helloWorld'> // 'Hello World'
    Missing types

    Note that we do not include UpperCase and LowerCase types. These would be too close to the existing TS types Uppercase and Lowercase.

    One could create either by doing like so:

    type LowerCase<T extends string> = Lowercase<DelimiterCase<T, ' '>>
    type UpperCase<T extends string> = Uppercase<DelimiterCase<T, ' '>>
    // or
    type LowerCase<T extends string> = ReturnType<typeof lowerCase<T>>
    type UpperCase<T extends string> = ReturnType<typeof upperCase<T>>

    Shallow object key transformation

    St.CamelKeys<{
      'hello-world': { 'foo-bar': 'baz' }
    }> // { helloWorld: { 'foo-bar': 'baz' } }
    St.ConstantKeys<{
      helloWorld: { fooBar: 'baz' }
    }> // { 'HELLO_WORLD': { fooBar: 'baz' } }
    St.DelimiterKeys<{ 'hello-world': { 'foo-bar': 'baz' } }, '.'>
    // { 'hello.world': { 'foo-bar': 'baz' } }
    St.KebabKeys<{
      helloWorld: { fooBar: 'baz' }
    }> // { 'hello-world': { fooBar: 'baz' } }
    St.PascalKeys<{
      'hello-world': { 'foo-bar': 'baz' }
    }> // { HelloWorld: { 'foo-bar': 'baz' } }
    St.SnakeKeys<{
      helloWorld: { fooBar: 'baz' }
    }> // { 'hello_world': { fooBar: 'baz' } }

    Deep object key transformation

    St.DeepCamelKeys<{
      'hello-world': { 'foo-bar': 'baz' }
    }> // { helloWorld: { fooBar: 'baz' } }
    St.DeepConstantKeys<{
      helloWorld: { fooBar: 'baz' }
    }> // { 'HELLO_WORLD': { 'FOO_BAR': 'baz' } }
    St.DeepDelimiterKeys<
      {
        'hello-world': { 'foo-bar': 'baz' }
      },
      '.'
    > // { 'hello.world': { 'foo.bar': 'baz' } }
    St.DeepKebabKeys<{
      helloWorld: { fooBar: 'baz' }
    }> // { 'hello-world': { 'foo-bar': 'baz' } }
    St.DeepPascalKeys<{
      'hello-world': { 'foo-bar': 'baz' }
    }> // { HelloWorld: { FooBar: 'baz' } }
    St.DeepSnakeKeys<{
      helloWorld: { fooBar: 'baz' }
    }> // { 'hello_world': { 'foo_bar': 'baz' } }

    Other exported type utilities

    St.IsDigit<'a'> // false
    St.IsDigit<'1'> // true
    St.IsLetter<'a'> // true
    St.IsLetter<'1'> // false
    St.IsLower<'a'> // true
    St.IsLower<'A'> // false
    St.IsUpper<'a'> // false
    St.IsUpper<'A'> // true
    St.IsSeparator<' '> // true
    St.IsSeparator<'-'> // true
    St.IsSeparator<'a'> // false
    St.IsSpecial<'a'> // false
    St.IsSpecial<'!'> // true
    St.IsSpecial<' '> // false

    Runtime-only utilities

    deepTransformKeys

    This function recursively converts the keys of an object to a custom format, but only at runtime level.

    import { deepTransformKeys, toUpperCase } from 'string-ts'
    
    const data = { helloWorld: 'baz' } as const
    
    type MyType<T> = { [K in keyof T as Uppercase<K>]: T[K] }
    const result = deepTransformKeys(data, toUpperCase) as MyType<typeof data>
    //    ^ { 'HELLOWORLD': 'baz' }

    🐝 Contributors

    Thanks goes to these wonderful people (emoji key):

    Guga Guichard
    Guga Guichard

    💻 📆 📣 🚧 📖 🐛 🚇 💬 🔬 👀 🤔 💡
    Landon Yarrington
    Landon Yarrington

    💻 🚧 📖 👀 🤔 💡 💬 🐛
    Guillaume
    Guillaume

    💻 🚧 📖 🐛 🚇 💬 🤔
    Matt Pocock
    Matt Pocock

    📖 💻 📣
    Andrew Luca
    Andrew Luca

    📖 📣
    Mjuksel
    Mjuksel

    💻 🤔

    This project follows the all-contributors specification. Contributions of any kind welcome!

    🫶 Acknowledgements

    This library got a lot of inspiration from libraries such as lodash, ts-reset, type-fest, HOTScript, and many others.