projects
@ewanc26/utils
@ewanc26/utils is a zero-dependency TypeScript utility library extracted from ewancroft.uk. It provides helpers for date and number formatting, URL manipulation, input validation, locale detection, and RSS feed generation.
Part of the website monorepo.
Installation
pnpm add @ewanc26/utils
Modules
Date & Locale
import { getUserLocale, formatLocalizedDate, formatRelativeTime } from '@ewanc26/utils';
getUserLocale();
// → 'en-GB' (from navigator.language, falls back to 'en-GB' in SSR)
formatLocalizedDate('2025-11-13T23:49:36Z');
// → '13 Nov 2025'
formatRelativeTime('2025-11-13T23:49:36Z');
// → '3d ago', '2h ago', 'just now', or localised date for older entries
formatRelativeTime returns relative strings (Xm ago, Xh ago, Xd ago) for recent dates, falling back to a localised short date beyond 7 days.
Number Formatting
import { formatCompactNumber, formatNumber } from '@ewanc26/utils';
formatCompactNumber(1500); // → '1.5K'
formatCompactNumber(1000000); // → '1M'
formatNumber(1234567); // → '1,234,567'
Both functions accept an optional locale string; otherwise they use navigator.language with an en-GB fallback.
URL Utilities
import { getDomain, atUriToBlueskyUrl, getBlueskyProfileUrl, isExternalUrl } from '@ewanc26/utils';
getDomain('https://www.example.com/path');
// → 'example.com'
atUriToBlueskyUrl('at://did:plc:abc/app.bsky.feed.post/xyz');
// → 'https://witchsky.app/profile/did:plc:abc/post/xyz'
getBlueskyProfileUrl('alice.bsky.social');
// → 'https://witchsky.app/profile/alice.bsky.social'
isExternalUrl('https://example.com');
// → true (when window.location.origin differs)
Validators & Text Helpers
import {
isValidTid,
isValidDid,
truncateText,
escapeHtml,
getInitials,
debounce,
throttle
} from '@ewanc26/utils';
isValidTid('3mfyqfgh24625'); // → true
isValidDid('did:plc:abc123'); // → true
truncateText('Hello world', 8); // → 'Hello...'
escapeHtml('<b>hi</b>'); // → '<b>hi</b>'
getInitials('Ewan Croft'); // → 'EC'
const debouncedFn = debounce(myFn, 300);
const throttledFn = throttle(myFn, 100);
RSS Generation
import {
generateRSSFeed,
generateRSSItem,
createRSSResponse,
type RSSChannelConfig,
type RSSItem
} from '@ewanc26/utils';
const channel: RSSChannelConfig = {
title: 'My Blog',
description: 'Posts from my blog',
link: 'https://mysite.com',
language: 'en-GB'
};
const items: RSSItem[] = [{
title: 'My Post',
link: 'https://mysite.com/blog/my-post',
description: 'Post content here',
pubDate: new Date().toUTCString(),
guid: 'https://mysite.com/blog/my-post'
}];
const xml = generateRSSFeed(channel, items);
// In a SvelteKit +server.ts:
return createRSSResponse(xml);
createRSSResponse returns a Response with Content-Type: application/rss+xml; charset=utf-8 and a configurable Cache-Control header (default: 1 hour).
Lower-level helpers — escapeXml, escapeXmlAttribute, normalizeCharacters, formatRSSDate, generateRSSItem — are also exported if you need to build feeds manually.
SSR Compatibility
All functions are SSR-safe. Functions that need navigator.language or window check for their availability and fall back to en-GB / sensible defaults on the server.
Tech Stack
TypeScript 5.9+. No runtime dependencies. Compiled to ESM with tsc.
Licence
See the website repository.
← all docs