Skip to content

Repository files navigation

specforge

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.

Quick start Languages Tests Version License

specforge mark


Why specforge?

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

The specforge advantage

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-Key headers 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
Loading

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 check lints your spec with configurable rules (.specforge.yaml)
  • specforge diff detects 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.


Features

Spec parsing & resolution

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.

Generated SDK runtimes

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

Language-specific highlights

TypeScript

  • Dual ESM/CJS package (sideEffects: false for 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
  • Logger interface with ConsoleLogger
  • RequestInterceptor / ResponseInterceptor / ResponseTransformer
  • @param, @returns, @throws JSDoc 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 deserialization
  • New{Union}FromJSON(raw json.RawMessage) for non-discriminated unions
  • WithValidation(true) for runtime request/response checking
  • WithCache(ttl), WithRateLimiter(limiter), WithLogger(logger)
  • WithRequestInterceptors() / WithResponseInterceptors() / WithResponseTransformers()

Rust

  • reqwest + serde + tokio async runtime
  • #[serde(flatten)] for allOf composition
  • impl PetEvent { fn discriminant() -> &str; fn is_pet_created() -> bool; } for oneOf
  • SseStream for SSE parsing over bytes_stream()
  • .validation(true), .cache_ttl(Duration), .rate_limiter(limiter), .logger(logger)
  • .http_client(reqwest::Client) for DI in tests
  • RequestInterceptor / ResponseInterceptor / ResponseTransformer traits

WASM plugins

  • specforge-plugin crate with Plugin trait and export_plugin! macro
  • Receives full IR as JSON, returns generated files
  • Compile to wasm32-wasip1 for any language emitter

CLI subcommands

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 help to see the full, current list — the CLI is the source of truth.

Quality gates

  • 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.rs generates + 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 warnings enforced in CI
  • Production hardening: thread safety audit, unwrap/expect audit, SDK integration tests, stability enforcement

VS Code Extension

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+G generate, Ctrl+Shift+V check)
  • 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:

  1. Install the extension from the vscode-extension/ directory
  2. Open a folder containing an OpenAPI spec
  3. Press Ctrl+Shift+G to generate an SDK

Quick start

1. Build the CLI

cargo build -p specforge-cli
# → target/debug/specforge

2. Generate an SDK

# 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 --strict

3. Call your API

TypeScript
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 the ApiClient directly. 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);
}

CLI reference

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

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, --version is a top-level flag (specforge -V), not a generate option.

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

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

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

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 ./sdk

specforge docs

specforge 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

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

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

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.


Architecture

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

Pipeline stages

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

IR highlights

// 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-$ref becomes a plain name
  • DeterminismIndexMap preserves spec order for bit-stable output

Generated project layouts

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


Runtime deep dive

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
Loading

Auth

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)

Retry defaults

  • 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

Idempotency

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).

Streaming (SSE)

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).

oneOf narrowing (TypeScript)

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.


Examples

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 run

Point 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

Testing & CI

Local

# 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

What each suite proves

Suite Command Coverage
Unit cargo test -p specforge-core --all-targets
cargo 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 Actions

.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

Release

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.1

Binary: cargo build --release -p specforge-clitarget/release/specforge.


Requirements

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)

Status & roadmap

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 a Model suffix
  • 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:

  • ServiceContainer DI, 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.json in 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,es flag)
  • 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-retry extensions)
  • 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.ts barrel)
  • specforge versions — list API versions in a spec directory
  • --version flag on generate — filter by version from directory
  • --profile flag on generate and emit — timing breakdown

v0.6.0 — testing, 3.1, lint config:

  • specforge test — mock server test generation (TS/Go/Rust)
  • OpenAPI 3.1 $ref sibling support (description/summary via allOf wrapping)
  • 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-wasm crate)
  • 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-plugin crate + export_plugin! macro)
  • Example WASM plugin (examples/plugin-example/)
  • Web UI for browsing IR (web-ui/index.html)
  • Spec validation middleware (validate module, 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 type array → nullable conversion, numeric exclusiveMinimum → 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 mapping support (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.


Ecosystem

GitHub Action

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: ./sdk

Commands: generate, check, diff, emit. Pin to a major version (@v1) for non-breaking updates, or a specific tag (@v1.0.0) for reproducibility.

VS Code Extension

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 --strict mode
  • 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+G generate, Ctrl+Shift+K check)
  • Status bar with quick access
  • Output channel for all commands
  • Progress notifications for long operations

Spec Marketplace

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.yaml

18 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.

External Emitters

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
done

See assets/ir-schema.json for the IR JSON Schema.

Deterministic Output

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.


Contributing

git clone <repo-url> && cd specforge
cargo build --workspace
./scripts/ci.sh quick     # before you start
# … hack …
./scripts/ci.sh           # before you push

Guidelines:

  1. Emitters only see the IR — never import openapiv3 outside specforge-core.
  2. Keep output deterministic — prefer BTreeMap / IndexMap over HashMap for emission order.
  3. Don’t nest /* */ inside JSDoc/templates — it breaks generated TypeScript.
  4. Large-spec gates are sacred — if GitHub/Stripe stop compiling, fix the emitter, don’t skip.
  5. Dedupe must hand each waiter a fresh body — never share one consumed Response.

License

MIT © specforge contributors

Commercial Use

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.



OpenAPI in. Typed clients out. Ship faster.

About

Forge production-ready client SDKs from OpenAPI 3.x. One IR, four emitters (TypeScript, Go, Rust, WASM). Full runtime: auth, retry, pagination, concurrency, dedupe, middleware, idempotency, SSE, caching, rate limiting, validation. 25+ CLI commands. Compile-gated on real specs (GitHub, Stripe). Multi-platform CI.

Topics

Resources

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages