# Changelog All notable changes to this project will be documented in this file. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [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=` 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 - **Guides** — `guides/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