first commit
This commit is contained in:
123
docs/api-stack/api/api-openapi/src/merger/merger.md
Normal file
123
docs/api-stack/api/api-openapi/src/merger/merger.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# merger
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
OpenAPI document transforms — merge, prefix, filter, redact internal
|
||||
routes. Multi-plugin servers compose the final published spec from
|
||||
multiple sources (the registry's `buildDocument()`, the Hono
|
||||
`getOpenAPI31Document()` output, hand-crafted addon docs), and the
|
||||
merger is the deterministic operation that combines them without
|
||||
losing information or producing collisions silently.
|
||||
|
||||
Every transform returns a new document — the inputs are never mutated —
|
||||
so consumers can pipeline `prefixPaths(removeInternalRoutes(merge(...)))`
|
||||
without worrying about aliasing.
|
||||
|
||||
## Features
|
||||
|
||||
- `mergeOpenAPIDocuments(base, ...docs)` — per-path-item path merge,
|
||||
spread-merge of components, dedup tags by name, concat servers.
|
||||
- `prefixPaths(doc, prefix)` — prepends a prefix to every path,
|
||||
collapsing duplicate slashes so `prefix="/api/"` + `path="/users"`
|
||||
yields `/api/users`.
|
||||
- `filterPathsByTag(doc, tags[])` — keeps only operations whose `tags`
|
||||
array intersects the supplied list.
|
||||
- `removeInternalRoutes(doc)` — drops operations marked `x-internal: true`.
|
||||
- `extractOpenAPIDoc(app, info)` — pulls a fully-rendered document out
|
||||
of a Hono `OpenAPIHono` for inspection or merging.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import {
|
||||
mergeOpenAPIDocuments,
|
||||
} from "@suaveplan/api-openapi/merger";
|
||||
|
||||
const base: OpenAPIDocument = {
|
||||
openapi: "3.1.0",
|
||||
info: { title: "Combined", version: "1.0.0" },
|
||||
paths: { "/a": { get: { responses: {} } } },
|
||||
};
|
||||
const addon: Partial<OpenAPIDocument> = {
|
||||
paths: { "/b": { post: { responses: {} } } },
|
||||
};
|
||||
const merged = mergeOpenAPIDocuments(base, addon);
|
||||
console.log(Object.keys(merged.paths)); // ["/a", "/b"]
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
extractOpenAPIDoc,
|
||||
filterPathsByTag,
|
||||
mergeOpenAPIDocuments,
|
||||
prefixPaths,
|
||||
removeInternalRoutes,
|
||||
} from "@suaveplan/api-openapi/merger";
|
||||
import { OpenAPIHono } from "@hono/zod-openapi";
|
||||
|
||||
const usersApp = new OpenAPIHono();
|
||||
const projectsApp = new OpenAPIHono();
|
||||
// … register routes …
|
||||
|
||||
const usersDoc = extractOpenAPIDoc(usersApp, {
|
||||
title: "Users",
|
||||
version: "1.0.0",
|
||||
});
|
||||
const projectsDoc = extractOpenAPIDoc(projectsApp, {
|
||||
title: "Projects",
|
||||
version: "1.0.0",
|
||||
});
|
||||
|
||||
const merged = mergeOpenAPIDocuments(
|
||||
{ openapi: "3.1.0", info: { title: "Public API", version: "1.0.0" }, paths: {} },
|
||||
prefixPaths(usersDoc, "/v1"),
|
||||
prefixPaths(projectsDoc, "/v1")
|
||||
);
|
||||
|
||||
// Hide internal endpoints + restrict to public-facing tags.
|
||||
const publicDoc = filterPathsByTag(
|
||||
removeInternalRoutes(merged),
|
||||
["users", "projects"]
|
||||
);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `function mergeOpenAPIDocuments(base, ...docs): OpenAPIDocument`
|
||||
- `function prefixPaths(doc, prefix): OpenAPIDocument`
|
||||
- `function extractOpenAPIDoc(app, info): OpenAPIDocument`
|
||||
- `function filterPathsByTag(doc, tags[]): OpenAPIDocument`
|
||||
- `function removeInternalRoutes(doc): OpenAPIDocument`
|
||||
- `interface MergeOptions { pathPrefix?: string; overridePaths?: boolean }` — reserved for future use.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`mergeOpenAPIDocuments` is left-associative — merging `(a, b, c)` is the
|
||||
same as `merge(merge(a, b), c)`. This property is asserted under
|
||||
fast-check at 200 runs in `tests/property.test.ts`, ensuring the merger
|
||||
remains a monoid even as future spread-rules land.
|
||||
|
||||
`mergeOpenAPIDocuments` does **not** detect cross-document collisions
|
||||
on the same `(method, path)`. The last value wins. That is the intended
|
||||
upstream behaviour and the only reasonable default — strict-detection
|
||||
mode is reserved for a follow-up.
|
||||
|
||||
`prefixPaths` uses `String.replace(/\/+/g, "/")` to collapse duplicate
|
||||
slashes. This is the only place double-slashes can be introduced (the
|
||||
prefix `/api/` plus path `/users` would yield `/api//users` without
|
||||
collapse). The transform is idempotent: applying it twice with the same
|
||||
prefix yields the same paths.
|
||||
|
||||
`filterPathsByTag` drops paths whose every operation is removed,
|
||||
preventing empty `PathItem` shells in the result. `removeInternalRoutes`
|
||||
applies the same rule for `x-internal`.
|
||||
|
||||
The transforms intentionally do not validate the input documents — they
|
||||
treat the input as best-effort structural data. Validation belongs to a
|
||||
separate spec-validator package (out of scope here).
|
||||
Reference in New Issue
Block a user