Usage Guide for @nevware21/ts-utils
This guide provides practical examples for using the @nevware21/ts-utils library in your JavaScript or TypeScript projects.
Table of Contents
- Installation
- Basic Usage
- Runtime Environment Helpers
- Scheduling Semantics and Known Limitations
- Advanced Usage
- Performance and Minification Benefits
Installation
Install the npm package:
npm install @nevware21/ts-utils --save
It is recommended to use the following definition in your package.json to maintain compatibility with future releases:
{
"dependencies": {
"@nevware21/ts-utils": ">= 0.12.5 < 2.x"
}
}
Basic Usage
Type Checking Functions
The library provides numerous type checking functions to safely verify types in JavaScript:
import {
isArray, isString, isObject, isFunction, isNumber, isBoolean,
isDefined, isUndefined, isNull, isNullOrUndefined, isTruthy
} from "@nevware21/ts-utils";
// Basic type checks
const value = "test";
if (isString(value)) {
// Work with string...
}
// Array check (safer than instanceof Array)
const arr = [1, 2, 3];
if (isArray(arr)) {
// Work with array...
}
// Null/undefined checks
if (!isNullOrUndefined(value)) {
// Safe to use value
}
// Truthiness check
if (isTruthy(value)) {
// Value is not false, 0, "", null, undefined, etc.
}
Type Utility Aliases
Type-only aliases help define stronger contracts for APIs and config models:
import type {
DeepPartial,
DeepReadonly,
DeepRequired,
Mutable,
NonEmptyArray,
ReadonlyRecord,
ValueOf
} from "@nevware21/ts-utils";
type ThemeMap = ReadonlyRecord<"light" | "dark", { primary: string }>;
type ThemeName = ValueOf<{ light: "light"; dark: "dark" }>;
type User = {
readonly id: string;
profile?: {
email?: string;
tags?: string[];
};
};
type UserPatch = DeepPartial<User>;
type FrozenUser = DeepReadonly<User>;
type NormalizedUser = DeepRequired<User>;
type WritableUser = Mutable<User>;
const batch: NonEmptyArray<User> = [{ id: "u1" }];
Array Operations
Array helper functions provide polyfill support and consistent API across environments:
import {
arrForEach, arrMap, arrFilter, arrFind, arrReduce,
arrIncludes, arrSlice, arrFrom
} from "@nevware21/ts-utils";
const numbers = [1, 2, 3, 4, 5];
// Iterate through array
arrForEach(numbers, (value, index) => {
console.log(`Value at ${index}: ${value}`);
});
// Transform array
const doubled = arrMap(numbers, (value) => value * 2);
// [2, 4, 6, 8, 10]
// Filter array
const evens = arrFilter(numbers, (value) => value % 2 === 0);
// [2, 4]
// Find element
const found = arrFind(numbers, (value) => value > 3);
// 4
// Create array from iterable
const arrayFromString = arrFrom("hello");
// ["h", "e", "l", "l", "o"]
Object Manipulations
Safely work with objects using these utility functions:
import {
objForEachKey, objAssign, objCopyProps, objDeepCopy,
objKeys, objValues, objEntries, objHasOwnProperty
} from "@nevware21/ts-utils";
const person = {
name: "Alice",
age: 30,
city: "Wonderland"
};
// Iterate through object keys
objForEachKey(person, (key, value) => {
console.log(`${key}: ${value}`);
});
// Safe property check
if (objHasOwnProperty(person, "age")) {
console.log(person.age);
}
// Deep copy objects
const personCopy = objDeepCopy(person);
// Get array of keys
const keys = objKeys(person);
// ["name", "age", "city"]
// Merge objects safely
const merged = objAssign({}, person, { job: "Developer" });
String Functions
String manipulation with built-in polyfill support:
import {
strTrim, strStartsWith, strEndsWith, strIncludes,
strCount, strLeft, strRight, strSubstring, strTruncate, strIsNullOrEmpty,
strCamelCase, strCapitalizeWords, strKebabCase, strSnakeCase
} from "@nevware21/ts-utils";
const text = " Hello World! ";
// Basic operations
const trimmed = strTrim(text); // "Hello World!"
const hasPrefix = strStartsWith(text, " He"); // true
const hasSuffix = strEndsWith(trimmed, "!"); // true
const includes = strIncludes(text, "World"); // true
// Get substrings
const leftPart = strLeft(trimmed, 5); // "Hello"
const rightPart = strRight(trimmed, 7); // "World!"
const truncated = strTruncate(trimmed, 8, "..."); // "Hello..."
const worldCount = strCount(trimmed, "l"); // 3
// Case transformations
const camelCase = strCamelCase("hello-world"); // "helloWorld"
const titleCase = strCapitalizeWords("hELLo-world from_ts-utils"); // "Hello-World From_Ts-Utils"
const kebabCase = strKebabCase("helloWorld"); // "hello-world"
const snakeCase = strSnakeCase("helloWorld"); // "hello_world"
// Conversion behavior differences
// strCapitalizeWords: keeps separators and lowercases the rest of each word
// strCamelCase: removes separators and joins words
// strKebabCase / strSnakeCase: normalize into a single delimiter style
// Check for empty strings
if (!strIsNullOrEmpty(text)) {
// Safe to work with text
}
Safe Operations
Perform operations safely without worrying about null/undefined errors:
import { safe, safeGet, safeGetLazy } from "@nevware21/ts-utils";
const obj = {
user: {
profile: {
name: "Alice"
}
}
};
// Safe function execution
const result = safe(() => {
// This code won't throw even if something is undefined
return obj.user.profile.address.street;
}, "Default value");
// Safe property access (nested)
const name = safeGet(obj, (o) => o.user.profile.name); // "Alice"
const address = safeGet(obj, (o) => o.user.profile.address, "Unknown"); // "Unknown"
// Lazy evaluation (only computed when needed)
const lazyValue = safeGetLazy(obj, () => {
// Complex calculation only performed when value is accessed
return calculateSomething(obj);
});
Runtime Environment Helpers
Check and access runtime environment features safely:
import {
isNode, isWebWorker, hasWindow, hasDocument,
getGlobal, getWindow, getDocument, getNavigator
} from "@nevware21/ts-utils";
// Environment detection
if (isNode()) {
// Node.js specific code
} else if (isWebWorker()) {
// Web Worker specific code
} else if (hasWindow()) {
// Browser-specific code
}
// Safe access to global objects
const global = getGlobal();
const doc = hasDocument() ? getDocument() : null;
Scheduling Semantics and Known Limitations
When using timer helpers, especially scheduleNextTick(), ordering depends on the runtime capabilities and whether you mix direct native APIs with ts-utils wrappers.
Fallback priority used by scheduleNextTick()
scheduleNextTick() resolves scheduling in this order:
- Native
process.nextTick(Node.js) - Native
queueMicrotask - Promise microtask fallback (
Promise.resolve().then(...)) - Timer-backed fallback (
setTimeout(..., 0))
Mixing direct native calls with scheduleNextTick()
- In modern browser/worker runtimes (where native
process.nextTickis not available),scheduleNextTick()typically uses nativequeueMicrotask. - If you directly call native
queueMicrotask()and also callscheduleNextTick(), both callbacks are queued in the same microtask queue, so ordering is insertion/FIFO based. - This means
scheduleNextTick()does not get special priority over a previously queued nativequeueMicrotask()callback in browser/worker environments. - For deterministic ordering, prefer one strategy per critical path: either use only native microtask APIs directly or use ts-utils wrappers (
scheduleNextTick()/scheduleMicrotask()) consistently.
Node.js note:
- In Node.js, native
process.nextTickhas existed for a long time andscheduleNextTick()uses it when available, so the browser/workerqueueMicrotaskmixing concern is generally not the primary issue.
Known limitation in timer-backed fallback environments
In older runtimes where process.nextTick, queueMicrotask, and Promise are not available, scheduleNextTick() falls back to setTimeout(..., 0).
In this mode:
scheduleNextTick()is no longer a true microtask.- It cannot preempt a user’s directly scheduled native timers that were already queued.
- Ordering between user timers and
scheduleNextTick()becomes macrotask queue ordering and may differ from microtask-like expectations.
Example:
import { scheduleNextTick } from "@nevware21/ts-utils";
setTimeout(() => {
console.log("user timeout");
}, 0);
scheduleNextTick(() => {
console.log("scheduleNextTick");
});
// In timer-backed fallback environments, output may be:
// "user timeout"
// "scheduleNextTick"
Practical guidance
- only use
scheduleTimeout(),scheduleMicrotask()andscheduleNextTick()to ensure the correct execution order. - Use
scheduleNextTick()for cross-runtime behavior, but do not assume it always has higher priority than directly queued nativequeueMicrotasksin browser/worker environments. - Avoid relying on strict microtask-before-timer guarantees in environments that require timer fallback (unless you only use the
scheduleXXXXX()functions) - For app-level deterministic ordering, pick one scheduling strategy per critical execution path and avoid mixing native and wrapped scheduling primitives.
Advanced Usage
Working with Iterators
Create and work with iterators and iterables:
import {
createIterator, createArrayIterator, makeIterable,
isIterable, isIterator, iterForOf
} from "@nevware21/ts-utils";
// Create a custom iterator
const rangeIterator = createIterator({
index: 0,
max: 5
}, (ctx) => {
if (ctx.index < ctx.max) {
return { value: ctx.index++, done: false };
}
return { done: true };
});
// Make an object iterable
const iterableObject = makeIterable({
items: [1, 2, 3, 4, 5]
}, function(this) {
return createArrayIterator(this.items);
});
// Loop using iterForOf (works with any iterable)
iterForOf(iterableObject, (value) => {
console.log(value);
});
Lazy Evaluation
Use lazy evaluation to defer expensive operations until needed:
import { getLazy, getWritableLazy } from "@nevware21/ts-utils";
// Create a lazy value
const lazyValue = getLazy(() => {
console.log("Computing expensive value...");
return performExpensiveCalculation();
});
// Value is calculated only when accessed
console.log("Before accessing lazy value");
const value = lazyValue.v; // "Computing expensive value..." is logged here
console.log(`The value is ${value}`);
// Writable lazy value
const writableLazy = getWritableLazy(() => "initial value");
console.log(writableLazy.v); // "initial value"
// Update the lazy value
writableLazy.v = "new value";
console.log(writableLazy.v); // "new value"
Deep Copy with Custom Handlers
Handle complex objects during deep copying:
import { objDeepCopy, IObjDeepCopyHandlerDetails, isMap } from "@nevware21/ts-utils";
class User {
constructor(public id: string, public name: string) {}
greet() {
return `Hello, my name is ${this.name}`;
}
}
// Custom deep copy handler
function customDeepCopyHandler(details: IObjDeepCopyHandlerDetails): boolean {
const value = details.value;
if (value instanceof User) {
// Create a new User instance
details.result = new User(value.id, value.name);
return true; // We handled this object type
}
// Let the default implementation handle other types
return false;
}
const original = {
user: new User("123", "Alice"),
settings: new Map([
["theme", "dark"],
["fontSize", 16]
])
};
const copy = objDeepCopy(original, customDeepCopyHandler);
console.log(copy.user.greet()); // "Hello, my name is Alice"
console.log(copy.user !== original.user); // true - different instances
Performance and Minification Benefits
Using ts-utils functions instead of direct JS methods provides better minification due to:
-
Function Name Minification: Helper functions from ts-utils get minified to single letters while standard JS methods cannot.
-
Error Prevention: Many ts-utils functions handle edge cases that standard JS methods might not.
-
Consistent API: Works across different browsers and environments without worrying about polyfills.
Example comparison of minified code:
// Using ts-utils (minified)
function fn(t){var r=[];if(a(t)){f(t,function(v,i){if(h(t,v)){r.push(i+":"+v)}})}else{k(t,function(k,v){if(v){r.push(k+"="+v)}})}return r}
// Using standard JS (minified)
function fn(t){var r=[];if(Array.isArray(t)){for(var i=0;i<t.length;i++){if(i in t){var v=t[i];if(t.hasOwnProperty(v)){r.push(i+":"+v)}}}}else{Object.keys(t).forEach(function(k){var v=t[k];if(v){r.push(k+"="+v)}})}return r}
While ts-utils adds a small overhead for the first usage, the savings increase significantly as you use more functions throughout your code.