npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

cardano-tessera-client

v0.3.0

Published

Typed HTTP client for a Tessera serving backend: the CIP-179 survey list, bundles, per-transaction responses, artifacts and health, decoded into cip-179 types, with the contract's version check, its constants and the per-network epoch calendar.

Readme

cardano-tessera-client

The typed client for a Tessera serving backend: the CIP-179 survey list, one survey's bundle, the responses a transaction carried, tally artifacts and health, decoded into cip-179 types, with the contract's version checked before the first read is trusted.

Best-effort and 0.x. The stability promise is the HTTP contract's own version (API_VERSION, changelog in backend/server/CHANGELOG.md of the Tessera repository), which this package tracks: the API is first the seam between Tessera's own frontend and backend, and this package exists so a host does not have to hand-write that seam again.

Install

npm install cardano-tessera-client cip-179

cip-179 is a peer dependency: the client returns its types and throws its Cip179DecodeError, and a host that also uses cip-179 directly must see one copy of that class for instanceof to hold.

Read a survey

import { createTesseraClient, currentEpoch } from "cardano-tessera-client";

const client = createTesseraClient({
  baseUrl: "https://tessera-backend-preprod.matthieu-pizenberg.workers.dev",
  network: "preprod",
});

const answer = await client.wholeBundle(
  "5910e44ca9bb9a41625280a1335a4a59941a15716d6959901c9b8e20a058649d:0",
);
if (!answer.ready) {
  // The backend has not completed its first refresh — wait, do not alarm.
} else {
  const { survey, responses, verdicts, govLinks, tip } = answer.body;
  // survey.definition and survey.ref feed <tessera-respond>, with
  // tipEpoch: currentEpoch("preprod").
}

Every snapshot-derived method (surveys, surveysByRefs, changes, changesSince, bundle, wholeBundle, responded, responsesByTx) answers { ready: true, body } | { ready: false }; the false branch is the backend's own 503 {"error":"snapshot not ready"}, an ordinary state before its first refresh. Any other non-2xx answer throws TesseraHttpError with the status. The first of those reads also fetches /health and refuses a backend serving another network than the one given, or another contract major than API_VERSION's; a minor the client does not know is accepted.

Input the contract would refuse is refused here first, with a RangeError and no request: a survey key not matching SURVEY_KEY_RE, a limit outside 1–MAX_PAGE_LIMIT, more than MAX_CREDENTIALS credentials, more than MAX_TX_STATUS_HASHES hashes, a hash that is not 64 lowercase hex characters.

Methods

| Method | Route | Answer | | :------------------------------------------ | :---------------------------------- | :------------------------------------- | | liveness() | GET /health | BackendLiveness | | health() | GET /api/health | BackendHealth | | surveys(params?) | GET /api/surveys (paged) | SnapshotAnswer<SurveyListPayload> | | surveysByRefs(keys) | GET /api/surveys?refs= | SnapshotAnswer<SurveyListPayload> | | changes(cursor, limit?) | GET /api/surveys?changes= | SnapshotAnswer<SurveyChangesPayload> | | changesSince(sinceUnix, limit?) | GET /api/surveys?since= | SnapshotAnswer<SurveyChangesPayload> | | bundle(survey, cursor?) | GET /api/surveys/{txHash}/{index} | SnapshotAnswer<SurveyBundlePayload> | | wholeBundle(survey) | every page of the above | SnapshotAnswer<SurveyBundlePayload> | | responded(credentials) | GET /api/responded | SnapshotAnswer<RespondedPayload> | | responsesByTx(txHash) | GET /api/responses/{txHash} | SnapshotAnswer<TxResponsesPayload> | | artifact(survey) / artifactByHash(hash) | the artifact routes | TallyArtifact \| null | | tip() | GET /api/tip | ChainTip | | txStatus(hashes) | GET /api/tx_status | Record<string, number \| null> | | pparams() | GET /api/pparams | unknown (evolution-sdk's shape) |

A survey is named by its record's ref or by its key, <txHash>:<index>. bundle is one page — enough for a host that only renders the survey, since the definition rides every page; wholeBundle follows nextCursor to the end with collectSurveyBundle's restart rule, for anything that counts or displays responses.

Mirror the surveys

A host that keeps its own copy of the surveys walks the list once — or names the instant it last ran — and then asks only for what changed:

let cursor = load(); // the string from the last tick, or null
if (cursor === null && lastRunUnix === null) {
  // Nothing to resume from: walk the list once, and keep where it ends.
  let page = await client.surveys({ limit: 200 });
  for (;;) {
    if (!page.ready) return;
    if (page.body.resync) return; // walk again next tick
    apply(page.body);
    if (page.body.nextCursor === null) break;
    page = await client.surveys({ limit: 200, cursor: page.body.nextCursor });
  }
  save(page.body.changesCursor!);
  return;
}

// Resume: from the last tick's cursor, or from when this copy last ran.
const delta =
  cursor === null
    ? await client.changesSince(lastRunUnix!)
    : await client.changes(cursor);
if (!delta.ready) return;
for (const key of delta.body.removed) forget(key); // before the rows
apply(delta.body);
save(delta.body.nextCursor);

A change is delivered once and never missed, and the consumer never handles a generation number. A delta always continues: nextCursor is never null, and the change selection never answers resync. A removal is advisory and can be transient (a reorg re-lands the transaction at a new slot), so state that cannot be rebuilt is confirmed with surveysByRefs before it is destroyed. The delta carries no filter and no counts: filter locally.

changesSince reports removals back to the first change-selection deploy on that backend (2026-09-04 on both testnets). A copy older than that gets its rows but not the sweeps of that era, so it starts from the walk instead.

Which epoch a host passes as tipEpoch

currentEpoch(network) — the calendar's epoch, from EPOCH_ZERO_UNIX and SECONDS_PER_EPOCH. A survey accepts responses through its endEpoch inclusive and the ledger's epoch is wall-clock, so the calendar is what decides "still open". A stored snapshot's tip.epoch lags it by up to one refresh interval, which around an epoch boundary shows a just-closed survey as open.

What a host must not claim

The host contract in interop/preprod.md of the Tessera repository lists the limits that travel with this data: a sealed response hides its answers and nothing else; a governance link is a discovery relation, not an endorsement; results are per role and never merged; countedByRole is provisional while a survey has no artifact.

Development

pnpm install
pnpm --filter cardano-tessera-client type-check
pnpm --filter cardano-tessera-client test
pnpm --filter cardano-tessera-client build   # emits dist/ for publishing

In the workspace the package is consumed straight from src; dist/ is only produced for publishing, where publishConfig.exports swaps the entry points to the compiled output at pnpm publish time.