name: welshman-lib
description: Use this skill when working with @welshman/lib: general-purpose utilities including LRU cache, EventEmitter, Deferred promises, TaskQueue, URL normalization, or other standalone helpers.
welshman/lib — General Utilities
@welshman/lib is a lightweight TypeScript utility library that forms the foundation of the welshman nostr stack. It provides common helpers used across all sibling packages: array/object manipulation, numeric helpers, async primitives, caching, event emission, and encoding utilities. It depends on @scure/base (for bech32/utf8 encoding) and events (Node.js EventEmitter polyfill).
Installation
Key Exports
Deferred Promises
| Export |
Description |
Deferred<T, E> |
Type: a Promise<T> with .resolve(T) and .reject(E) methods attached |
defer<T, E>() |
Creates a Deferred<T, E> — a promise with exposed .resolve() and .reject() |
makePromise<T, E>(executor) |
Creates a strongly-typed promise with typed error |
E defaults to T when omitted. defer<void>() for a signal-style deferred. thunk.complete in @welshman/app is a Deferred<void>.
EventEmitter
| Export |
Description |
Emitter |
Extends Node.js EventEmitter; all events also fire on the '*' listener with the event name prepended |
LRU Cache
| Export |
Description |
LRUCache<K, V> |
LRU cache; evicts least-recently-used entries when full |
cached(options) |
Memoizes a function with an LRU backing cache; exposes .cache and .pop() |
simpleCache(getValue) |
Minimal memoization wrapper with default settings |
Task Queue
| Export |
Description |
TaskQueue<Item> |
Processes items asynchronously in configurable batches |
Options: batchSize, batchDelay (ms), processItem. Methods: .push(item), .remove(item), .start(), .stop(), .clear(), .process(), .subscribe(cb).
URL Normalization
| Export |
Description |
normalizeUrl(url, options?) |
Normalizes a URL string (ported from sindresorhus/normalize-url) |
stripProtocol(url) |
Removes the protocol prefix (http://, wss://, etc.) |
displayUrl(url) |
Strips protocol, www., and trailing slash for display |
displayDomain(url) |
Extracts just the domain from a URL |
Note: normalizeUrl defaults to http:// protocol. Pass { defaultProtocol: 'https' } if needed.
Async Utilities
| Export |
Description |
sleep(ms) |
Returns a promise that resolves after ms milliseconds |
yieldThread() |
Yields to the event loop (microtask break) |
poll(options) |
Polls until a condition is met or an AbortSignal fires; options: { signal, condition, interval? } |
throttle(ms, fn) |
Returns a throttled version of fn |
throttleWithValue(ms, fn) |
Throttled function that returns the cached return value between updates |
batch(t, fn) |
First call fires fn([item]) immediately; subsequent calls within t ms are collected and fn is called with all accumulated items |
batcher(t, execute) |
Collects calls for t ms, then calls execute with all accumulated requests; each individual call returns a Promise<U> resolved with its result from the batch. Unlike batch, the first call is also deferred — nothing fires immediately. |
race(threshold, promises) |
Resolves when threshold fraction of promises complete |
Timestamp / Time Constants
| Export |
Description |
MINUTE, HOUR, DAY, WEEK, MONTH, QUARTER, YEAR |
Duration constants in seconds |
LOCALE |
User's default locale string |
TIMEZONE |
User's timezone offset string (e.g. +05:30) |
now() |
Current Unix timestamp in seconds |
ago(unit, count?) |
Unix timestamp from count units ago — e.g. ago(DAY, 7) |
int(unit, count?) |
Multiplies a time unit by count — e.g. int(HOUR, 2) = 7200 |
ms(seconds) |
Converts seconds to milliseconds |
secondsToDate(ts) / dateToSeconds(date) |
Convert between Unix seconds and Date |
createLocalDate(dateString, timezone?) |
Parses a date string as a local date in the given timezone |
formatTimestamp(ts) |
Formats Unix seconds as a short datetime string |
formatTimestampAsDate(ts) |
Formats Unix seconds as a long date string |
formatTimestampAsTime(ts) |
Formats Unix seconds as a time string |
formatTimestampRelative(ts) |
Formats Unix seconds as "x minutes ago" |
Note: All time constants are in seconds, not milliseconds. Use ms(n) to convert for setTimeout.
Number Utilities
| Export |
Description |
ensureNumber(x) |
parseFloat(x) — accepts string | number |
num(x) |
Returns x || 0 — converts undefined to 0 |
add(x, y) / sub(x, y) / mul(x, y) / div(x, y) |
Arithmetic with undefined-safe operands |
inc(x) / dec(x) |
Increment / decrement (undefined-safe) |
lt(x, y) / lte(x, y) / gt(x, y) / gte(x, y) |
Comparisons (undefined-safe) |
max(xs) / min(xs) / sum(xs) / avg(xs) |
Aggregates over (number | undefined)[] |
between([low, high], n) |
n > low && n < high (exclusive) |
within([low, high], n) |
n >= low && n <= high (inclusive) |
clamp([min, max], n) |
Constrains n to the range |
round(precision, x) |
Rounds to precision decimal places |
Array / Sequence Utilities
All return new arrays — no mutation.
| Export |
Description |
first(xs) / last(xs) |
First/last element (undefined if empty) |
ffirst(xs) |
First element of the first iterable in a nested iterable |
take(n, xs) / drop(n, xs) |
Slice from start / drop from start |
concat(...xs) |
Flattens vararg arrays into one, skipping any argument that is undefined |
append(x, xs) / prepend(x, xs) |
Add element to end / start |
remove(x, xs) |
Remove all occurrences of x |
removeAt(i, xs) |
Remove element at index i |
splitAt(n, xs) |
Split into [xs.slice(0, n), xs.slice(n)] |
insertAt(n, x, xs) |
Insert x at index n |
replaceAt(n, x, xs) |
Replace element at index n with x |
uniq(xs) / uniqBy(f, xs) |
Deduplicate |
sort(xs) |
Sorted copy (natural order) |
sortBy(f, xs) |
Sort by key function |
groupBy(f, xs) |
Returns Map<K, T[]> |
indexBy(f, xs) |
Returns Map<K, T> (last item wins per key) |
countBy(f, xs) |
Returns Map<K, number> |
partition(f, xs) |
Split into [passing, failing] |
chunk(n, xs) |
Split into fixed-size chunks of length n |
chunks(n, xs) |
Split into exactly n chunks |
toggle(x, xs) |
Add if absent, remove if present (pure) |
union(a, b) / intersection(a, b) / difference(a, b) / without(a, b) |
Set operations |
sample(n, xs) / shuffle(xs) / choice(xs) |
Random selection / shuffle / single random pick |
flatten(xs) |
Flatten one level |
ensurePlural(x) |
Wraps a value in [x] if it isn't already an array |
removeUndefined(xs) |
Filters out undefined values |
overlappingPairs(xs) |
Returns [[xs[0],xs[1]], [xs[1],xs[2]], ...] |
range(a, b, step?) |
Generator yielding numbers from a to b (exclusive) |
enumerate(xs) |
Generator yielding [index, item] tuples |
pluck<T>(k, xs) |
Maps xs to xs[k] |
fromPairs(pairs) |
Creates an object from [key, value] tuples |
initArray(n, f) |
Creates an array of length n using generator f |
isIterable(x) / toIterable(x) |
Check / wrap as iterable |
map(f, xs) / filter(f, xs) / reject(f, xs) |
Iterable-safe versions (accept any Iterable<T>) |
find(f, xs) / some(f, xs) |
Iterable-safe find / any-match |
Object Utilities
| Export |
Description |
isPojo(obj) |
Returns true if value is a plain object (not class instance, null, or array) |
pick(keys, obj) / omit(keys, obj) |
Include / exclude keys |
omitVals(vals, obj) |
Remove entries whose value is in vals |
filterVals(f, obj) |
Keep entries where f(value) is truthy |
mapKeys(f, obj) / mapVals(f, obj) |
Transform keys or values |
mergeLeft(a, b) / mergeRight(a, b) |
Shallow merge — left/right wins on conflicts |
deepMergeLeft(a, b) / deepMergeRight(a, b) |
Deep merge — left/right wins on conflicts |
switcher(key, map) |
Lookup with implicit map.default fallback |
mapPop(k, m) |
Gets and deletes key from a Map<K, T> — returns T | undefined |
Note: mergeLeft(a, b) means a wins — it spreads b first, then a on top.
TypeScript Utility Types
Functional / Combinator Helpers
| Export |
Description |
noop |
No-op function |
identity(x) |
Returns x unchanged |
always(x) |
Returns a function that always returns x |
not(x) |
Logical NOT |
complement(f) |
Returns (...args) => !f(...args) |
tap(f) |
Returns (x) => { f(x); return x } — runs a side effect and passes the value through |
bind(f, ...args) |
Partially applies f with leading args |
equals(a, b) |
Deep equality (handles arrays, Sets, plain objects) |
tryCatch(f, onError?) |
Calls f, swallows errors, returns undefined on failure |
thrower(message) |
Returns a function that throws new Error(message) when called |
once(f) |
Wraps f so it only executes once |
memoize(f) |
Single-slot memoization: caches last call; re-runs when args change |
call(f) |
Calls f() immediately — IIFE alternative; useful with async |
ifLet(x, f) |
Calls f(x) only if x is defined |
doLet(x, f) |
Calls f(x) and returns the result — scoped binding without a variable |
isDefined(x) / isUndefined(x) / assertDefined(x) |
undefined checks (not null) |
Curried Collection Helpers
Useful as .filter() / .map() callbacks:
| Export |
Description |
eq(v) / ne(v) |
x => x === v / x => x !== v |
prop(k) |
x => x[k] — pluck a property |
propIn(k, xs) |
x => xs.includes(x[k]) — property is in list |
nth(i) |
xs => xs[i] — element at index |
nthEq(i, v) |
xs => xs[i] === v |
nthNe(i, v) |
xs => xs[i] !== v |
nthIn(i, vs) |
xs => vs.includes(xs[i]) |
nthNotIn(i, vs) |
xs => !vs.includes(xs[i]) |
spec(values) |
x => all key-value pairs in values match x |
member(xs) |
x => xs.includes(x) |
assoc(k, v) |
obj => ({ ...obj, [k]: v }) — add/update property |
dissoc(k) |
obj => omit([k], obj) — remove property |
Bech32 / Hex / Binary Encoding
| Export |
Description |
hexToBech32(prefix, hex) |
Encodes hex string to bech32 (e.g. npub, note) |
bech32ToHex(b32) |
Decodes bech32 to hex |
bytesToHex(buffer) |
ArrayBuffer | Uint8Array to hex string |
hexToBytes(hex) |
Hex string to Uint8Array |
sha256(data) |
SHA-256 hash of binary data — async, returns hex string |
textEncoder |
Shared TextEncoder instance |
textDecoder |
Shared TextDecoder instance |
JSON / Storage / Network
| Export |
Description |
parseJson(str) |
Safe JSON.parse — returns undefined on error or empty input |
getJson(key) / setJson(key, val) |
localStorage get/set with JSON serialization |
fetchJson(url, opts?) |
Fetch JSON with optional method/headers/body |
postJson(url, data, opts?) |
POST JSON to a URL |
uploadFile(url, file) |
Upload a File object via multipart/form-data POST |
on(target, event, cb) |
Type-safe .on() wrapper — returns an unsubscribe () => void |
Randomness / IDs
| Export |
Description |
randomId() |
Generates a random string ID |
randomInt(min?, max?) |
Random integer in range (inclusive; default 0–9) |
String Utilities
| Export |
Description |
ellipsize(s, len, suffix?) |
Truncates at word boundary with an ellipsis suffix (default "...") |
displayList(xs, conj?, n?) |
Oxford-comma list — e.g. "a, b, and c" |
hash(s) |
Numeric hash from a string |
Map / Set Helpers
Common Patterns
Pattern 1 — Batching writes (IndexedDB / relay)
batch(t, f) — first call fires immediately; subsequent calls within t ms are accumulated and flushed together.
Pattern 2 — Grouping and indexing nostr events
Pattern 3 — Relative timestamp display
Pattern 4 — Subscribing to EventEmitter-based objects with on
on(target, event, handler) returns an unsubscribe function:
Pattern 5 — IIFE alternative with call
Integration Notes
- All welshman packages depend on
@welshman/lib — Deferred, Emitter, time constants, and collection utilities are shared across @welshman/net, @welshman/store, @welshman/util, etc.
@welshman/net uses Emitter (via Tracker, Repository, WrapManager), batch, and LRUCache internally. Socket extends Node's built-in EventEmitter directly.
@welshman/app thunks use Deferred<void> — thunk.complete resolves when all relays have responded or timed out.
batcher is used in @welshman/net for deduplicating concurrent fetch requests — pass it an execute function that returns results in the same order as its inputs.