Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Atlas

A modern OpenAPI reference and API client. One script tag, zero dependencies, 79 KB.

<div id="app"></div>
<script src="https://unpkg.com/@sillo/atlas"></script>
<script>
  Atlas.createApiReference('#app', { url: '/openapi.json' })
</script>

Or with no JavaScript at all:

<script src="https://unpkg.com/@sillo/atlas" data-url="/openapi.json"></script>

Why another one

Atlas Swagger UI Scalar
Standalone bundle 79 KB ~1.4 MB ~1 MB
As a module import 53 KB n/a n/a
Runtime dependencies none many many
Styles inlined separate CSS inlined
Recursive schemas rendered expands forever partial
Dangling $ref reported ignored blank page

Nothing is bundled that you did not ask for. Atlas builds DOM nodes directly — no framework, no virtual DOM, no runtime dependency of any kind. That is also why an OpenAPI description cannot inject script into the page: there is no code path that parses a string as markup.

Features

Three-pane reference. Sidebar grouped by tag, operation detail in the middle, request builder on the right. Light and dark, following the system by default.

The whole info block. Licence, terms of service, contact, and external documentation as links; every base URL; every security scheme with its type and its OAuth scopes; a heading and description per tag. These are the parts of a document that say who owns the API and what you may do with it, and a reference that drops them silently discards the only legal and human metadata in the file.

Search. ⌘K or /. Ranked, not filtered — typing user puts GET /users above a passing mention twelve operations down.

API client. Build a request from the schema-seeded form and send it. Real timing, status, size, headers, and a response viewer. Credentials persist across reloads.

Nine-language snippets. cURL, HTTPie, Python (httpx and requests), JavaScript fetch, Node axios, Go, PHP, Ruby. Generated from the same prepared request the Send button uses, so a snippet cannot drift from what the client actually does.

Install

npm install @sillo/atlas
import { createApiReference } from '@sillo/atlas'
import '@sillo/atlas/dist/atlas.css'

createApiReference('#app', { url: '/openapi.json' })

Configuration

Atlas.createApiReference('#app', {
  url: '/openapi.json',        // or: spec: {...} / spec: '{"openapi":...}'
  theme: 'auto',               // 'light' | 'dark' | 'auto'
  deepLinking: true,           // update location.hash while reading
  fetchHeaders: { Authorization: 'Bearer …' },   // for a private document
  servers: [{ url: 'https://api.example.com' }], // override the document
  onLoaded: (spec) => console.log(spec.operations.length),
  onError: (error) => console.error(error),
})

Returns { spec, destroy }.

Data attributes

<script
  src="atlas.standalone.js"
  data-url="/openapi.json"
  data-target="#docs"
  data-theme="dark"
  data-deep-linking="false"
></script>

Using the parts on their own

Everything is exported, so the pieces work outside the page:

import { parseSpec, prepareRequest, SNIPPET_LANGUAGES } from '@sillo/atlas'

const spec = parseSpec(JSON.parse(await fs.readFile('openapi.json', 'utf8')))
const request = prepareRequest(spec.operations[0], 'https://api.example.com',
  { path: {}, query: {}, header: {}, cookie: {} }, { values: {} }, spec.securitySchemes)

console.log(SNIPPET_LANGUAGES.find((l) => l.id === 'curl').generate(request))

Also exported: AtlasApp, renderOperation, renderSchema, renderInfo, renderTagHeading, createPanel, createSearch, codeBlock, markdown, highlighted; and RefResolver, walkSchema, exampleFromSchema, typeLabel, prepareRequest, sendRequest, SNIPPET_LANGUAGES, searchOperations, resolveServers, parseMarkdown, safeHref, tokenize.

What it handles that viewers usually do not

Recursive schemas. Comment.replies: Comment[] is a cycle. Eagerly inlining $ref does not terminate — the tab hangs. Atlas resolves lazily and reports a cycle as a named, readable reference. Mutual recursion (A → B → A) too.

Dangling references. A $ref pointing at nothing is shown as a diagnostic naming the pointer, instead of a blank section.

Path-level parameters. Merged into every operation under that path, with operation-level parameters winning on a name-and-location clash.

Converter-style paths. {widget_id:int} — sillo's form — is substituted like {widget_id}. Substituting only the first leaves a literal {widget_id:int} in the URL and a confusing 404.

readOnly and writeOnly. Excluded from request and response examples respectively, so a seeded body is one the API will actually accept.

Colliding operationIds. Deduplicated, because a duplicate anchor makes a deep link land on the wrong operation.

Escaped JSON pointers. #/components/schemas/a~1b resolves to the key a/b, in the right order — decoding ~0 first would turn ~01 into /.

Which server a request goes to. A document declaring http://localhost:8000 is correct on the author's machine and wrong on a colleague's port 8001, wrong on a LAN address, and wrong behind a proxy. When the document was fetched from the same origin as the page, the API is serving its own documentation — so that origin is offered as This server and selected by default. Nothing declared is hidden; the document's servers stay in the dropdown. Documentation hosted apart from the API keeps the declared server, because there the page origin is not the API.

Security

The OpenAPI document is untrusted input. Someone else may write the descriptions in the API you are hosting docs for.

  • Markdown is parsed to a block tree and rendered one element at a time — nothing ever reaches innerHTML.
  • Link schemes are an allowlist; javascript: and data: render as inert text.
  • Syntax highlighting tokenises to data, rendered as <span> children, so a JSON string in a response body cannot become markup.
  • The request panel updates in place rather than rebuilding, so typing into a parameter never replaces the focused element.
  • The bundle needs no external origins, so it works under a strict Content-Security-Policy.

There are tests for each of these, verified by reintroducing the bug: swapping the paragraph renderer to raw HTML turns two of them red.

Development

npm install
npm run dev        # demo at http://localhost:5173, rebuilds on change
npm test           # 79 tests
npm run check      # typecheck + test + build

dist/ is not committed to main — it is built locally and ignored. CI fails the build if it ever becomes tracked again.

Releasing

Bump the version in package.json on main, then run the Release workflow with the same version. It typechecks, tests, builds, and commits dist/ onto a detached commit that only the tag points at.

That is what lets

https://cdn.jsdelivr.net/gh/sillohq/atlas@v0.4.0/dist/atlas.standalone.js

work while main stays free of ~2 MB of build output per release. The release commit's parent is main's HEAD, so git log v0.4.0 shows the full history — the commit is a real point in the project, just not on the branch.

The tag is created by the workflow rather than pushed by hand. If a human tagged first, CI would have to force-move the tag onto the build commit, rewriting a ref other people may already have fetched.

The application is mounted into a real DOM (linkedom) and queried, so the tree under test is the tree a browser would get — nothing is serialised to markup and re-parsed.

npm test builds first, because every test imports from dist/.

Limitations

  • JSON only. A YAML document is detected and reported with a message saying so, rather than failing as invalid JSON. Convert it, or parse it yourself and pass spec.
  • $ref within one document. External file and URL references are reported, not followed.
  • OpenAPI 3.x. A Swagger 2.0 document is flagged and rendered on a best-effort basis.
  • The API client runs in the browser, so it is subject to CORS. A cross-origin failure is reported as such rather than as a bare "Failed to fetch", but it still needs the server to allow the origin.

Licence

MIT

About

A modern OpenAPI reference, API client and SDK generator, in React. One script tag, or a component.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages