@azure-net/tools
v2.0.0
Published
Azure-net tools library
Readme
@azure-net/tools
Small, typed utilities shared by the Azure Net packages. The public entry is safe to import during SSR; browser-backed helpers return safe fallback values when browser APIs are unavailable.
Installation
npm install @azure-net/toolsimport { ObjectUtil, DateUtil, DebounceUtil } from '@azure-net/tools';Environment
EnvironmentUtil exposes runtime checks:
isBrowser,isServer,isWebWorkerisDevelopment,isProductioncurrentEnvironment()currentMode()
For tree-shakeable build-time flags use the dedicated entry:
import { BROWSER, DEV, NODE } from '@azure-net/tools/environment';ObjectUtil
const source = { id: 1, name: 'Ada', password: 'secret' };
ObjectUtil.pick(source, ['id', 'name']);
ObjectUtil.omit(source, ['password']);
ObjectUtil.toEntries(source);
ObjectUtil.toEntries(source, { mapKey: (key) => key.toUpperCase() });
ObjectUtil.equals(left, right);
ObjectUtil.isAllKeysEmpty(value);Available methods:
clone(value)performs a shallow object or array clone.deepClone(value, structured = false)performs a deep clone.equals(left, right)performs symmetric, cycle-safe deep equality and preserves reference topology.compareAsString(left, right)compares JSON representations when JSON semantics are sufficient.toEntries(value, options?)returns typed{ key, value }entries and optionally maps keys.countProps,isObjectEmpty,isAllKeysEmpty,pick, andomitcover common object operations.
deepClone uses JSON serialization by default. This is predictable in SSR runtimes where structuredClone may not exist, but functions, undefined, symbols, prototypes, and non-JSON types are not preserved. Date values become strings. Pass true only when the runtime supports structuredClone and richer types or circular structures must be preserved:
const jsonSafeCopy = ObjectUtil.deepClone(value);
const structuredCopy = ObjectUtil.deepClone(value, true);DebugUtil
DebugUtil formats arbitrary values for diagnostics without mutating them.
const text = DebugUtil.stringify(value, { maxDepth: 6, maxEntries: 100 });
const html = DebugUtil.render(value, { theme: 'dark' });stringifysupports cycles,Map,Set,Date,RegExp, errors, typed arrays, bigint, symbols, and functions.renderreturns escaped, syntax-highlighted HTML. Objects and arrays use a readable code block; primitives use a compact gray value box.maxDepthandmaxEntriesprevent an accidental diagnostic render from traversing an unbounded graph.
The returned HTML is generated by the library and input strings are escaped, but it should still be mounted through the framework's normal trusted-markup mechanism intentionally.
DateUtil
Date strings are accepted only in these strict forms:
YYYY-MM-DD- ISO-like date-time:
YYYY-MM-DDTHH:mm[:ss[.fraction]][Z|+HH:mm|-HH:mm]
Impossible calendar dates, invalid times, malformed offsets, and arbitrary date-like strings are rejected. A date-only string is treated as a calendar date and is not shifted by UTC or timezone formatting.
DateUtil.isDate('2025-02-29'); // false
DateUtil.toDate('2025-05-23'); // "23.05.2025"
DateUtil.toTime('2025-08-15T12:00:00Z', { timeZone: 'Asia/Tokyo' }); // "21:00"
DateUtil.toFormat('2025-12-01', 'dd MM yyyy', { locale: 'en' });
DateUtil.compare('2025-01-01', '2025-01-02'); // -1
DateUtil.isBefore('2025-01-01', '2025-01-02'); // true
DateUtil.isAfter('2025-01-02', '2025-01-01'); // true
DateUtil.isSame('2025-01-01', '2025-01-01'); // true
DateUtil.isBetween('2025-01-02', '2025-01-01', '2025-01-03'); // true
DateUtil.isPast(date);
DateUtil.isFuture(date);The default locale is always en. Pass locale directly or install a resolver when locale is application state:
DateUtil.setLocale(() => currentLocale);
DateUtil.clearLocaleResolver();Frequently reused Intl.DateTimeFormat instances and generated month lists are cached.
Formatting tokens: yyyy, yy, MM (month name), mm, dd, d, HH, ii, ss.
TextUtil
TextUtil.pluralize(2, ['item', 'items']);
TextUtil.truncate('long text', 4); // "long..."
TextUtil.capitalize('hello');
TextUtil.decapitalize('Hello');
TextUtil.isEmptyOrWhitespace(' ');For truncate, maxLength is the number of source characters kept before ellipsis. The final output can therefore have length maxLength + ellipsis.length.
FormDataUtil
Converts nested objects to FormData and bracket-notation fields back to objects.
const form = new FormData();
form.append('user[name]', 'Ada');
form.append('tags[]', 'typescript');
const value = FormDataUtil.toObject(form);
const serialized = FormDataUtil.fromObject(value);toObject supports object paths, array indexes, and append syntax. Dangerous prototype path segments are rejected, and path depth and array indexes are bounded. fromObject supports arrays, Map, Set, Date, Blob, and File, and rejects cyclic input.
LocalStorageUtil
All methods are SSR-safe: set, get, delete, has, keys, getAll, and clear. Browser support is probed once and cached.
LocalStorageUtil.set('settings', { theme: 'dark' });
LocalStorageUtil.get<{ theme: string }>('settings');
LocalStorageUtil.get<string>('raw-json', { parse: false });Use createInstance to bind a key once. Default read options are also bound and can be overridden for an individual read:
const SessionStorage = LocalStorageUtil.createInstance<{ token: string }>('session');
SessionStorage.set({ token: 'token' });
SessionStorage.get();
SessionStorage.has();
SessionStorage.delete();
const RawToken = LocalStorageUtil.createInstance<string>('token', { parse: false });
RawToken.get();JSON serialization and storage quota/security failures are caught and do not leak exceptions from this helper.
Cookies
Browser-only cookie access with SSR-safe fallbacks.
Cookies.set('user', { id: 1 }, { expires: 7, sameSite: 'Lax' });
Cookies.get<{ id: number }>('user');
Cookies.get<string>('raw-json', { parse: false });
Cookies.getAll({ parse: false });
Cookies.has('user');
Cookies.delete('user');
Cookies.clear();Values are URI encoded. Reads parse JSON by default and return the decoded string when JSON parsing is not applicable.
DebounceUtil
const search = DebounceUtil.debounce(runSearch, 300);
search('a');
search('ab');
search.flush();
search.cancel();
const save = DebounceUtil.debounceAsync(saveDraft, 300);
await save(draft);
save.cancel();debounceAsync returns one promise per caller. All queued callers receive the last invocation's result or rejection. Timer-driven rejection is consumed internally after those promises are rejected, preventing a second unhandled rejection from the internal invocation.
ThrottleUtil
Creates a leading and trailing throttled function:
const update = ThrottleUtil.throttle(updatePosition, 100);
update();
update.pending();
update.flush();
update.cancel();Pending arguments are released after invocation or cancellation.
EventBus
Interfaces and type aliases can be used as event maps:
interface AppEvents {
ready: { id: string };
failed: Error;
}
const bus = new EventBus<AppEvents>({}, { mode: 'serial' });
const unsubscribe = bus.subscribe('ready', ({ id }) => console.log(id));
await bus.publish('ready', () => ({ id: '1' }));
unsubscribe();The default serial mode awaits listeners in registration order. mode: 'parallel' executes channel and catch-all listener snapshots concurrently. Listener exceptions are passed to onError when configured and otherwise logged.
AbortUtil
const { signal, cleanup } = AbortUtil.withTimeout(parentSignal, 5_000);
try {
await fetch(url, { signal });
} finally {
cleanup();
}
AbortUtil.isAbortError(error);Source cancellation and its reason are propagated. A timeout aborts with an error named TimeoutError. Call cleanup when the operation settles to release the timer and source listener.
PromiseUtil
if (PromiseUtil.isPromiseLike(value)) {
await value;
}The check intentionally uses the thenable contract instead of instanceof Promise, so it works across realms and with custom promise implementations.
ErrorUtil
const error = ErrorUtil.toError(caughtValue, 'Unknown operation error');Existing Error instances are returned unchanged. Strings, primitives, and error-like objects become an Error; the original value is retained as its non-enumerable cause.
UidGenerator
UidGenerator.generateUniqueString(24);
UidGenerator.generateUniqueNumber(1, 100);
UidGenerator.generateUuid();
UidGenerator.generateNanoId();
UidGenerator.generateTimestampId('order');
UidGenerator.generateHashId();
UidGenerator.isValidUuid(value);Random strings and numbers use rejection sampling to avoid modulo bias. Numeric bounds must be safe integers and generated values are within the inclusive range.
IntersectionObserverUtil
Observers with identical options share one native IntersectionObserver instance.
const observer = new IntersectionObserverUtil(element, {
callback(entry) {
console.log(entry.isIntersecting);
},
once: true,
onceMode: 'intersect',
triggerOnExit: false
});
observer.disconnect();The utility safely becomes inert when IntersectionObserver is unavailable.
DownloadUtil
DownloadUtil.download(blob, 'report.pdf');
DownloadUtil.download('https://example.com/report.pdf', 'report.pdf');Generated object URLs are revoked on the next task, after the synthetic click has consumed them. Calls are no-ops during SSR.
License
MIT
