ESI.ts Documentation Guide
Implements: DOC-02 · DOC-03 · DOC-04 · DOC-05 · DOC-06 — see CHARTER.md Part 7. DOC-01 is Gap today and lands with the 11.0 docs rewrite (ROADMAP Phase 7).
Where the documentation lives, how the site and the API reference are built and published, how examples in the guides are kept compiling, and what a pull request that adds a public export owes the documentation. The code is the fact: where a page and the code disagree, fix the page.
Where documentation lives
| What | Where | Canonical for |
|---|---|---|
| Orientation, install, first calls | README.md | Nothing it can link to instead; it points at a guide for depth |
| Engineering requirements and status | CHARTER.md | Every ARCH, DES, TEST, GATE, SEC, DOC, REL and PROC requirement |
| The 11.0 plan | ROADMAP.md | Phase schedule, definitions of done, the release gate |
| One topic per guide | guides/*.md | The topic in its title; each opens with an Implements: line naming the requirements it serves |
| API reference | TypeDoc from the TSDoc in src/ | Signatures, parameters, JSDoc |
| Documentation site | docs-site/ (VitePress) | Nothing: its guide and example pages are generated from the files above |
| Runnable programs | examples/*.ts | Whole programs; type-checked by npm run typecheck:examples in ci-fast.yml |
| Static Data Export | guides/SDE.md, guides/sde/*.md | The ./sde and ./sde/memory sub-paths; src/sde/README.md is a pointer to them |
| Test specification | tests/bdd/features/**/*.feature | Behaviour, as EARS requirements with Gherkin scenarios |
| Public API surface | etc/esi.ts.api.md | Every exported shape, regenerated by npm run api-report |
| Agent instructions | CLAUDE.md, AGENTS.md | How an agent works in the repository; the SemVer section is mirrored and checked |
| Changelog | CHANGELOG.md | Written by release-please from conventional commits |
The documentation site
https://lgriffin.github.io/ESI.ts/ is built from the repository, so it cannot say anything the guides and examples do not (DOC-03). Only docs-site/index.md (the home page), docs-site/.vitepress/config.ts and the theme are written by hand; everything else is generated at build time and git-ignored.
| Route | Built from | How |
|---|---|---|
/guide/ | README.md and every guides/*.md | scripts/docs/sync-docs.ts copies each file. A link to another guide becomes a link to its page, a link to any other repository file becomes a GitHub URL, and an image is copied beside the page. The sidebar sections are GUIDE_SECTIONS in scripts/docs/sync-docs-core.ts; a guide not listed there is still published, in the last section |
/examples/ | every examples/*.ts | One page per example from its header comment (title, description, Setup:, @nightly tier) and its source, imports rewritten to the package sub-paths a consumer uses (IMPORT_MAP in scripts/docs/examples-showcase-core.ts), plus an index grouped by category |
/api/ | the TSDoc in src/ | TypeDoc (next section), copied as static files |
/requirements/ | every tests/bdd/features/**/*.feature and the npm run ears report | npm run docs:ears writes the EARS dashboard (scripts/quality/ears-dashboard.ts, see TESTING.md) to docs-site/public/requirements/, copied as a static file. docs-site.yml runs npm run ears first, so the published page carries that run's verdicts |
The version in the navigation bar is read from package.json when the site builds. Edit the guide or the example, never a generated page; each generated page's "Edit this page" link opens its source. A new example needs a title line and an @nightly tag, which it already needs for the nightly run, and an import of a ../src path IMPORT_MAP does not know fails the build until it is mapped.
npm ci --prefix docs-site # once: the site's own dependencies (VitePress)
npm run docs:site # TypeDoc, docs:sync, docs:ears, then vitepress build into docs-site/.vitepress/dist/
npm run docs:site:preview # serve the built site at http://localhost:4173/ESI.ts/
npm run docs:site:dev # docs:sync, then VitePress with hot reload (run npm run docs first for /api/)
npm run docs:sync # regenerate the guide and example pages onlyWhere it is built:
| Where | What happens |
|---|---|
ci.yml documentation job | Runs npm run docs:site and validate:versions -- --site on every pull request, and uploads the built site as the documentation artifact for 30 days |
release.yml deploy-docs | Calls docs-site.yml after validate-release passes on the release's tag |
docs-site.yml (also workflow_dispatch) | Builds the site, checks its version, and publishes docs-site/.vitepress/dist to the gh-pages branch. Dispatch it on master to publish between releases |
VitePress fails the build on a link to a page that does not exist. Links that become GitHub URLs are checked by tests/tdd/scripts/sync-docs.test.ts, which fails when a guide links to a repository file that is not there. GitHub Pages must serve the gh-pages branch (repository Settings, Pages, "Deploy from a branch"); that setting is the maintainer's.
API reference (TypeDoc)
typedoc.json expands every module under src/ and writes HTML to docs-site/public/api/, which .gitignore excludes. No committed file lives under that directory, and npm run clean and npm run clean:docs delete only it (DOC-02).
npm run docs # Generate into docs-site/public/api/
npm run docs:watch # Regenerate on change
npm run docs:serve # Serve docs-site/public/api at http://localhost:8080
npm run clean:docs # Delete docs-site/public/api/Where it is built:
| Where | What happens |
|---|---|
ci.yml documentation job | Runs it as part of npm run docs:site on every pull request |
release.yml validate-release | Runs npm run docs, so a TypeDoc failure blocks the release |
docs-site.yml | Publishes it under /api/ with the site |
release.yml create-assets | Attaches docs.tar.gz to the GitHub release, listed in checksums.txt with the tarball and SBOM |
What TypeDoc shows is what the TSDoc says. The public classes it documents are the construction surfaces (EsiClient, EsiClientBuilder, CustomEsiClient, EsiApiFactory), the 39 domain clients, the auth classes (EveSsoClient, EsiTokenManager, the token stores), the middleware and its interfaces (ICache, IRateLimiter, ICircuitBreaker, IRetryStrategy, IDeduplicator, ILogger), the error classes, and the response types: 36 domain type files in src/types/ plus common.ts, branded.ts and api-responses.ts. Because entryPoints is all of src/, TypeDoc also renders modules the package does not export, including src/core/ports/, src/adapters/ and src/generated/operations.generated.ts. The exports map in package.json and etc/esi.ts.api.md decide what is public, not the reference.
Guide conventions
Every guide follows the same shape, so a reader can move between them without relearning the layout (DOC-05).
- First line after the title:
**Implements:**with the requirement identifiers the guide serves and a link to CHARTER.md. Seven guides still lack one (CHARTER DOC-05 lists them); add it when you next edit one. - Reachable from the README within one link.
- Plain declarative sentences. Say what the code does and name the file, script or CI job that proves it. A claim with no mechanism behind it is a requirement with status Gap, and belongs in the CHARTER as one.
- State the gap. Where the code falls short of the charter, say so in the guide and cite the requirement or issue. Do not soften it.
- Counts are generated. A count of something in the source (clients, routes, operations, requirements, scenarios, examples, test files) is written between markers,
<!-- metric:clients -->39<!-- /metric -->, never typed as a bare number.npm run docs:metricscomputes every metric intoetc/doc-metrics.jsonand rewrites the marked numbers;validate:versionsfails when one is stale. The metric names areMETRIC_NAMESinscripts/docs/doc-metrics-core.ts, and a marker must not start a line, because Markdown reads a line that opens with a comment as HTML. A count only a test run can measure stays prose with the date it was measured (DOC-04). - No duplication. When a topic has a canonical guide, link to it and summarise in one sentence (
DOC-01).
When a public export is added
DOC-06 asks that a pull request adding a public export carries its documentation with it:
- JSDoc on the export. TypeDoc and the API report both read it.
- A guide section in the canonical guide for the topic, with a type-checked example.
- An example under
examples/when the export is user-facing. - The API report:
npm run build && npm run api-report, and commitetc/esi.ts.api.md. Theapi-surfacejob fails when it is stale.
Only part of this is checked. npm run test:export-coverage -- --ci fails a pull request that adds an export no test references, and the API report diff makes the addition visible to the reviewer. There is no pull request template checklist yet, so items 1 to 3 are review items (DOC-06 is Gap).
Documentation examples are checked
A README example that does not compile costs more trust than a bug. npm run test:docs-examples finds every fenced ts or typescript block in README.md, guides/*.md, guides/sde/*.md and src/sde/README.md, and checks it against the packed package, the way a reader who copies it sees it:
- Builds and packs the library as the consumer contract does, and installs the tarball into a scratch consumer outside the repository.
- Writes each block to its own ES module next to a shared prelude, and type-checks all of them with
strictundermoduleResolution: nodenextandbundler. An import of a sub-path the package'sexportsmap does not list fails here. - Runs each block marked
runnablewith node, withfetchreplaced by a stub.
CI runs it in the doc-examples job, inside ci-success. npm test checks the annotations and the baseline without packing.
Fragments and the prelude
Most blocks are fragments: they use a client built in an earlier block, or a characterId the reader supplies. tests/doc-examples/prelude.d.ts declares those names as typed globals, so a fragment type-checks as written. A block that declares or imports a name itself shadows the global. When a new fragment needs a name the prelude lacks, add it there with the type a reader would have; do not type it any.
Annotations
A block is type-checked unless it says otherwise. Two annotations exist, and anything else is an error, so a typo cannot switch a check off:
| Annotation | Where | Effect |
|---|---|---|
runnable | The fence (```ts runnable) or a comment | Type-checked, then run against the stub transport in tests/doc-examples/stub-fetch.mjs. The block must build everything it uses: prelude names do not exist at runtime |
<!-- doc-example: no-check <reason> --> | A comment on the line above the fence (blank lines between are fine) | Not checked. The reason is required |
Use no-check for blocks that are not consumer code: method signature listings, fragments of a class body, and contributor examples that import from src/. Do not use it to silence an example that is wrong; fix the example.
Known-broken examples
When an example exposes a real mismatch that cannot be fixed in the docs alone, mark it no-check with a reason that names the bead tracking it, for example <!-- doc-example: no-check esi-abc.1 getFoo was removed; decide the replacement -->, and list its key in scripts/docs/doc-examples-baseline.json. The key is the file, the nearest heading, and the block's position under that heading: README.md#Quick Start [1].
The baseline only shrinks. The run fails when a no-check names a bead but is not listed, when a listed block no longer exists or no longer names a bead (remove the entry once the example is fixed), and when an entry is not on origin/master already. Without origin/master to compare with, the run fails closed; CI fetches it first.
Negative fixtures
tests/tdd/doc-examples/fixtures/ holds a compliant file and four that must be rejected: a block that does not parse (tsc reports no type errors while any file has a syntax error, so the checker drops such blocks and checks the rest again), a block calling a method that does not exist, an import of @lgriffin/esi.ts/dist/errors (present in the tarball but not exported), and a no-check without a reason. tests/tdd/doc-examples/doc-examples.test.ts puts them through the checker against a small stub package, and npm run test:docs-examples puts them through again against the packed library. If a fixture stops being rejected, both fail.
What changes in 11.0
ROADMAP Phase 7 rewrites the documentation against the new client. The site build (DOC-03), the generated counts (DOC-04) and the single testing guide (DOC-01: root TESTING.md and the former guides/MUTATION-TESTING.md folded into guides/TESTING.md, #273) have landed; nothing else in this list exists yet:
- The README becomes an orientation page written against
createEsiandesi.as(identity), every snippet checked bytest:docs-examples.guides/MULTI-CHARACTER.mdarrived with ROADMAP Phase 2 PR 11. - The Beads blocks in
AGENTS.mdandCLAUDE.mdbecome pointers (#276).
