docs(reference): document httpSurface, the redaction trio, and the token parsers - #746
Open
rejifald wants to merge 3 commits into
Open
docs(reference): document httpSurface, the redaction trio, and the token parsers#746rejifald wants to merge 3 commits into
rejifald wants to merge 3 commits into
Conversation
Owner
Author
|
Stacked on #753 ( #753 is merged into this branch so twoslash has a surface to check against — that is why the core files show up in this diff. Merge #753 first; those commits drop out of here once it lands, leaving this PR docs-only as intended. |
rejifald
added a commit
that referenced
this pull request
Aug 16, 2026
`parseDuration`, `parseBytes` and `parseRate` are replaced by `duration`, `size` and `rate` — one namespace per dimension, each carrying both directions, in the shape `bytes` uses (`bytes.parse` / `bytes.format`). The grammars only ever decoded. Making them public (#746) gave them callers who need to write a token back — a CLI printing the cap it enforced, a config round-trip, an error message quoting a limit in the grammar its author wrote — and each would have hand-rolled an encoder, which is the drift the export exists to prevent, running backwards. `format` is the EXACT inverse of `parse`: `parse(format(v))` returns `v` unchanged for every value `parse` can produce, pinned as a property over the whole numeric range rather than a table of cases. This is where the pair departs from `ms`, whose `ms(90_000)` is `'2m'` and reads back as 120_000 — a lossy encode is fine in a log line and disqualifying in anything that writes a value back, and P25's "a typo can never widen a cap" only holds if the encode direction cannot widen one either. Where no unit divides cleanly the base unit wins: `90_001` is `'90001ms'`, `1537` is `'1537b'` and not the exact-but- unreadable `'1.5009765625kb'`. The `ms`/`bytes` one-function overload is structurally unavailable here: P17/P25 widen every authored field to `number | string` and each read site funnels through the parser, so `parse(5_000)` must return `5_000`. Overloading the number arm would have broken every call site core makes of its own rule. Hard break, no alias (P19 scopes that obligation to the GA channel; this is rc). The old names are pinned absent from the barrel so they cannot drift back. Bundle: +0.41 / +0.20 KB gzip (entry / `import { stitch }`). Two thirds of the naive cost was recovered first — internals call plain functions with the namespaces as a thin facade, and each `format`'s unit table lives inside its function, since at module scope the minifier merges adjacent tables into one `var` statement that a live parse-side declarator then pins. What remains is ~0.1 KB: esbuild will not split an object literal to drop a dead property, so `format` ships wherever `parse` is live. The advertised whole-entry figure crosses 24 → 25 kB; propagated across the six `bundle-advertised-size` sites. Reference docs for the pair land in #746, which documented the parsers this replaces. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
rejifald
force-pushed
the
claude/interesting-elbakyan-1d8366
branch
2 times, most recently
from
August 16, 2026 14:57
ee3ba09 to
3189a9f
Compare
…ken parsers
Seven value exports on the `stitchapi` root barrel appeared on no page under
`apps/docs/content/docs` — not in Reference, not in a guide. Each is deliberately
public with a documented reason (the barrel comments in `packages/core/src/index.ts`,
and `public-api-surface.spec.ts`, which pins the parsers and `verdictOf` present
while pinning `classifyStatus`/`httpInterpret` ABSENT), so all seven are covered on
the Reference pages that already own their neighbours rather than on new pages.
`reference/helpers.mdx` gains two sections:
- **Secret redaction** — `registerSecretKey` / `isSecretKey` / `redactSecretsDeep`,
framed off the denylist they widen (exact names vs. contained stems), with
`apiKey({ in: 'query', name })`'s automatic registration named as the reason most
callers never touch the manual hook.
- **Duration, size, and rate tokens** — `parseDuration` / `parseBytes` / `parseRate`
(CONTRACT.md P17/P25), each tied to the fields it actually backs, plus why
`parseRate` throws where the other two fall back to `undefined`: a cap's fallback
keeps the ceiling where it was, but a rate's would silently remove the limit.
`reference/surfaces.mdx` gains a `httpSurface` subsection under "http — the default":
the default as a named value (`compose` resolves an omitted `kind` to it, so
`__config.kind` is never an empty slot) and the one place importing it earns its
keep — `kind: httpSurface` opting a member out of a surface inherited via `extends`.
The page was also the only Reference page with no `See also` (AUTHORING rule 7); it
has one now.
Two anti-pattern callouts, both verified against the built runtime rather than
inferred from the source:
- `isSecretKey('authorization')` and `isSecretKey('x-api-key')` are FALSE — headers
are scrubbed against a separate denylist (`redactHeaders`), so reaching for this
as a header check silently under-redacts.
- `parseBytes` on a `*.chars` field is a category error: `stream.buffer.chars` and
`trace.body.chars` count UTF-16 code units of decoded text, not bytes.
A third steers surface authors to `verdictOf` rather than `httpSurface.interpret`,
which is exactly the mis-composition the barrel comment warns about — that hook ends
in "the body is the value", the http surface's own choice of result.
Two adjacent fixes the sweep turned up:
- `guides/resilience/throttle.mdx` linked "duration token" at
`/docs/reference/config-types`, a page that says nothing about the grammar. It now
points at `helpers#parseduration`, which does (one source of truth per fact).
- The manifest description for `reference/helpers` was stale; re-synced.
Gates: `pnpm test` (69 passed, 1 skipped) and `pnpm check:types` in apps/docs,
`pnpm check:docs-links` (116 routes) and `pnpm check:format` at the root, and
`pnpm gen:docs` stays a no-op. Twoslash runs at `next build`, not in those gates, so
all 25 blocks on the three edited pages were run through `createTwoslasher` with the
`drafts-twoslash.spec.ts` config — 25/25 green, harness confirmed to fail a
deliberately broken block rather than pass vacuously.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`reference/seam.mdx` (#743) links to `reference/surfaces`, but nothing pointed back — and the surfaces page mints seam members in "Both spellings, one engine" (`api.stitch({ kind: downloadSurface, … })`), so it is a real neighbour, not just a co-resident of the Reference folder. AUTHORING rule 7 wants that link both ways. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The three parsers this page documented became namespace pairs — `parseDuration`, `parseBytes` and `parseRate` are now `duration`, `size` and `rate`, each carrying both directions in the shape `bytes` uses. The three subsections are rewritten against the new API. Each keeps the framing it had — the fields the grammar actually backs, the `chars` category-error callout, the fail-loud asymmetry on `rate` — and gains the encode direction and the round-trip clause that makes it usable: `parse(format(v))` returns `v` unchanged, which is what separates these from `ms`, whose `ms(90_000)` is `'2m'` and reads back as 120_000. Three things the rename changed rather than just renamed: - a new anti-pattern callout on `duration` — `format` is for values a person reads or re-authors, never for an emitted field, which stays a raw-ms number (CONTRACT.md P17's complement). The encode direction is new, so this way of misusing it is new too; - the `chars` callout now says why it matters MORE than it did: `size` no longer spells `Bytes` at the call site, so the type is the only thing left warning a reader that a `'64kb'` on decoded text is a category error; - `rate.format` rejects the same three values `rate.parse` throws on, rather than writing a token that would throw on the way back in. `throttle.mdx`'s "duration token" link follows the heading: `helpers#parseduration` → `helpers#duration`. The manifest description says "token grammars" where it said "parsers", since the page no longer documents only the decode half. Verified by the real `next build`, so twoslash type-checked every block against the new surface — and confirmed non-vacuous by putting a stale `parseDuration` import back into one block and watching the build fail on it. Stacked on the core change (`feat(core)!: the token grammars become `parse`/`format` pairs`), merged in here so twoslash has a surface to check against. That commit drops out of this diff once it lands on main. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
rejifald
force-pushed
the
claude/interesting-elbakyan-1d8366
branch
from
August 16, 2026 15:12
3189a9f to
492b778
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Seven value exports on the
stitchapiroot barrel appeared on no page underapps/docs/content/docs— not in Reference, not in any guide:httpSurface./surfacegraphqlSurface/verdictOfhad one mention each)registerSecretKey,isSecretKey,redactSecretsDeep./utilduration,size,rate./utilAre any of them internal-but-exported?
No — that was the first thing checked, and all seven are deliberately public with a written reason. The barrel comments in
packages/core/src/index.tsargue each one, andpublic-api-surface.spec.tspins the parsers andverdictOfpresent while pinningclassifyStatus/httpInterpretabsent ("three names on the barrel for one decision invites composing the wrong one"). So all seven are documented, on the Reference pages that already own their neighbours — no new pages, no manifest entries beyond a description re-sync.What changed
reference/helpers.mdx— two new sections:apiKey({ in: 'query', name })'s automatic registration named as the reason most callers never touch the manual hook.timeout.total,cache.ttl,circuit.cooldown·serve.body.max,@stitchapi/shell'sbuffer.max·throttle.rate), plus whyrate.parsethrows where the other two fall back toundefined: a cap's fallback leaves the ceiling where it was, but a rate's would silently remove the limit. Both directions are documented — see the note on the stacked core change below.reference/surfaces.mdx— ahttpSurfacesubsection under "http — the default": the default as a named value (composeresolves an omittedkindto it, so__config.kindis never an empty slot), and the one place importing it earns its keep —kind: httpSurfaceopting a member out of a surface inherited viaextends. This page was also the only Reference page with noSee also(AUTHORING rule 7); it has one now.Reviewer notes
Three anti-pattern callouts (AUTHORING rule 10), the first two from behaviour verified against the built runtime, not inferred from source:
isSecretKey('authorization')andisSecretKey('x-api-key')arefalse. Headers are scrubbed against a separate denylist (redactHeaders), so reaching for this predicate as a header check silently under-redacts. This is the one finding here I'd most want a second pair of eyes on.sizeon a*.charsfield is a category error —stream.buffer.chars/trace.body.charscount UTF-16 code units of decoded text, not bytes off the socket. This one got sharper under the rename: the oldparseBytessaid "bytes" at the call site andsizedoes not, so the type is now the only thing warning the reader.httpSurface.interpretinto your own surface — it ends in "the body is the value", the http surface's own choice of result. ComposeverdictOfinstead. This is exactly the mis-composition the barrel comment warns about.Two adjacent fixes the sweep turned up:
guides/resilience/throttle.mdxlinked "duration token" at/docs/reference/config-types, a page that says nothing about the grammar. It now points athelpers#parseduration, which does (rule 6, one source of truth per fact).reference/helperswas stale; re-synced.Known gap left out of scope:
registerSecretKey/isSecretKey/redactSecretsDeepare not in theFUNCTIONSpin list inpackages/core/test/public-api-surface.spec.ts, so an accidental removal of the trio would still slip past the suite. Deliberately not fixed here rather than widening a docs-only diff into core's tests; being handled separately.Stacked on the core rename
The three parsers this page was written to document became
parse/formatpairs while thisPR was open —
parseDuration/parseBytes/parseRateare nowduration/size/rate, onenamespace per dimension, in the shape
bytesuses. That core change is merged into this branch sotwoslash has a surface to check against; it drops out of this diff once it lands on
main.Review it as a stack — the core commit first, this docs commit second.
The three subsections are written against the new API and gained the encode direction:
formatis the exact inverse ofparse—parse(format(v))returnsvunchanged — which isthe clause the page leans on when it says a token is safe to write back. It is also where these
differ from
ms, whosems(90_000)is'2m'and reads back as 120 000.One new anti-pattern callout came with the new direction rather than with the rename:
formatisfor values a person reads or re-authors, never for an emitted field. Every emitted duration stays
a raw-ms
number(P17's complement), and formatting one into a payload turns a number every readercan use into a string each of them has to parse.
The known gap named below is now closed —
public-api-surface.spec.tspinsduration/size/ratepresent as parse/format pairs, pins the three old names absent, and the redaction trio isno longer the only unpinned group. That landed with the core change, not here.
Gates
pnpm test(apps/docs)pnpm check:types(apps/docs)pnpm check:docs-linkspnpm check:formatpnpm gen:docsRe-verified after the rename: the real
next buildran end to end on the merged branch, so twoslash type-checked every block against the new surface, and it was confirmed non-vacuous by putting a staleparseDurationimport back into one block and watching the build fail on it. Twoslash runs atnext build, not in the gates above, so all 25 blocks on the three edited pages were first run throughcreateTwoslasherwith thedrafts-twoslash.spec.tsconfig — 25/25 green, and the harness was confirmed to fail a deliberately broken block rather than pass vacuously. The pre-pushbuild-docshook then ran the realnext buildend to end, so twoslash has since verified them for real.🤖 Generated with Claude Code