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

@nynth/tether

v0.3.0

Published

Client for Tether, Nynth's memory layer. Plain TypeScript types, no runtime dependencies.

Readme

@nynth/tether

Client for Tether, Nynth's memory layer. Plain TypeScript types, no runtime dependencies, works anywhere fetch does.

npm install @nynth/tether
import { Tether } from '@nynth/tether';

const tether = new Tether({
  apiKey: process.env.TETHER_API_KEY,   // or TETHER_API_KEY
  baseUrl: process.env.TETHER_BASE_URL, // or TETHER_BASE_URL
  space: 'default',
});

await tether.remember('Invoices go out on the 1st of the month.', { kind: 'process' });

const { anchors } = await tether.context({
  query: 'how does billing work here',
  maxTokens: 800,
});

An API key and a baseUrl are both required, and the client throws at construction without either. There is no default endpoint: guessing one would send your credential and your content to a deployment you did not choose. The URL must be https, except on localhost.

Full documentation: https://tether-docs.eli-c44.workers.dev. Start with the guide on idempotency, then stored, searchable, and degraded — that one covers the behaviour that surprises every integration.

Many users: spaces and provisioning

tether.spaces.list() returns the spaces your key is granted. A provisioner key (created by an operator, with a namespace) creates spaces and mints scoped keys without the root token: tether.provision.putSpace(space, { private }), .mintCredential (the key is returned once; a taken name is already_exists with existingId), .revokeCredential and .eraseSpace. It acts only on what its namespace created, holds no grants and reaches no memory route. A space created private cannot be granted to a minted key once it holds data or is held (isSpaceNotEmpty, isSpaceHeld), so a leaked provisioner key cannot read it; shared spaces stay reachable, so guard the key anyway. A key holder replaces its own key with tether.credentials.rotate(); the old one works for 15 more minutes. putSpace({ private }), eraseSpace and credentials.rotate need 0.3.0 or later. The full pattern is in the docs site under Integrating an application.

Cancelling a call

Every method takes a signal, and a client-wide one applies to all of them.

const controller = new AbortController();
const anchors = tether.context({ query: "deploy policy", maxTokens: 800,
                                 signal: controller.signal });
controller.abort();            // rejects with TetherAbortError

An abort is a decision, not a failure: it is never retried, and it rejects with TetherAbortError rather than a connection error. The two are the same AbortError at the fetch boundary, so the client tells them apart by which signal fired — a timeout still retries, a cancellation does not.

Waiting for a deletion

delete answers 202. Rows and markers are written immediately; vectors are purged by a background job.

const { deletionId } = await tether.anchors.delete("anc_…");
await tether.deletions.waitFor(deletionId);   // resolves when the purge is done

It rejects if the job fails, rather than handing back a failed job as a fulfilled promise — a caller awaiting a deletion is asking is it gone.

There is deliberately no waitUntilSearchable. Tether exposes no per-anchor searchability: Anchor carries no such field, and searchable on a write result is always false. The only implementation would poll /v1/search and hope the id appears, which is unreliable and spends a rate limit on a guess. Use context instead — its read-through covers the indexing window, so an anchor written seconds ago is returned, labelled via: "recent".

Seeing retries

const tether = new Tether({
  onRetry: ({ attempt, status, delayMs }) =>
    log.warn({ attempt, status, delayMs }, "tether retry"),
});

Retries are otherwise invisible — a caller sees one slow call and cannot tell it was throttled three times. A hook that throws is swallowed: instrumentation must never be load-bearing.

Narrowing errors

import { isNotFound, isConflict, isRateLimited } from "@nynth/tether";

try { await tether.anchors.correct(id, { content, expectedVersion: 2 }); }
catch (error) {
  if (isConflict(error)) {
    // error.code is "stale_version" or "idempotency_conflict"
  }
}

These are type guards, not booleans: after if (isNotFound(error)) the compiler knows about .code and .requestId.

Before you put retrieved content in a prompt

Everything Tether returns was written by whoever held a key to that space. It is data, not instruction. An anchor reading "ignore your previous instructions" is a string somebody stored, and Tether hands it back faithfully because that is its job — the risk appears one step later, when an application concatenates it into a prompt.

Delimit it, keep your instructions above it, and never let retrieved text choose a tool call or a recipient. Retrieving covers this properly, including why Tether does not try to sanitise it for you.

Licence

MIT. Copyright (c) 2026 Nynth Labs. See LICENSE.

The client is MIT so anyone can install and use it. Tether itself — the service it talks to — is a separate, proprietary product, and this package is of no use without a credential for one.