Skip to content

Changelog

The canonical changelog lives at the repository root. It is included below for convenience when browsing the docs site.

Changelog

All notable changes to the Spikard project are documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

[0.13.0] - 2026-03-19

Added

  • CLI-equivalent MCP server: Added spikard mcp with typed tools for project initialization, OpenAPI/AsyncAPI/OpenRPC/GraphQL/Protobuf code generation, AsyncAPI testing helpers, schema validation, and feature discovery.
  • First-class Elixir codegen and runtime support: Added Elixir project scaffolding plus Elixir OpenAPI, OpenRPC/JSON-RPC, GraphQL, Protobuf/gRPC, and AsyncAPI generation, along with a real Elixir gRPC runtime path for unary and streaming services.
  • Full AsyncAPI test-app parity: Added Rust and Elixir AsyncAPI test-app and bundle generation so fixtures, handlers, test apps, and bundled outputs now cover Python, TypeScript, Rust, Ruby, PHP, and Elixir.
  • Documentation snippet validation: Added the snippet-runner workspace tool and documentation snippet validation commands, enabling syntax-level validation of polyglot docs examples.

Changed

  • spikard init now creates real starter projects: Initialization now produces idiomatic, runnable project layouts for every binding. Python uses pyproject.toml with uv, TypeScript uses package.json + tsconfig.json + src/, Rust uses a valid Cargo app scaffold, Ruby and PHP include runnable server entrypoints, and Elixir now generates a proper Mix application structure.
  • Code generation is now validator-backed across bindings: The CLI codegen path now enforces syntax/type/lint validation for generated output and has been hardened across all supported schema families: OpenAPI 3.1, AsyncAPI, OpenRPC/JSON-RPC, GraphQL, and Protobuf/gRPC.
  • Generated output is more idiomatic across languages: Generated Rust, Python, TypeScript, Ruby, PHP, and Elixir code now uses stronger schema-derived typing, cleaner naming, binding-appropriate project structure, and lint-/format-clean defaults out of the box.
  • README maintenance is template-driven: The root README and package/crate READMEs are now generated from shared templates, keeping CLI/MCP/codegen surface documentation aligned across the repo.

Fixed

  • Cross-binding codegen parity gaps: Closed numerous real generation issues across OpenAPI, OpenRPC, GraphQL, Protobuf, and AsyncAPI, including nested object typing, enum handling, semantic date/UUID types, inline model promotion, streaming service scaffolding, and generated file composition for all targets.
  • MCP/CLI parity and defaults: MCP tool defaults now match CLI defaults for init and core codegen commands, and the MCP surface is now covered by parity tests plus live stdio round-trip smoke tests.
  • gRPC runtime behavior across bindings: Fixed gRPC registration, wire framing, request compression, stream termination, status propagation, mixed-mode service handling, and helper/status normalization across the shared runtime and Node, Python, Ruby, PHP, and Elixir bindings.
  • Binding startup/runtime correctness: Fixed Python auto-reload, async startup, upload-file async I/O, Python 3.14 loop handling, Node/Ruby startup API cleanup, lifecycle hook wiring in PHP and generated Ruby apps, route metadata preservation, and DI error handling.
  • Docs and install guidance: Corrected installation, runtime, and codegen docs to match the shipped surface, including wheel/prebuilt guidance, AsyncAPI 3 support, OpenRPC support, GraphQL introspection JSON support, Protobuf include paths, and current startup/config APIs.

[0.12.0] - 2026-02-28

Added

  • Elixir benchmark apps: Added 3 Elixir benchmark applications to the benchmark harness — spikard-elixir, plug-bandit (Plug + Bandit), and phoenix (Phoenix API-only) — bringing the total to 21 benchmarked frameworks.
  • Benchmark Taskfile integration: New bench:update:elixir, bench:build:elixir, bench:run:spikard-elixir, bench:run:plug-bandit, and bench:run:phoenix tasks.
  • GraphQL testing parity across bindings: Unified GraphQL testing interface across Python, Node.js, Ruby, PHP, and Elixir with dedicated helper methods for queries, mutations, and subscriptions.
  • Elixir test client: New Spikard.TestClient module with full GraphQL helper parity matching other bindings.
  • PHP JsonRpcConfig tests: Comprehensive test coverage for JsonRpcConfig and JsonRpcConfigBuilder classes.
  • Python type stubs: Shipped _spikard.pyi in wheels for IDE/type-checker support (mypy, pyright, Pylance).

Fixed

  • axum-test v19 API migration: Updated spikard-rb and spikard-node test clients after TestServer::new() stopped returning Result in axum-test v19.
  • gRPC test fixture streaming detection: Fixed incorrect routing of unary RPCs through the bidirectional streaming path in Python and PHP gRPC fixture tests.
  • Python lint compliance: Resolved ruff PYI021 (docstrings in stubs), PYI044 (__future__ annotations in stubs), and RUF022 (unsorted __all__) in _spikard.pyi.
  • Biome lint/format: Fixed import ordering and unused imports in Node.js examples and test apps.
  • PHP coverage threshold: Brought PHP test coverage above the 85% CI gate.
  • CI pipeline stability: All 6 CI workflows (Rust, Python, Node, Ruby, PHP, Validate) now pass on main.

Changed

  • Dependency updates: Updated workspace dependencies including jsonschema 0.42→0.43, maturin 1.12.4→1.12.5, and refreshed lock files across all ecosystems.
  • CI actions: Bumped actions/upload-artifact to v7, actions/download-artifact to v8, dawidd6/action-download-artifact to v16.
  • Pre-commit alignment: Aligned .pre-commit-config.yaml with latest hook versions (shfmt, cargo-deny, cargo-machete, mypy mirrors).
  • Release publishing: Added idempotent publish checks across all package registries.

[0.11.0] - 2026-02-16

Added

  • Elixir bindings (first release): Introduced official Elixir support via Rustler NIF bindings, including typed Elixir-facing APIs, tuple-based error semantics ({:ok, value} / {:error, reason}), and ExUnit coverage for binding behavior.

Changed

  • Version synchronization for release: Bumped the workspace to 0.11.0 and synchronized package manifests across supported ecosystems for the Elixir release.

[0.10.2] - 2026-02-03

Fixed

  • Ruby gem packaging: Fixed vendor-crates.sh to patch ext/spikard_rb/Cargo.toml path from workspace-relative (../../../../crates/spikard-rb) to vendored (../../vendor/crates/spikard-rb). This was causing the published gem to fail installation with "failed to load manifest for dependency spikard-rb".

[0.10.1] - 2026-02-02

Performance

  • Static response fast-path with HashMap router: Added optimized routing path for all bindings, reducing dispatch overhead for static routes.
  • Reduced per-request allocations across HTTP pipeline: Minimized allocations in the core HTTP pipeline and all language bindings.
  • Sync benchmark handlers: Converted benchmark handlers from async to sync where unnecessary, improving benchmark accuracy.

Fixed

  • Python API: Critical fixes to align Python bindings with new API surface; resolved mypy errors in SSE and testing modules.
  • Python TestClient: Added cookies support to TestClient.
  • Node.js bindings: Fixed build configuration and tsx dependency for benchmark app.
  • Ruby bindings: Stabilized lockfile for CI.
  • Linting: Applied ruff and biome auto-fixes across Python and TypeScript.

Documentation

  • Comprehensive docs audit: Deleted orphaned files, consolidated duplicates, expanded all binding docs (Python, TypeScript, Ruby, PHP) with full API coverage.
  • New GraphQL guide: Added complete user guide with multi-language examples.
  • Navigation cleanup: Added 10 missing pages to mkdocs nav, fixed 15+ broken internal links.
  • Removed WASM references: Cleaned all user-facing docs of stale WASM mentions.
  • Benchmark results: Updated benchmark results and added performance comparison tables to all README files.

[0.10.0] - 2026-01-30

Removed

  • WASM bindings: The spikard-wasm crate has been removed from the workspace. WASM support will return in a future release targeting WASIp3 HTTP components.

Fixed

  • Node.js bindings: Resolved JSON deserialization regression in Node.js bindings.
  • Ruby bindings: Fixed lifecycle hooks implementation and improved test coverage.

Changed

  • Dependencies: Updated across all ecosystems (Rust, Python, Node.js, Ruby, PHP).

[0.9.2] - 2026-01-22

Performance

  • Arc::try_unwrap Pattern Across All Bindings: Eliminates unnecessary clones when Arc has unique ownership
  • Applied to Python, Ruby, PHP, and Node.js bindings for all RequestData fields
  • 30-40% reduction in FFI conversion overhead measured in Python bindings
  • Static singletons for empty collections in request_extraction.rs (shared Arc instances)
  • OnceLock caching optimizations in Python handler_request.rs (eliminated double-clone patterns)

Changed

  • BREAKING: RequestData Value Fields Arc-Wrapped (Phase 2 Performance Optimization)
  • query_params: Valuequery_params: Arc<Value> reduces per-request clones from 24-30 to 0-6
  • body: Valuebody: Arc<Value> enables zero-copy when refcount is 1 via Arc::try_unwrap
  • validated_params: Option<Value>validated_params: Option<Arc<Value>> maintains consistency
  • All RequestData construction sites updated across spikard-http crate
  • Test helpers and fixtures updated to wrap Value fields in Arc::new()
  • This is a pre-1.0 experimental breaking change; language bindings updated in this release

Fixed

  • Ruby handler: Eliminated double-clone in validated_params handling (line 274)
  • Ruby handler: Fixed raw body clone by using serde_json::from_slice directly
  • PHP response: Changed with_cookies to use &mut self (PHP heap-allocated object constraint)
  • All binding tests: Updated to match Arc-wrapped RequestData struct changes
  • Clippy lints: Fixed ptr_cast_constness and unnecessary_option_map_or_else warnings

[0.9.1] - 2026-01-12

Fixed

  • Added skill descriptions to AI-Rulez metadata so Codex skill YAML satisfies the required description field.

[0.9.0] - 2026-01-10

Added

  • Ruby gRPC Streaming Handlers: Full implementation of all 4 streaming modes for Ruby bindings
  • RubyGrpcRequest/RubyGrpcResponse wrapper types for Ruby FFI
  • RubyGrpcHandler implementing GrpcHandler trait with unary, server, client, and bidirectional streaming
  • DOS protection limits (payload size, metadata entries/size, stream message count, total bytes)
  • Handler timeout (30 seconds) to prevent hung handlers
  • SAFETY-documented unsafe blocks for Ruby GVL handling
  • Error message sanitization (log full errors, return generic messages to clients)
  • Magnus Ruby FFI integration with Opaque for proper GVL management

  • PHP gRPC Streaming Handlers: Full implementation of all 4 streaming modes for PHP bindings

  • PhpGrpcRequest/PhpGrpcResponse wrapper types for PHP FFI
  • PhpGrpcHandler implementing GrpcHandler trait
  • DOS protection and timeout handling matching Ruby implementation

  • gRPC Streaming Fixture Integration (Phases 1-4): Complete end-to-end fixture infrastructure for all 4 streaming modes

  • 30+ JSON fixture files covering Unary, ServerStreaming, ClientStreaming, and BidirectionalStreaming modes
  • Schema validation with semantic cross-reference checks (testing_data/grpc/schema_definitions.json)
  • Fixture validation script (scripts/validate_fixtures.py) with comprehensive error reporting
  • Cross-language parity tests verifying identical behavior across all 5 languages
  • Metadata and timeout support in all gRPC clients (Python, TypeScript, Ruby, PHP, Rust)
  • Stream generators for large fixture tests (sequential, random, timestamp-based patterns)
  • Helper functions eliminating test duplication across languages
  • CI workflow (ci-grpc-fixtures.yaml) for automated gRPC fixture validation

  • gRPC Fixture Testing Suite (120+ cross-language tests):

  • Python: 30+ parametrized pytest tests with fixture loading, 80%+ code coverage
  • TypeScript: 30+ vitest tests with metadata support and stream assertions
  • Ruby: 30+ RSpec tests with block-based connection management and cleanup
  • PHP: 30+ PHPUnit tests with PSR-12 compliance and 85%+ coverage
  • All tests use shared fixture files for consistent validation across ecosystems

  • gRPC server streaming support: Full server streaming RPC implementation

  • Added RpcMode enum for declaring handler capabilities (Unary, ServerStreaming, ClientStreaming, BidirectionalStreaming)
  • Added call_server_stream() trait method to GrpcHandler for streaming implementations
  • Added StreamingResponse type with optional trailers field for response metadata after stream completion
  • GrpcRegistry now stores (handler, RpcMode) tuples for proper request routing
  • Streaming utilities: message_stream_from_vec(), empty_message_stream(), single_message_stream(), error_stream() helpers

Changed

  • GrpcHandler trait breaking changes (semver major):
  • Removed supports_streaming_requests() method (replaced by rpc_mode())
  • Removed supports_streaming_responses() method (replaced by rpc_mode())
  • Added rpc_mode() -> RpcMode method with default implementation returning RpcMode::Unary
  • Added call_server_stream() method with UNIMPLEMENTED default for backward compatibility
  • GrpcRegistry registration now requires RpcMode parameter: registry.register(handler, rpc_mode)

Migration Guide

For existing unary handlers:

// Before
if !handler.supports_streaming_requests() {
    // route to unary
}

// After
match handler.rpc_mode() {
    RpcMode::Unary => { /* unary routing */ }
    RpcMode::ServerStreaming => { /* server streaming routing */ }
    _ => { /* other modes */ }
}

For implementing server streaming:

fn rpc_mode(&self) -> RpcMode {
    RpcMode::ServerStreaming
}

fn call_server_stream(
    &self,
    request: GrpcRequestData,
) -> Pin<Box<dyn Future<Output = Result<MessageStream, tonic::Status>> + Send>> {
    Box::pin(async {
        // Create and return message stream
    })
}
  • gRPC Client Streaming Support: Full implementation of gRPC client streaming mode
  • call_client_stream() trait method for handlers receiving message streams
  • StreamingRequest type containing service/method names, message stream, and metadata
  • HTTP/2 gRPC frame parsing with per-message size validation
  • Smart routing dispatches ClientStreaming mode to appropriate handler
  • Frame parser enforces max_message_size on each message in stream (not total body)
  • Helper utilities: parse_grpc_client_stream() for frame parsing with validation

Security

  • Per-message size enforcement: Client streaming now validates each gRPC frame against max_message_size, preventing resource exhaustion from large individual messages in multi-message streams
  • Stream resource limits: Handlers can return early errors without consuming entire stream, preventing memory buildup

Migration Guide - Client Streaming

To implement a client streaming handler:

use futures_util::StreamExt;

impl GrpcHandler for MyHandler {
    fn rpc_mode(&self) -> RpcMode {
        RpcMode::ClientStreaming
    }

    fn call_client_stream(
        &self,
        request: StreamingRequest,
    ) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
        Box::pin(async move {
            let mut stream = request.message_stream;
            let mut total = 0;

            // Consume stream message-by-message
            while let Some(msg_result) = stream.next().await {
                match msg_result {
                    Ok(message) => {
                        // Process message (size already validated)
                        total += decode_number(&message);
                    }
                    Err(status) => {
                        // Stream error (e.g., size limit exceeded)
                        return Err(status);
                    }
                }
            }

            Ok(GrpcResponseData {
                payload: encode_result(total),
                metadata: MetadataMap::new(),
            })
        })
    }
}

// Register with RpcMode::ClientStreaming
registry.register("mypackage.MyService", Arc::new(MyHandler), RpcMode::ClientStreaming);
  • gRPC Bidirectional Streaming Support: Full implementation of gRPC bidirectional streaming mode
  • call_bidi_stream() trait method for handlers with full-duplex message streams
  • Full-duplex communication: both client and server send streams of messages concurrently
  • Independent request and response streams with proper backpressure handling
  • Smart routing dispatches BidirectionalStreaming mode to appropriate handler
  • Reuses HTTP/2 frame parser with per-message size validation from client streaming
  • Supports chat, collaborative editing, and real-time bidirectional data flows

Migration Guide - Bidirectional Streaming

To implement a bidirectional streaming handler:

use futures_util::StreamExt;

impl GrpcHandler for MyHandler {
    fn rpc_mode(&self) -> RpcMode {
        RpcMode::BidirectionalStreaming
    }

    fn call_bidi_stream(
        &self,
        request: StreamingRequest,
    ) -> Pin<Box<dyn Future<Output = Result<MessageStream, Status>> + Send>> {
        Box::pin(async move {
            let request_stream = request.message_stream;

            // Echo each message back as it arrives
            let response_stream = request_stream.map(|msg_result| {
                match msg_result {
                    Ok(msg) => Ok(msg), // Echo back
                    Err(e) => Err(e),
                }
            });

            Ok(Box::pin(response_stream) as MessageStream)
        })
    }
}

// Register with RpcMode::BidirectionalStreaming
registry.register("mypackage.ChatService", Arc::new(MyHandler), RpcMode::BidirectionalStreaming);

Advanced pattern with async processing:

fn call_bidi_stream(
    &self,
    request: StreamingRequest,
) -> Pin<Box<dyn Future<Output = Result<MessageStream, Status>> + Send>> {
    Box::pin(async move {
        let mut request_stream = request.message_stream;
        let (tx, rx) = tokio::sync::mpsc::channel(100);

        // Spawn task to process incoming messages asynchronously
        tokio::spawn(async move {
            while let Some(msg_result) = request_stream.next().await {
                match msg_result {
                    Ok(msg) => {
                        let response = process_message(msg);
                        let _ = tx.send(Ok(response)).await;
                    }
                    Err(e) => {
                        let _ = tx.send(Err(e)).await;
                        break;
                    }
                }
            }
        });

        // Convert mpsc receiver to MessageStream
        let stream = tokio_stream::wrappers::ReceiverStream::new(rx);
        Ok(Box::pin(stream) as MessageStream)
    })
}

[0.8.3] - 2026-01-05

Fixed

  • Workspace lints: Added proper workspace lints configuration to eliminate unexpected tarpaulin_include cfg condition errors across all crates
  • Clippy warnings: Resolved 500+ clippy warnings in core crates (spikard, spikard-core, spikard-codegen, benchmark-harness, spikard-bindings-shared) to maintain zero-warning policy
  • Language binding lifetime parameters: Fixed lifetime parameter errors in all language binding crates (Python/PyO3, TypeScript/NAPI-RS, Ruby/Magnus, PHP/ext-php-rs, WASM/wasm-bindgen)
  • FFI binding crates: Added comprehensive clippy allow attributes for FFI-specific crates to suppress intentional FFI-related warnings
  • Ruby vendoring: Updated vendoring script to preserve workspace lints configuration during gem dependency vendoring
  • Code formatting: Fixed formatting inconsistencies across all binding crates to comply with project standards

[0.8.2] - 2026-01-02

Fixed

  • Homebrew bottles: Fixed bottle directory structure to include formula name and version path, resolving installation failures

[0.8.1] - 2026-01-01

Added

  • Homebrew bottles: Pre-built binaries for macOS arm64 (Sonoma, Sequoia) for faster brew install/upgrade

Fixed

  • Homebrew formula: Fixed missing SHA256 checksum that was causing installation failures

[0.8.0] - 2025-12-31

Added

  • gRPC/Protobuf Support: Full gRPC server implementation with code generation
  • Supports all 5 languages: Python, TypeScript, Ruby, PHP, Rust
  • Complete proto3 schema parsing and type generation
  • All 17 gRPC status codes with proper error handling
  • Unary RPC support (streaming modes planned for future releases)
  • FFI bindings for all language runtimes using Tonic
  • Comprehensive test coverage (672+ tests across all layers)
  • Complete documentation suite with 9 guides and 3 ADRs

  • Documentation Enhancements: Complete documentation transformation

  • Created 239 reusable code snippets across 5 languages
  • Added comprehensive testing guide with all languages
  • Added troubleshooting and code generation guides
  • Refactored all major guides with snippet extraction (77% line reduction)
  • Achieved full language parity (Python, TypeScript, Ruby, PHP, Rust) in all guides
  • Added 9 gRPC-specific guides with complete examples
  • Split init command into quickstart and reference documentation

[0.7.5] - 2025-12-31

Fixed

  • Ruby bindings: Avoid double-defining StreamingResponse to eliminate runtime redefinition warnings.
  • Release automation: Use Packagist username + token when triggering refreshes and skip gracefully if missing.
  • Test apps: Use registry-only pnpm installs, add uv fallback to pip, and validate npm/Packagist endpoints correctly.

[0.7.4] - 2025-12-31

Fixed

  • Ruby TestClient: Always execute handler calls under the GVL so Ruby VM access is valid when invoked from Rust threads.

[0.7.3] - 2025-12-31

Fixed

  • Ruby gem vendoring: Align tower-http features, tracing-subscriber env-filter, and tower-governor version so native builds match the workspace.
  • Cloudflare test generator: Keep workers-types version aligned with the lockfile to avoid frozen install failures.

[0.7.2] - 2025-12-30

Fixed

  • Ruby vendoring: Avoid rewriting spikard-http as http so the Ruby gem builds against the vendored crates.

[0.7.1] - 2025-12-30

Fixed

  • WASM test client: Load bundled WASM bindings from the package dist output so published builds work in tests.

[0.7.0] - 2025-12-30

Added

  • GraphQL code generation: Full support for generating typed GraphQL server code from schema files
  • Supports all 5 languages: Python, TypeScript, Ruby, PHP, Rust
  • Generates three output types: types, resolvers, and schema
  • Type-safe resolver signatures with proper parent/context/info parameters
  • Automatic RBS type definitions for Ruby
  • Strict type checking compliance (no Any types in Python, TypeScript)
  • Quality validation with mypy, TypeScript compiler, Steep, PHPStan
  • SDL schema reconstruction for runtime validation

  • spikard init command: Project scaffolding for new Spikard projects

  • Supports all 5 languages: Python, TypeScript, Ruby, PHP, Rust
  • Language-specific project structure generation
  • Automatic dependency initialization (pip, npm/pnpm, gem, composer, cargo)
  • Example handler files following language-specific patterns
  • Optional schema file integration for code generation

  • Quality validation framework: Automated validation of generated code

  • Language-specific syntax validation
  • Type checking integration (mypy, TypeScript, Steep, PHPStan)
  • Linting with native tools (Ruff, Biome, Rubocop, PHP-CS-Fixer)
  • Structured validation reports with detailed error messages

Changed

  • Code generation architecture refactored: All generators now use shared utilities
  • Centralized case conversion (snake_case, camelCase, PascalCase, kebab-case)
  • Unified string escaping for different contexts (JSON, GraphQL SDL, docstrings)
  • Consistent identifier sanitization with language-specific rules
  • Improved code quality and consistency across all generators

Fixed

  • OpenAPI generators: Critical bug fixes affecting generated code quality
  • Ruby: Fixed multi-line comment handling causing syntax errors
  • PHP: Corrected parameter ordering violations
  • TypeScript: Resolved forward reference errors in type definitions

  • OpenRPC generators: Fixed serialization issues causing double JSON encoding

  • AsyncAPI generators: Fixed critical type mapping issues across all languages

[0.6.2] - 2025-12-28

Fixed

  • Version bump and test app updates for consistency

[0.6.1] - Previous Release

See git history for detailed changes.


Project Structure

Codegen Modules

crates/spikard-cli/src/codegen/
├── common/              # Shared utilities (case conversion, escaping, sanitization)
├── quality/             # Quality validation framework
├── formatters/          # Language-specific formatters
├── graphql/             # GraphQL schema generators
├── openapi.rs           # OpenAPI generators
├── openrpc/             # OpenRPC generators
├── asyncapi/            # AsyncAPI generators
└── [language].rs        # Individual language generators

Init Module

crates/spikard-cli/src/init/
├── engine.rs            # Core initialization orchestration
├── scaffolder.rs        # ProjectScaffolder trait
└── [language].rs        # Language-specific scaffolders

Contributing

When adding new features or generators:

  1. Use shared utilities in codegen/common/ for case conversion and escaping
  2. Validate generated code using codegen/quality/QualityValidator
  3. Add fixtures to testing_data/ for new scenarios
  4. Update this changelog with all changes
  5. Run task test to ensure quality gates pass

For detailed guidelines, see: