All notable changes to this project will be documented in this file.

The format follows Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

[0.2.1] — 2025-03-16

Added

API

  • Aurinko.Auth — OAuth authorization URL builder, code exchange, and token refresh
  • Aurinko.APIs.Email — List, get, send, draft, update messages; delta sync; attachments; email tracking
  • Aurinko.APIs.Calendar — List/get calendars and events; create/update/delete events; delta sync; free/busy
  • Aurinko.APIs.Contacts — CRUD contacts; delta sync
  • Aurinko.APIs.Tasks — Task list and task management (list, create, update, delete)
  • Aurinko.APIs.Webhooks — Subscription management (list, create, delete)
  • Aurinko.APIs.Booking — Booking profile listing and availability
  • Aurinko.Types — Typed structs for Email, CalendarEvent, Calendar, Contact, Task, Pagination, SyncResult
  • Aurinko.Error — Structured, tagged error type with HTTP status mapping
  • Aurinko.HTTP.Client — Req-based HTTP client with retry, backoff, and connection pooling
  • Aurinko.Telemetry:telemetry events for all HTTP requests
  • Aurinko.Config — NimbleOptions-validated configuration
  • Full typespecs and @moduledoc/@doc coverage
  • GitHub Actions CI with matrix testing (Elixir 1.16/1.17, OTP 26/27)
  • Credo strict linting and Dialyzer integration

Middleware & Infrastructure

  • Aurinko.Cache — ETS-backed TTL response cache for all GET requests

    • Configurable TTL (default 60 s), max size (default 5 000 entries), and cleanup interval
    • LRU eviction when the entry limit is reached
    • Per-token cache invalidation via invalidate_token/1
    • Hit/miss/eviction statistics via stats/0
    • SHA-256 cache key derivation from {token, path, params}
    • get/1, put/3, delete/1, flush/0, build_key/3 public API
  • Aurinko.RateLimiter — Token-bucket rate limiter with dual buckets

    • Per-token bucket (default 10 req/s) and global bucket (default 100 req/s)
    • Configurable burst allowance (default +5 over steady-state)
    • Returns :ok or {:wait, ms} — the HTTP client sleeps and continues automatically
    • check_rate/1, reset_token/1, inspect_bucket/1 public API
    • ETS-backed buckets with automatic cleanup of stale entries after 5 min of inactivity
  • Aurinko.CircuitBreaker — Per-endpoint circuit breaker (closed → open → half-open)

    • Configurable failure threshold (default 5) and recovery timeout (default 30 s)
    • Tracks server_error, network_error, and timeout failure types; ignores not_found etc.
    • Half-open probe on timeout expiry; re-opens on probe failure, closes on probe success
    • call/2, status/1, reset/1 public API
    • ETS-backed state machine; named per normalised URL path (IDs replaced with :id)

HTTP Client (rewritten)

  • Aurinko.HTTP.Client — Req 0.5-based HTTP client with full middleware pipeline
    • Pipeline order: Rate Limiting → Cache Lookup → Circuit Breaker → HTTP + Retry → Cache Write → Telemetry
    • Exponential backoff with jitter for 429 and 5xx responses
    • Retry-After header parsing for 429 responses
    • Structured %Aurinko.Error{} on all failure paths (no raw exceptions leak)
    • Request packing via req_info map to keep internal function arities ≤ 8
    • get/3, post/4, patch/4, put/4, delete/3 public API

Streaming Pagination

  • Aurinko.Paginator — Lazy Stream-based pagination for all list endpoints
    • stream/3 — streams records across all pages on demand, never loading all into memory
    • sync_stream/4 — streams delta-sync records; captures next_delta_token via :on_delta callback
    • collect_all/3 — synchronous convenience wrapper returning {:ok, list}
    • Configurable :on_error:halt (default) or :skip per-page error handling

Sync Orchestrator

  • Aurinko.Sync.Orchestrator — High-level delta-sync lifecycle manager
    • sync_email/2 — full or incremental email sync; resolves or provisions delta tokens automatically
    • sync_calendar/3 — calendar sync with configurable time_min/time_max window
    • sync_contacts/2 — contacts sync (updated records only; no deleted stream)
    • Accepts :get_tokens, :save_tokens, :on_updated, :on_deleted callbacks
    • Automatic retry with backoff when Aurinko sync is not yet :ready
    • Records are delivered in batches of 200 via Stream.chunk_every/2

Webhook Support

  • Aurinko.Webhook.Verifier — HMAC-SHA256 signature verification

    • verify/3 — validates sha256=<hex> signature header; returns :ok or {:error, :invalid_signature}
    • sign/2 — test helper for generating valid signatures
    • Constant-time comparison via :crypto.hash/2 to prevent timing attacks (no plug_crypto dependency)
  • Aurinko.Webhook.Handler — Behaviour + dispatcher for webhook event processing

    • dispatch/4 — parses raw body, optionally verifies signature, routes eventType to handler module
    • @callback handle_event/3 behaviour for implementing custom handlers

Observability

  • Aurinko.Telemetry (expanded) — 7 telemetry events now emitted

    • [:aurinko, :request, :start] — before each HTTP request
    • [:aurinko, :request, :stop] — after each HTTP request (includes duration, cached flag)
    • [:aurinko, :request, :retry] — on each retry attempt (includes reason: :rate_limited, :server_error, :timeout)
    • [:aurinko, :circuit_breaker, :opened] — when a circuit opens (threshold exceeded or probe failure)
    • [:aurinko, :circuit_breaker, :closed] — when a circuit recovers
    • [:aurinko, :circuit_breaker, :rejected] — when a request is rejected by an open circuit
    • [:aurinko, :sync, :complete] — after a full sync run (updated count, deleted count, duration)
    • attach_default_logger/0 and detach_default_logger/0 for zero-config structured logging
    • Telemetry.Metrics definitions for Prometheus / StatsD reporters
  • Aurinko.Logger.JSONFormatter — Structured JSON log formatter

    • One JSON object per log line: time, level, msg, pid, module, function, line, request_id
    • Compatible with Datadog, Loki, Google Cloud Logging, and other log aggregation pipelines
    • Plug-in via config :logger, :console, format: {Aurinko.Logger.JSONFormatter, :format}

OTP Application

  • Aurinko.Application — Supervised OTP application with ordered start-up
    • Supervision order: Cache → RateLimiter → CircuitBreaker → HTTP.Client → Telemetry
    • Fail-fast config validation on start (raises Aurinko.ConfigError if credentials missing)
    • Structured startup summary logged at :info level
    • 5-second graceful shutdown timeout per child

Configuration (expanded)

  • Aurinko.Config extended with new validated keys (all via NimbleOptions):
    • Cache: :cache_enabled, :cache_ttl, :cache_max_size, :cache_cleanup_interval
    • Rate limiter: :rate_limiter_enabled, :rate_limit_per_token, :rate_limit_global, :rate_limit_burst
    • Circuit breaker: :circuit_breaker_enabled, :circuit_breaker_threshold, :circuit_breaker_timeout
    • Telemetry: :attach_default_telemetry
    • Config.merge/2 utility for per-request config overrides

Developer Experience

  • Guidesguides/getting_started.md and guides/advanced.md added to ExDoc
  • CI extended — Credo strict, Dialyzer, and ExCoveralls added as required checks
    • Elixir 1.16 / OTP 26 and Elixir 1.17 / OTP 27 matrix
    • Lint, format check, and Dialyzer run as separate CI jobs
  • New mix aliases: lint, test.all, quality
  • config/staging.exs — staging environment config with JSON logging pre-configured
  • config/runtime.exs — runtime config reading all settings from environment variables

Changed

  • Aurinko.HTTP.Client completely rewritten — previously a thin Req wrapper; now a full GenServer with the middleware pipeline described above
  • Aurinko.Telemetry expanded from request-only events to 7 events covering the full request lifecycle, circuit breaker state changes, and sync completion
  • Aurinko.Config schema extended; load!/0 now strips unknown application env keys before validation to avoid conflicts with middleware config keys
  • All API functions (Email, Calendar, Contacts, Tasks, Webhooks, Booking) now route through the full middleware pipeline automatically

Fixed

  • Dialyzer: removed unreachable is_list guard clause from get_header/2 (Req 0.5 always returns headers as a map)
  • Dialyzer: narrowed @spec format/4 return in JSONFormatter from iodata() to binary()
  • Dialyzer: removed {:error, :rate_limit_exceeded} from RateLimiter type and spec (function never returns it)
  • Dialyzer: narrowed @spec events/0 in Telemetry from list(list(atom())) to nonempty_list(nonempty_list(atom()))
  • Webhook verifier: replaced Plug.Crypto.secure_compare/2 (undeclared dependency) with a self-contained :crypto-based constant-time comparison

[0.1.0] — 2025-06-01

Added

  • Initial release with Starter Boilerplate elxir app