Adding a new primitive
This package only has one new leaf (Upstream) plus the reused Gateway
root — most capability growth here isn’t a new primitive at all, it’s a
new forwarding mechanism on Upstream itself (a new url= scheme, a
new backend shape for app=).
The real worked example in this package’s own history is
url="ipfs://<cid>/<path>" support (see the CHANGELOG’s “Unreleased”
entry), which is the right template because it’s the harder of the two
cases: unlike https:// (already just fetch()), ipfs: has no native
fetch() protocol handler at all, so it can’t be “one more branch inside
the same call” the way fileable’s loadSrc() and servable’s
resolveAsset() handle their own ipfs:// support. It touches:
src/forward.ts—rewriteIpfsUrl(target, gateway)translatesipfs://<cid>/<path>into a real${gateway}<cid>/<path>URL beforefetch()ever sees it.createUrlUpstream()checkstargetUrl.protocol === "ipfs:"and rewrites up front, done fresh per request inside the same forwarding handlerurl=already builds — not a compile-time rewrite, since the target is only known once a request actually arrives.DEFAULT_IPFS_GATEWAYmirrors fileable’s/servable’s own default ("https://ipfs.io/ipfs/").src/compile.ts’sTransformCtx— gained anipfsGateway?: stringfield, threaded throughtransformChildren()/transformUpstream()down toresolveUpstreamHandler()the same waypathPrefixalready is. This is the one part that isn’t boilerplate:Upstreamhandlers (including anyurl="ipfs://..."forwarding target) are built during hostable’s own pre-transform, which runs before servable’s owncompile()— and therefore before servable’s ownipfsGatewayoption would normally apply — so the option has to be threaded down through this package’s privateTransformCtxexplicitly rather than picked up for free. No new type was needed for the public option itself:CompileOptionsis imported directly from@johnhenry/servable(compile(tree, options: CompileOptions)), sooptions.ipfsGatewaywas already there — onlycompile()’s ownrootCtxconstruction needed the one extra field ({ pathPrefix: "", ipfsGateway: options.ipfsGateway }).- Real tests against a local mock gateway, not a live one
(
test/upstream-url.test.ts) — a realnode:httpserver stands in for the gateway, because every major public IPFS gateway (ipfs.io,dweb.link,w3s.link,nftstorage.link) currently rejects direct server-side fetches with429. Three cases: the happy path (Group prefixstripped first, then forwarded through${baseUrl}/ipfs/<cid>/...), a fixed base path joined with the forwarded request path, and a real gateway error (missing CID) surfacing as-is rather than being swallowed.
No types.ts/jsx-runtime.ts/components.ts changes at all for this
one, since it’s a new behavior inside an existing leaf’s existing url=
prop, not a new tag — contrast with servable’s own Adding a new
primitive (a genuinely new tag, Host,
which does touch RESERVED_TAGS/StructuralTag/a new factory) for when
a new primitive is warranted instead.