@wtfalch/utils
v0.1.0
Published
Small dependency-free helpers, one subpath per category: datetime, json, encoding, id, text, html, csv, format, sql, http, and node-only env, cli, crypto, node-http.
Readme
@wtfalch/utils
Small helpers with no runtime dependencies, one subpath per category. There is no root export: import the category you need, so a caller pulls in only that.
import { toIso } from '@wtfalch/utils/datetime';
import { csvField } from '@wtfalch/utils/csv';Node 22 or newer. The categories marked "any runtime" use only web-standard
APIs and run in Node, browsers and edge runtimes. The "Node only" ones import
node: modules.
The package holds no credentials, makes no network request and opens no socket. It never reads the environment on import.
Categories
| Subpath | Runs | Functions |
|---|---|---|
| datetime | any runtime | toIso, toIsoOrNull, toDate, toDateOrNull, toIsoDate, sleep |
| json | any runtime | canonicalJson |
| encoding | any runtime | toHex |
| id | any runtime | isUuid |
| text | any runtime | slugify |
| html | any runtime | escapeHtml |
| csv | any runtime | csvField |
| format | any runtime | formatBytes |
| sql | any runtime | sqlIdent, sqlStringLiteral |
| http | any runtime | jsonResponse, readJsonBody, parseBearer, BodyError |
| env | Node only | requiredEnv |
| cli | Node only | parseFlags, readStdin |
| crypto | Node only | sha256Hex, safeEqual |
| node-http | Node only | readNodeBody |
Functions
datetime
toIso(value: string | Date): stringis an ISO 8601 UTC instant. A string is always normalised, so a Postgres timestamp such as2026-10-01 12:00:00+00becomes2026-10-01T12:00:00.000Z. ThrowsRangeErroron an invalid date.toIsoOrNull(value)istoIso, withnullandundefinedgivingnull.toDate(value: Date | string): Datereturns aDateas is and parses a string.toDateOrNull(value)istoDate, withnullgivingnull.toIsoDate(value: Date | string | null | undefined): string | nullis a UTC calendar date,YYYY-MM-DD. A date-only string passes through. A timestamp string is converted to its UTC day, never passed through as a timestamp.sleep(ms: number, signal?: AbortSignal): Promise<void>resolves afterms, or at once whensignalaborts. It never rejects; the caller checkssignal.aborted.
json
canonicalJson(value: unknown): stringis deterministic JSON: keys sorted at every depth by UTF-16 code unit (never by locale), no whitespace, arrays in order. Safe to hash. It throwsTypeErrorfor anything JSON cannot hold (undefinedanywhere, functions, symbols, bigints,NaN,Infinity, class instances, cycles, nesting past 1000 levels), so a hash is never taken over a value a JSON column could not have held. The tests carry byte-for-byte vectors: inputs run through the three existing hash-chain implementations (all three gave the same bytes), plus the RFC 8785 reference fixtures copied unchanged from the keys package's test data.
encoding
toHex(bytes: Uint8Array | ArrayBuffer): stringis lower-case hex.
id
isUuid(value: unknown): value is stringaccepts the 8-4-4-4-12 form, any version, either case.
text
slugify(input, { maxLength = 128, fallback = '' })lower-cases, folds accents (Cafégivescafe), transliteratesæ ø ßand similar, and joins words with single hyphens. Other scripts are dropped. The cap never leaves a hyphen at the end. Returnsfallbackwhen nothing is left.
html
escapeHtml(value: string): stringescapes& < > " '. Safe in element text and in a quoted attribute value; not safe in a script, a style or an unquoted attribute.
csv
csvField(value: string | null | undefined): stringquotes a field when it holds a comma, quote or line break (RFC 4180). A field a spreadsheet would run as a formula (=,+,-,@, even after leading spaces or control characters, or opening with a tab, carriage return or line feed) gets a leading'. The tests include vectors from the existing copies' tests; where those copies only checked the first character, the tests assert the safer output.
format
formatBytes(bytes: number): stringgives512 B,48.0 KB,2.4 MB,3.0 GB,5.0 TB. ThrowsRangeErrorfor a negative or non-finite count.
sql
sqlIdent(name: string): stringdouble-quotes an identifier.sqlStringLiteral(value: string): stringsingle-quotes a string.
Both double any quote inside and throw RangeError on a NUL byte (sqlIdent
also on an empty name). Prefer bound parameters wherever a statement allows
them.
http
These build and read Request and Response objects. They open no socket.
jsonResponse(body, status = 200, headers?)is a JSONResponsewithcache-control: no-storeunlessheaderssets its own.readJsonBody(request, { maxBytes })reads a JSON body and never holds more thanmaxBytes. The cap is counted on the stream, so a missing or falsecontent-lengthdoes not bypass it. It throwsBodyErrorwith areason:content_type,missing,too_large,invalid_encodingorinvalid_json. The caller maps the reason to its own response.parseBearer(header, { maxLength = 8192 })returns the token fromAuthorization: Bearer <token>, ornull. The scheme is case-insensitive, one space follows it, and the token has no whitespace. Any key prefix check stays with the caller.BodyErroris the error class above.
env (Node only)
requiredEnv(name, env = process.env): stringreturns the value. An unset variable and an empty one both throwError('<name> is required').
cli (Node only)
parseFlags(argv, usage): Map<string, string>reads--name valuepairs. The last repeat wins. Anything else throwsError(usage).readStdin(stream = process.stdin): Promise<string>reads all of a stream as UTF-8, untrimmed.
crypto (Node only)
sha256Hex(input: string | Uint8Array): stringis the hex SHA-256 digest.safeEqual(a: string, b: string): booleancompares in constant time. Both sides are hashed first, so length is not revealed.
node-http (Node only)
readNodeBody(req: IncomingMessage, maxBytes: number): Promise<Buffer>reads a Node request body, counting bytes. Over the cap it stops buffering, discards the rest (bounded in time and size), then rejects withBodyError('too_large'), so the caller's 413 reaches the client.
Status
Published: no
Version 0.1.0 is built and tested. Publishing happens on a v* tag.
Licence
MIT.
