🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@amritk/resolve-refs

Package Overview
Dependencies
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@amritk/resolve-refs

Resolve and inline JSON Schema / OpenAPI $refs — internal, cross-file, and remote — with session caching and a default-deny SSRF guard.

Source
npmnpm
Version
0.4.0
Version published
Weekly downloads
568
45.64%
Maintainers
1
Weekly downloads
 
Created
Source

@amritk/resolve-refs

Resolve and inline JSON Schema / OpenAPI $refs — internal pointers, cross-file refs, and remote (http/https) documents — into a single dereferenced document.

  • One-pass, cached. Every unique ref is resolved once; the result is always finite. At a reference cycle the recursive branch is kept, not lost: the cycle point stays a $ref that resolves within the output document (a cross-file cycle target is hoisted into the root's $defs), so recursive schemas survive dereferencing intact.
  • Anchors + dynamic refs, $id-scoped. Beyond JSON-pointer $refs, plain-name $anchor references (#node), $dynamicRef/$dynamicAnchor (2020-12), and $recursiveRef/$recursiveAnchor (2019-09) are dereferenced too. $id base-URI scoping is modelled for the bundled-document case: a ref whose URI matches an embedded resource's $id resolves to it without fetching, and anchors bind within the resource that declares them (falling back to a document-global search). See $id scoping below for the exact subset.
  • Cross-file + remote. Relative refs resolve against the document they appear in (a ref inside a remote doc stays remote, one inside a local file stays local). Fetched remote documents are cached for the lifetime of the process.
  • Default-deny SSRF guard. Remote refs to loopback, private, link-local, and cloud-metadata (169.254.169.254) hosts are refused unless you opt in.
  • OpenAPI Reference Objects. A $ref whose only siblings are summary / description inlines the target with those annotations overriding — matching OpenAPI 3.1 Reference Object semantics, where an allOf wrapper would be invalid. Any other sibling keyword keeps the spec-correct allOf combination.

Usage

import { resolveRefs, resolveRefsFromFile } from '@amritk/resolve-refs'

// In-memory, internal (#/...) refs only. External refs (another file or an
// http(s) URL) are left in place and reported on `errors`, since this resolver
// can't load other documents — use resolveRefsFromFile for those:
const { resolved, errors } = resolveRefs(myDocument)

// From disk or a URL, including cross-file and remote refs:
const result = await resolveRefsFromFile('./schema.json')
const remote = await resolveRefsFromFile('https://api.example.com/schema.json', {
  allowedHosts: ['api.example.com'],
})

Options (resolveRefsFromFile)

OptionDefaultDescription
remotetrueWhether http(s) refs may be fetched at all.
allowedHosts[]If non-empty, only these hosts may be fetched. An explicit entry bypasses the private-host guard.
allowPrivateHostsfalseAllow loopback/private/link-local targets. Left off, these are refused as an SSRF guard.
headersExtra headers for remote requests (record, or (url) => headers for per-host credentials). Never sent across a cross-origin redirect.
fetchglobal fetchCustom fetch implementation. The SSRF guard still evaluates every hop before it is called.
timeoutMs30_000Abort an unresponsive remote fetch after this many milliseconds.
maxRedirects5Redirect hops to follow per remote document (each hop re-runs the SSRF guard).
maxBytes16 MiBRefuse to buffer a remote document larger than this.
cachetruePass false to bypass the process-wide session cache for this call — everything is re-fetched, nothing is stored.
parseJSON.parseCustom content parser (e.g. YAML-aware).
trackOriginsfalseRecord a per-node origin map on the result.

Errors (a missing file, a refused host, a bad URL) are collected on result.errors rather than thrown; the corresponding ref resolves to {} so the rest of the document still resolves.

clearRemoteCache() drops every cached remote document — useful in tests or long-lived sessions where remote schemas may change.

$id scoping

The supported subset, chosen for the bundled-document reality rather than the full spec:

  • A subschema with $id is an embedded resource: its $id (resolved against the enclosing base) becomes the base URI for everything inside it.
  • A ref whose URI — resolved against the enclosing base — matches an embedded resource's $id resolves to that resource without fetching. A pointer or anchor fragment on such a ref applies within that resource.
  • Anchors bind within the resource that declares them first; an anchor not found in scope falls back to a document-global search (compatibility with documents that reference across sibling resources).
  • A plain #/pointer fragment stays document-root-relative — the behavior bundled real-world documents rely on — even inside an embedded resource.
  • $dynamicRef prefers a $dynamicAnchor in scope, then degrades to $ref semantics. The full dynamic-scope algorithm (outermost anchor along the runtime reference chain) is not modelled.
  • Document retrieval is unaffected: which file/URL an external ref loads from is derived from the referencing document's location, never its $id — a root $id naming a remote URL cannot turn a local sibling-file ref into a network fetch.

Documents

Every document — local file or remote — is parsed as JSON. mjst works with JSON Schema documents only, so this resolver stays JSON-only and dependency-free. (The Loupe linter's sibling resolver additionally accepts YAML.)

Benchmarks

resolveRefs is single-pass: every unique $ref string is resolved exactly once and memoized, with a sentinel that breaks cycles. The bench/ suite isolates what that memoization buys by pitting it against a naive inliner that re-resolves each ref every time it is encountered — same inlined output, the cache is the only difference. Representative numbers (Bun 1.3, Linux x64 — your hardware will differ, run bun run bench yourself):

schemacachednaivespeedup
reuse-heavy (50 refs → 1 def)~35k ops/s~6k ops/s~5.8×
chain (40 $ref$ref links)~5k ops/s~0.6k ops/s~8.2×
cyclic tree~122k ops/s~117k ops/s~1.05×
wide-distinct (60 defs, each used once)~3.3k ops/s~6.1k ops/s~0.54×

The cache earns its keep exactly where you'd expect: a $def referenced from many sites, or a long indirection chain, is resolved once instead of re-walked every time. On a document where every ref is distinct — so the cache never hits — its bookkeeping is pure overhead and the naive walk is faster; that wide-distinct row is kept in the table precisely to show the trade honestly. Real API schemas lean heavily on shared $defs, which is the case the resolver is tuned for.

Opting into trackOrigins (which records where each inlined value came from) adds roughly 5–20% on top. Both strategies are asserted to produce byte-identical output before either is timed.

Keywords

json-schema

FAQs

Package last updated on 14 Jul 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts