trousse 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Maciej Lamberski
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.
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # trousse
2
+
3
+ [![codecov](https://codecov.io/gh/macieklamberski/trousse/branch/main/graph/badge.svg)](https://codecov.io/gh/macieklamberski/trousse)
4
+ [![npm version](https://img.shields.io/npm/v/trousse.svg)](https://www.npmjs.com/package/trousse)
5
+ [![license](https://img.shields.io/npm/l/trousse.svg)](https://github.com/macieklamberski/trousse/blob/main/LICENSE)
6
+
7
+ Personal toolbox of shared TypeScript utilities used across my projects: type guards, coercions, matching and array helpers.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm add trousse
13
+ ```
14
+
15
+ In published packages, prefer adding it as a dev dependency and bundling it at build time so it adds no runtime dependency.
@@ -0,0 +1,4 @@
1
+ //#region src/arrays.d.ts
2
+ declare const omitEmpty: <T>(array: Array<T | null | undefined>) => Array<T>;
3
+ //#endregion
4
+ export { omitEmpty };
package/dist/arrays.js ADDED
@@ -0,0 +1,9 @@
1
+ import { isPresent } from "./is.js";
2
+ //#region src/arrays.ts
3
+ const omitEmpty = (array) => {
4
+ const result = [];
5
+ for (const item of array) if (isPresent(item) && item !== "") result.push(item);
6
+ return result;
7
+ };
8
+ //#endregion
9
+ export { omitEmpty };
@@ -0,0 +1,11 @@
1
+ import { Nullish } from "./types.js";
2
+
3
+ //#region src/coercions.d.ts
4
+ declare const coerceString: (value: unknown) => string | undefined;
5
+ declare const coerceNumber: (value: unknown) => number | undefined;
6
+ declare const coerceBoolean: (value: unknown) => boolean | undefined;
7
+ declare const coerceDate: (value: unknown) => Date | undefined;
8
+ declare const coerceSingular: <T>(value: T | Array<T>) => T;
9
+ declare const coerceArray: <T>(value: Nullish<T | Array<T>>) => Array<T>;
10
+ //#endregion
11
+ export { coerceArray, coerceBoolean, coerceDate, coerceNumber, coerceSingular, coerceString };
@@ -0,0 +1,38 @@
1
+ import { isBoolean, isNonEmptyString, isNullish, isNumber, isString, isValidDate } from "./is.js";
2
+ //#region src/coercions.ts
3
+ const coerceString = (value) => {
4
+ if (isString(value)) return value;
5
+ if (isNumber(value)) return String(value);
6
+ };
7
+ const coerceNumber = (value) => {
8
+ if (isNumber(value)) return Number.isNaN(value) ? void 0 : value;
9
+ if (isNonEmptyString(value)) {
10
+ const numeric = +value;
11
+ return Number.isNaN(numeric) ? void 0 : numeric;
12
+ }
13
+ };
14
+ const trueRegex = /^\p{White_Space}*true\p{White_Space}*$/iu;
15
+ const falseRegex = /^\p{White_Space}*false\p{White_Space}*$/iu;
16
+ const coerceBoolean = (value) => {
17
+ if (isBoolean(value)) return value;
18
+ if (isNonEmptyString(value)) {
19
+ if (trueRegex.test(value)) return true;
20
+ if (falseRegex.test(value)) return false;
21
+ }
22
+ };
23
+ const coerceDate = (value) => {
24
+ if (isValidDate(value)) return value;
25
+ if (isNumber(value) || isNonEmptyString(value)) {
26
+ const date = new Date(value);
27
+ return Number.isNaN(date.getTime()) ? void 0 : date;
28
+ }
29
+ };
30
+ const coerceSingular = (value) => {
31
+ return Array.isArray(value) ? value[0] : value;
32
+ };
33
+ const coerceArray = (value) => {
34
+ if (isNullish(value)) return [];
35
+ return Array.isArray(value) ? value : [value];
36
+ };
37
+ //#endregion
38
+ export { coerceArray, coerceBoolean, coerceDate, coerceNumber, coerceSingular, coerceString };
@@ -0,0 +1,9 @@
1
+ import { omitEmpty } from "./arrays.js";
2
+ import { MaybePromise, Nullish, Pattern } from "./types.js";
3
+ import { coerceArray, coerceBoolean, coerceDate, coerceNumber, coerceSingular, coerceString } from "./coercions.js";
4
+ import { isBoolean, isFunction, isNonEmptyString, isNullish, isNumber, isObject, isPlainObject, isPresent, isString, isValidDate } from "./is.js";
5
+ import { isJsonLike, isParseableJson } from "./json.js";
6
+ import { anyWordMatchesAnyOf, endsWithAnyOf, includesAnyOf, isAnyOf, startsWithAnyOf } from "./matching.js";
7
+ import { sleep } from "./timers.js";
8
+ import { getPathSegments, isHostOf, isSubdomainOf, parseUrl } from "./urls.js";
9
+ export { type MaybePromise, type Nullish, type Pattern, anyWordMatchesAnyOf, coerceArray, coerceBoolean, coerceDate, coerceNumber, coerceSingular, coerceString, endsWithAnyOf, getPathSegments, includesAnyOf, isAnyOf, isBoolean, isFunction, isHostOf, isJsonLike, isNonEmptyString, isNullish, isNumber, isObject, isParseableJson, isPlainObject, isPresent, isString, isSubdomainOf, isValidDate, omitEmpty, parseUrl, sleep, startsWithAnyOf };
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ import { isBoolean, isFunction, isNonEmptyString, isNullish, isNumber, isObject, isPlainObject, isPresent, isString, isValidDate } from "./is.js";
2
+ import { omitEmpty } from "./arrays.js";
3
+ import { coerceArray, coerceBoolean, coerceDate, coerceNumber, coerceSingular, coerceString } from "./coercions.js";
4
+ import { isJsonLike, isParseableJson } from "./json.js";
5
+ import { anyWordMatchesAnyOf, endsWithAnyOf, includesAnyOf, isAnyOf, startsWithAnyOf } from "./matching.js";
6
+ import { sleep } from "./timers.js";
7
+ import { getPathSegments, isHostOf, isSubdomainOf, parseUrl } from "./urls.js";
8
+ export { anyWordMatchesAnyOf, coerceArray, coerceBoolean, coerceDate, coerceNumber, coerceSingular, coerceString, endsWithAnyOf, getPathSegments, includesAnyOf, isAnyOf, isBoolean, isFunction, isHostOf, isJsonLike, isNonEmptyString, isNullish, isNumber, isObject, isParseableJson, isPlainObject, isPresent, isString, isSubdomainOf, isValidDate, omitEmpty, parseUrl, sleep, startsWithAnyOf };
package/dist/is.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ //#region src/is.d.ts
2
+ declare const isString: (value: unknown) => value is string;
3
+ declare const isNumber: (value: unknown) => value is number;
4
+ declare const isBoolean: (value: unknown) => value is boolean;
5
+ declare const isFunction: (value: unknown) => value is (...args: Array<never>) => unknown;
6
+ declare const isPresent: <T>(value: T) => value is NonNullable<T>;
7
+ declare const isNullish: (value: unknown) => value is null | undefined;
8
+ declare const isObject: (value: unknown) => value is object;
9
+ declare const isPlainObject: (value: unknown) => value is Record<string, unknown>;
10
+ declare const isNonEmptyString: (value: unknown) => value is string;
11
+ declare const isValidDate: (value: unknown) => value is Date;
12
+ //#endregion
13
+ export { isBoolean, isFunction, isNonEmptyString, isNullish, isNumber, isObject, isPlainObject, isPresent, isString, isValidDate };
package/dist/is.js ADDED
@@ -0,0 +1,34 @@
1
+ //#region src/is.ts
2
+ const isString = (value) => {
3
+ return typeof value === "string";
4
+ };
5
+ const isNumber = (value) => {
6
+ return typeof value === "number";
7
+ };
8
+ const isBoolean = (value) => {
9
+ return typeof value === "boolean";
10
+ };
11
+ const isFunction = (value) => {
12
+ return typeof value === "function";
13
+ };
14
+ const isPresent = (value) => {
15
+ return value != null;
16
+ };
17
+ const isNullish = (value) => {
18
+ return value == null;
19
+ };
20
+ const isObject = (value) => {
21
+ return value !== null && typeof value === "object" && !Array.isArray(value);
22
+ };
23
+ const isPlainObject = (value) => {
24
+ return isObject(value) && value.constructor === Object;
25
+ };
26
+ const whitespaceOnlyRegex = /^\p{White_Space}*$/u;
27
+ const isNonEmptyString = (value) => {
28
+ return isString(value) && value !== "" && !whitespaceOnlyRegex.test(value);
29
+ };
30
+ const isValidDate = (value) => {
31
+ return value instanceof Date && !Number.isNaN(value.getTime());
32
+ };
33
+ //#endregion
34
+ export { isBoolean, isFunction, isNonEmptyString, isNullish, isNumber, isObject, isPlainObject, isPresent, isString, isValidDate };
package/dist/json.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ //#region src/json.d.ts
2
+ declare const isJsonLike: (value: string) => boolean;
3
+ declare const isParseableJson: (value: string) => boolean;
4
+ //#endregion
5
+ export { isJsonLike, isParseableJson };
package/dist/json.js ADDED
@@ -0,0 +1,19 @@
1
+ //#region src/json.ts
2
+ const jsonObjectStartRegex = /^\s*\{/;
3
+ const jsonObjectEndRegex = /\}\s*$/;
4
+ const jsonArrayStartRegex = /^\s*\[/;
5
+ const jsonArrayEndRegex = /\]\s*$/;
6
+ const isJsonLike = (value) => {
7
+ if (value.length < 2) return false;
8
+ return jsonObjectStartRegex.test(value) && jsonObjectEndRegex.test(value) || jsonArrayStartRegex.test(value) && jsonArrayEndRegex.test(value);
9
+ };
10
+ const isParseableJson = (value) => {
11
+ try {
12
+ JSON.parse(value);
13
+ return true;
14
+ } catch {
15
+ return false;
16
+ }
17
+ };
18
+ //#endregion
19
+ export { isJsonLike, isParseableJson };
@@ -0,0 +1,10 @@
1
+ import { Pattern } from "./types.js";
2
+
3
+ //#region src/matching.d.ts
4
+ declare const isAnyOf: (value: string, patterns: Array<Pattern>, parser?: (value: string) => string) => boolean;
5
+ declare const includesAnyOf: (value: string, patterns: Array<Pattern>, parser?: (value: string) => string) => boolean;
6
+ declare const startsWithAnyOf: (value: string, patterns: Array<Pattern>) => boolean;
7
+ declare const endsWithAnyOf: (value: string, patterns: Array<Pattern>) => boolean;
8
+ declare const anyWordMatchesAnyOf: (value: string, patterns: Array<Pattern>) => boolean;
9
+ //#endregion
10
+ export { anyWordMatchesAnyOf, endsWithAnyOf, includesAnyOf, isAnyOf, startsWithAnyOf };
@@ -0,0 +1,35 @@
1
+ //#region src/matching.ts
2
+ const whitespaceRegex = /\s+/;
3
+ const isAnyOf = (value, patterns, parser) => {
4
+ const parsedValue = parser ? parser(value) : value?.toLowerCase()?.trim();
5
+ return patterns.some((pattern) => {
6
+ if (pattern instanceof RegExp) return pattern.test(parsedValue);
7
+ return parsedValue === pattern.toLowerCase().trim();
8
+ });
9
+ };
10
+ const includesAnyOf = (value, patterns, parser) => {
11
+ const parsedValue = parser ? parser(value) : value?.toLowerCase();
12
+ return patterns.some((pattern) => {
13
+ if (pattern instanceof RegExp) return pattern.test(parsedValue);
14
+ return pattern && parsedValue?.includes(pattern.toLowerCase());
15
+ });
16
+ };
17
+ const startsWithAnyOf = (value, patterns) => {
18
+ const lowerValue = value.toLowerCase();
19
+ return patterns.some((pattern) => {
20
+ if (pattern instanceof RegExp) return pattern.test(lowerValue);
21
+ return pattern && lowerValue.startsWith(pattern.toLowerCase());
22
+ });
23
+ };
24
+ const endsWithAnyOf = (value, patterns) => {
25
+ const lowerValue = value.toLowerCase();
26
+ return patterns.some((pattern) => {
27
+ if (pattern instanceof RegExp) return pattern.test(lowerValue);
28
+ return pattern && lowerValue.endsWith(pattern.toLowerCase());
29
+ });
30
+ };
31
+ const anyWordMatchesAnyOf = (value, patterns) => {
32
+ return value.toLowerCase().split(whitespaceRegex).some((word) => isAnyOf(word, patterns));
33
+ };
34
+ //#endregion
35
+ export { anyWordMatchesAnyOf, endsWithAnyOf, includesAnyOf, isAnyOf, startsWithAnyOf };
@@ -0,0 +1,4 @@
1
+ //#region src/timers.d.ts
2
+ declare const sleep: (milliseconds: number) => Promise<void>;
3
+ //#endregion
4
+ export { sleep };
package/dist/timers.js ADDED
@@ -0,0 +1,8 @@
1
+ //#region src/timers.ts
2
+ const sleep = (milliseconds) => {
3
+ return new Promise((resolve) => {
4
+ setTimeout(resolve, milliseconds);
5
+ });
6
+ };
7
+ //#endregion
8
+ export { sleep };
@@ -0,0 +1,6 @@
1
+ //#region src/types.d.ts
2
+ type Nullish<T> = T | null | undefined;
3
+ type MaybePromise<T> = T | Promise<T>;
4
+ type Pattern = string | RegExp;
5
+ //#endregion
6
+ export { MaybePromise, Nullish, Pattern };
package/dist/urls.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ //#region src/urls.d.ts
2
+ declare const parseUrl: (value: string | URL, base?: string | URL) => URL | undefined;
3
+ declare const getPathSegments: (value: string | URL) => Array<string>;
4
+ declare const isHostOf: (url: string | URL, hosts: string | Array<string>) => boolean;
5
+ declare const isSubdomainOf: (url: string | URL, domains: string | Array<string>) => boolean;
6
+ //#endregion
7
+ export { getPathSegments, isHostOf, isSubdomainOf, parseUrl };
package/dist/urls.js ADDED
@@ -0,0 +1,23 @@
1
+ import { endsWithAnyOf, isAnyOf } from "./matching.js";
2
+ //#region src/urls.ts
3
+ const parseUrl = (value, base) => {
4
+ if (value instanceof URL && base === void 0) return value;
5
+ try {
6
+ return new URL(value, base);
7
+ } catch {}
8
+ };
9
+ const getPathSegments = (value) => {
10
+ return parseUrl(value)?.pathname.split("/").filter(Boolean) ?? [];
11
+ };
12
+ const isHostOf = (url, hosts) => {
13
+ const hostname = parseUrl(url)?.hostname;
14
+ if (!hostname) return false;
15
+ return isAnyOf(hostname, Array.isArray(hosts) ? hosts : [hosts]);
16
+ };
17
+ const isSubdomainOf = (url, domains) => {
18
+ const hostname = parseUrl(url)?.hostname;
19
+ if (!hostname) return false;
20
+ return endsWithAnyOf(hostname, (Array.isArray(domains) ? domains : [domains]).map((domain) => `.${domain}`));
21
+ };
22
+ //#endregion
23
+ export { getPathSegments, isHostOf, isSubdomainOf, parseUrl };
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "trousse",
3
+ "description": "Personal toolbox of shared TypeScript utilities: type guards, coercions, matching and array helpers.",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "https://github.com/macieklamberski/trousse.git"
7
+ },
8
+ "bugs": {
9
+ "url": "https://github.com/macieklamberski/trousse/issues"
10
+ },
11
+ "license": "MIT",
12
+ "author": "Maciej Lamberski",
13
+ "sideEffects": false,
14
+ "keywords": [
15
+ "utils",
16
+ "helpers",
17
+ "typescript",
18
+ "guards",
19
+ "coerce",
20
+ "predicates",
21
+ "toolbox"
22
+ ],
23
+ "type": "module",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "default": "./dist/index.js"
28
+ }
29
+ },
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "scripts": {
34
+ "prepare": "lefthook install",
35
+ "build": "tsdown src/index.ts --format esm --dts --clean --unbundle --no-fixed-extension",
36
+ "test": "bun test"
37
+ },
38
+ "devDependencies": {
39
+ "@types/bun": "^1.3.14",
40
+ "kvalita": "^1.15.0",
41
+ "tsdown": "^0.22.2"
42
+ },
43
+ "version": "1.0.0"
44
+ }