Skip to content

OpenAPI reference, DOM construction, API client, search, snippets

Package: @sillo/atlas (npm) Repository: https://github.com/sillohq/atlas Source root: atlas/src/ Bundle size: ~79 KB standalone (vs. Swagger UI ~1.4 MB)


Atlas is a zero runtime dependency OpenAPI reference and API client. It renders interactive API documentation from an OpenAPI 3.x or Swagger 2.0 document, with a built-in request builder, code snippet generator, and search.

79 KB standalone. No dependencies. No innerHTML. No framework.
ToolBundle Size
Atlas~79 KB
Swagger UI~1.4 MB
Scalar~1 MB
Redoc~900 KB
  1. Zero runtime dependencies: Only esbuild, linkedom, typescript as devDependencies (not shipped).
  2. XSS-safe: No innerHTML anywhere. All user content reaches the DOM as text nodes via textContent.
  3. Self-contained: Works under strict Content-Security-Policy without external CDN requests.
  4. Framework-free: Direct DOM manipulation. No React, Vue, or Svelte.
  5. Dual-use: ESM import for bundler integration, IIFE for <script> tag.

atlas/src/
├── index.ts # createApiReference, AtlasConfig, AtlasInstance
├── standalone.ts # <script> tag entry, autoMount()
├── spec/
│ ├── types.ts # ParsedSpec, Operation, Parameter, SecurityScheme, Diagnostic
│ ├── parse.ts # parseSpec — the central transformation
│ ├── resolve.ts # RefResolver, walkSchema
│ ├── example.ts # exampleFromSchema, typeLabel
│ └── servers.ts # resolveServers
├── client/
│ ├── request.ts # prepareRequest, sendRequest
│ └── snippets.ts # 9 language snippet generators
├── ui/
│ ├── dom.ts # el, append, replace, svg, button, tabs, etc.
│ ├── app.ts # AtlasApp — header, sidebar, content, panel, search
│ ├── highlight.ts # tokenize, JSON/Python/Go/Ruby/PHP/JS/TS/Shell
│ ├── markdown.ts # parseMarkdown, parseInline, safeHref
│ ├── render.ts # markdown, highlighted, codeBlock
│ ├── panel.ts # createPanel — server/auth/request/snippet/send cards
│ ├── schema.ts # renderSchema — expandable tree with cycle detection
│ ├── search.ts # searchOperations, createSearch — scored search dialog
│ ├── info.ts # renderInfo, renderTagHeading, describeScheme
│ └── operation.ts # renderOperation
└── styles/
└── atlas.css # 1189 lines, light/dark themes, responsive
graph TD
    A[OpenAPI Document] -->|fetch or inline| B[parseSpec]
    B --> C[RefResolver]
    C --> D[ParsedSpec]
    D --> E[AtlasApp]
    E --> F[Sidebar]
    E --> G[Content Area]
    E --> H[Panel]
    E --> I[Search]
    G --> J[renderOperation]
    J --> K[renderSchema]
    H --> L[prepareRequest]
    L --> M[sendRequest]
    L --> N[Code Snippets]

Source: atlas/scripts/build.mjs

OutputFormatSizeUse Case
dist/atlas.jsESM~53 KBBundler imports
dist/atlas.cjsCommonJS~53 KBrequire()
dist/atlas.standalone.jsIIFE + CSS inline~79 KB<script> tag
dist/atlas.cssCSS~15 KBESM importers who handle CSS

The stylePlugin esbuild plugin intercepts a virtual atlas:styles import and replaces it with JS that injects CSS as a <style id="atlas-styles"> element, guarded by an ID check to prevent double-injection.


Source: /Users/admin/sillo.build/atlas/src/index.ts

function createApiReference(
target: string | HTMLElement,
config?: AtlasConfig,
): AtlasInstance
FieldTypeDefaultPurpose
urlstringURL to fetch the OpenAPI document from
specobject | stringInline document object or JSON string
theme'light' | 'dark' | 'auto''auto'Color theme
deepLinkingbooleantrueUpdate location.hash while reading
fetchHeadersRecord<string, string>Extra headers for fetching a private spec
serversstring[]Override the document’s server list
onLoaded(spec: ParsedSpec) => voidCallback when ready
onError(error: Error) => voidCallback when loading/parsing fails
interface AtlasInstance {
spec?: ParsedSpec
destroy: () => void
}
  1. Resolve the target (CSS selector or HTMLElement).
  2. Show a loading spinner immediately.
  3. Asynchronously load the document (fetch or inline).
  4. Parse via parseSpec.
  5. Create AtlasApp with the parsed spec.
  6. Report errors (YAML detection, fetch failures, CORS hints).

Source: /Users/admin/sillo.build/atlas/src/standalone.ts

<script src="atlas.standalone.js" data-url="/openapi.json"></script>

autoMount() reads attributes from the <script> tag:

AttributeMaps To
data-urlconfig.url
data-targettarget
data-themeconfig.theme
data-deep-linkingconfig.deepLinking

If no container exists, creates one. Supports DOMContentLoaded deferred mounting.


Source: /Users/admin/sillo.build/atlas/src/spec/parse.ts (356 lines)

function parseSpec(document: OpenAPIDocument): ParsedSpec

The central transformation function. Turns a raw OpenAPI document into flat structures the UI renders.

graph TD
    A[Raw Document] --> B{Valid object?}
    B -->|No| C[Diagnostic: not an object]
    B -->|Yes| D{Has openapi or swagger?}
    D -->|No| E[Diagnostic: missing version]
    D -->|swagger 2.0| F[Diagnostic: Swagger 2.0 warning]
    D -->|openapi 3.x| G[Create RefResolver]
    G --> H[Collect operations from paths + webhooks]
    H --> I[Merge path-level parameters]
    I --> J[Deduplicate operationIds]
    J --> K[Read parameters, request bodies, responses]
    K --> L[Read servers with variable substitution]
    L --> M[Group by tag]
    M --> N[Validate all $refs]
    N --> O[ParsedSpec]

Iterates document.paths and document.webhooks, iterating all 8 HTTP methods per path item (get, post, put, delete, patch, options, head, trace).

mergeParameters uses a Map keyed by in:name. Operation-level parameters win on collision with path-level parameters.

uniqueId() generates URL-safe slugs. Appends -2, -3 etc. for collisions.

Responses are sorted: 2xx first, then by code, default last.

Reads servers[].variables and substitutes {variable} defaults.

  • Uses declared tag order from the document.
  • Untagged operations go to “Other” group.
  • Drops unused declared tags.
interface ParsedSpec {
title: string
version: string
description: string
info: Record<string, unknown>
servers: ResolvedServer[]
groups: TagGroup[]
operations: Operation[]
securitySchemes: SecurityScheme[]
schemas: Record<string, unknown>
document: OpenAPIDocument
diagnostics: Diagnostic[]
}

Source: /Users/admin/sillo.build/atlas/src/spec/resolve.ts (225 lines)

Handles lazy, cycle-safe $ref resolution within a single document.

class RefResolver {
constructor(document: OpenAPIDocument)
}
MethodSignaturePurpose
lookup(ref: string) => unknownFollow a JSON pointer (RFC 6901)
deref<T>(node: unknown) => T | undefinedFollow $ref if present, merge sibling keys
nameOf(node: unknown) => string | undefinedExtract display name from $ref
resolvedNameOf(node: unknown) => string | undefinedLike nameOf, returns undefined for dangling refs
validate(root: unknown) => BrokenRef[]Walk tree cycle-safe, find broken $refs

lookup correctly handles escaped characters in JSON pointers:

  • ~1 = /
  • ~0 = ~
  • Decoded in the right order (first ~1, then ~0).

deref merges sibling keys over the target (OpenAPI 3.1 pattern):

{"$ref": "#/components/schemas/Widget", "description": "Override"}

The description from the referencing node overrides the shared schema’s description.

interface BrokenRef {
ref: string
reason: 'not-found' | 'external' | 'malformed'
}

Walks an entire schema tree, cycle-safe, finding every unresolvable $ref. Uses an onPath Set to detect cycles. Reports broken/external references as diagnostics.


Source: /Users/admin/sillo.build/atlas/src/spec/resolve.ts

function walkSchema(
schema: unknown,
resolver: RefResolver,
visit: (node: Record<string, unknown>, path: string[]) => boolean | void,
options?: {
onCycle?: (ref: string, path: string[]) => void
},
): void

Walks a schema tree following $refs with cycle protection. visit is called for every node reached; returning false prunes that branch.

  • properties
  • patternProperties
  • items
  • additionalProperties
  • not
  • allOf, anyOf, oneOf
  • prefixItems

Tracks refs on the current path via a Set<unknown>. If a ref is already on the path, calls onCycle callback and does not descend further.


Source: /Users/admin/sillo.build/atlas/src/spec/servers.ts (114 lines)

function resolveServers(
declared: string[],
options?: { origin?: string; specUrl?: string },
): ResolvedServers

A document declaring http://localhost:8000 is correct on the author’s machine but wrong everywhere else.

graph TD
    A[Resolve servers] --> B{Page origin matches a declared server?}
    B -->|Yes| C[Use that declared server as default]
    B -->|No| D{Spec URL same origin as page?}
    D -->|Yes| E["Insert 'This server' (page origin) as default"]
    D -->|No| F[Keep declared servers unchanged]
    C --> G[All declared servers remain visible]
    E --> G
    F --> G
  • Relative server URLs resolved against page origin.
  • file:// pages (origin === 'null') are ignored.
  • Trailing slashes stripped for comparison.
  • Empty server list gets a / (same-origin) default.

Source: /Users/admin/sillo.build/atlas/src/spec/example.ts (221 lines)

function exampleFromSchema(
schema: unknown,
resolver: RefResolver,
options?: {
maxDepth?: number
includeReadOnly?: boolean
includeWriteOnly?: boolean
onPath?: Set<string>
},
): JsonValue
graph TD
    A[exampleFromSchema] --> B{Author-supplied example?}
    B -->|Yes| C[Return it]
    B -->|No| D{"examples[0] or default or enum[0] or const?"}
    D -->|Yes| E[Return it]
    D -->|No| F{allOf?}
    F -->|Yes| G[Merge each part's examples]
    F -->|No| H{oneOf/anyOf?}
    H -->|Yes| I[Pick first branch]
    H -->|No| J[Type-based generation]
    J --> K[array: one item]
    J --> L[object: each property recursively]
    J --> M[scalars: format-aware defaults]
FormatDefault
date-time"2026-01-01T00:00:00Z"
email"user@example.com"
uuidFixed UUID
uri"https://example.com"
string"string"
integer0
number0.0
booleantrue

Tracks $ref strings on the current path via onPath: Set<string>. Returns null for cycles. Also respects maxDepth (default 8).

  • Request bodies: includeWriteOnly: true (include writeOnly, exclude readOnly).
  • Response examples: includeReadOnly: true (include readOnly, exclude writeOnly).
function typeLabel(schema: unknown, resolver: RefResolver): string

Generates one-line type labels: array<Widget>, string . date-time, Widget | null.


Source: /Users/admin/sillo.build/atlas/src/ui/dom.ts (222 lines)

Atlas builds DOM directly: no framework, no virtual DOM, no innerHTML. Every user-supplied string reaches the page as a text node.

FunctionPurpose
el(tag, attrs?, children?)Create element. text -> textContent, class -> className
append(parent, ...children)Append children, strings become text nodes
replace(parent, ...children)replaceChildren() then append
frag(...children)DocumentFragment
svg(path, size?, className?)SVG icon from path data (Feather-style)
button(className, onClick, children?, attrs?)Button with click handler
copyButton(getText, label?)Copy-to-clipboard with feedback
externalLink(label, href, className)Safe link with safeHref check
statusDot(status, label?)Colored status indicator
methodBadge(method, pill?)HTTP method badge, colored by method
tabs(entries)Tab strip with lazy panel building
debounce(fn, ms)Standard debounce
formatBytes(bytes)Human-readable byte sizes

All SVG path data in ICONS constant: chevron, search, sun, moon, copy, send, menu, check, external.

The text attribute key maps to textContent (never innerHTML). This is the fundamental XSS defense, a JSON string in a response body cannot become markup.


Source: /Users/admin/sillo.build/atlas/src/ui/search.ts (166 lines)

function searchOperations(
operations: Operation[],
query: string,
): Operation[]
Match LocationScore
Summary starts with term100
Summary contains term60
Path contains term55
Method equals term50
Tags contain term30
Description contains term12
  • All terms must match: If any term scores 0, the operation is excluded.
  • Deprecated penalty: -25.
  • Sort: Score descending, then path alphabetically as tiebreaker.
  • Limit: Top 40 results.
  • Empty query: Returns first 12 operations.

createSearch(operations, onSelect) builds a modal dialog with:

  • Input field with debounced search (80ms).
  • Arrow key navigation, Enter to select, Escape to close.
  • Mouse hover tracking.
  • data-active attribute for highlighted item.

Source: /Users/admin/sillo.build/atlas/src/client/request.ts (282 lines)

function prepareRequest(
operation: Operation,
server: string,
inputs: Record<string, string>,
auth: Record<string, string>,
securitySchemes: SecurityScheme[],
documentSecurity: Record<string, string[]>[],
): PreparedRequest

Produces a single PreparedRequest object that is both sent by the client and printed by every snippet. This is the key design decision: snippets cannot drift from what the client sends.

interface PreparedRequest {
method: string
url: string // Fully qualified
server: string // Base URL
path: string // With params substituted
query: [string, string][]
headers: [string, string][]
cookies: [string, string][]
body: string | null
contentType: string | null
missing: string[] // Required params left empty
}

Handles both {id} and {id:int} (sillo converter form) via regex. URL-encodes values.

Operation-level security overrides document-level. Supports:

Scheme TypeHeader
HTTP BearerAuthorization: Bearer <token>
HTTP BasicAuthorization: Basic <token>
apiKey (header)<name>: <value>
apiKey (query)Query parameter
apiKey (cookie)Cookie header entry
OAuth2 / OpenID ConnectAuthorization: Bearer <token>
function sendRequest(
request: PreparedRequest,
options?: { timeout?: number },
): Promise<ResponseResult>

Uses fetch() with:

  • AbortController timeout (default 30s).
  • Forbidden header filter: Browser-prohibited headers (Host, Connection, Content-Length, etc.) are excluded.
  • CORS error detection with actionable messages.

Returns: status, statusText, headers, body, parsedJson, durationMs, sizeBytes, error.


Source: /Users/admin/sillo.build/atlas/src/client/snippets.ts (351 lines)

IDLabelSyntax
curlcURLbash
httpieHTTPiebash
python-httpxPython . httpxpython
python-requestsPython . requestspython
javascript-fetchJavaScript . fetchjavascript
node-axiosNode . axiosjavascript
goGogo
phpPHPphp
rubyRubyruby

Each generator takes the same PreparedRequest the Send button uses.

graph LR
    A[User inputs] --> B[prepareRequest]
    B --> C[sendRequest]
    B --> D[curl snippet]
    B --> E[python-httpx snippet]
    B --> F[javascript-fetch snippet]
    B --> G[... 6 more]

The PreparedRequest is built once. Both the Send button and all 9 snippet generators consume the same object. Drift is impossible.

function pythonLiteral(value: unknown, indent?: number): string

Converts JSON values to Python literals: True/False/None instead of true/false/null.

sh() function uses single-quote escaping: 'text with '\''quote'\''s'.


Source: /Users/admin/sillo.build/atlas/src/ui/highlight.ts (287 lines)

function tokenize(code: string, syntax: string): Token[]

Tokenizes code into classified tokens (data, not HTML).

JSON, Python, JavaScript/TypeScript, Go, Ruby, PHP, Bash/Shell.

ClassExample
tok-keyJSON key
tok-strString literal
tok-numNumber
tok-booltrue/false
tok-nullnull
tok-kwKeyword (if, func, def)
tok-comComment
tok-fnFunction call

Custom hand-written parser that:

  • Tracks strings, distinguishing keys (followed by :) from values.
  • Handles escaped characters in strings.
  • Recognizes numbers (with decimals, exponents), booleans, null.

Tokenizes to data (Token[]), never to HTML strings. The renderer in render.ts turns tokens into <span> children via textContent.

merge(tokens) collapses adjacent same-class tokens to keep the DOM small.


Source: /Users/admin/sillo.build/atlas/src/ui/markdown.ts (248 lines)

function parseMarkdown(source: string): Block[]

Parses Markdown into a block tree AST, not HTML.

TypeDescription
paragraphConsecutive non-blank, non-special lines
heading# through ######, offset by +2 (so # becomes <h3>)
codeFenced code blocks with optional language tag
listOrdered (1.) and unordered (-, *, +)
quoteBlockquotes, recursively parsed
tablePipe tables with alignment (:---, :---:, ---:)
TypeSyntax
textPlain text
codeBackticks
strong** or __
em* or _
link[text](url)

Only recognized when the line after the header is a delimiter row (| --- |). This prevents prose containing | from being mistaken for a table. Escaped pipes (\|) stay inside their cell.

function safeHref(href: string): string | null

Allowlist for link schemes: https:, http:, mailto:, tel:, #, /, ./, ../, and relative URLs. Strips control characters and whitespace before checking (browsers interpret java\tscript: as javascript:).


Source: /Users/admin/sillo.build/atlas/src/ui/panel.ts (410 lines)

function createPanel(
spec: ParsedSpec,
resolver: RefResolver,
servers: ResolvedServer[],
defaultServerIndex: number,
): PanelHandle
graph TD
    P[Panel] --> S1[1. Server Card]
    P --> S2[2. Auth Card]
    P --> S3[3. Request Card]
    P --> S4[4. Snippet Card]
    P --> S5[5. Send Card]
    S5 --> R[Response Card]
CardContent
ServerDropdown (multiple servers) or input (single server)
AuthInput per required security scheme, persisted to localStorage under atlas.auth
RequestInput per parameter (path/query/header/cookie) with location and required tags, textarea for body
SnippetLanguage selector dropdown + code block with copy button
SendSend button, shows response card after

Shows status dot, duration, size, and tabs for Body (pretty-printed JSON with syntax highlighting) and Headers.

function seedInputs(
operation: Operation,
resolver: RefResolver,
): Record<string, string>

Pre-fills the form from schema examples so an operation is runnable immediately. Only required parameters are pre-filled (optional ones left empty to avoid silently sending filters).

Priority: parameter.example > schema.example > schema.default > enum[0] > exampleFromSchema.

The panel is built once per operation, then updated in place (not rebuilt) to preserve focus and caret position. Typing into a parameter never replaces the focused element.


Source: /Users/admin/sillo.build/atlas/src/styles/atlas.css (1189 lines)

Everything is scoped under .atlas. ~50 CSS custom properties for colors, spacing, fonts, and breakpoints.

Defined on .atlas selector. Neutral grays. Accent color is sillo crimson #fc0345.

Defined on .atlas[data-theme='dark']. Near-black backgrounds (#050505), bright method colors, brightened accent hover. Sets color-scheme: dark.

MethodColor
GETTeal
POSTBlue
PUTAmber
PATCHPurple
DELETERed

Pill badges use --at-method-fg (white on light, near-black on dark) to ensure readability.

graph TD
    A[Page load] --> B{localStorage has theme?}
    B -->|Yes| C[Use stored theme]
    B -->|No| D{prefers-color-scheme: dark?}
    D -->|Yes| E[Use dark]
    D -->|No| F[Use light]
    C --> G[Set data-theme attribute]
    E --> G
    F --> G
    G --> H{theme == 'auto'?}
    H -->|Yes| I[Watch matchMedia changes]
    H -->|No| J[Static]
WidthLayout
> 1280pxFull 3-pane (480px panel)
1080-1280pxNarrower panel (400px)
768-1080pxPanel below content (full width)
< 768pxSidebar becomes slide-out drawer
  • prefers-reduced-motion disables all animations and transitions.
  • Focus-visible outlines.
  • ARIA roles on tabs, search dialog, navigation.

Source: /Users/admin/sillo.build/atlas/src/ui/app.ts (334 lines)

Orchestrates the entire UI.

Layout: Header (sticky) > Body (flex: sidebar + main + panel)

  • Hamburger nav toggle.
  • Brand title + version badge.
  • Search button (Cmd+K/Ctrl+K or /).
  • Theme toggle button.
  • Tag groups (collapsible).
  • Operations per group with method badge + summary label.
  • “Powered by Atlas” footer pinned below scrollable nav.

Uses IntersectionObserver (not scroll listeners) to track which operation section is visible. Auto-selects in sidebar and updates panel.

Root margin: -80px 0px -60% 0px to account for header height.

Uses history.replaceState (not hash assignment) to avoid polluting browser history. openFromHash() reads location.hash on load.

ShortcutAction
Cmd+K / Ctrl+KOpen search
/Open search (when not in input)
EscapeClose search

isTyping() check prevents search from firing while editing parameters.


Source: /Users/admin/sillo.build/atlas/scripts/build.mjs

Uses esbuild with three output formats. Targets ES2021, Chrome/Firefox 100, Safari 15. Sourcemaps enabled.

Source: /Users/admin/sillo.build/atlas/scripts/dev.mjs

Builds first (to avoid serving empty dist), then starts a file server on port 5173. Runs esbuild watch in parallel. Guards against path traversal.

Runs on push to main, PRs, and weekly schedule. Node 20/22 matrix. Typecheck + build + test. Reports bundle sizes.

Manual trigger with version input. Creates a detached commit containing dist/ and tags it. This allows jsDelivr CDN serving while keeping main free of build output.

Tests use Node’s built-in node:test and node:assert/strict. DOM tests use linkedom (standards-compliant DOM implementation).

79 tests across 6 test files:

FileTestsCovers
render.test.js35Operations, sidebar, XSS, markdown, search
styles.test.js4Method pills, dark theme, specificity
snippets.test.js5Path substitution, auth, shell quoting
servers.test.js12Same-origin, LAN, proxy, file://
spec.test.js16Parsing, recursion, readOnly/writeOnly

End of document 44-ATLAS.md