{"id":12810,"library":"api-ref-bundler","title":"JSON/API Document Reference Bundler","description":"api-ref-bundler is a JavaScript/TypeScript utility library designed to resolve and consolidate all external and internal `$ref` references within JSON-based API documents. It supports a wide range of specification formats including JsonSchema, Swagger 2.x, OpenAPI 3.x, AsyncAPI 2.x, and AsyncAPI 3.x. The current stable version is 0.5.1, with recent updates focusing on performance and new specification support, notably AsyncAPI v3.x. Key differentiators include its zero-dependency footprint, browser and Node.js compatibility, explicit handling of circular references, and a resolver-agnostic design, requiring users to provide their own logic for reading and parsing source paths. This approach offers flexibility but also shifts the responsibility for file I/O and deserialization to the developer. It ships with full TypeScript support.","status":"active","version":"0.5.1","language":"javascript","source_language":"en","source_url":"https://github.com/udamir/api-ref-bundler","tags":["javascript","jsonschema","openapi","swagger","asyncapi","api","ref","bundler","dereference","typescript"],"install":[{"cmd":"npm install api-ref-bundler","lang":"bash","label":"npm"},{"cmd":"yarn add api-ref-bundler","lang":"bash","label":"yarn"},{"cmd":"pnpm add api-ref-bundler","lang":"bash","label":"pnpm"}],"dependencies":[],"imports":[{"note":"Package is ESM-first. CommonJS `require` might lead to issues in newer Node.js environments or bundlers.","wrong":"const { bundle } = require('api-ref-bundler')","symbol":"bundle","correct":"import { bundle } from 'api-ref-bundler'"},{"note":"`bundle` and `dereference` are named exports, not default.","wrong":"import dereference from 'api-ref-bundler'","symbol":"dereference","correct":"import { dereference } from 'api-ref-bundler'"},{"note":"Use `import type` for type-only imports to prevent bundling issues and improve tree-shaking.","wrong":"import { BundleOptions } from 'api-ref-bundler'","symbol":"BundleOptions","correct":"import type { BundleOptions } from 'api-ref-bundler'"},{"note":"For browser usage without a build step, the library exposes a global `ApiRefBundler` object after loading via CDN.","wrong":"import { bundle } from 'api-ref-bundler' // In browser script without build step","symbol":"ApiRefBundler (Browser Global)","correct":"<!-- In browser HTML --> <script src=\"https://cdn.jsdelivr.net/npm/api-ref-bundler@latest\"></script>"}],"quickstart":{"code":"import { promises as fs } from 'fs'\nimport * as path from 'path'\nimport { bundle, dereference } from 'api-ref-bundler'\n\n// A custom resolver function is required to load the document content.\n// It must handle both JSON and potential Markdown references.\nconst resolver = async (sourcePath: string) => {\n  try {\n    const filePath = path.join(process.cwd(), './', sourcePath);\n    const data = await fs.readFile(filePath, 'utf8');\n    // Assume .md files are plain text, others are JSON.\n    return sourcePath.slice(-3) === '.md' ? data : JSON.parse(data);\n  } catch (error) {\n    console.error(`Error resolving ${sourcePath}:`, error);\n    throw error;\n  }\n}\n\n// Example: Bundling all external references into a single document\nasync function runBundling() {\n  console.log('--- Bundling Example ---');\n  try {\n    const bundledSchema = await bundle('my-api-spec.json', resolver, { ignoreSibling: true });\n    console.log('Bundled Schema (first 500 chars):', JSON.stringify(bundledSchema, null, 2).substring(0, 500) + '...');\n  } catch (errors) {\n    console.error('Bundling errors:', errors);\n  }\n}\n\n// Example: Full dereference (removing all references)\nasync function runFullDereference() {\n  console.log('\\n--- Full Dereference Example ---');\n  const onErrorHook = (msg: string) => {\n    console.error(`Dereference Error: ${msg}`);\n    throw new Error(msg);\n  }\n  try {\n    const fullyDereferencedSchema = await dereference('my-api-spec.json', resolver, { hooks: { onError: onErrorHook } });\n    console.log('Fully Dereferenced Schema (first 500 chars):', JSON.stringify(fullyDereferencedSchema, null, 2).substring(0, 500) + '...');\n  } catch (errors) {\n    console.error('Full dereference errors:', errors);\n  }\n}\n\n// Example: Partial dereference (removing references only in a specific path)\nasync function runPartialDereference() {\n  console.log('\\n--- Partial Dereference Example ---');\n  try {\n    // Assuming 'my-api-spec.json' has a path like '#/components/schemas/User'\n    const partialDereferencedPart = await dereference('my-api-spec.json#/components/schemas/User', resolver);\n    console.log('Partially Dereferenced Segment (first 200 chars):', JSON.stringify(partialDereferencedPart, null, 2).substring(0, 200) + '...');\n  } catch (errors) {\n    console.error('Partial dereference errors:', errors);\n  }\n}\n\n// In a real scenario, you'd create 'my-api-spec.json' and any referenced files.\n// For demonstration, let's mock a simple schema.\nasync function setupMockFiles() {\n  await fs.writeFile('my-api-spec.json', JSON.stringify({\n    \"openapi\": \"3.0.0\",\n    \"info\": { \"title\": \"Mock API\", \"version\": \"1.0.0\" },\n    \"paths\": {},\n    \"components\": {\n      \"schemas\": {\n        \"User\": { \"type\": \"object\", \"properties\": { \"id\": { \"type\": \"string\" }, \"name\": { \"$ref\": \"#/components/schemas/Name\" } } },\n        \"Name\": { \"type\": \"string\" },\n        \"Address\": { \"$ref\": \"./address.json\" }\n      }\n    }\n  }), 'utf8');\n  await fs.writeFile('address.json', JSON.stringify({ \"type\": \"object\", \"properties\": { \"street\": { \"type\": \"string\" } } }), 'utf8');\n}\n\nasync function main() {\n  await setupMockFiles();\n  await runBundling();\n  await runFullDereference();\n  await runPartialDereference();\n}\n\nmain();","lang":"typescript","description":"Demonstrates how to use `bundle` and `dereference` functions with a custom file system resolver for API documents, including handling of Markdown references and error hooks, using mock files for a runnable example."},"warnings":[{"fix":"Thoroughly test existing AsyncAPI 2.x documents after upgrading. If encountering issues, refer to the AsyncAPI v3 specification for structural changes and adjust your input documents or custom resolver logic accordingly.","message":"Version 0.5.0 introduced full support for AsyncAPI v3.x. While designed for compatibility, users with complex AsyncAPI 2.x documents might experience changes in how references are resolved or processed, especially concerning the restructured architecture (top-level operations, channel-scoped messages) of AsyncAPI v3. Review your AsyncAPI documents and resolution logic if upgrading from versions prior to 0.5.0 and using AsyncAPI.","severity":"breaking","affected_versions":">=0.5.0"},{"fix":"Implement a robust `resolver` function that takes a `sourcePath` string and returns the parsed content (object for JSON, string for Markdown/text). Ensure it handles file system lookups, network requests, or any other data source your application uses.","message":"The library explicitly states 'no parser included - bring your own!' and 'no concept of resolvers - you are in charge of the whole reading & path parsing process'. This means you *must* provide a custom `resolver` function that handles reading file content (e.g., from disk, network, or memory) and parsing it (e.g., `JSON.parse` for JSON, or returning raw string for Markdown). Failure to provide a correct resolver will result in errors.","severity":"gotcha","affected_versions":">=0.1.0"},{"fix":"If your documents contain circular references and you want them to be resolved into their respective nodes during dereferencing, set `enableCircular: true` in the `DereferenceOptions`. Otherwise, implement `onCycle` hook to handle them gracefully.","message":"When using `dereference`, be aware of circular references. While the library supports them, you might need to enable the `enableCircular` option in `DereferenceOptions` if you want circular `$refs` to be converted into actual nodes rather than potentially causing errors or infinite loops in your subsequent processing.","severity":"gotcha","affected_versions":">=0.1.0"},{"fix":"Carefully consider the `ignoreSibling` option based on your specification's requirements. For OpenAPI/Swagger documents where `$ref` typically stands alone, `ignoreSibling: true` is usually appropriate. For other JSON Schema use cases where `$ref` can co-exist with other properties, ensure this option is set correctly (e.g., `false` or omitted if you want to merge).","message":"The `ignoreSibling` option, available for both `bundle` and `dereference`, controls whether content adjacent to a `$ref` is preserved or ignored. If set to `true`, any sibling properties next to a `$ref` will be discarded, which is common in OpenAPI/Swagger specifications where `$ref` should be the sole property. Incorrect usage can lead to unexpected loss of data.","severity":"gotcha","affected_versions":">=0.1.0"}],"env_vars":null,"last_verified":"2026-04-19T00:00:00.000Z","next_check":"2026-07-18T00:00:00.000Z","problems":[{"fix":"Ensure your project is configured for ESM. If using Node.js, add `\"type\": \"module\"` to your `package.json` or use `.mjs` file extension. If using a bundler (e.g., Webpack, Rollup), verify its configuration for handling ESM imports. As a workaround for CommonJS, you might try `const { bundle } = await import('api-ref-bundler');` for dynamic ESM import.","cause":"This error typically occurs in a CommonJS environment or certain bundler configurations when trying to import an ESM-first package using named exports directly, or when the bundler fails to correctly transpile ESM `import` statements.","error":"TypeError: (0 , api_ref_bundler__WEBPACK_IMPORTED_MODULE_2__.bundle) is not a function"},{"fix":"Double-check the `sourcePath` being passed to the `resolver` and ensure the file exists. Verify that `path.join` (if used in your resolver) constructs the correct absolute or relative path. Ensure your application has read permissions for the file system location.","cause":"The custom `resolver` function failed to find or read the specified file. This often happens due to incorrect file paths, insufficient permissions, or the file not existing at the expected location relative to the script or `process.cwd()`.","error":"Error: ENOENT: no such file or directory, open 'path/to/missing-file.json'"},{"fix":"Refine the logic within your `resolver` to correctly identify the content type based on file extension, MIME type, or content sniffing. Only apply `JSON.parse` to content confirmed to be JSON. For non-JSON content like Markdown, return it as a raw string as intended by the library.","cause":"Your `resolver` function attempted to `JSON.parse` content that was not valid JSON. This commonly happens if a `.md` (Markdown) or other non-JSON file is incorrectly identified and passed to `JSON.parse`.","error":"SyntaxError: Unexpected token '...' in JSON at position X"},{"fix":"If you want circular references to be converted into their respective nodes during dereferencing, set `enableCircular: true` in your `DereferenceOptions`. Alternatively, implement the `onCycle` hook within `DereferenceOptions` to provide custom logic for handling or reporting cyclic references without throwing an error.","cause":"The dereferencing process encountered a circular reference, and the `enableCircular` option was not set to `true` or the `onCycle` hook was not implemented to handle it.","error":"Error: Cyclic $ref detected for 'some/path/to/ref'"}],"ecosystem":"npm","meta_description":null,"install_score":null,"install_tag":null,"quickstart_score":null,"quickstart_tag":null,"pypi_latest":null}