Pristine logo

Pristine

Hex VersionHex DocsGitHubLicense

Pristine is the shared runtime and build-time bridge for first-party OpenAPI-based Elixir SDKs.

The recommended provider-SDK boundary is:

The retained build-time seam is Pristine.OpenAPI.Bridge.run/3.

Pristine.context/1 also remains available when you want full manual ports-and-adapters control, but provider SDKs should treat Pristine.Core.* and Pristine.OpenAPI.* as internal implementation detail rather than as the blessed SDK contract.

Runtime Boundary

Use Pristine.foundation_context/1 for the recommended production runtime:

context =
  Pristine.foundation_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.new(System.fetch_env!("API_TOKEN"))]
  )

Execute a normalized request spec through Pristine.execute_request/3:

request_spec = %{
  id: "widgets.list",
  method: :get,
  path: "/v1/widgets",
  path_params: %{},
  query: %{"limit" => 10},
  headers: %{},
  body: nil,
  form_data: nil,
  auth: nil,
  security: [%{"bearerAuth" => []}],
  request_schema: nil,
  response_schema: nil,
  resource: "widgets",
  retry: "widgets.read",
  rate_limit: "widgets.integration",
  circuit_breaker: "core_api",
  telemetry: "request.widgets"
}

{:ok, response} = Pristine.execute_request(request_spec, context)

Pristine.execute_request/3 also accepts the generated request maps emitted by Pristine.SDK.OpenAPI.Client. In both cases, the same runtime path validation, serialization, auth, retry, telemetry, rate-limit, and circuit-breaker wiring still applies.

Pristine.SDK.* exposes the stable runtime-facing types used by downstream SDKs:

Manual Context Construction

Use Pristine.context/1 when you want complete control over the raw runtime ports and 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,
    retry: Pristine.Adapters.Retry.Noop,
    telemetry: Pristine.Adapters.Telemetry.Noop
  )

That lower-level constructor is useful for bespoke clients and tests. The Foundation profile exists so production callers do not have to hand-wire the same resilience and telemetry stack repeatedly.

OAuth Provider Construction

SDK-facing OAuth provider construction stays tied to OpenAPI security scheme metadata, not to manifests.

provider =
  Pristine.SDK.OAuth2.Provider.from_security_scheme!(
    "notionOAuth",
    %{
      "type" => "oauth2",
      "flows" => %{
        "authorizationCode" => %{
          "authorizationUrl" => "/v1/oauth/authorize",
          "tokenUrl" => "/v1/oauth/token",
          "scopes" => %{"workspace.read" => "Read workspace data"}
        }
      },
      "x-pristine-flow" => "authorizationCode",
      "x-pristine-token-content-type" => "application/json"
    },
    site: "https://api.notion.com"
  )

Pristine.SDK.OAuth2 uses the in-tree Pristine.Adapters.OAuthBackend.Native backend by default. Browser launch and loopback callback capture stay optional adapter seams:

Manual paste-back still works without those adapters, and persisted token load/save/refresh orchestration lives in Pristine.OAuth2.SavedToken on top of the token-source boundary.

Build-Time Bridge

Pristine.OpenAPI.Bridge.run/3 is the retained first-party build-time seam for SDK generation. It is not the normal consumer runtime entry.

The bridge needs at least a base module and output directory:

result =
  Pristine.OpenAPI.Bridge.run(
    :widgets_sdk,
    ["openapi/widgets.json"],
    base_module: WidgetsSDK,
    output_dir: "lib/widgets_sdk/generated",
    source_contexts: %{
      {:get, "/v1/widgets"} => %{
        title: "Widgets",
        url: "https://docs.example.com/widgets"
      }
    }
  )

sources = Pristine.OpenAPI.Bridge.generated_sources(result)

The returned %Pristine.OpenAPI.Result{} contains:

That lets first-party SDK generators reuse the same IR, generated files, and docs manifest without exposing a manifest-shaped runtime API.

Guides