billdogeng 1.0.0-beta.1

Official BilldogEng server SDK for Rust — Analytics, Feature Flags (remote + local eval), Surveys, Messaging, and LLM observability.
Documentation

billdogeng (Rust)

Official BilldogEng server SDK for Rust — the engagement suite for server-side use: Analytics, Feature Flags (remote + local evaluation), Surveys (data API), Messaging dispatch, and LLM observability.

This crate mirrors the canonical Node reference implementation (billdogeng-node) idiomatically in Rust: a synchronous, thread-safe API with a background flush thread for analytics batching.

Install

[dependencies]
billdogeng = "1"

Quickstart

use std::collections::HashMap;
use billdogeng::{BilldogEng, BilldogEngOptions, DispatchParams, FlagEvalOptions, LlmTraceParams};
use serde_json::json;

let bd = BilldogEng::new("bd_test_xxx", BilldogEngOptions {
    // host: "https://api.billdog.io/v1".into(), // default
    flush_at: 20,             // batch size that triggers a flush
    flush_interval_ms: 10_000, // background flush cadence
    local_evaluation: true,   // server-only: evaluate flags locally
    ..Default::default()
});

// ─ Analytics ─ (batched + flushed automatically)
bd.capture("user-123", "order_completed",
    json!({ "revenue": 49.99 }).as_object().unwrap().clone(),
    HashMap::from([("company".into(), "acme".into())]));
bd.identify("user-123", json!({ "email": "a@b.com", "plan": "pro" }).as_object().unwrap().clone());
bd.group_identify("company", "acme", json!({ "seats": 50 }).as_object().unwrap().clone());
bd.alias("user-123", "anon-abc");

// ─ Feature flags ─
let on = bd.is_feature_enabled("new_checkout", "user-123", &FlagEvalOptions::default())?;
let variant = bd.get_feature_flag("paywall_test", "user-123", &FlagEvalOptions::default())?;     // Bool | Variant | None
let payload = bd.get_feature_flag_payload("paywall_test", "user-123", &FlagEvalOptions::default())?;
let all = bd.get_all_flags("user-123", &FlagEvalOptions::default())?;

// ─ Surveys (data API) ─
let surveys = bd.surveys.list(Some("user-123"), None)?;
let config = bd.surveys.fetch(&surveys[0].id, Some("user-123"), None)?;

// ─ Messaging dispatch ─ (Bearer JWT auth, not the API key)
bd.messaging.dispatch(&DispatchParams {
    project_id: "project-uuid".into(),
    channel: "push".into(),
    content: json!({ "title": "Hi", "body": "There" }).as_object().unwrap().clone(),
    targeting: None,
    scheduling: None,
    template_id: None,
    access_token: "<supabase-session-jwt>".into(),
})?;

// ─ LLM observability ─
bd.capture_trace(&LlmTraceParams {
    trace_id: "t-1".into(), span_id: "s-1".into(), model: "gpt-4o".into(),
    input_text: "prompt".into(), output_text: "completion".into(),
    prompt_tokens: Some(100), completion_tokens: Some(50),
    duration_ms: Some(820), cost_usd: Some(0.003),
    ..Default::default()
})?;

// Flush remaining events + stop the background thread before exit.
bd.shutdown()?;
# Ok::<(), billdogeng::BilldogEngError>(())

Configuration

Option Default Description
host https://api.billdog.io/v1 API base URL
flush_at 20 Batch size that triggers a flush
flush_interval_ms 10000 Background flush cadence (ms)
max_queue_size 1000 Drop oldest events past this many queued
gzip true Gzip large request bodies
local_evaluation false Evaluate feature flags locally (server-only)
request_timeout_ms 10000 Per-request timeout (ms)
max_retries 3 Retry attempts for 5xx / 429 / network errors
group_type_index Stable group-type → $group_0..4 index map
enable_logging false Verbose diagnostics to stderr

Authentication uses the x-api-key: <apiKey> header (bd_test_* sandbox / bd_live_* live) on every request, except messaging dispatch, which authenticates with a Supabase session Bearer JWT + project membership.

Analytics batching & delivery

  • Events accumulate in an in-memory queue and ship as a single batched POST to /ingest-events.
  • A flush happens on flush_at, every flush_interval_ms, or on flush() / shutdown().
  • Failed flushes re-queue their batch (never lose events on a transient failure); the transport also retries with exponential backoff (1s, 2s, 4s).
  • Bodies are gzip-compressed when large enough to benefit.

Feature flags — local evaluation

With local_evaluation: true, the SDK fetches flag definitions once (POST /feature-flag-definitions), caches them for 5 minutes, and evaluates deterministically on-process:

  1. missing/inactive → false
  2. ALL targeting_rules must match person_properties, else false
  3. bucket = murmurhash3("{key}.{distinctId}") % 100; ON iff bucket < rollout_percentage
  4. multivariate: walk variants by cumulative rollout within the ON bucket → variant key

The murmurhash3 (32-bit, seed 0) implementation is byte-identical across web / iOS / Android / all server SDKs, so a user buckets the same everywhere.

Build & test

cargo build
cargo test

License

MIT