---
title: "Registry"
description: "A manifest of keys and lazy imports. Keys and aliases answer at once. Records load on the first lookup for their collection. How to register yours."
canonical_url: "https://puzzles.agntn.dev/guide/registry"
---
# Registry

> A manifest of keys and lazy imports. Keys and aliases answer at once. Records load on the first lookup for their collection. How to register yours.

## Six functions

```ts
import {
  collectionKeys,
  hasCollection,
  getCollection,
  requireCollection,
  collections,
  registerCollection,
} from "@agntn/puzzles";

collectionKeys(); // ["80-bit", "bits", "ballet", …, "zden"], every key in the manifest
hasCollection("warpwallet"); // true, an alias for warp
await getCollection("bits"); // loads src/collections/bits and returns the instance
await getCollection(name); // undefined when name is nothing the manifest knows
await requireCollection("nope"); // throws UnknownCollectionError
await collections(); // every collection, loaded once, in manifest order
```

The first two are synchronous. They read the manifest and nothing else. The rest await an `import()`. The promise for a collection is shared, so two parallel callers load one module, not two.

A built-in key or alias comes back typed by the queries its collection takes. `(await requireCollection("bits")).get(71)` compiles, `(await getCollection("gsmg")).get()` too, and `get(1)` on `warpwallet` is a type error, not a silent miss. A plain `string` gets the loose `AnyCollection`, since nobody knows which collection it names until runtime.

## The manifest

```ts [src/collections/index.ts]
export const builtins = [
  { key: "80-bit", load: () => import("./80-bit.ts").then((m) => m.eightyBit) },
  { key: "bits", load: () => import("./bits.ts").then((m) => m.bits) },
  // ...
] as const;
```

Only the key lives here. Importing `@agntn/puzzles` evaluates this list and the core. Not one record, not one collection class. The verification crypto doesn't: `verify` imports it on its first call. The CLI never imports the barrel, so `puzzles --help` stays light. A bundler sees a literal `import()` for each collection and splits it into its own chunk. Only ever ask about `bits`? The other collections stay unloaded. `sideEffects` in `package.json` names the CLI entry alone.

The cost is a new way to be wrong. A collection module that exists on disk and isn't in the list is invisible to the registry. A test compares the directory with the manifest, so it can't drift quietly. And `@agntn/puzzles/collections/<key>` serves the module directly either way.

## Old names

`peter_todd` resolves to `hash-collision` and `warpwallet` to `warp`. That holds in `hasCollection`, `getCollection` and every identifier lookup. They're the names the earlier JSON dataset used. Scripts written against it still say them, and they should keep working.

`b1000` is the newest old name. It resolves to `bits`, and since the puzzle numbers never moved, `b1000/71` still finds its puzzle. What comes back answers `bits/71` to `id()`, though. A script that stored the old identifiers and compares strings will notice.

Same deal for the collections that used to be snake_case. `movie_enigma` still finds `movie-enigma`, and `hash_collision/sha1` finds `hash-collision/sha1`. Only the collection segment gets this. The puzzle names inside were renamed without aliases, so `zden/level_1` is a miss now and `zden/level-1` is the one to ask for. Aliases live in a `Map`, so `constructor` and `__proto__` are unknown keys, not Object prototype properties.

## Registering yours

```ts
import { registerCollection } from "@agntn/puzzles";

registerCollection({ key: "mine", load: () => import("./mine").then((m) => m.mine) });
registerCollection(mine); // or an instance, registered as an already resolved entry
```

An entry with a loader stays lazy, the same as the built-ins. An instance registers as resolved. The same instance twice is a no-op. Another instance or entry under an existing key replaces what the key held, `bits` included. A fork with corrected data can stand in for the shipped one. Keep its query type, though: the types for `bits` still say `get(71)`. Every aggregate view refreshes on the next read. `all()`, `collectionSummaries()`, `dataVersion()` and `dataset()` are memoized per registry snapshot, and a registration starts a new one. `stats()` and the tools recompute from those views on every call. The whole recipe is in [Custom collections](https://puzzles.agntn.dev/guide/custom).

## Where the collections come from

```text
Collection<Query> (abstract)
├── NamedCollection        get("challenge-1") or get("warp/challenge-1")
│   ├── BalletCollection
│   ├── BitimageCollection
│   ├── CoinArtistCollection
│   ├── CoinGamesCollection
│   ├── DogesGambitCollection
│   ├── DugCollection
│   ├── HashCollisionCollection
│   ├── IAmABananaAmaaCollection
│   ├── LedgerDonjonCollection
│   ├── LuckyLurkerCollection
│   ├── MoveOverBrokersCollection
│   ├── RushwalletCollection
│   ├── SmithLyleMooreCollection
│   ├── WalkingBanksCollection
│   ├── WarpCollection
│   ├── WickexCollection
│   └── ZdenCollection
├── NumericCollection      get(71) or get("71") or get("bits/71")
│   ├── BitsCollection
│   ├── CryptoPuzzlesCollection
│   ├── GrycoinCollection
│   ├── MiniCollection
│   ├── QuizchainCollection
│   ├── Quizchain2Collection
│   ├── TeikhosCollection
│   └── WeaveCollection
└── SingletonCollection    get() or get("gsmg")
    ├── BitaddressCollection
    ├── BitapsCollection
    ├── BookQuizCollection
    ├── BraveNewWorldCollection
    ├── EightyBitCollection
    ├── GenesisCollection
    ├── GreatRiddleCollection
    ├── GsmgCollection
    ├── HuntingTimeCollection
    ├── LiberteGuidantLePeupleCollection
    ├── MineshopCollection
    ├── MovieEnigmaCollection
    ├── NatashaOtomoskiCollection
    ├── NineVw04Collection
    ├── PathToGreatnessCollection
    ├── PhyCollection
    ├── PicturePuzzleCollection
    ├── PowerfulMossCollection
    ├── ProofOfWritingCollection
    ├── RealBigBlockCollection
    ├── SatoshiBirthdayQuizCollection
    ├── SatoshiMazeCollection
    ├── SchoolOfBitcoinCollection
    ├── SeedPhraseCollection
    ├── TriviaBrainwalletCollection
    └── WealthInPoetryCollection
```

Each collection is a class with a static `key`, a static `author` built with `party()` and keyed for its [author page](https://puzzles.agntn.dev/authors), and the puzzle list. The three bases differ only in what `get` accepts. A singleton refuses to hold anything but the one puzzle whose id equals its key. It refuses at construction time, so a singleton id can't drift from the manifest.


## Sitemap

See the full [sitemap](https://puzzles.agntn.dev/sitemap.md) for all pages.
