@mikespa/kit
v0.4.0
Published
Personal toolkit of small, framework-agnostic utilities (formatting, strings, math).
Maintainers
Readme
@mikespa/kit
A small, framework-agnostic toolkit. Built to stop copy-pasting the
same formatCHF / slugify / clamp functions between Next.js projects.
No runtime dependencies. Tree-shakeable via subpath exports.
Install
npm install @mikespa/kitUsage
Import only what you need via subpaths — this keeps bundles small and is self-documenting at the call site.
import { formatDate, formatCHF, formatSecs, formatHours } from '@mikespa/kit/format';
import { slugify, truncate } from '@mikespa/kit/string';
import { clamp, timeDiffHours } from '@mikespa/kit/math';
formatDate('2026-06-19'); // "19.06.2026"
formatCHF(1234.5); // "1'234.50 CHF"
formatSecs(9000); // "2h30"
formatHours(2.5); // "2h30"
slugify('Hello, World!'); // "hello-world"
clamp(15, 0, 10); // 10
timeDiffHours('08:00', '10:30'); // 2.5Or import everything from the root if you don't care about tree-shaking:
import { formatCHF, slugify, clamp } from '@mikespa/kit';Modules
./format
| Function | Description | Default output |
| --- | --- | --- |
| formatDate(input) | Date as DD.MM.YYYY | "19.06.2026" |
| formatTime(input) | Time (24h) | "15:30" |
| formatDateTime(input) | Date + time (locale-formatted) | "19 juin 2026, 15:30" |
| formatDateRange(start, end) | Compact date range (locale-formatted) | "01.06.2026 – 15.06.2026" |
| formatRelative(input) | Relative to now (locale-formatted) | "il y a 3 heures" |
| isToday(input) | Whether date is today | true |
| isPast(input) | Whether date is in the past | true |
| isFuture(input) | Whether date is in the future | false |
| isDateInRange(input, start, end) | Whether date falls within [start, end] | true |
| startOfDay(input) | Midnight of the given date | Date |
| endOfDay(input) | 23:59:59.999 of the given date | Date |
| parseSwissDate(input) | Parse "DD.MM.YYYY" → ISO string | "2026-06-19" |
| formatNumber(value) | Locale-aware thousands | "1 234 567" |
| formatCHF(amount) | Swiss CHF, apostrophe sep. | "1'234.50 CHF" |
| formatCurrency(value) | Intl currency (CHF default) | "1 234.50 CHF" |
| formatCompact(value) | Compact notation | "1,2 k" |
| parseCHF(input) | Parse Swiss amount → number | 1234.5 |
| formatPercent(value) | Percentage | "45,6%" |
| formatBytes(bytes) | Human-readable file size | "1.5 KB" |
| formatPhone(input) | Swiss phone number | "+41 79 123 45 67" |
| isValidPhone(input) | Validate Swiss phone number shape | true |
| maskPhone(input) | Mask phone number for safe display | "+41 79 *** ** 67" |
| formatIBAN(input) | IBAN with spaces | "CH56 0483 5012 3456 7800 9" |
| isValidIBAN(input) | Validate IBAN check digits (any country) | true |
| maskIBAN(input) | Mask IBAN for safe display | "CH56 **** **** **** **** 9" |
| formatAHV(input) | AHV/OASI number, grouped | "756.9217.0769.85" |
| isValidAHV(input) | Validate AHV/OASI check digit | true |
| maskAHV(input) | Mask AHV number for safe display | "756...85" |
| formatQRReference(input) | QR-bill/ISR reference, grouped | "21 00000 00003 13947 14300 09017" |
| isValidQRReference(input) | Validate QR-bill/ISR check digit | true |
| formatUID(input) | UID/VAT number, grouped | "CHE-116.281.710" |
| isValidUID(input) | Validate UID/VAT check digit | true |
| isValidSwissPostalCode(input) | Validate 4-digit postal code range | true |
| formatSecs(seconds, step?) | Duration from seconds | "2h30" / "45min" |
| formatMinutes(minutes, step?) | Duration from minutes | "2h30" / "45min" |
| formatHours(hours, step?) | Duration from decimal hours | "2h30" / "45min" |
| formatHoursDecimal(hours) | Decimal duration from hours | "2.50h" |
| formatMinutesDecimal(minutes) | Decimal duration from minutes | "45.00min" |
| formatSecsDecimal(seconds) | Decimal duration from seconds | "90.00s" |
| parseDurationInput(input) | Parse "2h30" / "2.5" → minutes | 150 |
All format* functions that wrap Intl accept an optional locale parameter
(defaults to 'fr-CH') so consuming projects can override per-call.
For apps with a variable/per-request locale (e.g. a language switcher, or SSR with
per-request locale), createFormatters(locale) returns those same functions
pre-bound to one locale, so call sites don't repeat { locale } everywhere. It's
a plain factory — no shared/global state, safe under concurrent requests:
import { createFormatters } from '@mikespa/kit/format';
const fmt = createFormatters('de-CH');
fmt.formatDate(order.date);
fmt.formatCurrency(order.total);createFormatters binds: formatDate, formatTime, formatDateTime,
formatRelative, formatNumber, formatCompact, formatCurrency, formatPercent.
./string
| Function | Description |
| --- | --- |
| slugify(input) | URL-friendly slug |
| truncate(input, maxLength) | Truncate with suffix |
| capitalize(input) | First letter uppercase |
| pluralize(count, singular) | Simple pluralizer |
./math
| Function | Description |
| --- | --- |
| clamp(value, min, max) | Clamp to range |
| lerp(start, end, t) | Linear interpolate |
| round(value, decimals) | Round to N decimals |
| timeDiffHours(from, to, overnight?) | Hours between "HH:MM" strings |
./array
| Function | Description |
| --- | --- |
| groupBy(arr, keyFn) | Group items into a Record by key |
| uniqueBy(arr, keyFn) | Deduplicate by derived key |
| sortBy(arr, keyFn, dir?) | Sort by derived key, asc or desc |
./object
| Function | Description |
| --- | --- |
| pick(obj, keys) | New object with only specified keys |
| omit(obj, keys) | New object with specified keys removed |
Development
npm install
npm run dev # build in watch mode
npm run build # build once (output -> dist/)
npm test # run tests
npm run test:watch
npm run typecheckLicense
MIT
