Files
suaveplan/packages/api-stack/api/api-openapi/src/namespacing/namespacing.md
2026-05-27 23:29:53 +01:00

3.1 KiB

namespacing

(no submodules)

Purpose

The namespacing module turns a plugin's structural identity (name, displayName) plus a registered route's (method, path) tuple into the deterministic OpenAPI surface fields — tag name, component-schema prefix, normalised OAS path, and operationId. It is the bridge between the plugin-namespaced runtime and the single merged OpenAPI 3.1 document @suaveplan/api-openapi emits.

Determinism matters: clients regenerate SDKs against the document and expect stable operationIds across boots. Collisions — two plugins producing the same id — would silently overwrite the earlier route in client toolchains, so the module raises OPENAPI.OPERATION_ID_COLLISION the moment a duplicate appears.

Features

  • tagFor(plugin) — prefers displayName, falls back to name.
  • componentPrefix(plugin) — <pluginName> literal, used by the emitter as the prefix for components.schemas entries.
  • normaliseRoutePath(path) — rewrites Express-style :id parameters into the OAS {id} form. Idempotent.
  • operationIdFor(plugin, method, path) — full deterministic id: <plugin>.<method>.<oasPath>.
  • assignOperationIds(inputs) — bulk walk that respects pre-set operationId values and raises a typed error on collision.

Usage: Basic

import {
    assignOperationIds,
    operationIdFor,
    tagFor,
} from "@suaveplan/api-openapi/namespacing";

const plugin = { name: "billing", displayName: "Billing" };
operationIdFor(plugin, "GET", "/invoices/:id");
// => "billing.get./invoices/{id}"
tagFor(plugin); // => "Billing"

Usage: Advanced

import { assignOperationIds } from "@suaveplan/api-openapi/namespacing";
import type { RegisteredRoute } from "@suaveplan/api-openapi/types";

const plugins = [
    { name: "billing", displayName: "Billing" },
    { name: "users" },
];
const routes: ReadonlyArray<{
    plugin: (typeof plugins)[number];
    route: RegisteredRoute;
}> = [
    { plugin: plugins[0]!, route: { method: "GET", path: "/invoices/:id", tags: [] } },
    { plugin: plugins[1]!, route: { method: "GET", path: "/users/:id", tags: [] } },
];
const namespaced = assignOperationIds(routes);
// Throws OPENAPI.OPERATION_ID_COLLISION if any duplicate appears.

API Reference

  • NamespacePluginInfo — { name, displayName? }.
  • tagFor(plugin) → string.
  • componentPrefix(plugin) → string.
  • normaliseRoutePath(path: string) → string.
  • operationIdFor(plugin, method, path) → string.
  • assignOperationIds(inputs) → NamespacedRoute[].
  • NamespacedRoute — { pluginName, tag, operationId, method, path, route }.

Implementation Notes

  • Path normalisation uses the regex :([A-Za-z_][\w-]*) so colons preceded by non-identifier characters (e.g. :443 in a server URL) are not rewritten.
  • Method is lower-cased to match OAS path-item key casing.
  • Pre-set operationId overrides the derived one — useful for stable ids that pre-date the convention. Collisions still raise.
  • The module does not know about strictness; the emitter applies strictness to missing schemas separately.