{"_id":"@accorudo/openapi","name":"@accorudo/openapi","dist-tags":{"latest":"0.0.1"},"versions":{"0.0.1":{"name":"@accorudo/openapi","type":"module","version":"0.0.1","description":"Extract Accorudo contract bundles into an intermediate representation and emit OpenAPI 3.1 specs.","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./dist":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"types":"./dist/index.d.ts","sideEffects":false,"keywords":["typescript","api","openapi","codegen","accorudo"],"author":{"name":"Stavros Tsioulis"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/stavros-tsioulis/accorudo.git","directory":"packages/openapi"},"bugs":{"url":"https://github.com/stavros-tsioulis/accorudo/issues"},"homepage":"https://accorudo.stavrostsioulis.com","publishConfig":{"access":"public"},"engines":{"node":">=20"},"dependencies":{"ts-morph":"^28.0.0","yaml":"^2.9.0"},"devDependencies":{"@types/node":"^26.4.1","typescript":"^7.0.2","vitest":"^5.0.0","@accorudo/client":"0.0.1","@accorudo/core":"0.0.1"},"scripts":{"build":"tsc","test":"vitest run"},"_id":"@accorudo/openapi@0.0.1","_integrity":"sha512-fnoKy9e7fGf/oHkcVR2aFj3qATOqMBtDkPMyHbNqzltSeYqiQUx0zHGomVuJ/WsTqUJcXH0Kg5YiACOCcDGlJQ==","_resolved":"/private/var/folders/3p/1h2y67gj1010wcdssdw5pb0r0000gn/T/45624438a4c177b693cd46202a30debe/accorudo-openapi-0.0.1.tgz","_from":"file:accorudo-openapi-0.0.1.tgz","_nodeVersion":"24.5.0","_npmVersion":"11.5.1","dist":{"integrity":"sha512-fnoKy9e7fGf/oHkcVR2aFj3qATOqMBtDkPMyHbNqzltSeYqiQUx0zHGomVuJ/WsTqUJcXH0Kg5YiACOCcDGlJQ==","shasum":"fbcfc24492988461460dfb53999948bd15c238b4","tarball":"https://registry.npmjs.org/@accorudo/openapi/-/openapi-0.0.1.tgz","fileCount":43,"unpackedSize":250933,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCtSVXj9bVQwpPo7auwpaur3dmHxri1ODodU/Zn/Cy9aQIhAKT9LWF67UIaFIg+rxtEZ3TvA+IqdUG0Yt5C+zWbF4RR"}]},"_npmUser":{"name":"stavros-tsioulis","email":"contact@stavrostsioulis.com"},"directories":{},"maintainers":[{"name":"stavros-tsioulis","email":"contact@stavrostsioulis.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/openapi_0.0.1_1788611394711_0.23233122953635998"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-05T12:29:54.534Z","0.0.1":"2026-09-05T12:29:54.873Z","modified":"2026-09-05T12:29:55.066Z"},"maintainers":[{"name":"stavros-tsioulis","email":"contact@stavrostsioulis.com"}],"description":"Extract Accorudo contract bundles into an intermediate representation and emit OpenAPI 3.1 specs.","homepage":"https://accorudo.stavrostsioulis.com","keywords":["typescript","api","openapi","codegen","accorudo"],"repository":{"type":"git","url":"git+https://github.com/stavros-tsioulis/accorudo.git","directory":"packages/openapi"},"author":{"name":"Stavros Tsioulis"},"bugs":{"url":"https://github.com/stavros-tsioulis/accorudo/issues"},"license":"MIT","readme":"# @accorudo/openapi\n\nExtracts an [Accorudo](https://accorudo.stavrostsioulis.com) contract bundle (an `api.ts` route registry plus its `routes/` and `models/`) into an intermediate representation (IR), and emits that IR as an OpenAPI 3.1 document.\n\nThis package is a programmatic API only — the CLI lives in `@accorudo/cli` (`init` / `import` / `export` / `sync`), which wraps this package's `parseOpenApi`/`generateContracts` (import direction) and `extractBundle`/`emitOpenApi` (export direction).\n\n## Why an IR\n\nA `Route<...>` alias only resolves to concrete `method` / `path` / `pathParams` / `query` / `body` / `response` values after TypeScript's checker resolves its generics (`CombineRouteFragments`, indexed access, imported fragments). Regex or AST-only parsing can't do that, so extraction goes through [ts-morph](https://ts-morph.com) and the real type checker.\n\nRather than emitting OpenAPI directly off the AST, extraction produces a `ContractBundle` — operations plus `components.{schemas,parameters,responses,requestBodies,securitySchemes}` — and OpenAPI is just one emit target on top of that IR. This keeps import, export, and future codegen from re-implementing the same walk.\n\n## Install\n\n```bash\npnpm add @accorudo/openapi\n```\n\n## Usage\n\n```ts\nimport { extractBundle, emitOpenApi, stringifyOpenApi } from \"@accorudo/openapi\";\n\nconst bundle = extractBundle(\"./contracts/main\", { name: \"main\" });\nconst doc = emitOpenApi(bundle, { info: { title: \"Main API\", version: \"1.2.0\" } });\n\nconsole.log(stringifyOpenApi(doc, \"yaml\")); // or \"json\"\n```\n\nThe reverse direction — generate contract files from an OpenAPI spec:\n\n```ts\nimport { parseOpenApi, parseOpenApiText, generateContracts } from \"@accorudo/openapi\";\nimport fs from \"node:fs\";\n\nconst doc = parseOpenApiText(fs.readFileSync(\"./openapi/main.yaml\", \"utf8\"), \"yaml\");\nconst bundle = parseOpenApi(doc, { name: \"main\" });\n\nfor (const file of generateContracts(bundle)) {\n  console.log(file.path); // \"api.ts\", \"routes/widget.ts\", \"models/widget.ts\", ...\n}\n```\n\n`extractBundle(bundleDir, options)` reads `bundleDir/api.ts` (configurable via `entryFile`), auto-detects the route registry type exported from it (an object type whose every property structurally has `method` and `path` — this is what lets it skip over a co-exported `Api<Routes>` alias), and walks every operation and every `@openapi.component`-tagged type reachable under `bundleDir`.\n\nIf the bundle isn't self-contained (it imports app types via `~/types/*` or similar), pass `tsConfigFilePath` so those resolve:\n\n```ts\nextractBundle(\"./contracts/main\", { tsConfigFilePath: \"./tsconfig.json\" });\n```\n\nBy default, `components.schemas` entries that no operation actually reaches are dropped from the result. Pass `includeUnusedComponents: true` to keep them.\n\n## Annotations\n\nExtraction and emission are driven by `@openapi.*` JSDoc tags on route aliases, model types, and their properties — `@openapi.component`, `@openapi.type`/`@openapi.format`, `@openapi.nullable`/`@openapi.required`, `@openapi.inline`, `@openapi.response CODE [Ref]`, `@openapi.tag`, `@openapi.security`, and more. See [OpenAPI annotations](https://accorudo.stavrostsioulis.com/openapi/annotations) for the full reference — this package implements that vocabulary.\n\nReusable `@openapi.component parameters` types resolve to `$ref`s automatically wherever they're used as a query/path property's type (e.g. `limit?: LimitParameter` inside a `*QueryParams` type) — `@openapi.parameter Name` on the route alias is only needed for a parameter that isn't otherwise a literal property of `pathParams`/`query` (e.g. a shared header).\n\n## Public API\n\n| Export | Purpose |\n|---|---|\n| `extractBundle(bundleDir, options?)` | Walk a contract bundle into a `ContractBundle` IR |\n| `filterBundleByTags(bundle, tags)` | Drop operations (and now-unreachable components) not matching any of `tags` |\n| `emitOpenApi(bundle, options?)` | Convert IR into a plain `OpenApiDocument` object |\n| `parseOpenApi(doc, options?)` | Convert a raw `OpenApiDocument` into a `ContractBundle` IR |\n| `generateContracts(bundle, options?)` | Convert IR into `{ path, content }[]` TypeScript contract files |\n| `stringifyOpenApi(doc, \"yaml\" \\| \"json\")` | Serialize an `OpenApiDocument` |\n| `parseOpenApiText(text, \"yaml\" \\| \"json\")` | Parse spec text into an `OpenApiDocument` |\n| `parseOpenApiJSDoc` + `getFlag`/`getValue`/`getList`/`getPair(s)`/`getMapping`/`getNumber`/`omitTag` | Low-level `@openapi.*` tag parsing, if you're building your own emitter |\n| `walkType`, `buildComponentIndex`, `WalkContext` | The schema walker, for advanced/custom extraction |\n| `AccorudoOpenApiError` | Thrown on unresolvable registries or bundle type errors |\n\nAll IR types (`ContractBundle`, `OperationNode`, `SchemaNode`, ...) are exported from the package root.\n\n## Scope\n\n- ✅ `contracts/ → ContractBundle → OpenAPI` (export)\n- ✅ `OpenAPI → ContractBundle` (import) — `parseOpenApi`\n- ✅ `ContractBundle → contracts/` (codegen) — `generateContracts`\n- ❌ CLI — lives in `@accorudo/cli` (separate package)\n\n### Known limitations of `parseOpenApi` / `generateContracts`\n\n- `allOf` is flattened into a merged `object` node — composition semantics are not preserved.\n- A formatless `integer` schema is parsed with a synthetic `format: \"int32\"` so it round-trips back to `\"integer\"` on export, since the IR has no separate `\"integer\"` kind.\n- Inline (non-`$ref`) secondary responses are promoted into synthesized, mechanically-named `components.responses` entries during codegen, because the `@openapi.response CODE Ref` annotation can only reference a named response.\n- Inline `header`/`cookie` parameters are promoted into synthesized `components.parameters` entries during parsing, since `OperationNode` only has slots for path/query parameters.\n- Model file grouping (`models/<tag>.ts` vs `models/common.ts`) is a deterministic single-tag-reachability rule and may place a schema differently than a human would.\n- No `CombineRouteFragments` synthesis (always one flat `RouteFragment` per path) and no response-wrapper-generic inference (`Success<T>`/`CollectionSuccess<T>`) — generated names and shapes are mechanical. Review generated files before committing, per the CLI's own `import` guidance.\n\n## Development\n\n```bash\npnpm --filter @accorudo/openapi test   # vitest\npnpm --filter @accorudo/openapi build  # tsc -> dist/\n```\n\n`test/fixtures/widget-bundle` is a small hand-written bundle exercising components, `$ref` vs `@openapi.inline`, path/query parameters (including reusable parameter components), multiple response codes, security schemes, and a multipart route. `test/emit.spec.ts` extracts it and diffs the result against `test/fixtures/widget-bundle.expected.yaml`.\n","readmeFilename":"README.md","_rev":"1-bdcd48fb6c1831d62b640be0f2e15272"}