ewan's projects — docs

projects

@ewanc26/utils

March 6, 2026

# library# utilities# typescript# monorepo

@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>');         // → '&lt;b&gt;hi&lt;/b&gt;'
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