Stable

openpkg-tsryanwaits/openpkg-ts

A machine-readable standard for package APIs

GitHubDemo ↗

Problem
TypeDoc, API Extractor, TSDoc: every tool emits its own private format. No standard, no interop.
Standard
OpenPkg is the spec, hosted at openpkg.dev; openpkg-ts is its TypeScript reference implementation.
Extract
extractSpec() gives you the full public API as a typed spec.
Output
Functions, types, interfaces, generics. Resolved JSON out.
Agents
Primitives first: extractSpec, diffSpec, recommendSemverBump. The CLI is a thin wrapper over the same SDK.
The Problem

No Standard, No Interop

REST APIs have OpenAPI. GraphQL has introspection. TypeScript has tools: TypeDoc emits JSON, API Extractor emits .api.json, TSDoc standardizes comments. None of it interoperates. Each format is one tool's working memory, versioned to that tool, readable mostly by that tool.

So every consumer that needs a package's API surface, doc generators, diff tools, CI gates, agents, re-extracts it from source with the compiler and keeps the result to itself. Same function, two incompatible answers:

TypeDoc JSON
{
"name": "__namedParameters",
"type": { "type": "reference",
"target": 14 }
}
// internal reflection model:
// names no caller typed,
// ids into TypeDoc's own tree
API Extractor .api.json
{
"kind": "Function",
"excerptTokens": [
{ "kind": "Content",
"text": "declare function " }
]
}
// token stream built for
// Microsoft's docs pipeline
Extract It

One Function Call

OpenPkg is the interchange format those tools never agreed on: a versioned spec, hosted at openpkg.dev, with JSON Schema doing the type representation. openpkg-ts extracts it from any package in one call: functions, types, interfaces, classes, generics.

extract.ts
import { extractSpec } from '@openpkg-ts/sdk';
const { spec, diagnostics } = await extractSpec({
entryFile: './src/index.ts',
});
The Output

What the Spec Holds

An OpenPkg document describes every public export as a contract another tool can consume. Named types resolve as real references. A literal union becomes a real enum. A destructured parameter keeps its names and defaults, because that's the function's actual calling contract.

spec.json
{
"openpkg": "0.4.0",
"exports": [{
"name": "connect",
"kind": "function",
"parameters": [{
"name": "options",
"schema": {
"type": "object",
"properties": {
"host": { "type": "string" },
"port": { "type": "number", "default": 5432 },
"mode": { "enum": ["json", "markdown", "html"] }
}
}
}],
"returns": { "$ref": "#/types/Client" }
}]
}
Built for Agents

Primitives First

One document, many consumers: diffSpec, recommendSemverBump, and validateSpec all take the same spec that extractSpec produced. That's the interop the private formats never had. An agent imports the function and gets a typed result back; the CLI is a thin wrapper over the same SDK.

diff.ts
import { diffSpec, recommendSemverBump } from '@openpkg-ts/spec';
const diff = diffSpec(oldSpec, newSpec);
// { added, removed, breaking, docsOnly }
const { bump, reason } = recommendSemverBump(diff);
// bump: 'major' | 'minor' | 'patch'
Get Started

Three Packages

sdk for extraction, spec for validation and diffing, cli for humans (a thin wrapper over the sdk). Point extractSpec at any entry file and get a typed spec back, no config.

npm install @openpkg-ts/sdk @openpkg-ts/spec