Skip to content

Releases: purescript/pursuit

v0.9.14

Choose a tag to compare

@thomashoneyman thomashoneyman released this 07 Jul 20:02
b22fa73
  • Fix the version selector getting stuck on "Loading …" after a restart (@thomashoneyman)

    The version dropdown on package pages was initialized by a per-page inline script that Yesod bundles into a dynamic /static/widget/<hash>.js file, whose contents live only in the running backend's in-memory store. Any backend restart (such as a deploy) orphaned those widgets: nginx kept serving already-cached HTML that referenced them, the script 404'd, and the selector never populated. The version selector, search box, and search "load more" link are now initialized from the statically embedded Pursuit.js, which is served from disk and survives restarts.

v0.9.13

Choose a tag to compare

@thomashoneyman thomashoneyman released this 06 Jul 22:10
c7f5cff
  • Avoid duplicate package decodes for old-version deprecation badges.
  • Document the deprecation marker cache in the changelog.

v0.9.12

Choose a tag to compare

@thomashoneyman thomashoneyman released this 06 Jul 20:57
c0693ea
  • Challenge browser-like scraper traffic to package pages with Anubis
    (@thomashoneyman)

    Distributed scraper fleets (thousands of IPs presenting spoofed browser
    user agents, so robots.txt does not apply) crawl the module documentation
    pages of large generated packages hard enough to take the server down.
    Deploys now install Anubis, a
    challenge proxy, and nginx routes cache misses under /packages/ through
    it: clients with browser-like user agents must pass a JavaScript
    proof-of-work challenge once, while non-browser clients (curl, package
    uploads, editor tooling, badge fetchers) and known-good search engine
    crawlers pass through untouched. Cached package pages, /search, and all
    other routes bypass Anubis entirely.

  • Serve cached pages to browsers, and treat HEAD like GET (@thomashoneyman)

    The nginx Accept-header map had no default, so the composite Accept
    strings real browsers send ("text/html,application/xhtml+xml,...") never
    matched the page cache, and every browser page view was re-rendered by the
    backend. Cache lookups now default to the HTML representation, making
    cache hits real for browsers - and confining the Anubis detour above to
    genuine cache misses. HEAD requests now take the same cache/challenge path
    as GET instead of going straight to the backend, where Yesod runs the full
    GET handler for them - previously a full-cost decode that looked free.

  • Give large package decodes an aggregate memory budget (@thomashoneyman)

    v0.9.11 serialised decodes of package docs JSON files of 5MB or more, but
    concurrent decodes of files just below that cutoff could still exhaust the
    heap: scrapers crawling the ~1,900 module pages of next-purs-rsc (a 4.4MB
    file) repeatedly took the server down by stacking four or five decodes of
    it at once. Decodes of files of 1MB or more now share a 16MB in-flight
    budget instead: a decode starts once the total size of large files being
    decoded fits within the budget (or immediately if it is the only large
    decode running, so files bigger than the budget itself are still served).
    The queue remains bounded, failing fast with a 503 when full, and the
    search index regeneration remains exempt from the bound.

    Budget admission is first-come-first-served: without an ordering, a file
    too large to share the budget (which must wait until nothing else is in
    flight) can be overtaken indefinitely by a steady stream of smaller
    decodes - load testing showed a crawler burst starving the search index
    build behind a react-icons decode for over an hour.

  • Type search queries containing type variables now also match more concrete
    types: a -> HTMLElement finds HTMLAnchorElement -> HTMLElement the same
    way _ -> HTMLElement does (#395). Unlike a wildcard, instantiating a query
    variable charges a small penalty, so results that unify with the query
    directly rank first, and repeated variables must be instantiated
    consistently (a -> a ranks Int -> Int above Int -> String). Based on
    #396 by @klntsky. (@thomashoneyman)

  • The package and module badges on search results are now links to the
    package page and module docs page (#424, @joprice). Builtin modules such
    as Prim have no package page, so their package badge remains plain text.

  • Write cached responses atomically (@thomashoneyman)

    Every request that renders a cacheable page writes the response body to
    the same cache file, so concurrent requests for one page raced on it: the
    losers failed with "resource busy (file is locked)" and were served a 500
    (#482 - under a 400-connection homepage flood, a third of responses), and
    a half-written file was briefly visible to nginx, which serves the cache
    directory directly. Responses are now written to a temporary file and
    renamed into place, which is atomic; the same flood now returns no errors.

v0.9.11

Choose a tag to compare

@thomashoneyman thomashoneyman released this 05 Jul 18:14
732a544

What's changed

  • Serialise decoding of large package files: rendering a documentation page decodes the entire docs JSON for that package version, and a few generated packages (react-icons, elmish-html, deku, ...) have 10-25MB files whose concurrent decodes could exhaust the heap and restart the server. Decodes of files over 5MB now run one at a time behind a bounded FIFO queue (excess requests fail fast with 503); smaller packages are unaffected. Version pages also no longer decode the same file twice. (#487)
  • robots.txt now disallows SEO/bulk crawlers (Semrush, Ahrefs, DotBot, MJ12, Amazonbot, Bytespider, PetalBot).
  • The page cache is evicted of files not accessed in 90 days by a weekly cron job installed by the deploy, so it cannot fill the disk.
  • Heap limit raised to -M3400m now that the server has swap.
  • deploy/SERVER.md documents server-only and DigitalOcean account state.

Full Changelog: v0.9.10...v0.9.11

v0.9.10

Choose a tag to compare

@thomashoneyman thomashoneyman released this 04 Jul 13:44
6bc1edc

What's changed

  • Fix the OOM crash loop: the search index is now built one package at a time instead of decoding every package into memory at once. The server had been OOM-killed ~56×/day; measured peak live memory for the index rebuild is now ~715MB (was 3.6GB+ and never completed). A package file which cannot be decoded is skipped with a logged error instead of silently aborting the build, and the new index is fully evaluated on the regeneration thread before being published. (#486)
  • pursuit.service: use both vCPUs and cap the heap below available RAM (-N2 -A64m -M3G).
  • CI: update deprecated GitHub Actions, pin the runner to ubuntu-24.04 (matching the server, upgraded 2026-07-03) and stack to 3.9.3.

Full Changelog: v0.9.9...v0.9.10

v0.9.9

Choose a tag to compare

@thomashoneyman thomashoneyman released this 06 Dec 21:46
fca936d

v0.9.8

Choose a tag to compare

@JordanMartinez JordanMartinez released this 19 Jul 00:55
ea6a220

v0.9.7

Choose a tag to compare

@JordanMartinez JordanMartinez released this 10 Mar 00:04
8d490b9
  • Update Pursuit version in pursuit.cabal (@JordanMartinez)

    The previous release still indicates v0.9.4

v0.9.6

Choose a tag to compare

@JordanMartinez JordanMartinez released this 06 Mar 23:14

v0.9.5

Choose a tag to compare

@JordanMartinez JordanMartinez released this 06 Mar 22:00
6e6ff77

Due to an Ubuntu brownout, CI did not build this release properly.

Please use v0.9.6.