Skip to content

Manifest-driven hexagonal core for generating Elixir SDKs and services with pluggable ports/adapters for transport, schema, retries, telemetry, streaming, and multipart.

License

Notifications You must be signed in to change notification settings

nshkrdotcom/pristine

Repository files navigation

Pristine logo

Pristine

Manifest-driven, hexagonal SDK generator for Elixir

Hex Version Hex Docs CI Status License


Pristine separates domain logic from infrastructure through a clean ports and adapters architecture. Define your API in a declarative manifest, then generate type-safe Elixir SDKs with built-in resilience patterns, streaming support, and comprehensive observability.

Features

  • Hexagonal Architecture — Clean separation via ports (interfaces) and adapters (implementations)
  • Manifest-Driven — Declarative API definitions in JSON, YAML, or Elixir
  • Code Generation — Generate type modules, resource modules, and clients from manifests
  • Type Safety — Sinter schema validation for requests and responses
  • Resilience Built-In — Retry policies, circuit breakers, and rate limiting
  • Streaming Support — First-class SSE (Server-Sent Events) handling
  • Observable — Telemetry events throughout the request lifecycle
  • Extensible — Swap adapters for transport, auth, serialization, and more

Installation

Add Pristine to your dependencies:

def deps do
  [
    {:pristine, "~> 0.1.0"}
  ]
end

Quick Start

1. Define Your API Manifest

{
  "name": "myapi",
  "version": "1.0.0",
  "base_url": "https://api.example.com",
  "endpoints": [
    {
      "id": "get_user",
      "method": "GET",
      "path": "/users/{id}",
      "resource": "users",
      "response": "User"
    },
    {
      "id": "create_user",
      "method": "POST",
      "path": "/users",
      "resource": "users",
      "request": "CreateUserRequest",
      "response": "User"
    }
  ],
  "types": {
    "User": {
      "fields": {
        "id": {"type": "string", "required": true},
        "name": {"type": "string", "required": true},
        "email": {"type": "string"}
      }
    },
    "CreateUserRequest": {
      "fields": {
        "name": {"type": "string", "required": true},
        "email": {"type": "string"}
      }
    }
  }
}

2. Generate SDK Code

mix pristine.generate \
  --manifest manifest.json \
  --output lib/myapi \
  --namespace MyAPI

3. Use the Generated SDK

# Create a client
client = MyAPI.Client.new(
  base_url: "https://api.example.com",
  transport: Pristine.Adapters.Transport.Finch,
  transport_opts: [finch: MyApp.Finch],
  auth: [{Pristine.Adapters.Auth.Bearer, token: "your-token"}]
)

# Make API calls
{:ok, user} = MyAPI.Users.get(client.users(), "user-123")
{:ok, new_user} = MyAPI.Users.create(client.users(), "John Doe", email: "john@example.com")

Architecture

Pristine implements a hexagonal (ports and adapters) architecture:

┌─────────────────────────────────────────────────────────┐
│                    Your Application                     │
├─────────────────────────────────────────────────────────┤
│                  Generated SDK Layer                    │
│              (Client, Resources, Types)                 │
├─────────────────────────────────────────────────────────┤
│                    Pristine Core                        │
│      Pipeline │ Manifest │ Codegen │ Streaming          │
├─────────────────────────────────────────────────────────┤
│                        Ports                            │
│    Transport │ Serializer │ Auth │ Retry │ Telemetry    │
├─────────────────────────────────────────────────────────┤
│                       Adapters                          │
│     Finch │ JSON │ Bearer │ Foundation │ Gzip │ SSE     │
└─────────────────────────────────────────────────────────┘

Ports define interface contracts. Adapters provide implementations. Swap adapters to change behavior without touching domain logic.

Available Adapters

Category Adapters
Transport Finch, FinchStream
Serializer JSON
Auth Bearer, APIKey
Retry Foundation, Noop
Circuit Breaker Foundation, Noop
Rate Limit BackoffWindow, Noop
Telemetry Foundation, Raw, Reporter, Noop
Compression Gzip
Streaming SSE

Runtime Execution

Execute endpoints without code generation:

# Load manifest
{:ok, manifest} = Pristine.load_manifest_file("manifest.json")

# Build context with adapters
context = Pristine.context(
  base_url: "https://api.example.com",
  transport: Pristine.Adapters.Transport.Finch,
  transport_opts: [finch: MyApp.Finch],
  serializer: Pristine.Adapters.Serializer.JSON,
  auth: [{Pristine.Adapters.Auth.Bearer, token: "your-token"}],
  retry: Pristine.Adapters.Retry.Foundation,
  telemetry: Pristine.Adapters.Telemetry.Foundation
)

# Execute endpoint
{:ok, result} = Pristine.execute(manifest, :get_user, %{}, context,
  path_params: %{"id" => "123"}
)

Streaming Support

Handle SSE streams with first-class support:

context = Pristine.context(
  stream_transport: Pristine.Adapters.Transport.FinchStream,
  # ... other config
)

{:ok, response} = Pristine.Core.Pipeline.execute_stream(
  manifest, :stream_endpoint, payload, context
)

# Consume events lazily
response.stream
|> Stream.each(fn event ->
  case Pristine.Streaming.Event.json(event) do
    {:ok, data} -> process(data)
    {:error, _} -> :skip
  end
end)
|> Stream.run()

Resilience Patterns

Configure retry policies in your manifest:

{
  "retry_policies": {
    "default": {
      "max_attempts": 3,
      "backoff": "exponential",
      "base_delay_ms": 1000
    }
  },
  "endpoints": [
    {
      "id": "important_call",
      "retry": "default"
    }
  ]
}

Built-in support for:

  • Exponential backoff with jitter
  • Circuit breakers per endpoint
  • Rate limiting with server-driven backoff
  • Idempotency keys for safe retries

Documentation

Development

# Install dependencies
mix deps.get

# Run tests
mix test

# Type checking
mix dialyzer

# Linting
mix credo --strict

# Format code
mix format

# Run all checks
mix test && mix dialyzer && mix credo --strict

Dependencies

Pristine integrates with several companion libraries:

Library Purpose
Foundation Retry, backoff, circuit breaker
Sinter Schema validation
Finch HTTP client
Jason JSON encoding

Contributing

Contributions are welcome! Please read our contributing guidelines and submit pull requests to the GitHub repository.

License

MIT License. See LICENSE for details.

About

Manifest-driven hexagonal core for generating Elixir SDKs and services with pluggable ports/adapters for transport, schema, retries, telemetry, streaming, and multipart.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •  

Languages