npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@webpieces/openapi-generator

v0.4.906

Published

Render an ApiDocModel to OpenAPI 3.1.0 — the canonical internal document and the customer-published one, JSON and YAML, from one generation pass. Ships the wp-openapi bin.

Downloads

13,181

Readme

@webpieces/openapi-generator

Render a webpieces API contract to OpenAPI 3.1.0.

wp-openapi --manifest path/to/openapi.manifest.json --out path/to/dist [--format json|yaml|both]

Which documents you get is a property of your CONTRACTS

@ApiPath('/stores')
@ApiType(SVC_TO_SVC, EXTERNAL_CUSTOMER)     // which documents this contract feeds
export abstract class StoreApi { ... }

| @ApiType value | file | contents | |---|---|---| | SVC_TO_SVC | full-private-openapi.json | every method, hidden ones included, plus an internal-use-only line in info.description. Nothing renders it for humans | | EXTERNAL_CUSTOMER | public-openapi.json | the customer contract, minus every { hidden: true } method | | MCP | mcp-openapi.json | the same contracts with the x-mcp-* extensions an MCP generator reads |

A document no contract declares is not written. No @ApiType at all means SVC_TO_SVC only — fail-closed: a contract reaches customers by SAYING SO, so grep -rn EXTERNAL_CUSTOMER is your entire customer-facing surface on one screen.

--format picks the serialization of whichever documents exist, and defaults to both.

diff full-private-openapi.json public-openapi.json is the complete list of what you do not show a customer. Neither is committed — both are build output (see "In an nx workspace" below); diff the two the build wrote when you need that list.

Hiding ONE method

/**
 * Rebuilds a store's menu cache.
 *
 * Not published: an operator tool, and the customer contract has no concept of our cache.
 */
@Endpoint(POST, '/reindex', WRITE, RPC, { hidden: true })

The reason goes in the JSDoc — where the rest of the endpoint's documentation already lives. The method is absent from public-openapi.json: no path, no operation, no schema, no prose, and nothing announcing that anything was withheld.

Hiding is a documentation decision and never an access-control one. The route is still served and still demands whatever credential it declares.

The manifest

It carries exactly what is not a property of the code:

{
    "title": "Example Orders API",
    "version": "1.0.0",
    "descriptionFile": "description.md",
    "servers": [{ "url": "https://api.example.com", "description": "Production." }],
    "apis": [
        { "entry": "src/OrdersApi.ts", "tag": "Orders" },
        { "entry": "src/EventsApi.ts", "tag": "Webhooks", "kind": "webhook" }
    ],
    "securitySchemeNames": ["PartnerApiKey", "PartnerOrganization"],
    "errors": {
        "entry": "src/ApiErrors.ts",
        "type": "ApiErrorResponse",
        "responses": [{ "status": "400", "description": "The request failed validation." }]
    },
    "responseHeaders": [
        {
            "entry": "src/ResponseHeaders.ts",
            "nameConstant": "REQUEST_ID_HEADER",
            "description": "Correlates this response with our logs."
        }
    ]
}

Every path is relative to the manifest's own directory.

Source suffixes and published schema names

An optional schemaNames list controls published type names independently of source validation. It works in contract manifests and "kind": "components" manifests:

"schemaNames": [
  { "paths": ["../keep-my-names/**"], "stripSuffixes": [] },
  { "paths": ["../partner-apis/**", "../shared-dtos/**"], "stripSuffixes": ["Dto"] }
]

Globs match the type's declaring file, relative to the manifest's directory. The first matching entry wins; no match or an explicit empty list preserves the name. Only one exact, case-sensitive trailing suffix is removed, in configured order. OrderDto becomes Order; Dto alone and OrderDTO remain unchanged.

The same published names are used for schema keys, every reference and discriminator mapping, bundled documents, MCP tool schemas, and streaming schemas. Source names remain available to validation and diagnostics. If stripping makes two reached declarations share a published name, generation fails naming both declarations and their locations. Existing unstripped package-qualified homonyms remain supported.

Configure matching policies on every producer and consumer in a components chain. The downstream generator checks that the upstream document actually exports the expected schema name; a missing or inconsistent policy fails generation.

This combines with required-type-suffix: use an entry with exempt: true to preserve published source names, or set exempt: false and enforce Dto gradually with NEW_AND_MODIFIED_CODE while stripping it from generated documentation. The chained-components regression test renames source declarations and checks that all generated OpenAPI and MCP artifacts remain byte-identical.

Every suffix-enforcement entry requires an explicit exempt boolean and non-empty suffixes, including exempt entries. Retaining the suffixes records the policy that re-enabling the entry will enforce. suffixes: [] is rejected with migration advice. For example: { "paths": ["published/**"], "suffixes": ["Dto", "Api"], "exempt": true }.

The ORDER of apis[] is the published sidebar order. Alphabetising it is not a cleanup.

securitySchemeNames holds the published KEYS only. The schemes themselves, and the AND-ed security requirement, are DERIVED from the contract's apiKey(regime, credentials). A securitySchemes block in the manifest would be a second copy of header names the running server never reads, and nothing could contradict it.

responseHeaders names a CONSTANT, never a header string. JSON cannot import, so a literal there is a copy that a rename leaves silently stale. The generator folds the const and hard-fails if it cannot.

DTO libraries: chained documents, one owner per schema (#1058)

A schema is defined in exactly ONE document: the one belonging to the package whose source declares the TypeScript type (the nearest package.json above the declaration). A contract document defines its own package's types and references every other package's with a package-qualified $ref:

{ "$ref": "@myorg/lang-api-dtos/components.openapi.json#/components/schemas/PassageItemDto" }

So a DTO library publishes a components-only document. Its manifest declares "kind": "components" and names entry files instead of contracts:

{ "kind": "components", "title": "Lang DTOs", "entries": ["src/index.ts"] }

wp-openapi then writes components.openapi.json — openapi, info (whose version IS the package version), x-webpieces-id (the URI above; OpenAPI 3.1's root admits no $id) and components.schemas holding every type the library exports and declares. A DTO library that uses another DTO library references it the same way, so chains have any depth. It needs --format json or both.

The referenced documents are found by the same package lookup McpToolCatalog.fromPackages uses: node resolution from the manifest, then the file beside a built package's package.json, or — for a workspace source directory — the outputPath of the target its openapi-components-generate dependsOn.

It FAILS CLOSED. A full-private / public document that reaches a type declared in a package publishing no components document is refused, naming the type, its package and the fix: move the type into a DTO library (as a …Dto string enum when it is a literal union), or give that package a components document (in nx: tag it generate:openapi-components). There is no allow-list. A referenced schema missing from an existing document is refused as stale. Two DIFFERENT types of one name in one package are refused too; two packages' same-named types are simply two schemas in two documents.

Inheritance is FLATTENED in every form — a base declared in another package contributes its fields; there is no allOf.

Every split document has a *.bundled.json sibling (public-openapi.bundled.json, …) with every schema pulled into its own components.schemas and no external reference, for code generators, gateways and docs sites that read one file. A schema keeps its name there unless two packages' types of one name meet, when the other package's is qualified (myorg.lang-api-dtos.LocalizedDescriptionsDto).

mcp-openapi.json and the MCP tool catalogs never reference anything — they inline every schema from any package, exactly as before, and require no components document anywhere.

MCP

MCP in @ApiType requires @WpMcpTool on at least one method, and @WpMcpTool on a contract that does not declare MCP is an error. Membership has one spelling.

The agent reads the SAME description a human does — the method's JSDoc, byte for byte. Two authored copies of one paragraph drift the first time somebody edits one, so @WpMcpTool('search_stores', 'Search stores') carries only what JSDoc cannot say: the stable protocol name and the REQUIRED human-readable title, which each catalog entry carries as title and tools/list publishes as title and annotations.title (#1180). readOnlyHint, destructiveHint and idempotentHint are computed from the endpoint's declared operation; openWorldHint comes from { openWorld: true } on the endpoint.

Alongside mcp-openapi.json, a run writes ONE mcp-<ContractClass>-tools.json per contract that declares MCP and has at least one @WpMcpTool (e.g. mcp-PartnerOrdersApi-tools.json). Those are not documents: they are the RUNTIME catalogs WpMcpServer boots from, so the schema an agent is shown in tools/list and the schema the server validates a call against are the same bytes. One file per contract because a server binds contracts, and checks each binding against exactly its own contract's file. --format does not apply to them.

It refuses to publish a field it has no schema for

A field the model could not map renders as an empty schema, which in JSON Schema means "anything". wp-openapi exits non-zero instead, naming the JSON pointer of every one, and writes nothing. There is no flag to switch that off — the cure is at the contract, by naming the type.

Worked example

apps/app-example/partner-api in this repo: a real contract, its manifest, golden documents under src/__tests__/goldens/, and a spec that regenerates and diffs them. The chained form is proven by src/__tests__/ChainedComponents.spec.ts here: a three-level chain (contract → DTO library A → DTO library B), every document checked by an OpenAPI 3.1 validator.

In an nx workspace

Inside nx, nobody chooses --out. A DTO library tagged generate:openapi-components gets an inferred openapi-components-generate target the same way, and every generating target that references one dependsOn ^openapi-components-generate. Tag the api library generate:openapi and the @webpieces/nx-webpieces-rules plugin infers an openapi-generate target that writes into the outputPath of the library's build (the @nx/js:tsc target it dependsOn), so the documents are packed and published inside the api library's package, and are never committed. It runs THIS package from the consumer's node_modules and refuses one older than it needs. See .claude/rules/api-docs.md in webpieces-ts.

Dependencies

typescript, @webpieces/api-doc-model and @webpieces/core-util. No nx, no repo paths, no app types.