New:Socket for Asana Is Now Available.Learn more
Sign In

@ultimat3/http

Package Overview
Dependencies
Maintainers
1
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ultimat3/http - npm Package Compare versions

Comparing version
9.0.0
to
10.0.0
+11
-0
CLAUDE.md

@@ -190,2 +190,13 @@ # @ultimat3/http

view and keeps `Object.hasOwn`.
- **A problem document's `type` and its `docs` are two different questions, `As of 2026-08-23`.**
Both used to be `https://ultimate.dev/errors/<code>` — one string, twice, and a host that
answers **404** on every 4xx and 5xx this package has ever rendered. `docs` is now core's
`ERROR_DOCS_URL`, one wiki page for every code, and it is never spelled here: a construction site
omits `docs:` and `UltimateError` resolves it. `type` did NOT follow it there. It is RFC 9457's
primary identifier for the problem KIND — a client switches on it — so collapsing it onto one
page would have given a 422 and a 403 the same identifier. `problemTypeFor(code)` answers
`urn:ultimate:error:<CODE>`: per code, stable, and a URN has no host to rot. `finalize.ts`'s
`lastResort` spells its one `type` as a literal because that function calls nothing, and
`pipeline-finalize.test.ts` pins the literal against `problemTypeFor('X_INTERNAL')` so the two
cannot drift. Never assert either value as a copied string — import the constant.
- Statuses live in `error-map.ts` only. No other file writes a status number. The framework's

@@ -192,0 +203,0 @@ table (`ERROR_STATUS`) is closed; an app declares its own codes' statuses with

+5
-5
{
"name": "@ultimat3/http",
"version": "9.0.0",
"version": "10.0.0",
"description": "Owned request lifecycle over Bun.serve: router, ordered pipeline, problem+json errors",

@@ -34,7 +34,7 @@ "license": "MIT",

"dependencies": {
"@ultimat3/core": "9.0.0",
"@ultimat3/i18n": "9.0.0",
"@ultimat3/schema": "9.0.0",
"@ultimat3/time": "9.0.0"
"@ultimat3/core": "10.0.0",
"@ultimat3/i18n": "10.0.0",
"@ultimat3/schema": "10.0.0",
"@ultimat3/time": "10.0.0"
}
}

@@ -208,2 +208,9 @@ # @ultimat3/http 🌐

A problem document's `type` and `docs` answer different questions and are two different
values. `type` is `problemTypeFor(code)` — `urn:ultimate:error:X_BODY_INVALID`, the RFC-9457
identifier a client switches on, per code, with no host to resolve or rot. `docs` is
`@ultimat3/core`'s `ERROR_DOCS_URL`, one wiki page for every code, because a code lives there
in a table row and a table row has no anchor. Assert against `problemTypeFor` and
`ERROR_DOCS_URL`, never against a copy of either string.
## Boundaries

@@ -210,0 +217,0 @@

// The one place a framework error code becomes an HTTP status. A table, not a
// switch chain: adding a code elsewhere in the framework means adding a row here,
// and a missing row is a loud 500 rather than a silently wrong 200.
import { renderCauseValue, singleLine, stringField } from '@ultimat3/core';
import { ERROR_DOCS_URL, renderCauseValue, singleLine, stringField } from '@ultimat3/core';
import { errorStatusInvalid, HTTP_ERROR_TITLES } from './errors';

@@ -158,2 +158,9 @@

X_PERMISSION_UNKNOWN: 500,
// 500, and the page IS the point — deliberately not a 4xx to keep this table quiet.
// `enforce()` was handed a surface no adapter answers to, which reaches a request only through a
// config-driven route table, a surface name off the wire or a JS host; none of those is a value
// the caller can correct, and a 400 would tell them to fix a request that is not the problem.
// It is the third authz-dispatch fault beside the two rows above and takes their status for the
// same reason: the declaration is wrong, not the call.
X_POLICY_SURFACE_UNKNOWN: 500,
// @ultimat3/query — the read declares no id, so no cursor can name a position in it. The one

@@ -355,3 +362,8 @@ // paging failure that is NOT the caller's: the fix is an edit to the read's own select, nothing

fix: str(error, 'fix') ?? `x errors explain ${code} --json # then fix the throwing call site`,
docs: str(error, 'docs') ?? `https://ultimate.dev/errors/${code}`,
// Core's one constant, never a per-code URL: `wiki/` is the only public documentation surface
// and a code lives there in a table row, which has no anchor. An `UltimateError` already
// resolved this at construction, so the fallback only fires for a throwable the framework did
// not build — and it must not be the `https://ultimate.dev/errors/<code>` link that answered
// 404 on every problem document this package has ever rendered.
docs: str(error, 'docs') ?? ERROR_DOCS_URL,
status: statusFor(code),

@@ -362,2 +374,43 @@ stack: str(error, 'stack'),

/**
* `Retry-After`, in whole seconds, for a refusal that computed one — or `undefined`.
*
* The contract it reads is already written down by the packages BELOW this one: `@ultimat3/auth`'s
* `kdfOverloaded` says "`retryAfterSeconds` rides in `meta` because this package cannot reach an
* HTTP header; the host reads it onto `Retry-After`", and `rateLimited` in this package carries the
* same field. Nothing was the host. So a 503 shed by the KDF gate and a 429 from an account lockout
* both told the caller to come back and never said when — which is the shed-with-no-delay pattern
* the `admit` stage exists to avoid, one layer in.
*
* Total, for `str`'s reason one function up: `meta` is a property read on a value this package did
* not build, and it is read in the frame that decides what the caller sees.
*/
export function retryAfterOf(error: unknown): number | undefined {
if (typeof error !== 'object' || error === null) return undefined;
try {
const meta: unknown = (error as Record<string, unknown>)['meta'];
if (typeof meta !== 'object' || meta === null) return undefined;
const seconds: unknown = (meta as Record<string, unknown>)['retryAfterSeconds'];
if (typeof seconds !== 'number' || !Number.isFinite(seconds) || seconds < 0) return undefined;
// At least one second, exactly as `RateLimitDecision.retryAfterSeconds` is clamped: `0` reads
// as "retry now", which is the stampede a Retry-After exists to spread.
return Math.max(1, Math.ceil(seconds));
} catch {
return undefined;
}
}
/**
* RFC-9457 `type`, per code. A URN, and deliberately not a URL: `type` is the document's PRIMARY
* identifier for the problem KIND — a client switches on it — while `docs` is where a human goes
* to read about it, and those stopped being the same string when `docs` became one wiki page for
* every code. Collapsing `type` onto that page too would have given a 422 body-invalid and a 403
* forbidden the same identifier, which is the one thing a `type` may not do.
*
* A URN has no host to resolve, so it cannot rot the way `https://ultimate.dev/errors/<code>` did
* — it was never dereferenceable and never claimed to be, which RFC 9457 §3.1.1 explicitly allows.
* `code` carries the same string as a plain member for a reader that would rather not parse a URI.
*/
export const problemTypeFor = (code: string): string => `urn:ultimate:error:${singleLine(code)}`;
/** RFC-9457 problem document. `code`/`cause`/`fix`/`docs` are our extensions. */

@@ -383,3 +436,3 @@ export interface ProblemDocument {

return {
type: facts.docs,
type: problemTypeFor(facts.code),
title: facts.title,

@@ -386,0 +439,0 @@ status: facts.status,

@@ -108,3 +108,8 @@ // The HTTP layer's stable error codes. Every throw in this package goes through a

const docsFor = (code: HttpErrorCode): string => `https://ultimate.dev/errors/${code}`;
// No `docs:` below. `UltimateError` fills it from `describeErrorCode(code).docs`, which is
// `@ultimat3/core`'s `ERROR_DOCS_URL` — one page for every code, never one per code, because
// `wiki/` is the framework's only public documentation surface and a code lives there in a TABLE
// ROW, which has no anchor. The `https://ultimate.dev/errors/<code>` links this file built until
// 9.x answered 404, host included — and this package put them in `type` AND `docs` of every
// problem document, so the dead link was on every 4xx and 5xx an app has ever served.

@@ -130,3 +135,2 @@ /** Base class for every error this package throws. Never throw a bare `Error`. */

fix: init.fix,
docs: docsFor(init.code),
...(init.meta === undefined ? {} : { meta: init.meta }),

@@ -133,0 +137,0 @@ });

@@ -24,3 +24,6 @@ // The tail of the lifecycle, guarded. `Pipeline.handle` promises a Response to every caller, and

JSON.stringify({
type: 'https://ultimate.dev/errors/X_INTERNAL',
// Spelled out rather than built: this function calls nothing, which is what "last resort"
// means. `problemTypeFor('X_INTERNAL')` is the one it must equal, and
// `pipeline-finalize.test.ts` asserts that equality so the two cannot drift apart in silence.
type: 'urn:ultimate:error:X_INTERNAL',
title: 'unhandled server error',

@@ -27,0 +30,0 @@ status: 500,

@@ -32,5 +32,7 @@ // The public surface of @ultimat3/http. Explicit, never `export *`: what is not

factsOf,
problemTypeFor,
registerErrorStatus,
renderErrorLines,
resetErrorStatus,
retryAfterOf,
statusFor,

@@ -37,0 +39,0 @@ toProblem,

@@ -329,5 +329,12 @@ // Token-bucket rate limiting. The store is an interface so the same limiter runs in-memory in

const now = (): number => clock.now().getTime();
// `Object.hasOwn`, never `buckets[name]` — the same read `error-map.ts`'s `statusFor` and
// `naming.ts` already take for a table of this shape. `buckets` is a plain object literal, so it
// holds every name on `Object.prototype`: `rateLimit: 'constructor'` read `Object` itself out of
// it, and a `Bucket` whose `capacity` is `undefined` is a limiter that decides nothing. Author-
// controlled, and still the one form — a table indexed by a name is indexed through `hasOwn`.
const declared = (name: string): Bucket | undefined =>
Object.hasOwn(options.config.buckets, name) ? options.config.buckets[name] : undefined;
const bucketFor = (name: string): Bucket =>
options.config.buckets[name] ??
options.config.buckets[options.config.defaultBucket] ??
declared(name) ??
declared(options.config.defaultBucket) ??
DEFAULT_RATE_LIMIT.buckets['default'] ?? { capacity: 60, refillPerSecond: 1 };

@@ -334,0 +341,0 @@

@@ -22,3 +22,3 @@ // One function per stage name: what each stage of the lifecycle DOES. The package's three-way

import { checkCsrf, selfOrigin } from './csrf';
import { factsOf } from './error-map';
import { factsOf, retryAfterOf } from './error-map';
import {

@@ -353,6 +353,18 @@ bodyInvalid,

}
const retryAfter =
facts.code === 'X_RATE_LIMITED' && ctx.rateLimit !== undefined
? { 'retry-after': String(ctx.rateLimit.retryAfterSeconds) }
: {};
// The limiter's own decision first — it is the live one and it knows this request's bucket —
// then whatever the THROWABLE computed. Only the first half existed, so every other refusal
// that had a delay to give told the caller to come back without saying when:
// `X_ACCOUNT_LOCKED` is a 429 with no `Retry-After` at all, and `X_OVERLOADED` from
// `@ultimat3/auth`'s KDF gate carries the number in `meta` under a comment saying the host
// reads it. Nothing was the host.
// `> 0` and not merely `!== undefined`: `RateLimitDecision.retryAfterSeconds` is `0` on an
// ALLOWED request, so a handler raising `X_RATE_LIMITED` from a limiter of its own — an
// action's declared `rateLimit`, `@ultimat3/auth`'s credential limiter — was answered
// `retry-after: 0`, which is "retry now" and is the stampede the header exists to spread.
const decided =
facts.code === 'X_RATE_LIMITED' && (ctx.rateLimit?.retryAfterSeconds ?? 0) > 0
? ctx.rateLimit?.retryAfterSeconds
: undefined;
const seconds = decided ?? retryAfterOf(error);
const retryAfter = seconds === undefined ? {} : { 'retry-after': String(seconds) };
return problem(error, {

@@ -359,0 +371,0 @@ instance: ctx.url.pathname,