Generates and syncs a consumer contract version of an OpenApi document reflecting what your app actually uses.
This project is currently experimental and something I'm exploring in my free time.
Expect things to change, break, or be completely rewritten without notice.
There is no guarantee that this project will ever reach a stable or final release —
it may remain a personal experiment indefinitely.
Contributions, discussions, and ideas are welcome — just keep in mind the exploratory nature of the project.
Contract tests help prevent accidental API breakage — but they’re expensive to write and maintain.
Most applications only use a subset of their provider’s API surface, thus traditional contract tests require you to manually:
- Track every endpoint your code touches
- Track every property your code uses
- Maintain fixtures and mocks for each
- Update them every time the API or client evolves
Wouldn’t it be better if your code told you what it used automatically?
This tool reads your TypeScript project and your provider’s OpenAPI spec, then automatically generates a “used-only” OpenAPI that reflects what your app actually calls and reads.
You can then use standard OpenAPI diff tools (like oasdiff or pb33f/openapi-changes) to catch real breaking changes — and ignore noise.
flowchart TB
subgraph Testing["Testing"]
direction TB
fe_test_1["frontend-app #1<br>(uses id, email)"]
fe_test_2["frontend-app #2<br>(uses id)"]
be_test_1["backend-service<br>(removes email)"]
end
subgraph Staging["Staging"]
direction TB
fe_stg_1["frontend-app #1<br>(uses id, email)"]
fe_stg_2["frontend-app #2<br>(uses id)"]
be_stg_1["backend-service<br>(removes email)"]
end
Staging ~~~ Testing
be_stg_1 --> be_test_1
fe_test_1:::envred
fe_test_2:::envgreen
be_test_1:::envgreen
fe_stg_1:::envgreen
fe_stg_2:::envgreen
be_stg_1:::envgreen
classDef envgreen fill:#1aad57,stroke:#222,stroke-width:2px,color:#fff
classDef envred fill:#f23a3d,stroke:#222,stroke-width:2px,color:#fff
flowchart LR
TS["Your TypeScript Codebase"] --- AST["TypeScript AST & Type Checker"]
OAS["Provider OpenAPI (full)"]
AST -->|used types, endpoints, props| FILT["Usage-aware filter"]
OAS --> FILT
FILT --> OAS_SUB["Used-only OpenAPI"]
OAS_SUB --> DIFF["Diff Tool<br/>(oasdiff, pb33f/openapi-changes)"]
OAS --> DIFF
DIFF --> CI["CI status<br/>(breaking? ❌ : ✅)"]
-
Load your project
Usests-morphand the TypeScript compiler API to inspect your source files, respecting yourtsconfig.json. -
Analyze usage
Traverses the AST and collects all symbols/types that appear in:- Variable property access (regular access, destruction, etc)
- Function arguments (only whats required in parameters)
- JSX props (same as functions arguments)
-
Resolve to OpenAPI entities
Matches your used types to schemas, operations, and parameters in the provided OpenAPI spec. -
Prune unused parts
Removes everything not directly or transitively used by your app — leaving a used-only spec. -
Diff
Runoasdifforopenapi-changesbetween the provider spec and your generated used-only spec:- ❌ Breaking: removed or modified properties you actually use
- ✅ Ignored: removed or modified properties you never touch
openapi: 3.0.0
info:
title: Provider API
version: 1.0.0
paths:
/users/{id}:
get:
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/User"
components:
schemas:
User:
type: object
required:
- id
- email
properties:
id:
type: string
email:
type: string
displayName:
type: string
marketingOptIn:
type: booleanexport async function run(id: string) {
const user = await userClient.getUser(id) // Returns a 'User'
console.log(user.email); // only 'email' is used
}openapi: 3.0.0
info:
title: Provider API (used-only)
version: 1.0.0
paths:
/users/{id}:
get:
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/User"
components:
schemas:
User:
type: object
required:
- email
properties:
email:
type: string# 1️⃣ Install
npm i -D contract-usage-openapi
# 2️⃣ Generate used-only spec
npx contract-usage-openapi --tsconfig ./tsconfig.json --entry ./src --input-openapi ./provider.yaml --output-openapi ./used-only.yaml
# 3️⃣ Diff
npx oasdiff diff ./provider.yaml ./used-only.yaml --fail-on-breaking✅ Real type-checked usage (no regex guessing)
✅ Attempts to detect real usage, fallsback to "will typescript compiler break if this is removed/changed?"
🚫 Doesn’t mock APIs — it only generates specs
🚫 Casted (obj[prop as string]) or untyped access will be ignored
- Basic property accesses (regular, destruction, etc)
- Basic top-level argument vs parameter usage detection
- Identify nested types in argument vs parameter usage detection
- Basic union types
- Cross-file checks
- Dont mix up types with same structure
- Remove unused endpoints
- Remove unused properties
- Better generics support
- Object spread support
- Perf improvements
- JSX support
- Handle union arguments
- Handle intersection arguments
- Better union/intersection spread in component props
- Add more tests for element access around component props and spread
- Decide if optional param properties should consider argument property as used
- Handle request body params
- Handle request query params
- Add workaround for proxied methods
- Multiple generics support
- Single
forEachDescendantcall (perf improvement) - Nested OpenApi schemas (still deciding if its worth suporting it)
- Much more
PRs and issues are welcome!
If you find an unsupported use-case, please open a PR adding a test.