Forge production-ready client SDKs from OpenAPI 3.x specs.
One language-neutral IR. Emitters for TypeScript, Go, Rust, and WASM.
With runtime validation, documentation generation, and a plugin system.
Most OpenAPI generators have critical shortcomings that leave teams building runtime infrastructure by hand:
| Problem with other generators | specforge’s answer |
|---|---|
Incomplete types — nullable, oneOf, allOf produce broken or any-typed output |
Full composition support: allOf property merging, oneOf type guards, discriminator mapping, nullable propagation |
| No runtime — you get types but no client, auth, retry, or error handling | Production-ready runtime: auth providers, exponential backoff, pagination helpers, concurrency control, middleware, idempotency keys, SSE streaming |
| Single language — each generator is a silo with different behavior | One IR, four targets: TypeScript, Go, Rust, and WASM plugins share the same resolved spec |
| No validation — generated code trusts the server blindly | Runtime request/response validation catches contract violations in dev and tests |
| No testing — you write mock servers by hand | specforge test generates mock server tests from example responses |
| No documentation — separate tools for API docs | specforge docs generates a static HTML documentation site |
| No CI integration — manual diffing and linting | specforge diff detects breaking changes, specforge check lints specs, GitHub Action for one-line CI |
1. Parse once, emit many
Your OpenAPI spec is parsed and resolved into a language-neutral IR (Intermediate Representation). Every emitter — TypeScript, Go, Rust, or a custom WASM plugin — walks the same IR. This means:
- Consistent behavior across languages (same retry logic, same pagination, same auth)
- Adding a new language means writing one emitter, not re-implementing the parser
- The IR is a documented, versioned JSON schema (
assets/ir-schema.json)
2. SDKs that actually work in production
specforge doesn’t just generate types — it generates complete client libraries with:
- Auth — Bearer tokens (static or dynamic), API keys, custom providers
- Retry — Full-jitter exponential backoff with configurable max retries and per-attempt timeouts
- Pagination — Cursor and offset pagination helpers that walk all pages automatically
- Concurrency — Async semaphore to limit in-flight requests (
maxConcurrent) - Deduplication — In-flight request coalescing for GET/HEAD/OPTIONS (one upstream call, N waiters)
- Middleware — Composable request/response middleware chain (logging, tracing, header injection)
- Idempotency — Automatic
Idempotency-Keyheaders on POST/PUT/PATCH/DELETE - Streaming — SSE and chunk streaming helpers with proper error handling
- Validation — Runtime request/response body validation against the spec
3. Multi-language with one source of truth
Generate SDKs for all your teams from the same spec:
graph LR
A[“📄 OpenAPI 3.x<br/>YAML / JSON”] --> B[“⚙️ specforge-core<br/>parse · resolve<br/>language-neutral IR”]
B --> C[“🔧 emitters<br/>TS · Go · Rust · WASM<br/>typed client + runtime”]
style A fill:#1a0f0a,stroke:#f97316,color:#fef3c7
style B fill:#1a0f0a,stroke:#ef4444,color:#fef3c7
style C fill:#1a0f0a,stroke:#fbbf24,color:#fef3c7
Each generated SDK is a standalone project — no shared runtime dependency, no version coupling. The TypeScript SDK is a dual ESM/CJS package, the Go SDK uses only stdlib, and the Rust SDK uses reqwest + serde.
4. Built for CI
specforge checklints your spec with configurable rules (.specforge.yaml)specforge diffdetects breaking changes between two spec versions — exit code 1 on breaking changes- GitHub Action for one-line CI integration
- Deterministic output for reproducible builds and effective caching
5. Full IDE integration
The VS Code extension provides 24 commands, auto-validation on save, context menus, keyboard shortcuts, and a status bar — everything you need to work with OpenAPI specs without leaving your editor.
6. Extensible via WASM plugins
Need Kotlin? Swift? Python? Build a custom emitter as a WASM plugin:
use specforge_plugin::{Plugin, PluginResult, GeneratedFile};
struct MyPlugin;
impl Plugin for MyPlugin {
fn generate(&self, ir_json: &str) -> PluginResult {
// Parse the IR, emit files for your language
}
}
specforge_plugin::export_plugin!(MyPlugin);The plugin receives the full IR as JSON and returns generated files. Compile to WASM and run with specforge.
| Feature | Benefit |
|---|---|
| OpenAPI 3.0 + 3.1 | Handles both spec versions transparently. 3.1 type arrays, $ref siblings, and numeric exclusiveMinimum are auto-converted to 3.0 for parsing. |
Full $ref resolution |
Named types stay named — no exponential inlining blow-ups. Self-referential and mutual $ref cycles are safe by construction. |
| Composition support | allOf merges properties (last-wins, required union). oneOf/anyOf generate type guards. Discriminator mapping preserved. |
| AllOf type aliases | When allOf has one $ref member, Go emits embedded structs and Rust emits #[serde(flatten)] — proper composition, not flat merging. |
| Deterministic output | IndexMap preserves spec order. Same spec + same version = identical output. Bit-stable for caching and diffing. |
| Spec linting | 8 configurable rules (duplicate operation IDs, missing descriptions, unused schemas, etc.) with .specforge.yaml config. |
| Breaking change detection | specforge diff compares two specs: removed operations, new required parameters, type changes. Exit code 1 for CI gates. |
Every generated SDK is a complete, production-ready client — not just types.
| Capability | What it does | Why it matters |
|---|---|---|
| Typed models + operations | Full request/response types with proper optionality | Catch type errors at compile time, not in production |
| Auth providers | Bearer (static/dynamic), API key (header/query), custom | Swap credentials without touching client code |
| Retry + backoff | Full-jitter exponential backoff, configurable max retries | Handle transient failures gracefully without thundering herd |
| Per-attempt timeouts | Configurable timeout on each retry attempt | Prevent hung requests from blocking your app |
| Pagination helpers | Cursor and offset pagination that walks all pages | One call instead of manual loop + cursor management |
| Concurrency semaphore | maxConcurrent limits in-flight requests |
Prevent overwhelming the API or hitting rate limits |
| In-flight dedupe | Coalesces identical GET/HEAD/OPTIONS requests | N concurrent callers → 1 upstream call, N shared results |
| Middleware chain | Composable request/response middleware | Add logging, tracing, header injection without modifying the client |
| Idempotency keys | Auto-generated Idempotency-Key on unsafe methods |
Safe retries on POST/PUT/PATCH/DELETE without duplicate side effects |
| SSE streaming | Server-Sent Event parser with proper error handling | Real-time data streams without manual parsing |
| Runtime validation | Validate request/response bodies against the spec | Catch API contract violations in dev and tests, not production |
| oneOf type guards | isPetEventPetCreated(), narrowPetEvent() (TS); is_pet_created(), discriminant() (Rust) |
Safely narrow union types at runtime (TS guards are union-scoped to avoid collisions when two unions share an arm) |
| Response caching | ETag-based caching with TTL expiry and 304 handling | Avoid re-fetching unchanged data |
| Rate limiting | Token bucket and sliding window rate limiters | Prevent overwhelming APIs |
| Logging | Pluggable Logger interface (ConsoleLogger, NoopLogger) |
Structured request/response logging |
| Telemetry | Request metrics, error tracking, cache hit/miss | Monitor SDK performance |
| i18n | Localized error messages (8 locales) | International error handling |
| Interceptors | Request/response body transformers | Post-process data without middleware |
| Dependency injection | ServiceContainer for all 3 SDKs (HTTP client, cache, rate limiter, logger, telemetry) |
Test with mock HTTP clients |
| Validation middleware | Auto-validate all requests/responses against the spec | Catch contract violations without manual validation |
TypeScript
- Dual ESM/CJS package (
sideEffects: falsefor tree-shaking) - Native
fetch— no runtime dependencies - Discriminated union error types (
ApiError) is{Union}{Arm}()/narrow{Union}()union-scoped type guards for oneOf unions- Per-model
validatePet()functions - Response caching with ETags
- Token bucket / sliding window rate limiting
Loggerinterface with ConsoleLoggerRequestInterceptor/ResponseInterceptor/ResponseTransformer@param,@returns,@throwsJSDoc on generated functions
Go
- Stdlib only (
net/http,encoding/json) — zero third-party dependencies - Embedded structs for allOf composition
New{Union}(m map[string]any)for discriminated oneOf deserializationNew{Union}FromJSON(raw json.RawMessage)for non-discriminated unionsWithValidation(true)for runtime request/response checkingWithCache(ttl),WithRateLimiter(limiter),WithLogger(logger)WithRequestInterceptors()/WithResponseInterceptors()/WithResponseTransformers()
Rust
reqwest+serde+tokioasync runtime#[serde(flatten)]for allOf compositionimpl PetEvent { fn discriminant() -> &str; fn is_pet_created() -> bool; }for oneOfSseStreamfor SSE parsing overbytes_stream().validation(true),.cache_ttl(Duration),.rate_limiter(limiter),.logger(logger).http_client(reqwest::Client)for DI in testsRequestInterceptor/ResponseInterceptor/ResponseTransformertraits
WASM plugins
specforge-plugincrate withPlugintrait andexport_plugin!macro- Receives full IR as JSON, returns generated files
- Compile to
wasm32-wasip1for any language emitter
| Command | What it does |
|---|---|
specforge generate |
Generate an SDK from an OpenAPI spec |
specforge check |
Lint and validate a spec with configurable rules |
specforge diff |
Compare two specs — markdown/JSON/color output, inline schema diffs |
specforge emit |
Dump the resolved IR as JSON (for external tools / plugins) |
specforge init |
Scaffold a new OpenAPI spec with a /health endpoint |
specforge convert |
Convert between OpenAPI 3.0 and 3.1 |
specforge merge |
Merge multiple spec files into one |
specforge migrate |
Generate a migration guide between two spec versions |
specforge docs |
Generate a static HTML API documentation site |
specforge test |
Generate mock server tests from spec examples |
specforge version |
Apply API versioning (URL/header/query) to a spec's endpoints |
specforge versions |
List API versions in a spec directory |
specforge workspace |
Generate SDKs for all specs in a workspace config |
specforge workspace-init |
Generate a workspace config from a directory |
specforge profile |
Profile the performance of API endpoints from an OpenAPI spec |
specforge plugin |
Manage WASM emitter plugins |
specforge analyze |
Detect unused schemas, duplicates, optimization opportunities |
specforge mock |
Start a local mock server from spec examples |
specforge export |
Export Swagger Editor-compatible spec (inline $ref) |
specforge demo |
Generate a realistic demo Petstore spec |
specforge evolution |
Track schema changes over git commits |
specforge infer |
Generate an OpenAPI spec from sample JSON |
specforge verify |
Validate a running API against the spec |
specforge market |
Browse/search/manage specs in the marketplace |
specforge market search |
Search specs by name, description, or tags |
specforge market list |
List all curated specs with ratings |
specforge market info |
Get detailed spec information |
specforge market add |
Add a spec to the marketplace |
specforge changelog |
Auto-generate CHANGELOG from spec changes |
Run
specforge helpto see the full, current list — the CLI is the source of truth.
- 304 unit tests across core, emitters, and CLI (255 core + 17 TS + 14 Go + 18 Rust)
- 24 test fixtures (0.02MB to 14MB): petstore, GitHub, Stripe, Kubernetes, Atlassian, OpenAI, Vercel, Linode, Bitbucket, Adyen, Notion, Spotify, Adobe AEM, CircleCI, Okta, and more
- Compile gates: every emitter has a test that regenerates the SDK into a temp crate and compiles it — Go (
go build), Rust (cargo check), TypeScript (tsc --noEmit). On top of that,regression.rsgenerates + compiles SDKs from real-world specs (GitHub ~9MB, Stripe ~7.6MB) in all three languages. - E2E smoke: mock server × list/show/create + auth/retry/pagination (all 3 langs)
- E2E advanced: concurrency serialisation, dedupe single-flight, middleware rewrite, idempotency-key on POST, SSE parse (all 3 langs)
- Performance benchmarks: criterion benchmarks + 13 perf tests (petstore < 100ms, GitHub/Stripe < 10s)
- Multi-platform CI: Linux, macOS, Windows (GitHub Actions matrix)
- Cross-compiled releases: 5 targets (linux amd64/arm64, macOS Intel/Apple Silicon, Windows)
- Deterministic output verified: all 3 emitters have a byte-identical regeneration test (same spec + version = same output)
- Zero clippy warnings:
cargo clippy --workspace --all-targets -- -D warningsenforced in CI - Production hardening: thread safety audit, unwrap/expect audit, SDK integration tests, stability enforcement
The specforge VS Code extension provides a complete IDE experience for OpenAPI development:
| Feature | What it does |
|---|---|
| Generate SDK | Pick language (TS/Go/Rust), generate to output directory |
| Check Spec | Validate spec with --strict mode |
| Diff Spec | Compare two spec versions side-by-side |
| Preview IR | View resolved intermediate representation as JSON |
| Analyze Spec | Find unused schemas, duplicates, optimization opportunities |
| Security Analysis | Audit auth requirements across all operations |
| Show Dependency Graph | Mermaid visualization of schema relationships |
| Generate Docs | Static HTML documentation site |
| Generate Tests | Mock server tests from spec examples |
| Merge Specs | Combine multiple spec files into one |
| Migrate | Generate migration guide between spec versions |
| Mock Server | Start local mock server from spec examples |
| Export | Swagger Editor compatible output |
| Infer | Generate OpenAPI spec from sample JSON |
| Verify | Validate running API against spec |
| Evolution | Track schema changes over git commits |
IDE integration:
- Auto-validate specs on save (configurable)
- Context menus (right-click YAML/JSON files)
- Keyboard shortcuts (
Ctrl+Shift+Ggenerate,Ctrl+Shift+Vcheck) - Status bar with mock server indicator
- Output channel for all CLI output
- Progress notifications for long operations
- Interactive pickers for language, format, and version
Quick start:
- Install the extension from the
vscode-extension/directory - Open a folder containing an OpenAPI spec
- Press
Ctrl+Shift+Gto generate an SDK
cargo build -p specforge-cli
# → target/debug/specforge# TypeScript (default) — native fetch, dual ESM/CJS package
./target/debug/specforge generate openapi.yaml -o ./sdk-ts -l ts
# Go — stdlib net/http only
./target/debug/specforge generate openapi.yaml -o ./sdk-go -l go \
-n github.com/acme/widget-go
# Rust — reqwest + serde
./target/debug/specforge generate openapi.yaml -o ./sdk-rs -l rust \
-n widget_sdk
# Lint a spec without generating
./target/debug/specforge check openapi.yaml
./target/debug/specforge check openapi.yaml --strictTypeScript
import { createClient, bearerAuth } from "./sdk-ts/src/index.ts";
import { ApiClient } from "./sdk-ts/src/client";
import { streamSse } from "./sdk-ts/src/streaming";
// `bearerAuth` / `apiKeyAuth` are emitted only when the spec declares those
// security schemes. Specs with no security emit `anonymousAuth` instead.
const client = createClient({
baseUrl: "https://api.example.com",
auth: bearerAuth(() => process.env.API_TOKEN!),
maxConcurrent: 8,
dedupe: true,
idempotency: true,
retry: { maxRetries: 3 },
});
const page = await client.pets.listPets({ limit: 20 });
// Streaming (SSE) — request() lives on the underlying ApiClient.
const api = new ApiClient({ baseUrl: "https://api.example.com" });
const res = await api.request("GET", "/events");
for await (const ev of streamSse(res)) {
console.log(ev.event, ev.data);
}
createClient()returns a tree-shakeable tag namespace (client.pets.listPets). For raw requests / streaming, use theApiClientdirectly. The auth helpers (bearerAuth,apiKeyAuth) appear only when your spec declares those schemes.
Go
c := sdk.NewClient().
WithBaseURL("https://api.example.com").
WithBearerToken(os.Getenv("API_TOKEN")).
WithTimeout(10 * time.Second).
WithMaxConcurrent(8).
WithDedupe(true).
WithIdempotency(true).
WithRetry(sdk.DefaultRetryOptions())
c.Use(func(ctx context.Context, req *sdk.MiddlewareRequest, next func(context.Context, *sdk.MiddlewareRequest) (*sdk.MiddlewareResponse, error)) (*sdk.MiddlewareResponse, error) {
start := time.Now()
res, err := next(ctx, req)
log.Printf("%s %s %v", req.Method, req.URL, time.Since(start))
return res, err
})
pets, err := c.ListPets(ctx, 20)
// Streaming SSE
res, err := c.DoStream(ctx, "GET", "/events", nil, nil)
defer sdk.DrainAndClose(res)
it := sdk.NewSseIterator(res)
for it.Next() {
ev := it.Event()
fmt.Println(ev.Event, ev.Data)
}Rust
use std::time::Duration;
use widget_sdk::{api, Client};
use widget_sdk::streaming::SseStream;
let client = Client::builder()
.base_url("https://api.example.com")
.bearer_token(std::env::var("API_TOKEN")?)
.timeout(Duration::from_secs(10))
.max_concurrent(8)
.dedupe(true)
.idempotency(true)
.build()?;
let pets = api::list_pets(&client, Some(20)).await?;
// Streaming SSE
let res = client.request_stream(reqwest::Method::GET, "/events", &[], None).await?;
let mut sse = SseStream::new(res.bytes_stream());
while let Some(ev) = sse.next_event().await? {
println!("{}: {}", ev.event, ev.data);
}Run specforge --help for the full, current command list (the CLI is the source of truth — see the table above). The most-used commands:
specforge generate <SPEC> # Generate an SDK (the main command)
specforge check <SPEC> # Lint a spec (--strict = warnings as errors)
specforge diff <OLD> <NEW> # Breaking-change detection (exit 1 on breaking)
specforge emit <SPEC> # Dump resolved IR as JSON (for external emitters)
specforge <CMD> --help # Per-command flags
specforge generate [OPTIONS] <SPEC>
Arguments:
<SPEC> Path to OpenAPI YAML or JSON
Options:
-o, --out <DIR> Output directory [default: ./generated]
-l, --lang <LANG> ts | go | rust [default: ts]
-n, --name <NAME> Package / module / crate name override
--version <VERSION> API version (when spec is a directory)
--profile Output timing breakdown for each pipeline stage
--include-webhooks Emit webhook handler types (OpenAPI 3.1)
--locale <LOCALE> Comma-separated locale codes for i18n errors (e.g. en,es)
--changelog Auto-generate CHANGELOG.md in the output dir
--changelog-previous <F> Previous spec to diff against (use with --changelog)
--version-prefix <PFX> Apply a URL path versioning prefix (e.g. v1)
--plugin <NAME> Use a WASM plugin emitter by name
-v, --log-level <LEVEL> error|warn|info|debug|trace [default: info]
-h, --help
-V, --versionis a top-level flag (specforge -V), not agenerateoption.
| Language | -n means |
Default if omitted |
|---|---|---|
| ts | npm package name in package.json |
@<title-slug>sdk |
| go | Go module path in go.mod |
github.com/example/<title>-go |
| rust | Cargo crate name | <title>_sdk |
specforge check [OPTIONS] <SPEC>
Arguments:
<SPEC> Path to OpenAPI YAML or JSON
Options:
--strict Treat warnings as errors
--deprecations List deprecated operations and schemas
--disable <RULE> Disable a lint rule (repeatable)
--enable <RULE> Enable a lint rule (repeatable)
--severity <RULE:SEV> Set rule severity: error|warning|off (repeatable)
--config <FILE> Path to a lint config YAML file (.specforge.yaml)
-v, --log-level <LEVEL> error|warn|info|debug|trace [default: info]
specforge diff [OPTIONS] <OLD> <NEW>
Arguments:
<OLD> Path to the old (baseline) OpenAPI spec
<NEW> Path to the new OpenAPI spec
Options:
--breaking-only Show only breaking changes
-v, --log-level <LEVEL> error|warn|info|debug|trace [default: info]
Exit code 1 if breaking changes are found — use in CI to gate releases.
specforge emit [OPTIONS] <SPEC>
Arguments:
<SPEC> Path to OpenAPI YAML or JSON
Options:
-v, --log-level <LEVEL> error|warn|info|debug|trace [default: warn]
Outputs the resolved IR as pretty-printed JSON to stdout. Use this to build external emitters:
specforge emit openapi.yaml | my-custom-emitter --input - --output ./sdkspecforge docs [OPTIONS] <SPEC>
Arguments:
<SPEC> Path to OpenAPI YAML or JSON
Options:
-o, --out <DIR> Output directory [default: ./docs]
-v, --log-level <LEVEL> error|warn|info|debug|trace [default: info]
Generates a static HTML documentation site with color-coded HTTP method badges, schema listings, and the base URL.
specforge test [OPTIONS] <SPEC>
Arguments:
<SPEC> Path to OpenAPI YAML or JSON
Options:
-o, --out <DIR> Output directory [default: ./tests]
-l, --lang <LANG> ts | go | rust [default: ts]
-v, --log-level <LEVEL> error|warn|info|debug|trace [default: info]
Generates mock server test files that start a local HTTP server from the spec's example responses and verify the SDK can call each operation. Supports TypeScript (http module), Go (httptest), and Rust (TcpListener).
specforge versions [OPTIONS] <SPEC>
Arguments:
<SPEC> Path to a spec file or directory containing versioned specs
Options:
-v, --log-level <LEVEL> error|warn|info|debug|trace [default: info]
Lists all API versions found in a directory. Supports flat (v1.yaml, v2.yaml) and nested (v1/openapi.yaml, v2/openapi.yaml) conventions. Use with specforge generate specs/ --version v2 to generate for a specific version.
specforge migrate [OPTIONS] <OLD> <NEW>
Arguments:
<OLD> Path to the old (baseline) OpenAPI spec
<NEW> Path to the new OpenAPI spec
Options:
-o, --out <FILE> Output file (default: stdout)
-v, --log-level <LEVEL> error|warn|info|debug|trace [default: info]
Generates a migration guide in Markdown format comparing two spec versions. Lists deprecated operations, removed operations, new required parameters, and schema changes.
specforge is a Cargo workspace with a hard boundary: emitters never touch openapiv3 types. They only see the IR.
specforge/
├── crates/
│ ├── specforge-core/ # parse · $ref resolve · IR
│ ├── specforge-ts/ # TypeScript emitter + rich runtime templates
│ ├── specforge-go/ # Go emitter (stdlib HTTP client)
│ ├── specforge-rust/ # Rust emitter (reqwest + serde)
│ ├── specforge-wasm/ # WASM target for browser-based parsing
│ ├── specforge-plugin/ # Plugin SDK for WASM emitter plugins
│ └── specforge-cli/ # `specforge` binary + regression / e2e tests
├── fixtures/
│ ├── petstore.yaml # small vendored fixture (always online)
│ └── sample-api.yaml # auth · oneOf · cursor pagination sample
├── examples/ # consumer stubs (regenerate via script)
├── scripts/
│ ├── ci.sh # local CI mirror
│ └── generate-examples.sh
├── assets/ # logo + banner
├── CHANGELOG.md
├── CONTRIBUTING.md # setup, PR checklist, architecture rules
├── SECURITY.md # vulnerability reporting policy
├── RELEASE.md
└── .github/
├── workflows/ci.yml
├── ISSUE_TEMPLATE/ # bug report + feature request templates
└── PULL_REQUEST_TEMPLATE.md
| Stage | Crate | Responsibility |
|---|---|---|
| Parse | specforge-core |
YAML/JSON → openapiv3::OpenAPI |
| Resolve | specforge-core |
$refs, security schemes, operations → Document IR |
| Emit | specforge-{ts,go,rust} |
IR → idiomatic project on disk |
| WASM | specforge-wasm |
Core parsing/resolving compiled to WASM for browser use |
| Plugins | specforge-plugin |
SDK for building custom WASM emitter plugins |
| Orchestrate | specforge-cli |
CLI flags, logging, language dispatch |
// Simplified view of crates/specforge-core/src/ir.rs
pub struct Document {
pub title: String,
pub version: String,
pub base_url: Option<String>,
pub security: Vec<SecurityScheme>,
pub schemas: SchemaRegistry, // named models, stable order
pub operations: Vec<Operation>,
}
pub enum Type {
Scalar(Scalar),
StringEnum { variants: Vec<String>, nullable: bool },
Array { item: Box<Type>, nullable: bool },
Map { value: Box<Type> },
Reference { name: String, nullable: bool },
Composition(Composition), // allOf | oneOf | anyOf + optional Discriminator
Any,
Unknown,
}Design rules that keep large specs tractable (GitHub ~965 schemas / 1209 ops, Stripe ~1431 / 587):
- References stay references — no exponential inlining
- Cycles are safe — self-
$refbecomes a plain name - Determinism —
IndexMappreserves spec order for bit-stable output
TypeScript -l ts
sdk-ts/
├── package.json # dual ESM/CJS, sideEffects: false
├── tsconfig.json # strict TS 5.6+
├── tsup.config.ts
├── README.md
└── src/
├── index.ts # createClient() + re-exports
├── client.ts # fetch core (ApiClient + retry/dedupe/telemetry)
├── auth.ts
├── retry.ts
├── paginate.ts
├── concurrency.ts # async semaphore
├── dedup.ts # in-flight GET coalescing (buffered bodies)
├── middleware.ts
├── idempotency.ts # Idempotency-Key generation
├── streaming.ts # streamBytes / streamLines / streamSse
├── cache.ts # ETag/conditional GET response cache
├── ratelimit.ts # token-bucket + sliding-window rate limiters
├── telemetry.ts # TelemetryHooks interface + MetricsCollector
├── logging.ts # pluggable Logger (ConsoleLogger)
├── interceptors.ts # request/response body interceptors
├── validate.ts # generated per-model validators
├── validation-middleware.ts # schema validation as middleware
├── service_container.ts # DI grouping (fetch/cache/logger/telemetry)
├── errors.ts # discriminated-union ApiError
├── models/<Name>.ts # one file per schema (+ oneOf guards)
└── api/<Tag>.ts # one class per tag
Go -l go
sdk-go/
├── go.mod
├── README.md
├── client.go # Client + ServiceContainer, auth, DoJSON + DoStream pipeline
├── cache.go # ETag/conditional GET response cache
├── concurrency.go # Semaphore
├── dedup.go # RequestDeduper
├── idempotency.go # Idempotency-Key UUID
├── interceptors.go # request/response body interceptors
├── logging.go # pluggable Logger (ConsoleLogger)
├── middleware.go # composable request/response middleware
├── models.go # typed models + per-model validators
├── paginate.go # CursorPaginate / OffsetPaginate generics
├── ratelimit.go # token-bucket + sliding-window rate limiters
├── retry.go # exponential backoff + jitter
├── streaming.go # NewSseIterator / StreamLines
├── telemetry.go # TelemetryHooks interface + MetricsCollector
├── validate.go # runtime request/response validation
├── validation_middleware.go # validation as middleware
└── api_<tag>.go # methods on *Client
Zero third-party deps for the happy path (net/http, encoding/json).
Rust -l rust
sdk-rs/
├── Cargo.toml # reqwest · serde · tokio · futures-util · bytes
├── README.md
└── src/
├── lib.rs
├── api/ # one module per tag, one fn per operation
├── cache.rs # ETag/conditional GET response cache
├── client.rs # Client + ClientBuilder + ServiceContainer + request_stream
├── concurrency.rs # async in-flight semaphore
├── dedup.rs # coalesce identical in-flight safe requests
├── error.rs # typed Error enum
├── idempotency.rs # auto Idempotency-Key for safe retries
├── interceptors.rs # request/response body interceptors
├── logging.rs # pluggable Logger trait + Console/Noop impls
├── middleware.rs # request/response middleware chain
├── models.rs # one struct/enum per schema + validators
├── paginate.rs # cursor/offset pagination helpers
├── ratelimit.rs # token-bucket + sliding-window rate limiters
├── retry.rs # exponential backoff + jitter
├── streaming.rs # SseStream for server-sent events
├── telemetry.rs # TelemetryHooks trait + MetricsCollector
├── validate.rs # generated per-model validators
└── validation_middleware.rs # schema validation as middleware
WASM plugin -l wasm (via specforge-plugin)
plugin/
├── Cargo.toml # crate-type = ["cdylib"]
├── src/
│ └── lib.rs # impl Plugin + export_plugin! macro
└── README.md
Build: cargo build --target wasm32-wasip1 --release
All three clients share the same conceptual request pipeline:
graph TD
A["🚀 operation call"] --> B["build URL / query / body"]
B --> C["acquire concurrency permit<br/>(optional)"]
C --> D{"dedupe in-flight?<br/>GET/HEAD/OPTIONS"}
D -->|yes| E["share result with<br/>concurrent callers"]
D -->|no| F["retry loop"]
F --> G["apply auth"]
G --> H["attach Idempotency-Key<br/>(unsafe methods, once per loop)"]
H --> I["per-attempt timeout"]
I --> J["middleware chain"]
J --> K["transport<br/>fetch / net/http / reqwest"]
K --> L{"classify error"}
L -->|retriable| F
L -->|success| M["decode JSON → typed model"]
L -->|non-retriable| N["throw"]
E --> M
style A fill:#1a0f0a,stroke:#f97316,color:#fef3c7
style M fill:#1a0f0a,stroke:#22c55e,color:#fef3c7
style N fill:#1a0f0a,stroke:#ef4444,color:#fef3c7
| Language | Static bearer | Dynamic token | API key header |
|---|---|---|---|
| TS | bearerAuth(() => token) |
async getter supported | apiKeyAuth(header, getKey) |
| Go | WithBearerToken(tok) |
BearerAuth{GetToken: …} |
WithAPIKey(header, key) |
| Rust | .bearer_token(tok) |
Auth::BearerFn(…) |
.api_key(header, key) |
- Max retries: 2 (3 total attempts)
- Backoff: full jitter, base 500ms, cap 8s
- Retriable statuses:
408,429,502,503,504 - Retriable methods:
GET,HEAD,PUT,DELETE,(OPTIONS)+ transport/timeouts
Unsafe methods (POST / PUT / PATCH / DELETE) automatically receive a stable Idempotency-Key for the entire retry loop (one key generated, reused on retries). Disable with idempotency: false / WithIdempotency(false) / .idempotency(false).
| Language | Entry point | Parser |
|---|---|---|
| TS | client.request("GET", "/events") |
streamSse(res) / streamLines / streamBytes |
| Go | client.DoStream(ctx, "GET", "/events", …) |
NewSseIterator(res) |
| Rust | client.request_stream(GET, "/events", …) |
SseStream::new(res.bytes_stream()) |
Streaming calls do not retry (bodies are not replayable).
import { isPetEventPetCreated, narrowPetEvent, type PetEvent } from "./models/PetEvent";
function handle(event: PetEvent) {
if (isPetEventPetCreated(event)) {
console.log(event.pet.name); // narrowed
return;
}
switch (narrowPetEvent(event)) {
case "PetUpdated": /* … */ break;
case "PetDeleted": /* … */ break;
}
}TS guard names are union-scoped (
is{Union}{Arm}) so two unions sharing an arm type don't collide.narrow{Union}()is the ergonomic entry point.
Ready-to-run consumer stubs live under examples/:
./scripts/generate-examples.sh # regenerate sdk/ trees from fixtures/petstore.yaml
cd examples/petstore-ts && npm i && npx tsx main.mts
cd examples/petstore-go && go run .
cd examples/petstore-rust && cargo runPoint them at your own server with PETSTORE_URL=….
Repo fixtures for generator development:
| Fixture | What it stresses |
|---|---|
fixtures/petstore.yaml |
Small happy path — list / show / create |
fixtures/sample-api.yaml |
Bearer auth, enums, oneOf + discriminator, cursor pages |
# Full suite — build, unit, large-spec compile gates, e2e (smoke + advanced), tsc
./scripts/ci.sh
# Faster loops
./scripts/ci.sh quick # unit + petstore generate
./scripts/ci.sh regression # includes GitHub/Stripe go build + cargo check
./scripts/ci.sh e2e # smoke + advanced e2e| Suite | Command | Coverage |
|---|---|---|
| Unit | cargo test -p specforge-core --all-targetscargo test -p specforge-ts --lib |
IR construction, TS naming/types/models/ops |
| Regression | cargo test -p specforge-cli --test regression |
Petstore generate (TS/Go/Rust); GitHub + Stripe resolve; Go/Rust generate + compile on large specs |
| E2E smoke | cargo test -p specforge-cli --test e2e_smoke |
Petstore basics + sample-api auth / 503-retry / cursor pagination (3 langs) |
| E2E advanced | cargo test -p specforge-cli --test e2e_advanced |
Concurrency serialisation, dedupe single-flight, middleware header rewrite, idempotency-key on POST, SSE parse (3 langs) |
Large specs download on demand into target/spec-cache/ and are skipped (not failed) when offline. Compile steps skip only if go / cargo are missing; a failed compile when the tool is present fails CI.
.github/workflows/ci.yml runs on every push/PR:
test— full suite (Rust stable + Go 1.23 + Node 20, cached cargo + spec-cache)quick— build + unit + petstore-only regression for a fast signal
See RELEASE.md for the full checklist. Short version:
# 1. bump [workspace.package].version in Cargo.toml
# 2. update CHANGELOG.md
./scripts/ci.sh full
./scripts/generate-examples.sh
git tag -a v1.6.1 -m "specforge v1.6.1"
git push origin v1.6.1Binary: cargo build --release -p specforge-cli → target/release/specforge.
| Tool | Required for |
|---|---|
| Rust 1.75+ (rustc/cargo) | Building specforge itself; Rust SDK cargo check |
| Go 1.22+ | Go SDK go build + e2e (optional but recommended) |
| Node 18+ / npm | TypeScript tsc + e2e (optional but recommended) |
v1.6.1 — codegen robustness + readability cleanup:
- Doc-comment escaping (Rust + Go emitters) — real-world specs (GitHub, Stripe) with backticks,
/*/*/, and multi-paragraph descriptions no longer break generated code - Reserved-name collision protection (Go + Rust) — spec models named like built-in SDK types (
ValidationError, etc.) get aModelsuffix - Per-emitter compile gates — generated SDKs are regenerated into a temp crate and compiled (
cargo check/go build/tsc --noEmit) in CI - Regression tests now cover GitHub (~9MB) + Stripe (~7.6MB) specs in Rust + Go — 4 previously-failing tests now pass
- 259 tests passing; clippy warnings 120 → 2
v1.6.0 — compile-gate tests + telemetry/validation middleware:
-
ServiceContainerDI,RequestInterceptor/ResponseInterceptor, response transformers - Validation middleware (
validation_middleware.rs) wired into all emitters - Telemetry hooks wired into the TypeScript client
v1.5.0 — web UI redesign, spec marketplace:
- Professional light theme — complete rewrite (1713 lines, design system)
- Spec marketplace — 18 curated specs,
specforge market list/search/info/add - Browse Specs section in web UI with cards, search, and tag filtering
- 227 tests passing (core 210 + TS 17)
v1.4.0 — VS Code extension comprehensive update:
- VS Code extension: 3 → 24 commands (all CLI subcommands)
- 6 configuration settings, 3 keyboard shortcuts
- Context menus, status bar, auto-validate, progress notifications
v1.3.0 — production-ready milestone:
- All tests green — 221 passing (core 204 + TS 17)
- 21 test fixtures (0.02MB to 14MB, 4 OpenAPI versions)
- Stability enforcement — IR version +
specforge-version.jsonin generated SDKs - SDK integration tests — validate TS/Go/Rust against mock servers
- Thread safety audit — no deadlocks or race conditions
- Unwrap/expect audit — production code audited for panics
- SDK changelog generation (
specforge changelog) - ServiceContainer for TS/Go/Rust DI
- Spec validation middleware
v1.2.0 — mock server, logging, i18n, interceptors:
-
specforge mock— local mock server from spec examples - SDK logging hooks (Logger interface, ConsoleLogger, NoopLogger)
- i18n support (8 locales,
--locale en,esflag) - Request/response interceptors for all 3 SDKs
- Response transformers for post-processing
-
specforge export— Swagger Editor compatible output -
specforge demo— realistic Petstore spec with examples -
specforge evolution— schema change tracking over git -
specforge infer— generate OpenAPI from sample JSON -
specforge verify— validate running APIs against spec
v1.1.0 — dashboard, 3.1 parser, security, graph:
-
specforge dashboard— HTML metrics visualization with Chart.js - OpenAPI 3.1 native parser (
Schema31,parse_31(), unsupported feature warnings) -
specforge security— auth analysis (text/JSON/markdown output) -
specforge graph— Mermaid/DOT dependency diagrams -
specforge analyze— unused schemas, duplicates, large models, recommendations - contentMediaType/contentEncoding support (
Scalar::Base64,Scalar::Binary) - Per-operation retry policies (
x-retryextensions) - Diff improvements — markdown/JSON/color output, inline schema diffs
- JSDoc/docstrings from spec descriptions in generated code
v1.0.0 — production-ready milestone:
- SDK rate limiting — token bucket + sliding window (TS/Go/Rust)
- Telemetry hooks — request metrics, error tracking, cache hit/miss
-
specforge migrate— generate migration guides between spec versions - Deprecation tracking + comments in generated code
- 6 external test fixtures (GitHub, Stripe, Kubernetes, Twilio)
- Website at
specforge.deepwhaleai.com - Go pipeline fixes (validate.go, hyphenated fields)
- Test count: 176
v0.9.0 — caching, webhooks, workspace:
- Response caching with ETags (TS/Go/Rust SDKs)
- OpenAPI 3.1 webhooks support (
--include-webhooks) -
specforge workspace— multi-spec generation from config -
specforge workspace-init— generate workspace config from directory
v0.8.0 — DI, merge, 3.1 expansion:
- Rust
http_client()builder for dependency injection in tests -
specforge merge— combine multiple spec files into one - OpenAPI 3.1 expanded:
const,dependentRequired,prefixItems - 3.1 feature detection (
detect_31_features())
v0.7.0 — tree-shaking, versioning, profiling:
- Tree-shakeable TS API modules (per-tag imports,
src/api/index.tsbarrel) -
specforge versions— list API versions in a spec directory -
--versionflag ongenerate— filter by version from directory -
--profileflag ongenerateandemit— timing breakdown
v0.6.0 — testing, 3.1, lint config:
-
specforge test— mock server test generation (TS/Go/Rust) - OpenAPI 3.1
$refsibling support (description/summaryviaallOfwrapping) - Configurable lint rules (
.specforge.yaml,--disable/--enable/--severity) - New lint rules:
missing-operation-id,path-trailing-slash,deprecated-operation
v0.5.0 — validation, WASM, docs:
- Runtime validation middleware (TS/Go/Rust SDKs)
- WASM-compiled specforge (
specforge-wasmcrate) -
specforge docs— static HTML API documentation generator - Web UI WASM integration (client-side parsing)
v0.4.0 — plugins, web UI, validation:
- WASM plugin SDK (
specforge-plugincrate +export_plugin!macro) - Example WASM plugin (
examples/plugin-example/) - Web UI for browsing IR (
web-ui/index.html) - Spec validation middleware (
validatemodule, 51 tests) - Plugin documentation (
PLUGINS.md)
v0.3.0 — ecosystem, DX, and stability:
- JSON Schema for IR (
assets/ir-schema.json,emit --schema) - Streaming IR emission (
emit --stream— NDJSON for large specs) - Parallel file generation (rayon)
-
specforge init— scaffold new OpenAPI specs -
specforge convert— 3.0 ↔ 3.1 conversion - GitHub Action (
action.yml— generate/check/diff/emit) - VS Code extension scaffolding
- Stability policy (
STABILITY.md) - Incremental generation guide (
INCREMENTAL.md)
v0.2.2 — OpenAPI 3.1, plugin system, mascot:
- OpenAPI 3.1 support — transparent
typearray → nullable conversion, numericexclusiveMinimum→ boolean -
specforge emit— dump resolved IR as JSON for external emitters / plugins - Fox blacksmith mascot + forge color scheme
v0.2.1 — allOf composition, spec diff, and benchmarks:
- AllOf type aliases — Go embed, Rust
#[serde(flatten)] -
specforge diff <old> <new>— breaking-change detection for CI - Generation benchmarks (
scripts/bench.sh) — GitHub/Stripe specs
v0.2.0 — composition, ergonomics, and release infrastructure:
- Discriminator
mappingsupport (all 3 emitters) - Go oneOf helpers (
New{Union},{Union}Discriminant,New{Union}FromJSON) - Rust oneOf helpers (
discriminant(),is_*(),into_*(),as_*()) - AllOf property merging (last-wins, required union)
- Spec linting (
specforge check [--strict]) - Go + Rust streaming middleware (
StreamMiddleware) - Cross-compiled release binaries (5 targets)
- Crates.io publish workflow
- Multi-platform CI (Linux, macOS, Windows)
- Richer generated READMEs (errors, pagination, concurrency, middleware, streaming)
All roadmap items completed through v1.6.1. Production-ready: real-world specs (GitHub, Stripe) generate + compile in all languages, with per-emitter compile gates in CI.
Use specforge in CI with the official GitHub Action:
- uses: amafjarkasi/specforge-openapi-sdk-codegen@v1
with:
command: generate
spec: openapi.yaml
lang: ts
output: ./sdkCommands: generate, check, diff, emit. Pin to a major version (@v1) for non-breaking updates, or a specific tag (@v1.0.0) for reproducibility.
The specforge VS Code extension provides a full IDE experience for working with OpenAPI specs:
24 commands accessible via Ctrl+Shift+P:
- Generate SDK — Pick language, generate to output directory
- Check Spec — Validate with
--strictmode - Diff Spec — Compare two spec versions
- Preview IR — View resolved intermediate representation
- Analyze Spec — Find unused schemas, optimization opportunities
- Security Analysis — Audit auth requirements
- Show Dependency Graph — Mermaid visualization
- Generate Docs — Static HTML documentation
- Generate Tests — Mock server tests
- Merge Specs — Combine multiple files
- Migrate — Generate migration guide
- Mock Server — Start local mock server
- Export — Swagger Editor compatible output
- Infer — Generate spec from sample JSON
- Verify — Validate running API against spec
- Evolution — Track schema changes over git
IDE integration:
- Auto-validate specs on save (configurable)
- Context menus (right-click YAML/JSON)
- Keyboard shortcuts (
Ctrl+Shift+Ggenerate,Ctrl+Shift+Kcheck) - Status bar with quick access
- Output channel for all commands
- Progress notifications for long operations
Browse and share OpenAPI specs with the community:
# List all specs
specforge market list
# Search by name, description, or tags
specforge market search github
# Get detailed info
specforge market info stripe
# Add your own spec
specforge market add ./my-api.yaml18 curated specs included: GitHub, Stripe, Petstore, Kubernetes, Spotify, Notion, Twilio, Vercel, Okta, Atlassian, Adyen, Bitbucket, Linode, CircleCI, LaunchDarkly, Adobe AEM, 1Password, Ably.
Browse specs in the Web UI with search, tag filtering, and ratings.
Build custom emitters using specforge emit:
# Pipe the IR to your custom emitter
specforge emit openapi.yaml | my-emitter --lang kotlin --output ./sdk
# Stream mode for large specs
specforge emit openapi.yaml --stream | while read -r line; do
echo "$line" | process-schema
doneSee assets/ir-schema.json for the IR JSON Schema.
specforge guarantees deterministic output — the same spec + version always produces identical files. Use this for:
- CI caching (hash spec + version as cache key)
- Reproducible builds
- Diff-friendly generated code
See INCREMENTAL.md for CI caching strategies.
git clone <repo-url> && cd specforge
cargo build --workspace
./scripts/ci.sh quick # before you start
# … hack …
./scripts/ci.sh # before you pushGuidelines:
- Emitters only see the IR — never import
openapiv3outsidespecforge-core. - Keep output deterministic — prefer
BTreeMap/IndexMapoverHashMapfor emission order. - Don’t nest
/* */inside JSDoc/templates — it breaks generated TypeScript. - Large-spec gates are sacred — if GitHub/Stripe stop compiling, fix the emitter, don’t skip.
- Dedupe must hand each waiter a fresh body — never share one consumed
Response.
MIT © specforge contributors
specforge is released under the MIT License, which permits commercial use, modification, distribution, and private use without restriction. You are free to:
- Use specforge in proprietary commercial products
- Include specforge in closed-source commercial applications
- Sell products built with or powered by specforge
- Use specforge in enterprise internal tools
- Modify specforge for commercial purposes
No attribution is required in the MIT license, though attribution is appreciated.
No copyleft restrictions — unlike GPL, the MIT license does not require you to open-source your own code when using specforge.
Commercial support is available by contacting the maintainers at specforge.deepwhaleai.com.