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 refreshAurinko.APIs.Email— List, get, send, draft, update messages; delta sync; attachments; email trackingAurinko.APIs.Calendar— List/get calendars and events; create/update/delete events; delta sync; free/busyAurinko.APIs.Contacts— CRUD contacts; delta syncAurinko.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 availabilityAurinko.Types— Typed structs for Email, CalendarEvent, Calendar, Contact, Task, Pagination, SyncResultAurinko.Error— Structured, tagged error type with HTTP status mappingAurinko.HTTP.Client— Req-based HTTP client with retry, backoff, and connection poolingAurinko.Telemetry—:telemetryevents for all HTTP requestsAurinko.Config— NimbleOptions-validated configuration- Full typespecs and
@moduledoc/@doccoverage - 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 allGETrequests- 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/3public 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
:okor{:wait, ms}— the HTTP client sleeps and continues automatically check_rate/1,reset_token/1,inspect_bucket/1public 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, andtimeoutfailure types; ignoresnot_foundetc. - Half-open probe on timeout expiry; re-opens on probe failure, closes on probe success
call/2,status/1,reset/1public 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
429and5xxresponses Retry-Afterheader parsing for429responses- Structured
%Aurinko.Error{}on all failure paths (no raw exceptions leak) - Request packing via
req_infomap to keep internal function arities ≤ 8 get/3,post/4,patch/4,put/4,delete/3public API
Streaming Pagination
Aurinko.Paginator— LazyStream-based pagination for all list endpointsstream/3— streams records across all pages on demand, never loading all into memorysync_stream/4— streams delta-sync records; capturesnext_delta_tokenvia:on_deltacallbackcollect_all/3— synchronous convenience wrapper returning{:ok, list}- Configurable
:on_error—:halt(default) or:skipper-page error handling
Sync Orchestrator
Aurinko.Sync.Orchestrator— High-level delta-sync lifecycle managersync_email/2— full or incremental email sync; resolves or provisions delta tokens automaticallysync_calendar/3— calendar sync with configurabletime_min/time_maxwindowsync_contacts/2— contacts sync (updated records only; no deleted stream)- Accepts
:get_tokens,:save_tokens,:on_updated,:on_deletedcallbacks - 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 verificationverify/3— validatessha256=<hex>signature header; returns:okor{:error, :invalid_signature}sign/2— test helper for generating valid signatures- Constant-time comparison via
:crypto.hash/2to prevent timing attacks (noplug_cryptodependency)
Aurinko.Webhook.Handler— Behaviour + dispatcher for webhook event processingdispatch/4— parses raw body, optionally verifies signature, routeseventTypeto handler module@callback handle_event/3behaviour 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/0anddetach_default_logger/0for zero-config structured loggingTelemetry.Metricsdefinitions 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}
- One JSON object per log line:
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.ConfigErrorif credentials missing) - Structured startup summary logged at
:infolevel - 5-second graceful shutdown timeout per child
Configuration (expanded)
Aurinko.Configextended with new validated keys (all viaNimbleOptions):- 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/2utility for per-request config overrides
- Cache:
Developer Experience
- Guides —
guides/getting_started.mdandguides/advanced.mdadded 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
mixaliases:lint,test.all,quality config/staging.exs— staging environment config with JSON logging pre-configuredconfig/runtime.exs— runtime config reading all settings from environment variables
Changed
Aurinko.HTTP.Clientcompletely rewritten — previously a thinReqwrapper; now a full GenServer with the middleware pipeline described aboveAurinko.Telemetryexpanded from request-only events to 7 events covering the full request lifecycle, circuit breaker state changes, and sync completionAurinko.Configschema extended;load!/0now 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_listguard clause fromget_header/2(Req 0.5 always returns headers as a map) - Dialyzer: narrowed
@spec format/4return inJSONFormatterfromiodata()tobinary() - Dialyzer: removed
{:error, :rate_limit_exceeded}fromRateLimitertype and spec (function never returns it) - Dialyzer: narrowed
@spec events/0inTelemetryfromlist(list(atom()))tononempty_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