shelving/util/stringmodule

Core helpers for checking, converting, sanitising, and transforming strings. Used throughout Shelving wherever user input is cleaned or URLs are generated.

Things to know:

  • getString() converts boolean, number, Date, and arrays to strings but returns undefined for anything else (e.g. objects, null). Use requireString() to throw instead.
  • sanitizeText() is for single-line input (titles, labels). sanitizeMultilineText() is for longer content — it preserves \n but normalises everything else.
  • simplifyString() uses Unicode normalisation (NFKD) so accented/ligature characters (é, ) collapse to plain ASCII equivalents before stripping. This makes it reliable for search and slug generation.
  • splitString() differs from String.prototype.split() in that excess segments are concatenated onto the last segment rather than discarded, and it enforces min/max segment counts.
  • getWords() honours quoted phrases: "hello world" is one word, not two.

Usage

Type checking and conversion

ts
import { isString, getString, requireString } from "shelving/util";

isString("hi");       // true
isString(42);         // false

getString(42);         // "42"
getString(true);       // "true"
getString(new Date()); // ISO string e.g. "2024-01-15T00:00:00.000Z"
getString({});         // undefined

Length checks

ts
import { isStringLength, assertStringLength } from "shelving/util";

isStringLength("hello", 1, 10); // true
isStringLength("", 1);          // false

Sanitising user input

ts
import { sanitizeText, sanitizeMultilineText } from "shelving/util";

sanitizeText("  Hello\x00  World  ");  // "Hello World"
sanitizeMultilineText("line1\r\nline2\n\n\nline3"); // "line1\nline2\n\nline3"

Slugs and refs

ts
import { simplifyString, getSlug, requireSlug, getRef } from "shelving/util";

simplifyString("Héllo Wörld! 😂"); // "hello world"
getSlug("Hello World!");           // "hello-world"
getSlug("!!!");                    // undefined (empty after simplification)
requireSlug("!!!");                // throws RequiredError
getRef("Hello World");             // "helloworld" (no separator)

Word splitting and limiting

ts
import { getWords, limitString, splitString, getFirstLine } from "shelving/util";

getWords(`hello "world foo" bar`);  // ["hello", "world foo", "bar"]
limitString("A long string here", 10); // "A long…"
splitString("a/b/c", "/", 2);         // ["a", "b/c"]  — excess joins last segment
getFirstLine("line one\nline two");    // "line one"

Joining

ts
import { joinStrings } from "shelving/util";

joinStrings(["a", "b", "c"], ", "); // "a, b, c"

Functions

Go

isString()function

Is an unknown value a string?

isString(value: unknown): value is string
Go

assertString()function

Assert that a value is a string.

assertString(value: unknown, caller: AnyCaller = assertString): asserts value is string
Go

getString()function

Convert an unknown value to a string, or return undefined if conversion fails.

getString(value: unknown): string | undefined
Go

requireString()function

Convert a possible string to a string, or throw RequiredError if conversion fails.

requireString(value: PossibleString, caller: AnyCaller = requireString): string
Go

isStringLength()function

Is a value a string with a length between min and max?

isStringLength(value: unknown, min = 0, max = Number.POSITIVE_INFINITY): value is string
Go

assertStringLength()function

Assert that a value is a string with a length between min and max.

assertStringLength(value: unknown, min?: number, max?: number, caller: AnyCaller = assertString): asserts value is string
Go

requireStringLength()function

Convert a possible string to a string with min/max length, or throw RequiredError if conversion fails.

requireStringLength(value: PossibleString, min?: number, max?: number, caller: AnyCaller = requireString): string
Go

isStringBetween()function

Does a string have a length between min and max?

isStringBetween(str: string, min = 0, max = Number.POSITIVE_INFINITY): boolean
Go

joinStrings()function

Concatenate an iterable set of strings together.

joinStrings(strs: Iterable<string> & NotString, joiner = ""): string
Go

sanitizeText()function

Sanitize a single line of text.

sanitizeText(str: string): string
Go

sanitizeWord()function

Sanitize a single word of text.

sanitizeWord(str: string): string
Go

sanitizeMultilineText()function

Sanitize multiple lines of text.

sanitizeMultilineText(str: string): string
Go

simplifyString()function

Simplify a string by removing anything that isn't a number, letter, or space.

simplifyString(str: string): string
Go

getSlug()function

Convert a string to a kebab-case URL slug, or return undefined if conversion resulted in an empty ref.

getSlug(str: string): string | undefined
Go

requireSlug()function

Convert a string to a kebab-case URL slug, or throw RequiredError if conversion resulted in an empty ref.

requireSlug(str: string, caller: AnyCaller = requireSlug): string
Go

getRef()function

Convert a string to a unique ref e.g. abc123, or return undefined if conversion resulted in an empty string.

getRef(str: string): string | undefined
Go

requireRef()function

Convert a string to a unique ref e.g. abc123, or throw RequiredError if conversion resulted in an empty string.

requireRef(str: string, caller: AnyCaller = requireRef): string
Go

getWords()function

Return an array of the separate words and "quoted phrases" found in a string.

getWords(str: string): ImmutableArray<string>
Go

getFirstLine()function

Get the (trimmed) first full line of a string.

getFirstLine(str: string): string
Go

isUppercaseLetter()function

Is the first character of a string an uppercase letter?

isUppercaseLetter(str: string): boolean
Go

isLowercaseLetter()function

Is the first character of a string a lowercase letter?

isLowercaseLetter(str: string): boolean
Go

limitString()function

Limit a string to a given length.

limitString(str: string, maxLength: number, append = "…"): void
Go

splitString()function

Divide a string into parts based on a separator.

splitString(str: string, separator: string, min: 1, max: 1, caller?: AnyCaller): readonly [string]
splitString(str: string, separator: string, min: 2, max: 2, caller?: AnyCaller): readonly [string, string]
splitString(str: string, separator: string, min: 3, max: 3, caller?: AnyCaller): readonly [string, string, string]
splitString(str: string, separator: string, min: 4, max: 4, caller?: AnyCaller): readonly [string, string, string, string]
splitString(str: string, separator: string, min?: 1, max?: number, caller?: AnyCaller): readonly [string, ...string[]]
splitString(str: string, separator: string, min: 2, max?: number, caller?: AnyCaller): readonly [string, string, ...string[]]
splitString(str: string, separator: string, min: 3, max?: number, caller?: AnyCaller): readonly [string, string, string, ...string[]]
splitString(str: string, separator: string, min: 4, max?: number, caller?: AnyCaller): readonly [string, string, string, string, ...string[]]
splitString(str: string, separator: string, min?: number, max?: number, caller?: AnyCaller): ImmutableArray<string>
Go

trimString()function

Trim a string (as a function, so it can be used in mapping).

trimString(str: string): string
Go

isNonEmptyString()function

Does a string have length?

isNonEmptyString(str: string): boolean

Types

Go

NotStringtype

Type that never matches the string type.

{
	toUpperCase?: never;
	toLowerCase?: never;
}
Go

PossibleStringtype

Things that can be reliably converted to a string with no confusion.

boolean | string | number | Date
Go

Segmentstype

Series of string segments with at least one segment (this is what you actually get back when you split a string).

readonly [string, ...string[]]