10. A registry: where libraries are found
A registry is a website where people find libraries announced on the AT Protocol (tcm release --announce,
releases), from every tcm server that announces them. It finds the announcements
itself, checks each against its author's signed repository, and mirrors the code it names, re-hashed as it arrives, so
it can show any library without trusting where it came from. Anyone can run one; none is the authority. tcm's own is
at https://tcm.codes/registry, the one tcm lib search asks unless it's told
another. It was called the hub until November 2026 (tcm hub, at https://tcm.codes/hub, which is gone: tcm-ts
0.2.0's tcm lib search needs --hub https://tcm.codes/registry); the hub is to be something else, a place people
list their codebases.
$ tcm registry --port 8080 url http://127.0.0.1:8080/ jobs recurring: ingest (1m) registry: read 3 record(s) from the accounts the relay lists
10.1 Running one
tcm registry runs the registry tcm ships (an app of tcm's own, as the web UI is) with tcm start, its jobs
beside it, until it's stopped.
--dir <dir>(defaulttcm-registry) is where it keeps everything: its tables and jobs, and the codebase it mirrors libraries into. It's made if need be; a newer tcm updates the app in it and keeps the rest.--portand--hostare where it listens (--host 0.0.0.0to serve beyond this machine, behind a proxy that adds TLS;::for IPv6 as well).--base <path>serves it under a path of a site (/registry), behind a proxy that passes it that path, astcm serve --proxydoes: its pages, assets and/apiroutes are all under it (under a path of a site). Without it, it's the site's root.JETSTREAM_API_KEY, a key for Jetstream's archive (bsky.network), lets a new registry read every announcement ever made. Without one it reads what accounts hold now (below).
10.1.1 On the internet
- Behind a proxy that adds TLS, with
--host 0.0.0.0: the proxy must say the address visitors asked (Host, orx-forwarded-host, andx-forwarded-proto: https), as Railway's andtcm serve --proxydo. The registry's OAuth client document names that address, and its--base(https://tcm.codes/registry/api/oauth/client, coming back tohttps://tcm.codes/registry/), and an account's server reads it there, so signing in needs the registry at an https address on a domain. --diron storage that outlives the process (a volume): its tables, where it got to in Jetstream, and its mirror. A registry started without them catches up from the start again.- Never
TCM_ATPROTO_LOCAL(below): it lets anyone who announces make the registry ask addresses on its own network.
tcm's own runs on Railway: how it's deployed, and a checklist for setting it up, are in developing.
10.2 How it finds and checks releases
- Every minute, it reads Jetstream (v2,
codes.tcm.*commits) from where it left off until the stream goes quiet, writing what it read and where it got to in one transaction. A new release is queued to be mirrored as it's written. - Catching up: with an API key, a new registry replays Jetstream's archive, then the live stream. Without one, it asks
the relay which accounts have
codes.tcm.releaserecords (com.atproto.sync.listReposByCollection), reads each one's from its PDS, then reads the live stream from 36 hours back, as far as it reaches. A registry that was down for longer catches up the same way. - Each release is checked against its author's repository before it shows as theirs: its record is fetched with
com.atproto.sync.getRecord, and the signed commit and the path to the record are checked against the key in the author's DID document. One that doesn't check out (signed by another key, or in no repository) is failed, and never shown as anyone's. - Its code is fetched by hash from the servers the record names, as
tcm lib addfetches a library, and mounted read-only in the registry's codebase by its author's DID (a handle can change hands, a DID can't). What arrives is re-hashed, so no source can send other code. - What a release says is what its signed record says: its package, version, notes and sources are taken from the record its author's repository signs, not from the event that brought it. When Jetstream says a record changed or was deleted, the registry asks the repository again before it changes anything: an event alone changes nothing (a Jetstream that lies can't rewrite or withdraw a release, nor a review or advisory, whose bodies are the signed records' too).
- A deleted record withdraws its release, once its repository no longer holds it: it says so, and its code stays, for what still uses it. A version announced again as another namespace is failed: a version names one release. A deleted record the registry never checked is dropped: it named nobody's code.
- Only checked releases say whose code is whose. Anyone can announce someone else's namespace hash as their own release; theirs fails, and takes nothing over: a namespace's author, for its yanks and for what's said of it, is whoever has a release of it the registry checked.
10.2.1 What a registry asks, and how
A registry fetches from servers others name: a did:web's host, a handle's server, a DID's PDS, a release's sources, a visitor's codebase. Anyone can make it ask one (an announcement, a star, a visit), so it asks them as it would ask any stranger:
- Only at public addresses, over https: a server whose name resolves to a private, loopback or link-local address isn't asked (sources and codebases may be plain http, since what arrives is checked by hash).
- Redirects aren't followed, and it gives up on one that takes too long, or answers with more than a record's proof or a listing could be (2 MB, 8 MB).
- So much, and no more: a listing (the relay's accounts, an account's records) is read to 50 pages; a release whose namespace names more than 50,000 definitions isn't mirrored, nor more than 1,000 releases of one account; visitors' follows and codebases are remembered for 2,000 at most.
- With no credentials: no token goes to a source or a codebase, whatever the registry's environment holds.
- Shapes are checked first: a did:web is a host (and a port) alone; a handle is a domain name; a PDS is an https server's address alone, nothing after it. A release, review or star whose DID, document or PDS doesn't pass is failed, not tried again.
A registry on a network of its own (dev-env, a test's) sets TCM_ATPROTO_LOCAL=1 to allow plain http and local
addresses: never on one that faces the internet.
10.3 What it shows
Every page has the registry's search at the top (⌘K or / goes to it), which finds as you type:
the libraries that match, each with its first few matching definitions (arrows choose, Enter opens one, or with none
chosen the search page); and its two tabs: Activity, and Libraries, with how many there are. Its controls are the
web UI's, so they work from the keyboard as the forge's do. Where code runs is a bar coloured as the legend says: anywhere purple, the server orange,
the browser teal. What a release reaches is said in a line, from its code: "reaches nothing outside itself", "only
fetchTiles reaches the network", or, when what reaches it is a package's doing, "through" that package.
- Activity, the home page: what's happened lately, newest first: releases, each with how its API changed since the version before (a semver level and what was added or broke), where its code runs, and what it reaches; and reviews, advisories and yanks (what people say). Beside it, what to show: Everything, or only Releases, Reviews or Advisories; signed in, also People I follow and Libraries I use (below), and the libraries your codebase uses, with their advisories.
- Libraries: every announced package, by its author, most used, newest or most reviewed first, with how many there are and from how many servers (their sources' hosts), but for those yanked (below). Each has its description, where it runs, what it reaches, the versions with advisories and how many reviews it has, its licence, and its stars.
- Search across every library's newest version, by Name (names, then words in docs where no name matches),
Docs (words in docs alone) or Type (
(s: Shape) => number), astcm finddoes; a query that looks like a type is searched by type unless you choose. Each name is as its library has it (geo.area), with where it runs. A library's own definitions are searched, not its tests nor the libraries it holds; the library with the best match comes first, then the most used; by type, Close matches (a more general or more specific type) come after. Filters keep only what reaches nothing outside itself (its code and what it uses, by hash, and the packages it uses), what's in a reviewed library, or what runs in the browser (or anywhere).tcm lib searchasks the same as JSON (/api/search?q=…). - A library's page, by its author's handle (
/@alice.dev/geo, or a version:/@alice.dev/geo/1.2.0, which its version picker goes to): signed by whom, its README, its code (each definition on the web UI's pages), its versions (each yanked one marked so, with why), its dependents (exactly, by the hashes their releases hold, with a bar for each version), and what tcm checked and what others say of it (Trust). A yank by its author, and its advisories, are said at the top, beside its stars and a button to star it. Beside them: thetcm lib addline to copy, and the one that adds it by its namespace's whole hash, from any source; where it runs; what it reaches; its sources, by host, the one it was fetched from marked; and the libraries it holds. - What changed between two versions (
/@alice.dev/geo/changes/1.1.0...1.2.0), astcm lib upgradesays it: each breaking change and addition with its signature before and after, what changed with the same API, and how many of what the registry has use each version. - A person's page (
/@alice.dev): their Bluesky name, picture and bio (their profile record, checked against their signed repository, read again daily; the picture is served by their PDS); their activity, libraries, reviews and stars (the libraries they've starred, newest first:?tab=stars); how many libraries (not counting those yanked) and releases they have, where they serve code (their releases' sources), and where their code runs. It has no Follow button: the registry may write only stars.
Like the web UI, it runs no code it mirrors: what it says of a library (where it runs, what it reaches, how its API changed) is worked out from the code.
Reviews, advisories and yanks come the same way as releases: from Jetstream (codes.tcm.review, codes.tcm.advisory,
codes.tcm.yank), and, catching up, from the accounts the relay lists. Each shows only once it checks against its
writer's signed repository, as a release does; a yank counts only from the release's author (whoever has a release
of it the registry checked), and a deleted record goes.
10.3.1 Yanked libraries
A yank is a release's author's word that it shouldn't be used, so the registry stops offering it, but still says so to whoever comes to it. Only a yank by the release's own author counts (above: checked against their repository, from whoever has a release of that namespace the registry checked); anyone else's changes nothing.
- A yanked version leaves its library's version picker and isn't its latest: the newest version not yanked is
(in the libraries list, search,
tcm lib search, the add line, and a library's page when no version is asked). - A library whose every version is yanked leaves the Libraries list and its count (and "from how many servers"), search, and its author's page (their libraries, and how many).
- Its page still opens, by its address or a version's: "Yanked by @alice.dev" and why, at the top, and, if a version is left, which is the latest. A yanked version asked for is in its picker, marked yanked; its Versions tab lists every version, yanked ones marked so. Its code stays, by hash, for what uses it.
- The yank stays in Activity, as the release it yanks does, and a library your codebase uses stays in "Libraries your codebase uses", marked yanked: who uses it is who most needs to know.
10.4 Signing in, and stars
A visitor signs in with their AT Protocol account (Bluesky's, or any other): Sign in, at the top of every page, asks for their handle and sends them to their account's server to say yes (AT Protocol OAuth, from their browser), then back to the page they were on. Signed in, their picture there opens a menu: their page, their stars, People I follow, Libraries I use, and Sign out.
- Their browser holds the sign-in: its keys and tokens never reach the registry, which may only write stars (scope
atproto repo:codes.tcm.star). The registry is told only their DID (a cookie), for what to show them; it's not a proof of who they are, and nothing the registry keeps depends on it. - People I follow, on Activity: what the people they follow on Bluesky released or said (their follows as the
AppView says,
app.bsky.graph.getFollows, read again after a few minutes). - Libraries I use, on Activity: what's happened to the libraries in releases they've announced (exactly, by the
hashes their releases hold), and to the libraries in a codebase they give the registry: a
tcm serveaddress, whose main branch's libraries (astcm liblists them) are matched by their author and package, or by hash. The address is kept in their browser (a cookie), and Forget it drops it; it must be an http(s) address at a public server, and the codebase readable without a token (--public): it's asked as any server others name is (above), never as a directory or a remote's name. - Stars: on a library's page, ☆ Star writes a
codes.tcm.starrecord to their own repository, about the library across its versions (its author's DID and its package, at the key<did>:<package>: one per person per library), and pressing it again deletes it (signed out, pressing it says to sign in first). The registry checks it there at once, and counts only stars that check against their writer's signed repository, as it does releases; ones it reads from Jetstream are checked the same way. A star counts only at its own place (at://<did>/codes.tcm.star/<did>:<package>), and each person once. Asking the registry to check a star again is limited to a few times a minute from each address. Stars are shown as counts, on the libraries list and a library's page, and by who starred: each person's page lists what they've starred (a star is a public record in their repository already).
On a domain, the registry is its own OAuth client: tcm registry serves its client metadata at /api/oauth/client (under
its --base), and an account's server reads it there. On localhost or 127.0.0.1 it's a development client, which needs nothing
served; it comes back to 127.0.0.1.
10.5 Settings
| Variable | What it is | Default |
|---|---|---|
JETSTREAM_API_KEY | a key for Jetstream's archive | none: catch up from the relay |
TCM_JETSTREAM | the Jetstream (v2) to read | https://jetstream.us-east.bsky.network |
TCM_RELAY | the relay asked who has release records | https://relay1.us-east.bsky.network |
TCM_ATPROTO_DIRECTORY | a PLC directory that also resolves handles, instead of DNS, HTTPS and plc.directory | none |
TCM_ATPROTO_LOCAL | 1 for a network on this machine or a test's: plain http and local addresses allowed (what a registry asks) | unset: https and public addresses only |
TCM_APPVIEW | the AppView visitors' follows are read from, and their browsers resolve handles with | TCM_ATPROTO_DIRECTORY, else https://public.api.bsky.app |
TCM_REGISTRY_CODEBASE | the codebase it mirrors into | <dir>/.tcm/data/registry (.tcm/data/hub in a registry made while it was called the hub) |
TCM_REGISTRY_EVERY | how often it reads Jetstream | 1m |
tcm dev tcmts.registry.App, in tcm's codebase, serves it while you work on it (developing), and
tcm run tcmts.dev.registryTrial <tcm.js> runs a packed tcm's registry on the end-to-end tests' fake network, with a library
released twice, a review and a profile, to look at its pages. The
records it reads are the records library's, which an app of your own can add too.