Skip to Content
  • Website
Codsen
  • Home
  • Open Source
  • Articles
  • About

prevOpen Source→codsen-utilsnext

codsen-utils1.10.2

Various utility functions

Downloads per monthChangelogMIT Licenselibera manifesto
  • the top
  • Installation
  • Quick Take
  • Examples
  • IDEA
  • API — CHARACTER CO…
  • API — CHARACTER SET…
  • API — CHARACTER CH…
  • API — TYPE CHECKS
  • API — ARRAY AND OBJECT HE…
  • API — STRING HELPERS
  • API — INCLUDES()
  • API — MATCH()
  • API — END OF LINE
  • API — CODSENCLI()
  • API — VERSION
  • API — TYPES
  • Changelog

No 3rd party dependencies. All dependencies and devDependencies, checked recursively, are Codsen packages.

Permalink to InstallationInstallation

Permalink to Quick TakeQuick Take

Permalink to ExamplesExamples

  • Classify individual characters
  • Parse typed, short and repeatable CLI flags
  • Assemble CLI help text
  • Deeply clone a cyclic object graph
  • Detect and resolve line endings
  • Test whether a value exists
  • Find overlapping substring occurrences
  • Match a string against literal and regular-expression choices
  • Intersect arrays in the first array's order
  • Reject a character that is not a digit
  • Omit keys and clone retained values
  • Check own properties safely
  • Distinguish plain objects from other object values
  • Remove selected array values without mutating the input
  • Remove one trailing slash
  • Sort strings with the shared comparator
  • Replace part of a string
  • Reuse shared text symbols and HTML tag sets
  • Trim selected characters from both string edges
  • Check common JavaScript value types
  • Deduplicate values while preserving order
  • Match wildcard allow-lists and deny-lists

Idea

The small helpers that nearly every Codsen package needs: character classification, type guards, a few array and object utilities, EOL detection, wildcard matching and a CLI argument parser. They live here so that each of the other packages can depend on one place instead of on lodash, meow and friends.

Everything is a named export, so bundlers can tree-shake whatever you don’t use:

import { isNumberChar } from "codsen-utils";

console.log(isNumberChar("z"));
// => false
console.log(isNumberChar("0"));
// => true

This package is aimed at Codsen’s own programs first. The API is stable, but it grows to fit what those programs need.

API — character constants

Named constants for the characters which are easy to mistype and hard to spot in source code:

ExportCharacterDescription
leftSingleQuote
leftSingleQuote‘U+2018opens in a new tab
rightSingleQuote
rightSingleQuote’U+2019opens in a new tab, also an apostrophe
leftDoubleQuote
leftDoubleQuote“U+201Copens in a new tab
rightDoubleQuote
rightDoubleQuote”U+201Dopens in a new tab
rawNDash
rawNDash–en dash, U+2013opens in a new tab
rawMDash
rawMDash—em dash, U+2014opens in a new tab
rawNbsp
rawNbsp non-breaking space, U+00A0opens in a new tab
ellipsis
ellipsis…U+2026opens in a new tab
hairspace
hairspace U+200Aopens in a new tab
thinSpace
thinSpace U+2009opens in a new tab
singlePrime
singlePrime′U+2032opens in a new tab, for feet and minutes
doublePrime
doublePrime″U+2033opens in a new tab, for inches and seconds
rawReplacementMark
rawReplacementMark�U+FFFDopens in a new tab
multiplicationSign
multiplicationSign×U+00D7opens in a new tab
backslash
backslash\escapes cleanly in template literals
backtick
backtick`escapes cleanly in template literals
import { rawMDash, rightSingleQuote } from "codsen-utils";

console.log(`John${rightSingleQuote}s`);
// => "John’s"

API — character sets

ExportTypeContents
punctuationChars
Type: string[]
punctuationCharsstring[][".", ",", ";", "!", "?"] — the sentence-ending and clause-separating characters
voidTags
Type: string[]
voidTagsstring[]The 14 HTML void elementsopens in a new tab — area, base, br and so on
inlineTags
Type: Set<string>
inlineTagsSet<string>The 55 HTML inline elements — a, abbr, b, span and so on. It’s a Set, so use .has()
import { inlineTags, voidTags } from "codsen-utils";

console.log(inlineTags.has("span"));
// => true
console.log(voidTags.includes("br"));
// => true

API — character checks

Each takes an unknown value and returns a boolean. None of them throw — a wrong type is simply false, so you can feed them str[i - 1] without checking whether the index exists.

FunctionReturns true when the input is
isNumberChar(value)
isNumberChar(value)a string whose first character is 0–9
isLetter(value)
isLetter(value)exactly one Unicode code point in the Letter category — a, ž, я, 東, and 𐐀 all pass
isLatinLetter(value)
isLatinLetter(value)a string whose first character is A–Z or a-z only
isLowercaseLetter(value)
isLowercaseLetter(value)exactly one lowercase Unicode code point, including letters outside the Basic Multilingual Plane
isUppercaseLetter(value)
isUppercaseLetter(value)exactly one uppercase Unicode code point, including letters outside the Basic Multilingual Plane
isWhitespaceChar(value)
isWhitespaceChar(value)a string whose first character is whitespace, including the non-breaking space
isQuote(value)
isQuote(value)", ', or any of the four curly quotes listed above
isCurrencyChar(value)
isCurrencyChar(value)a single-character currency symbol — $, £, €, ¥ and so on
isCurrencySymbol(value)
isCurrencySymbol(value)any known currency symbol, including multi-character ones such as CHF, BZ$ and B/.

isCurrencyChar() and isCurrencySymbol() differ only in length. Use the first when walking a string character by character, and the second when you already have a whole token:

import { isCurrencyChar, isCurrencySymbol } from "codsen-utils";

console.log(isCurrencyChar("CHF"));
// => false
console.log(isCurrencySymbol("CHF"));
// => true

API — type checks

FunctionReturns true when
isStr(something)
isStr(something)it’s a string. Narrows to string
isNum(something)
isNum(something)it’s a finite number. Narrows to number
isInt(something)
isInt(something)it’s a non-negative safe integer. Narrows to number
isBool(something)
isBool(something)it’s a boolean. Narrows to boolean
isNull(something)
isNull(something)it’s null. Narrows to null
isRegExp(something)
isRegExp(something)it’s a usable same-realm or cross-realm regular expression, including a subclass. Narrows to RegExp
isDate(something)
isDate(something)it’s a same-realm or cross-realm Date, including an invalid date. Narrows to Date
isPlainObject(value)
isPlainObject(value)it’s an object literal, Object.create(null), new Object(), or a cross-realm plain object — but not an array, null, or a class instance. Narrows to PlainObject
existy(x)
existy(x)it’s neither null nor undefined. Note 0 and "" are “existy”
import { existy, isPlainObject } from "codsen-utils";

console.log([isPlainObject({}), isPlainObject([]), isPlainObject(null)]);
// => [true, false, false]
console.log([existy(0), existy(null), existy(undefined)]);
// => [true, false, false]

isPlainObject() checks the container’s prototype, not whether its values are JSON-safe. Records can contain functions, undefined, bigint values, cycles, and symbol-keyed properties and still be plain objects. Use the exported JSONObject type only when your data already satisfies the JSON value contract.

API — array and object helpers

FunctionDescription
deepClone(value)
deepClone(value)Clones a supported value graph without retaining object or collection references. Generic — the return type matches the input
deepCloneWithMetadata(value)
deepCloneWithMetadata(value)Applies the same clone contract and returns { value, hasRepeatedReferences }
uniq(input)
uniq(input)Returns a shallow copy of the array with only unique elements
pullAll(input, remove)
pullAll(input, remove)Returns a shallow copy of input without the elements listed in remove. Every valid-array path returns a new array; the input is never mutated
intersection(a, b)
intersection(a, b)Returns the elements present in both arrays. An alternative to lodash.intersection
omit(obj, keysToRemove)
omit(obj, keysToRemove)Returns a copy of the plain object without the named keys. An alternative to lodash.omit
hasOwnProp(obj, prop)
hasOwnProp(obj, prop)Safely checks an own string, number, or symbol key. Inherited properties return false, as do null and undefined inputs
compareFn(a, b)
compareFn(a, b)A localeCompare-based compare function to pass to Array.prototype.sort()
import { compareFn, omit, pullAll, uniq } from "codsen-utils";

console.log(pullAll([1, 2, 3, 4], [2, 4]));
// => [1, 3]
console.log(pullAll(undefined, undefined));
// => []
console.log(uniq([1, 1, 2, 3, 3]));
// => [1, 2, 3]
console.log(omit({ a: 1, b: 2, c: 3 }, ["b"]));
// => { a: 1, c: 3 }
console.log(["b", "a", "C"].sort(compareFn));
// => ["a", "b", "C"]

Deep-clone contract

deepClone() preserves cycles and repeated references inside the new graph. It supports arrays; plain and null-prototype records; dates; maps; sets; array buffers and views; regular expressions; errors; URLs; and ordinary class instances whose behaviour relies on cloneable own state. These built-in categories also work with cross-realm values. Primitive values and functions retain their identity.

Own __proto__ data stays an own property instead of changing the clone’s prototype, and null-prototype records remain null-prototype records. Enumerable accessors on records and arrays are read once and become data properties. Classes which depend on private fields or other hidden internal slots are outside the clone contract.

Use deepCloneWithMetadata() when you also need to know whether the source graph reused an object:

import { deepCloneWithMetadata } from "codsen-utils";

const shared = { enabled: true };
const result = deepCloneWithMetadata({ left: shared, right: shared });

console.log(result.hasRepeatedReferences);
// => true
console.log(result.value.left === result.value.right);
// => true
console.log(result.value.left === shared);
// => false

API — string helpers

FunctionDescription
stringSplice(str, index, count, add)
stringSplice(str, index, count, add)Replaces count characters starting at index with the string add, and returns the new string
findAllIdx(value, substring)
findAllIdx(value, substring)Returns an array of the indexes of every occurrence of substring
removeTrailingSlash(value)
removeTrailingSlash(value)Drops a single trailing / and trims the result. Anything which is not a string is passed through untouched
import { findAllIdx, removeTrailingSlash, stringSplice } from "codsen-utils";

console.log(stringSplice("abcdef", 2, 3, "XY"));
// => "abXYf"
console.log(findAllIdx("scissors", "s"));
// => [0, 3, 4, 7]
console.log(removeTrailingSlash("a/b/"));
// => "a/b"

API — includes()

The function includes() is imported like this:

Like Array.prototype.includes(), but the array can hold a mix of strings and regexes, and it’s matched against a string. It’s a friendly API — it will not throw if the inputs are wrong:

import { includes } from "codsen-utils";

console.log(includes(["a", /^b/], "bcd"));
// => true
console.log(includes(["a"], "z"));
// => false

Only usable regular expressions participate. RegExp.prototype, proxies, and objects which only spoof the RegExp tag are ignored without throwing. Subclasses and cross-realm regular expressions work. A global expression starts and finishes with lastIndex set to 0; other expressions keep JavaScript’s native lastIndex behaviour.

API — match()

The function match() is imported like this:

Matches a whole string against one or more wildcard patterns:

Input argumentTypeObligatoryDescription
input
Type: String
Obligatory: yes
inputStringyesString to match.
patterns
Type: String or array of strings
Obligatory: yes
patternsString or array of stringsyesOne pattern or an array of them.
options
Type: Plain object
Obligatory: no
optionsPlain objectnoOptional Options Object.
KeyTypeDefaultDescription
caseSensitiveMatch
Type: Boolean
Default: false
caseSensitiveMatchBooleanfalseSet to true to match letter case exactly.

The rules are:

  • Patterns are anchored — they must consume the whole input, not a part of it.
  • * stands for zero or more characters, and it does cross line breaks.
  • A leading ! negates a pattern. Any negative pattern which matches vetoes the result outright, no matter what the positive ones did.
  • Given only negative patterns, anything they don’t catch passes.
  • An empty pattern array matches nothing.
  • \ escapes the character after it, so \* means a literal asterisk and \\ means a literal backslash.
  • Matching walks code points, not UTF-16 code units, so a wildcard can never consume half of a surrogate pair.
import { match } from "codsen-utils";

console.log(match("index.js", ["*.js", "!*.test.js"]));
// => true
console.log(match("index.test.js", ["*.js", "!*.test.js"]));
// => false
console.log(match("A.JS", ["*.js"], { caseSensitiveMatch: true }));
// => false

API — end of line

FunctionDescription
detectEol(str)
detectEol(str)Reports the line ending used in the string, checking for "\r\n" first, then "\n", then "\r". Returns undefined if the string has none
resolveEolSetting(str, eolSetting, defaultEolChar)
resolveEolSetting(str, eolSetting, defaultEolChar)Turns an "lf"/"crlf"/"cr" setting into the actual character. When eolSetting is missing, it falls back to what detectEol() found, then to defaultEolChar (which itself defaults to "\n"). It throws if defaultEolChar is not one of the three EOL characters
import { detectEol, resolveEolSetting } from "codsen-utils";

console.log(JSON.stringify(detectEol("a\r\nb")));
// => "\r\n"
console.log(JSON.stringify(resolveEolSetting("a\r\nb", "lf")));
// => "\n"
console.log(JSON.stringify(resolveEolSetting("a\r\nb", null)));
// => "\r\n"

That pairing is the point: resolveEolSetting() lets a program offer an explicit “write CRLF” option while still honouring whatever the input file already used when the caller says nothing.

API — codsenCLI()

The function codsenCLI() is imported like this:

An in-house stand-in for meowopens in a new tab. It parses argv the way a CLI expects: flags with values, short flags, bundles, --no- negation, camelCase names and a -- escape hatch. It prints help or version on request.

Input argumentTypeObligatoryDescription
helpText
Type: String
Obligatory: no
helpTextStringnoWhat --help prints.
options
Type: Plain object
Obligatory: no
optionsPlain objectnoFlag schema and the consuming program’s package.json.

The Options Object has the following shape:

KeyTypeDefaultDescription
pkg
Type: Plain object
Default: undefined
pkgPlain objectundefinedThe consuming program’s package.json contents.
flags
Type: Plain object
Default: undefined
flagsPlain objectundefinedFlag schema, keyed by the flag’s camelCase name. See the table below.
argv
Type: Array of strings
Default: process.argv.slice(2)
argvArray of stringsprocess.argv.slice(2)The arguments to parse.
description
Type: String or false
Default: pkg.description
descriptionString or falsepkg.descriptionPrinted above the help text. Set to false to omit it.
version
Type: String
Default: pkg.version
versionStringpkg.versionThe version to print for --version.
helpIndent
Type: Number
Default: 2
helpIndentNumber2How many spaces the help text is indented by.
autoHelp
Type: Boolean
Default: true
autoHelpBooleantruePrint help and exit when the only argument is --help.
autoVersion
Type: Boolean
Default: true
autoVersionBooleantruePrint version and exit when the only argument is --version.
booleanDefault
Type: Boolean
Default: false
booleanDefaultBooleanfalseValue given to declared boolean flags the user didn’t pass. Set to undefined to leave them unset.

Each entry under flags describes one flag:

KeyTypeDescription
type
Type: "boolean", "string", "number"
type"boolean", "string", "number"Coercion applied to whatever the user typed. If undeclared, the value is passed through as a raw string (or true for a bare flag).
shortFlag
Type: String
shortFlagStringSingle-letter alias, for example p to serve --pad as -p.
default
Type: Any
defaultAnyUsed when the flag is absent from argv.
isMultiple
Type: Boolean
isMultipleBooleanCollect every occurrence into an array instead of last-one-wins.

The function returns a plain object:

Returned object’s keyTypeDescription
input
Type: Array of strings
inputArray of stringsPositional arguments, in the order they were given.
flags
Type: Plain object
flagsPlain objectParsed flags, keyed by their camelCase names.
pkg
Type: Plain object
pkgPlain objectThe package.json contents you passed in.
help
Type: String
helpStringThe assembled help text, ready to print.
showHelp
Type: Function
showHelpFunctionPrints the help text, then exits (code 2 unless told otherwise).
showVersion
Type: Function
showVersionFunctionPrints the version, then exits with code 0.

For example:

import { codsenCLI } from "codsen-utils";

const cli = codsenCLI("Usage: mytool <file>", {
  pkg: { name: "mytool", version: "1.2.3", description: "Does a thing" },
  flags: {
    pad: { type: "boolean", shortFlag: "p" },
    name: { type: "string", default: "anon" },
    tag: { type: "string", isMultiple: true },
  },
  argv: ["a.txt", "-p", "--tag", "x", "--tag", "y"],
});

console.log(cli.input);
// => ["a.txt"]
console.log(cli.flags);
// => { pad: true, tag: ["x", "y"], name: "anon" }

--no- negation and the -- escape hatch work as you’d expect:

codsenCLI("h", { flags: { pad: { type: "boolean", default: true } }, argv: ["--no-pad"] }).flags;
// => { pad: false }

codsenCLI("h", { flags: {}, argv: ["a", "--", "--b"] }).input;
// => ["a", "--b"]

Number flags accept ordinary decimal and exponent spellings, including leading-dot values. Attached and separate values use the same grammar:

const flags = {
  ratio: { type: "number", shortFlag: "r" },
};

codsenCLI("h", { flags, argv: ["--ratio", "-.5"] }).flags.ratio;
// => -0.5

codsenCLI("h", { flags, argv: ["-r.5e2"] }).flags.ratio;
// => 50

API — version

You can import version:

API — types

This package is written in TypeScript and exports the following types:

TypeDescription
JSONValue
Type: JSONValue
JSONValueAny JSON-representable value — string, number, boolean, null, JSONObject or JSONArray
JSONObject
Type: JSONObject
JSONObjectAn object whose values are all JSONValues
JSONArray
Type: JSONArray
JSONArrayAn array of JSONValues
JsonObject
Type: JsonObject
JsonObjectA mapped-type variant of JSONObject where every key is optional
Obj
Type: Obj
ObjAn alias of JSONObject, kept for the packages which already import it under that name
PlainObject
Type: PlainObject
PlainObjectThe truthful result of isPlainObject(): a record whose string, number, or symbol keys have unknown values
DeepCloneResult<T>
Type: DeepCloneResult<T>
DeepCloneResult<T>The value and hasRepeatedReferences metadata returned by deepCloneWithMetadata()
EolChar
Type: EolChar
EolChar"\n", "\r\n" or "\r" — what detectEol() and resolveEolSetting() deal in
EolSetting
Type: EolSetting
EolSetting"lf", "crlf" or "cr" — what a user-facing EOL option accepts
MatchOptions
Type: MatchOptions
MatchOptionsThe Optional Options Object of match()
CliFlagType
Type: CliFlagType
CliFlagType"boolean", "string" or "number" — what a flag’s value gets coerced to, once parsed
CliFlag
Type: CliFlag
CliFlagOne entry of the codsenCLI() flag schema
CliPkg
Type: CliPkg
CliPkgThe package.json shape codsenCLI() reads name, version, description and bin from
CliOptions
Type: CliOptions
CliOptionsThe Options Object of codsenCLI()
CliResult
Type: CliResult
CliResultWhat codsenCLI() returns
import type {
  CliFlag,
  CliFlagType,
  CliOptions,
  CliPkg,
  CliResult,
  DeepCloneResult,
  EolChar,
  EolSetting,
  JSONArray,
  JSONObject,
  JSONValue,
  JsonObject,
  MatchOptions,
  Obj,
  PlainObject,
} from "codsen-utils";

Permalink to changelogChangelog

Open Changelog
↑ back to top
prev next

Copyright

All rights reserved © Roy Revelt 2026
All our open source packages are under MIT licenceopens in a new tab

Activities

🐛 See a bug? Raise an issueopens in a new tab
💘 Check out the Indiewebopens in a new tab and Libera manifestoopens in a new tab