{"_id":"@airnub/wellknown-api-catalog","name":"@airnub/wellknown-api-catalog","dist-tags":{"next":"0.1.0-alpha.2","latest":"0.1.0-alpha.2"},"versions":{"0.1.0-alpha.2":{"name":"@airnub/wellknown-api-catalog","version":"0.1.0-alpha.2","description":"RFC 9727 /.well-known/api-catalog handler with Linkset JSON output","license":"Apache-2.0","main":"dist/index.cjs","module":"dist/index.mjs","types":"dist/index.d.ts","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.cjs"}},"scripts":{"build":"tsup","test":"pnpm run build && vitest run","lint":"eslint \"src/**/*.ts\" \"tests/**/*.ts\"","prepare":"pnpm run build"},"keywords":["api","catalog","linkset","well-known","rfc9727","openapi","graphql"],"engines":{"node":">=18"},"dependencies":{"forwarded-http":"^0.3.0","proxy-addr":"^2.0.7"},"devDependencies":{"@types/express":"^4.17.21","@types/node":"^20.11.19","@types/supertest":"^2.0.16","@typescript-eslint/eslint-plugin":"^7.2.0","@typescript-eslint/parser":"^7.2.0","eslint":"^8.57.0","eslint-config-prettier":"^9.1.0","express":"^4.18.3","fastify":"^4.26.1","prettier":"^3.2.5","supertest":"^6.3.4","tsup":"^8.0.2","typescript":"5.5.4","vitest":"^1.3.1"},"_id":"@airnub/wellknown-api-catalog@0.1.0-alpha.2","gitHead":"b711110104a3ff2fd3b991e941f40e6949d58b46","_nodeVersion":"20.19.5","_npmVersion":"10.8.2","dist":{"integrity":"sha512-Z54uUKHYL+8eoemfWym0YBmFyb4cFPp998OFFfeSKy6C8pUP3ldTNfZf32NObN7xqHc53Mwyei9TLkxJCJZ/uQ==","shasum":"6c8d34d27896c2e3cb4de6ff8d5f1c4bace1e213","tarball":"https://registry.npmjs.org/@airnub/wellknown-api-catalog/-/wellknown-api-catalog-0.1.0-alpha.2.tgz","fileCount":7,"unpackedSize":87520,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIH5wifoQ+lvCIFff0pXuEHalVAEzY0olX/o9drBw8vKTAiAs8h0rSaZ3W38xi8FHNtJHmd6i3buoCIdJPViLPCcKZQ=="}]},"_npmUser":{"name":"alangunning","email":"alangunning@gmail.com"},"directories":{},"maintainers":[{"name":"alangunning","email":"alangunning@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/wellknown-api-catalog_0.1.0-alpha.2_1763481443263_0.1253571040789827"},"_hasShrinkwrap":false}},"time":{"created":"2025-11-18T15:57:23.178Z","0.1.0-alpha.2":"2025-11-18T15:57:23.476Z","modified":"2025-11-18T15:57:23.809Z"},"maintainers":[{"name":"alangunning","email":"alangunning@gmail.com"}],"description":"RFC 9727 /.well-known/api-catalog handler with Linkset JSON output","keywords":["api","catalog","linkset","well-known","rfc9727","openapi","graphql"],"license":"Apache-2.0","readme":"# @airnub/wellknown-api-catalog\n\n[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://github.com/airnub-labs/wellknown/blob/main/LICENSE)\n[![npm version](https://img.shields.io/npm/v/@airnub/wellknown-api-catalog.svg)](https://www.npmjs.com/package/@airnub/wellknown-api-catalog)\n[![npm downloads](https://img.shields.io/npm/dm/@airnub/wellknown-api-catalog.svg)](https://www.npmjs.com/package/@airnub/wellknown-api-catalog)\n[![CI](https://github.com/airnub-labs/wellknown/actions/workflows/ci.yml/badge.svg)](https://github.com/airnub-labs/wellknown/actions/workflows/ci.yml)\n[![Docs](https://img.shields.io/badge/docs-GitHub%20Pages-blue)](https://airnub-labs.github.io/wellknown/)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5.5-blue.svg)](https://www.typescriptlang.org/)\n\nPublish an RFC 9727 `/.well-known/api-catalog` endpoint backed by Linkset JSON (RFC 9264) so humans, SDKs, and AI coding agents can discover the live API surface area of your host.\n\n## Features\n\n- **RFC-compliant** – Emits `application/linkset+json` with the `api-catalog` link relation and RFC 9727 profile header\n- **Spec-agnostic** – Link to OpenAPI, AsyncAPI, GraphQL SDL, JSON Schema, or any other format via RFC 8631 service link relations\n- **Proxy-aware** – Reconstructs externally-visible origin using `Forwarded` and `X-Forwarded-*` headers with trust-proxy support\n- **Framework integrations** – Drop-in handlers for Express, Fastify, Next.js App Router, and Supabase Edge Functions\n- **TypeScript native** – Fully typed with comprehensive IntelliSense support\n- **Zero configuration** – Sensible defaults with automatic Content-Type, profile parameters, and Link headers\n- **Helper functions** – Built-in helpers for OpenAPI, GraphQL, AsyncAPI, and JSON Schema with correct MIME types\n\n## Installation\n\nThis package is currently in pre-release (`0.1.0-next.x`). Install via the `next` dist-tag until the first stable release:\n\n```bash\nnpm install @airnub/wellknown-api-catalog@next\n```\n\n```bash\npnpm add @airnub/wellknown-api-catalog@next\n```\n\n## Quick Start\n\nAll RFC complexity (well-known paths, Content-Types, profile URIs) is handled automatically.\n\n### Next.js App Router\n\nCreate `app/.well-known/api-catalog/route.ts`:\n\n```ts\nimport { NextRequest, NextResponse } from 'next/server';\nimport { createNextApiCatalogRoutes } from '@airnub/wellknown-api-catalog';\n\nexport const { GET, HEAD } = createNextApiCatalogRoutes(\n  {\n    apis: [\n      {\n        id: 'my-api',\n        basePath: '/api/v1',\n        specs: [{ href: '/api/v1/openapi.json' }],\n      },\n    ],\n  },\n  NextRequest,\n  NextResponse\n);\n```\n\n### Express\n\n```ts\nimport express from 'express';\nimport { registerExpressApiCatalog } from '@airnub/wellknown-api-catalog';\n\nconst app = express();\n\nregisterExpressApiCatalog(app, {\n  apis: [\n    {\n      id: 'my-api',\n      basePath: '/api/v1',\n      specs: [{ href: '/api/v1/openapi.json' }],\n    },\n  ],\n});\n```\n\n### Fastify\n\n```ts\nimport Fastify from 'fastify';\nimport { registerFastifyApiCatalog } from '@airnub/wellknown-api-catalog';\n\nconst fastify = Fastify();\n\nregisterFastifyApiCatalog(fastify, {\n  apis: [\n    {\n      id: 'my-api',\n      basePath: '/api/v1',\n      specs: [{ href: '/api/v1/openapi.json' }],\n    },\n  ],\n});\n```\n\n### Supabase Edge Functions / Deno\n\nCreate `supabase/functions/api-catalog/index.ts`:\n\n```ts\nimport { serve } from 'https://deno.land/std/http/server.ts';\nimport { createApiCatalogHandler } from 'npm:@airnub/wellknown-api-catalog';\n\nserve(\n  createApiCatalogHandler({\n    apis: [\n      {\n        id: 'my-api',\n        basePath: '/api/v1',\n        specs: [{ href: '/api/v1/openapi.json' }],\n      },\n    ],\n  })\n);\n```\n\nWorks with Supabase Edge Functions, Deno Deploy, Cloudflare Workers, and any Fetch API runtime.\n\n## Overview\n\nAgents increasingly rely on live HTTP calls, but today they often depend on hand-curated OpenAPI URLs, plugin manifests, or stale documentation. RFC 9727 introduces a single, predictable discovery point: `/.well-known/api-catalog`. When that endpoint returns a Linkset describing every API on a host, agents can:\n\n1. `GET /.well-known/api-catalog`\n2. Parse the `linkset` array (each entry is an API anchor)\n3. Follow `service-desc` links to machine-readable specs (OpenAPI, GraphQL, AsyncAPI, JSON Schema…)\n4. Fetch those specs to construct clients, tooling configs, or safety checks directly against your live infrastructure\n\nNo more guessing, scraping docs, or relying on out-of-date manifests.\n\n## Usage\n\n### Helper Functions\n\nThe package provides helper functions for common API specification formats with correct MIME types and profile URIs.\n\n#### OpenAPI\n\n```typescript\nimport { openApiSpec } from '@airnub/wellknown-api-catalog';\n\n// OpenAPI 3.1 (default)\nopenApiSpec('/api/openapi.json');\n\n// OpenAPI 3.0\nopenApiSpec('/api/openapi.json', '3.0');\n```\n\n#### GraphQL\n\n```typescript\nimport { graphqlSchemaSpec } from '@airnub/wellknown-api-catalog';\n\n// GraphQL SDL schema (default)\ngraphqlSchemaSpec('/api/schema.graphql');\n\n// GraphQL introspection result\ngraphqlSchemaSpec('/api/introspection', { format: 'introspection' });\n```\n\n#### AsyncAPI\n\n```typescript\nimport { asyncApiSpec } from '@airnub/wellknown-api-catalog';\n\n// AsyncAPI 3.0 (default)\nasyncApiSpec('/api/asyncapi.json');\n\n// AsyncAPI 2.0\nasyncApiSpec('/api/asyncapi.json', '2.0');\n```\n\n#### JSON Schema\n\n```typescript\nimport { jsonSchemaSpec } from '@airnub/wellknown-api-catalog';\n\n// JSON Schema 2020-12 (default)\njsonSchemaSpec('/api/schema.json');\n\n// JSON Schema 2019-09\njsonSchemaSpec('/api/schema.json', '2019-09');\n\n// JSON Schema draft-07\njsonSchemaSpec('/api/schema.json', '07');\n```\n\n### Linkset Output\n\n`buildApiCatalogLinkset` returns a payload that mirrors RFC 9264:\n\n```json\n{\n  \"linkset\": [\n    {\n      \"anchor\": \"https://api.example.com/apis/service-one\",\n      \"service-desc\": [\n        { \"href\": \"/apis/service-one/openapi.json\", \"type\": \"application/vnd.oai.openapi+json\" }\n      ],\n      \"service-doc\": [\n        { \"href\": \"https://docs.example.com/service-one\", \"type\": \"text/html\" }\n      ]\n    }\n  ],\n  \"linkset-metadata\": [\n    {\n      \"profile\": \"https://www.rfc-editor.org/info/rfc9727\",\n      \"publisher\": \"example-publisher\"\n    }\n  ]\n}\n```\n\nEvery API anchor is a fully-qualified origin plus base path with trailing slashes trimmed. Specs default to the `service-desc` relation (unless you override the `rel` per entry), and the metadata block announces the RFC 9727 profile plus the optional `publisher` you supply in the config.\n\n### AI / Agent Workflow\n\n```mermaid\nsequenceDiagram\n  participant Agent\n  participant Host\n\n  Agent->>Host: GET /.well-known/api-catalog\n  Host-->>Agent: 200 application/linkset+json\n  Agent->>Host: GET <service-desc href> (e.g., OpenAPI JSON)\n  Host-->>Agent: 200 application/vnd.oai.openapi+json\n  Agent->>Agent: Parse spec, generate client, enforce auth/policies\n```\n\n## API Reference\n\n### Express\n\n#### `registerExpressApiCatalog(app, config)`\n\nRegisters GET and HEAD handlers at `/.well-known/api-catalog`.\n\n```ts\nimport { registerExpressApiCatalog } from '@airnub/wellknown-api-catalog';\n\nregisterExpressApiCatalog(app, {\n  publisher: 'my-company',\n  apis: [\n    {\n      id: 'my-api',\n      title: 'My API',\n      basePath: '/api/v1',\n      specs: [\n        { href: '/api/v1/openapi.json', type: 'application/vnd.oai.openapi+json' },\n        { rel: 'service-doc', href: 'https://docs.example.com', type: 'text/html' },\n      ],\n    },\n  ],\n});\n```\n\n#### `createExpressApiCatalogHandler(config)`\n\nLow-level function that returns an Express request handler for GET requests.\n\n```ts\nimport { createExpressApiCatalogHandler } from '@airnub/wellknown-api-catalog';\n\napp.get('/.well-known/api-catalog', createExpressApiCatalogHandler(config));\n```\n\n#### `createExpressApiCatalogHeadHandler(config)`\n\nLow-level function that returns an Express request handler for HEAD requests.\n\n```ts\nimport { createExpressApiCatalogHeadHandler } from '@airnub/wellknown-api-catalog';\n\napp.head('/.well-known/api-catalog', createExpressApiCatalogHeadHandler(config));\n```\n\n### Fastify\n\n#### `registerFastifyApiCatalog(fastify, config)`\n\nRegisters GET and HEAD routes at `/.well-known/api-catalog`.\n\n```ts\nimport { registerFastifyApiCatalog } from '@airnub/wellknown-api-catalog';\n\nregisterFastifyApiCatalog(fastify, {\n  publisher: 'my-company',\n  apis: [\n    {\n      id: 'my-api',\n      title: 'My API',\n      basePath: '/api/v1',\n      specs: [\n        { href: '/api/v1/openapi.json', type: 'application/vnd.oai.openapi+json' },\n      ],\n    },\n  ],\n});\n```\n\n#### `fastifyApiCatalogPlugin`\n\nFastify plugin for use with `fastify.register()`. Note: config must be wrapped in `{ config: ... }`.\n\n```ts\nimport { fastifyApiCatalogPlugin } from '@airnub/wellknown-api-catalog';\n\nawait fastify.register(fastifyApiCatalogPlugin, { config });\n```\n\n### Next.js\n\n#### `createNextApiCatalogRoutes(config, NextRequest, NextResponse)`\n\nReturns GET and HEAD route handlers for Next.js App Router.\n\n```ts\nimport { createNextApiCatalogRoutes } from '@airnub/wellknown-api-catalog';\nimport { NextRequest, NextResponse } from 'next/server';\n\nexport const { GET, HEAD } = createNextApiCatalogRoutes(\n  config,\n  NextRequest,\n  NextResponse\n);\n```\n\n### Framework-Agnostic\n\n#### `createApiCatalogHandler(config)`\n\nReturns a Fetch API handler (works with Deno, Cloudflare Workers, Supabase Edge Functions).\n\n```ts\nimport { createApiCatalogHandler } from '@airnub/wellknown-api-catalog';\n\nconst handler = createApiCatalogHandler(config);\n```\n\n#### `buildApiCatalogLinksetForOrigin(config, origin)`\n\nLow-level function that builds the Linkset JSON for a given origin.\n\n```ts\nimport { buildApiCatalogLinksetForOrigin } from '@airnub/wellknown-api-catalog';\n\nconst linkset = buildApiCatalogLinksetForOrigin(config, 'https://api.example.com');\n```\n\n#### `createGetResponse(linkset, origin)`\n\nCreates a GET response object with proper headers.\n\n```ts\nimport { createGetResponse } from '@airnub/wellknown-api-catalog';\n\nconst response = createGetResponse(linkset, origin);\n```\n\n#### `createHeadResponse(origin)`\n\nCreates a HEAD response object with proper headers.\n\n```ts\nimport { createHeadResponse } from '@airnub/wellknown-api-catalog';\n\nconst response = createHeadResponse(origin);\n```\n\n### Constants\n\n```ts\nimport {\n  RFC9727_PROFILE,\n  API_CATALOG_PATH,\n  LINKSET_CONTENT_TYPE,\n  API_CATALOG_LINK_REL,\n} from '@airnub/wellknown-api-catalog';\n\nconsole.log(RFC9727_PROFILE); // https://www.rfc-editor.org/info/rfc9727\nconsole.log(API_CATALOG_PATH); // /.well-known/api-catalog\nconsole.log(LINKSET_CONTENT_TYPE); // application/linkset+json; profile=\"...\"\nconsole.log(API_CATALOG_LINK_REL); // api-catalog\n```\n\n## Configuration\n\n### Origin Strategies\n\nChoose how anchors are materialized:\n\n#### From Request (default)\n\nBuilds anchors from the incoming request with proxy support:\n\n```ts\n{\n  originStrategy: {\n    kind: 'fromRequest',\n    trustProxy: true // or false, or IP/subnet list, or custom function\n  }\n}\n```\n\n`trustProxy` mirrors Express semantics:\n- `true` – Trust all proxies\n- `false` – Don't trust any proxies (default)\n- Array of IP/subnet strings – Trust specific proxies (compatible with `proxy-addr` syntax)\n- Function – Custom trust function\n\nWhen trusted, `Forwarded` / `X-Forwarded-*` headers determine the scheme and host.\n\n#### Fixed Origin\n\nHard-code the public origin (great for serverless functions or API gateways):\n\n```ts\n{\n  originStrategy: {\n    kind: 'fixed',\n    origin: 'https://api.example.com',\n    basePath: '/apis' // optional common prefix\n  }\n}\n```\n\n### API Configuration\n\nEach API entry supports:\n\n```ts\n{\n  id: 'unique-api-id',           // Required: unique identifier\n  title: 'My API',                // Optional: human-readable title\n  basePath: '/api/v1',            // Required: API base path\n  absoluteAnchor: 'https://...',  // Optional: override computed anchor\n  specs: [                        // Required: array of spec references\n    {\n      href: '/path/to/spec',      // Required: spec URL\n      rel: 'service-desc',        // Optional: link relation (default: 'service-desc')\n      type: 'application/json',   // Optional: MIME type\n      profile: 'https://...',     // Optional: profile URI\n    }\n  ]\n}\n```\n\n## Advanced Topics\n\n### Security Considerations\n\n`trustProxy` defaults to `false`. Only enable `trustProxy: true` (or whitelist addresses) when you control the proxy hop closest to your application. Otherwise an attacker could spoof `Forwarded` headers and publish incorrect origins. For zero-trust scenarios, keep `trustProxy: false` or use the `fixed` strategy so anchors always reflect your local listener configuration.\n\n### Custom Framework Integration\n\nFor custom frameworks or edge runtimes, use the core builder functions:\n\n```ts\nimport {\n  buildApiCatalogLinksetForOrigin,\n  createGetResponse,\n  createHeadResponse,\n} from '@airnub/wellknown-api-catalog';\n\nfunction handleApiCatalog(request) {\n  const origin = new URL(request.url).origin;\n  const config = { apis: [...] };\n\n  if (request.method === 'GET') {\n    const linkset = buildApiCatalogLinksetForOrigin(config, origin);\n    const response = createGetResponse(linkset, origin);\n    return new Response(response.body, {\n      status: response.status,\n      headers: response.headers,\n    });\n  }\n\n  if (request.method === 'HEAD') {\n    const response = createHeadResponse(origin);\n    return new Response(null, {\n      status: response.status,\n      headers: response.headers,\n    });\n  }\n}\n```\n\n### How Agents Can Consume the Catalog\n\n1. Fetch the catalog and iterate over `linkset` entries\n2. Use `anchor` as the API base URL\n3. Look for `service-desc` links; inspect `type` + `profile` to detect OpenAPI, GraphQL, JSON Schema, AsyncAPI, or custom specs\n4. Download the spec, build runtime clients, or feed it to an LLM toolchain\n5. Optional: follow `service-doc` for human docs, `status` for health endpoints, or `service-meta` for terms/auth notes\n\n## Standards\n\nThis package implements the following RFCs:\n\n- **RFC 9727** – Defines `/.well-known/api-catalog`, the `api-catalog` link relation, and the requirement to advertise catalogs with the `https://www.rfc-editor.org/info/rfc9727` profile\n- **RFC 9264** – Specifies Linkset JSON (`application/linkset+json`), the payload format emitted by this package\n- **RFC 8631** – Enumerates link relations like `service-desc`, `service-doc`, `service-meta`, and `status`\n- **RFC 7239** – Details the `Forwarded` header used to safely reconstruct the externally-visible origin even when you sit behind proxies or CDNs\n\n## Versioning\n\nThis package follows semantic versioning. Breaking changes to the config types or Linkset emission format will trigger a major version bump. Pin to the latest minor within a major stream for stability.\n\n## Contributing\n\nContributions are welcome! Please see the [main repository](https://github.com/airnub-labs/wellknown) for contribution guidelines.\n\n## License\n\nCopyright © 2024-2025 Airnub Technologies Limited\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.\n\n## Links\n\n- **Documentation**: https://airnub-labs.github.io/wellknown/\n- **Repository**: https://github.com/airnub-labs/wellknown\n- **Issues**: https://github.com/airnub-labs/wellknown/issues\n- **npm**: https://www.npmjs.com/package/@airnub/wellknown-api-catalog\n","readmeFilename":"README.md","_rev":"1-2d4015b8cb14b6089e1c61f104f26890"}