Skip to content

feat: add synchronous dereferenceSync and bundleSync methods#425

Open
vanhumbeecka wants to merge 1 commit into
APIDevTools:mainfrom
vanhumbeecka:feat/sync-dereference
Open

feat: add synchronous dereferenceSync and bundleSync methods#425
vanhumbeecka wants to merge 1 commit into
APIDevTools:mainfrom
vanhumbeecka:feat/sync-dereference

Conversation

@vanhumbeecka

@vanhumbeecka vanhumbeecka commented Jul 22, 2026

Copy link
Copy Markdown

Note

This PR is AI-generated — it was written by Claude (model: Fable 5, Anthropic) via Claude Code, under the direction and review of @vanhumbeecka.

Summary

Adds a synchronous API for the common case where the schema is already a JavaScript object and all of its $ref pointers can be resolved in memory:

  • dereferenceSync(schema, [options])
  • bundleSync(schema, [options])

Both are available as instance methods, static methods, and named exports, mirroring the async API.

Usage

import $RefParser, { dereferenceSync, bundleSync } from "@apidevtools/json-schema-ref-parser";

const mySchema = {
  type: "object",
  definitions: {
    name: { type: "string" },
  },
  properties: {
    firstName: { $ref: "#/definitions/name" },
    lastName: { $ref: "#/definitions/name" },
  },
};

// No async/await needed — usable in constructors, module scope, getters, etc.
const schema = $RefParser.dereferenceSync(mySchema);
console.log(schema.properties.firstName); // => { type: "string" }

// Reference equality is maintained, same as the async API
schema.properties.firstName === schema.properties.lastName; // => true

// Instance methods and named exports work too
const parser = new $RefParser();
parser.dereferenceSync(mySchema);
parser.$refs.circular; // => false

bundleSync(mySchema); // sync counterpart of bundle()

// All existing options are supported
const cloned = dereferenceSync(mySchema, { mutateInputSchema: false });

Compound documents (embedded schema resources with their own $id) work, including external-looking refs that resolve in memory:

const schema = $RefParser.dereferenceSync({
  $id: "https://example.com/schemas/customer",
  $schema: "https://json-schema.org/draft/2020-12/schema",
  type: "object",
  properties: {
    shipping_address: { $ref: "/schemas/address" }, // resolves to the embedded resource below
  },
  $defs: {
    address: {
      $id: "https://example.com/schemas/address",
      type: "object",
    },
  },
});

References that would require I/O fail fast with a clear error:

$RefParser.dereferenceSync({ $ref: "definitions/person.yaml" });
// JSONParserError: Cannot resolve external reference "definitions/person.yaml"
// synchronously; use the asynchronous dereference method instead

$RefParser.dereferenceSync("my-schema.yaml");
// Error: Expected a schema object. dereferenceSync only supports schema objects;
// to dereference a file path or URL, use the asynchronous dereference method instead

Motivation

A synchronous API has been requested since 2015 (#14, 34 comments; #82 closed as its duplicate). #14 was closed pointing to json-schema-reader as the successor with a planned sync option, but that project stalled. Meanwhile users resort to the workaround from that thread — importing lib/dereference directly and hand-building the $refs map — or to event-loop-blocking wrappers that don't work in browsers.

The core engine (lib/dereference.ts, lib/bundle.ts, and all pointer/ref logic) is already fully synchronous; only the public API plumbing and the resolver I/O are async. When a schema object with only in-memory-resolvable refs is passed, the async path never touches a resolver — so a sync API requires no changes to the engine at all.

Scope and design

Strictly limited to in-memory schemas — deliberately. This PR does not add sync file reading or any sync resolver plumbing; that boundary is what kept previous attempts from converging.

  • Input must be a schema object. File paths/URLs throw an Error pointing to the async API.
  • External $refs that would require I/O throw a JSONParserError up front. The check (assertNoUnresolvedExternalRefs in lib/resolve-external.ts) mirrors the $id-scope resolution rules of the async crawler, so compound documents — external-looking refs into embedded schema resources with their own $id — work fully.
  • No changes to dereference.ts, bundle.ts, or any pointer/ref logic. No new dependencies. The async path is untouched.
  • Because no I/O is performed, both methods behave identically in Node.js and browsers.

Testing

  • New test/specs/sync/sync.spec.ts (14 tests, written test-first): internal refs with reference equality, circular refs + $refs.circular flag, compound documents, sync/async output-parity tests for both methods, and both error guards.
  • Full node suite: 491 passed. Browser (jsdom) suite: 458 passed. tsc --noEmit and eslint lib clean.

Docs

  • New sections in docs/ref-parser.md; feature bullet + example in README.md.

Closes #14
Closes #82

🤖 Generated with Claude Code

Adds a synchronous API for schema objects whose $ref pointers can all be
resolved in memory. dereferenceSync() and bundleSync() accept a schema
object (not a file path or URL) and reuse the existing, already-synchronous
dereference/bundle engine, so behavior is identical to the async methods.

External references that would require I/O throw a JSONParserError up
front, with a message pointing to the asynchronous API. References to
embedded schema resources (compound documents with their own $id) remain
fully supported. Since no I/O is performed, both methods work identically
in Node.js and browsers.

Closes the long-standing request in APIDevTools#82 / APIDevTools#14.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vanhumbeecka
vanhumbeecka marked this pull request as ready for review July 22, 2026 07:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sync versions of dereference, bundle, etc...? Syncronous API

1 participant