@scratchyjs/utils
v26.3.22-1774223623833
Published
General-purpose server utilities for the Scratchy framework. Includes safe redirect validation, concurrent promise helpers, typed HTTP response factories, interval generator, IP address / locale / Sec-Fetch header utilities, and more.
Readme
@scratchyjs/utils
General-purpose server utilities for the Scratchy framework. Includes safe redirect validation, concurrent promise helpers, typed HTTP response factories, interval generator, IP address / locale / Sec-Fetch header utilities, and more.
Installation
pnpm add @scratchyjs/utilsAPI
safeRedirect(to, defaultRedirect?): string
Validates a redirect path so it stays within the same origin. URL-decodes the
input before checking to block percent-encoded bypass attempts (%2e%2e,
%2F%2F). Falls back to defaultRedirect ("/" by default) when the path is
unsafe.
import { safeRedirect } from "@scratchyjs/utils";
reply.redirect(safeRedirect(request.query.redirectTo));
// "/dashboard" → "/dashboard" ✅
// "//evil.com" → "/" 🛡️
// "../etc" → "/" 🛡️promiseHash(hash): Promise<AwaitedPromiseHash>
Promise.all for objects — resolves all values concurrently while preserving
the key names.
import { promiseHash } from "@scratchyjs/utils";
const { user, posts } = await promiseHash({
user: fetchUser(id),
posts: fetchPosts(id),
});timeout(promise, ms, message?): Promise<T>
Races promise against a timer. Throws TimeoutError if the timer fires first.
import { TimeoutError, timeout } from "@scratchyjs/utils";
try {
const data = await timeout(fetchData(), 5000);
} catch (err) {
if (err instanceof TimeoutError) {
/* handle timeout */
}
}interval(ms, options?): AsyncGenerator
Async generator that yields on a fixed interval until an AbortSignal fires.
Useful for SSE routes.
import { interval } from "@scratchyjs/utils";
const controller = new AbortController();
for await (const _ of interval(1000, { signal: controller.signal })) {
reply.raw.write(`data: ${Date.now()}\n\n`);
}redirectBack(request, options?): string
Returns a safe path based on the Referer header, normalizing absolute http(s)
URLs to a relative path, or options.fallback ("/" by default) when absent.
getClientIPAddress(request): string | null
Extracts the real client IP from a Fastify request, respecting X-Forwarded-For
and similar headers.
getClientLocales(request): Locales
Parses the Accept-Language header into an ordered array of locale strings.
isPrefetch(request): boolean
Returns true when the request carries a Purpose: prefetch or
Sec-Purpose: prefetch header.
Response helpers
Typed Response factories with the correct Content-Type header:
| Function | Content-Type |
| -------------------------- | --------------------------------------- |
| notModified() | (304, no body) |
| javascript(body, init?) | application/javascript; charset=utf-8 |
| stylesheet(body, init?) | text/css; charset=utf-8 |
| html(body, init?) | text/html; charset=utf-8 |
| xml(body, init?) | application/xml; charset=utf-8 |
| txt(body, init?) | text/plain; charset=utf-8 |
| pdf(body, init?) | application/pdf |
| image(body, type, init?) | image/<type> |
Sec-Fetch helpers
import {
fetchDest,
fetchMode,
fetchSite,
isUserInitiated,
} from "@scratchyjs/utils";
const dest = fetchDest(request); // "document" | "script" | …
const mode = fetchMode(request); // "navigate" | "cors" | …
const site = fetchSite(request); // "same-origin" | "cross-site" | …
const user = isUserInitiated(request); // boolean