docs: Astro Starlight documentation site - #233
Conversation
Adds packages/docs, a Starlight site covering the library, and turns the two examples into in-page playgrounds. The playgrounds have no backend. scripts/build-playgrounds.mjs bundles each example's own Worker into the page alongside its own client and routes the client's fetch of the RPC path into the Worker's fetch handler, so the batching and round-trip counts the demos report are real while the site stays static. Source panes are read from the repo at build time, so a renamed file or a missing #region fails the build rather than rendering an empty tab.
Syncs the site with the two changes merged from main. URL joins the by-value type lists on What Can Be Passed, the API reference and the validation guide's coverage paragraph, and the protocol reference gains the ["url", href] expression. The validation guide gains a Schema evolution section for #227: which cross-version changes a validator accepts, and the distinction that matters once extra arguments are allowed -- they are dropped before the method runs, so an implementation still cannot read an argument no validator checked, while extra object properties are forwarded unvalidated. Also collapses the empty title bar Expressive Code leaves above terminal code blocks, and adds a wrangler config that serves dist/ from a Worker's asset store.
|
commit: |
|
This demo code is correct and well-designed — the pipelining example genuinely demonstrates the feature. The There's one subtle interaction worth noting: in the playground shim (build-playgrounds.mjs:159), I've reviewed the substantive code paths: the build-time bundler, the example Worker/API/server, the demo client, the Astro component, and the doc accuracy against the actual library. This PR is a documentation site plus example scaffolding. No changes touch
The documentation I spot-checked (security guide, api cheat sheet) matches the actual library behavior and signatures. The code is defensive and well-commented. LGTM |
Replaces the nautical theme and the logo with a dark-first palette: a near-black with a blue undertone, a deep saturated blue as the structural colour, and an electric azure for anything interactive. Cloudflare orange appears in three places and nowhere else -- the hero pulses, the primary call to action, and the current sidebar page -- because a fourth use is the point at which it stops meaning anything. Light mode is a second scheme rather than an inversion, and the chrome stays near-black in both. The landing page now runs a 3D network in WebGL. It is raw WebGL2 with no dependency: the page claims the library is under 10 kB with nothing behind it, and shipping a 3D framework to draw points and lines would undercut that in the first paint. The animation is the argument rather than decoration -- each pulse leaves a node, crosses several hops and returns along the same path, which is what pipelining actually buys you. Everything degrades to a designed state: the CSS gradient with no JavaScript or no WebGL2, a single still frame under prefers-reduced-motion, and a parked loop off-screen or on a hidden tab. GPU objects live in one struct that is rebuilt on webglcontextrestored, so a driver reset or a wake from sleep does not leave a dead rectangle. Additive blending cannot darken a pale page, so light mode composites the same geometry normally, as ink. Also fixes a horizontal overflow the new landing page exposed: cards holding a code block would not shrink below the widest unwrapped line, so the whole document scrolled sideways below about 1140px. And drops the explicit `sharp` dependency, which existed only to optimise the raster logo.
Derived from an analysis of the Hacker News discussion of the Cap'n Web launch announcement -- the twenty most frequently asked questions and the ten most common points of confusion in that thread. Each one was checked against the docs; this closes the gaps. Two new guides: - guides/comparisons.md -- positioning against tRPC/oRPC, JSON-RPC and GraphQL, an honest account of why .map() relocates the N+1 problem rather than solving it, why the library is scoped to JS/TS and what to use instead, a direct answer to the "isn't this CORBA again" objection, and the protocol-versus-library distinction. - guides/sessions.md -- session lifetime and why nothing is persisted, designing so a dropped session is survivable, reconnection patterns including the React one, resumable subscriptions, API versioning, and the load-balancing and scale-in consequences of long-lived sessions. Expanded in place: - concepts/map.md -- which side of the wire actually runs your callback, why there is no index argument, why filter()/reduce() are deliberately absent, the failure modes TypeScript does not catch, and the cost of nesting. - guides/security.md -- the Object.prototype and toJSON hardening the protocol performs, unbounded memory from un-awaited calls, and the server-side shape of the authentication example. - concepts/promises.md -- one round trip is a claim about awaits, not about message count. - guides/validation.md -- why generating validators from types is not defining the interface twice. - Smaller additions on the name, the protocol-vs-library framing, why JSON rather than a binary format, streaming versus callbacks, async generators, documenting an API without OpenAPI, runtime requirements beyond "any JavaScript", and Workers RPC convergence. Also corrects start/pipelining-tour.md, which said .map() runs the callback on the server. The recording is made on the calling side; only the RPCs it captured replay on the peer. Every behavioural claim added here was verified against src/ or the test suite rather than taken from the thread. Several thread answers are now out of date -- streams have since shipped with automatic flow control -- and are documented as the code actually behaves.
The root README had grown into a 821-line manual that duplicated the documentation site, and protocol.md duplicated reference/protocol. Two copies of the same material means one of them is always the stale one -- the README still claimed Workers RPC had streams that Cap'n Web lacked, which stopped being true some releases ago. Delete protocol.md. Its content lives in packages/docs/src/content/docs/reference/protocol.md, which is a strict superset: same eight top-level messages and eighteen expression types, plus a UInt8Array typo fixed and a section on why JSON rather than a binary format. Trim README.md from 821 lines to 187: what it is, why it is interesting, how to install it, a hello-world client and server, the pipelining example that makes the point, and then links into the docs. Everything removed was verified present in the docs first, page by page. The one detail the docs did not already state -- that disposing a non-root stub releases the object on the peer without closing the connection -- has been added to transports/index. Also repoints the wire-protocol references in the bonk agent definition, including the process rule that protocol changes must update the spec in the same PR, and rewrites the provenance section of packages/docs/README which described the root files as canonical. Documentation links are relative paths into packages/docs, so they resolve on GitHub and npm today. They should become URLs once the site has a published home.
Each documentation page now builds its own Open Graph image at /og/<slug>.png, in the site's own idiom rather than a generic template. The card reuses the hero's motif: nodes on a jittered sphere wired to their nearest neighbours, with three edges lit in Cloudflare orange to stand for calls in flight -- the same restraint the rest of the theme applies to that colour. The network is seeded from the page slug, so every page gets a different one and none of them change between builds. src/lib/og-network.ts is a deliberately separate, much smaller implementation of the hero's idea, because the hero needs GPU buffers and a frame loop and this needs a string of SVG. Satori lays out the text and resvg rasterizes. Both run only at build time. That means committing font data -- Satori has to measure glyphs to break lines -- so Inter is subset to the characters titles actually use, about 80 kB per weight instead of 400. Nothing is served to a browser and the site still ships no web fonts. The sidebar moves from astro.config.mjs to src/sidebar.mjs so that the card and the navigation cannot disagree about which group a page is in. Starlight already emits og:title, og:description, og:url and twitter:card, so the Head override adds only the image tags. Open Graph wants an absolute URL and `site` is still driven by DOCS_SITE_URL, so without that variable the tag falls back to a root-relative path. Correct in a browser, and it becomes absolute the moment the site has a canonical home.
The two existing examples both make the pipelining argument. Neither shows what a session *is*, which turns out to be the thing people most often get wrong: they expect stubs to survive a reconnect, or expect the library to re-establish them. The new example is a WebSocket session with a button that severs it. It demonstrates, in order: authentication as a capability rather than a header, one round trip to connect and subscribe, server push through a client-side RpcTarget, every stub rejecting once the socket dies, and a reconnect that resumes without a gap because the client kept a cursor of its own. Untick "resume from cursor" and the gap appears, which is the point -- the event log lives outside the session, and recovery is the application's job. The docs playground previously only handled HTTP examples, by shimming fetch. It now also handles WebSocket ones: `new WebSocket()` on the configured path returns one end of an in-page pair, with the other end handed to a real newWebSocketRpcSession running the Worker's main interface. The transport reads WebSocket.CONNECTING and friends off the constructor, so the shim carries those too. Everything but the network hop is genuine, including the disconnect and the broken-stub rejections. Also in the bundler: examples can now declare static assets to copy next to the page, which a zero-build example needs for a plain <link> stylesheet, and client.css is only linked when esbuild actually emitted one -- two 404s on the existing pages.
Two example files were shown as `#region` excerpts, because both had their RPC code tangled up with a few hundred lines of DOM wiring, chart drawing and CSS. The region markers made the docs tabs look tidy without fixing the actual problem, and an excerpt is a claim the reader cannot check: it asserts that the rest of the file does not matter, and offers no way to confirm it. So the files are split instead, the way the new example already was: batch-pipelining public/index.html inline script -> demo.js + main.js worker-react App.tsx #region runs -> runs.ts demo.js and runs.ts contain the two strategies and nothing else -- no DOM, no React, no chart -- which is what the tab was trying to point at. main.js and App.tsx keep the wiring. The latency slider now reaches the RPC code as an argument rather than through a getElementById inside it, and runs.ts also picks up the deliberate validation failure, so it holds every RPC call the app makes. With no excerpts left, `region` comes out of PlaygroundFile and readRepoRegion out of source.ts. The docs now have exactly one way to show source: the whole file, read from the repo at build time. Also: batch-pipelining's favicon was still the pre-redesign palette, and the React shell had none at all, so the playground was requesting a /favicon.ico that does not exist. Verified the demos still behave, in the playground and against a real `wrangler dev`: 1 round trip vs 3 either way, and no console errors.
Popped out of the docs, the three example pages looked like three different projects. batch-pipelining had a title bar, a centred column and a footer; worker-react was full-bleed with a stray orange stripe pinned to the top of the viewport and no footer; session-recovery was centred but had neither bar nor footer, and set its own 15px body font. They now share the chrome, to the pixel: a title bar linking home, a 60rem column with matching padding, the same h1 scale and lede treatment, and a footer crediting the source and pointing at the relevant concept page. All of it still comes off when `data-embedded` is set, because the docs page around the iframe supplies a title and a frame of its own. The orange stripe is gone rather than restyled. Links no longer use the accent either -- orange is spent on the one primary action per page, which is what makes it read as the primary action -- so a `--link` colour joins the palettes and the buttons converge on the same pill. worker-react also loses its wrapper div's inline styles and its bullet list, which just restated the two section headings underneath it. Its h1 was the product name, which the title bar now carries; the h1 is the claim being demonstrated, as on the other two. Checked standalone and embedded, light and dark, at eight widths from 360 to 1920: no overflow anywhere, chrome hidden in every embed, and all three demos still produce 1 round trip against 3.
A Starlight docs site under
packages/docs, and the twoexamples/turned into playgrounds embedded in their own pages.The playgrounds have no backend. Each example's real Worker is bundled into the page next to its real client, and the client's
fetchof the RPC path is routed into the Worker'sfetchhandler, so the batching and round-trip counts the demos report are genuine while the site stays static and deployable anywhere.Draft: the preview deploy currently sits behind an account-wide Cloudflare Access policy, so there is no public URL to link yet.