Documentation
¶
Overview ¶
Package cohorly is the official server-side Go SDK for Cohorly, a hosted product analytics platform. Its API mirrors the official Mixpanel Go SDK: a synchronous, context-based Client for tracking events, updating user profiles and aliasing identities, plus an optional BufferedClient that queues events and flushes them in the background with the shared Cohorly SDK retry contract.
Quickstart:
client := cohorly.NewClient("PROJECT_TOKEN",
cohorly.WithAPIHost("https://cohorly-service.velloalabs.com"))
err := client.Track(ctx, []*cohorly.Event{
client.NewEvent("signup", "user-1", map[string]any{"plan": "pro"}),
})
Index ¶
- Constants
- Variables
- type BufferedClient
- type BufferedOption
- type Client
- func (c *Client) Alias(ctx context.Context, alias string, distinctID string) error
- func (c *Client) Close() error
- func (c *Client) GetAllFlags(ctx context.Context, distinctID string) (map[string]FlagResult, error)
- func (c *Client) GetFeatureFlag(ctx context.Context, key string, distinctID string, opts ...FlagOption) (variant string, enabled bool, err error)
- func (c *Client) GetFeatureFlagPayload(ctx context.Context, key string, distinctID string, opts ...FlagOption) (json.RawMessage, error)
- func (c *Client) IsFeatureEnabled(ctx context.Context, key string, distinctID string, opts ...FlagOption) (bool, error)
- func (c *Client) NewEvent(name string, distinctID string, properties map[string]any) *Event
- func (c *Client) PeopleDelete(ctx context.Context, distinctID string) error
- func (c *Client) PeopleIncrement(ctx context.Context, distinctID string, add map[string]float64) error
- func (c *Client) PeopleSet(ctx context.Context, people []*PeopleProperties) error
- func (c *Client) PeopleSetOnce(ctx context.Context, people []*PeopleProperties) error
- func (c *Client) PeopleUnset(ctx context.Context, distinctID string, unset []string) error
- func (c *Client) Track(ctx context.Context, events []*Event) error
- type Event
- type FlagDefinition
- type FlagOption
- type FlagResult
- type FlagRule
- type FlagVariant
- type HTTPError
- type Option
- type PeopleProperties
Constants ¶
const ( // DefaultFlushInterval is how often the background loop flushes the // queue when nothing else wakes it. DefaultFlushInterval = 10 * time.Second // DefaultFlushBatchSize is the number of events sent per flush request. DefaultFlushBatchSize = 50 // DefaultQueueCapacity is the maximum number of queued events; when // full, the oldest events are dropped. DefaultQueueCapacity = 1000 )
Buffered client defaults.
const DefaultAPIHost = "https://cohorly-service.velloalabs.com"
DefaultAPIHost is the API host used when WithAPIHost is not provided.
const DefaultFlagPollInterval = 30 * time.Second
DefaultFlagPollInterval is how often flag definitions are refetched when WithFlagPollInterval is not provided.
const EmptyDistinctID = ""
EmptyDistinctID can be passed to NewEvent when the event has no associated user. Note that Cohorly expects a distinct_id on every event; use this only when you set distinct_id yourself via the properties map.
const MaxTrackBatch = 500
MaxTrackBatch is the maximum number of events the Cohorly server accepts in a single /track request. Track transparently chunks larger slices into sequential requests of at most this size.
const Version = "0.2.0"
Version is the SDK version, stamped on every event as $lib_version.
Variables ¶
var ErrInvalidToken = errors.New("cohorly: invalid token")
ErrInvalidToken indicates the server rejected the project token (HTTP 401). It is surfaced via errors.Is on the *HTTPError returned by API calls.
Functions ¶
This section is empty.
Types ¶
type BufferedClient ¶
type BufferedClient struct {
*Client
// contains filtered or unexported fields
}
BufferedClient wraps a Client with an in-memory queue and a background flush loop. Enqueue is non-blocking; events are delivered in batches and, on retryable failures, kept and retried with the shared Cohorly SDK retry contract:
- 429/5xx/network error: keep the queue and retry with exponential backoff (base 2s, doubling, cap 10 min, +/-20% jitter), honoring Retry-After (capped at 10 min).
- 413: halve the flush batch size (floor 1) and retry; a single event that alone exceeds the server body cap is dropped.
- 400 (and other permanent 4xx): drop the batch.
- 401: keep the queue, back off at the maximum delay.
- Queue capped (default 1000 events); the oldest events are dropped first.
Server batch rejections are atomic, so retrying the same payload never duplicates events. A BufferedClient is safe for concurrent use.
func NewBufferedClient ¶
func NewBufferedClient(client *Client, opts ...BufferedOption) *BufferedClient
NewBufferedClient creates a BufferedClient on top of client and starts its background flush loop. Call Close to flush remaining events and stop the loop.
func (*BufferedClient) Close ¶
func (b *BufferedClient) Close(ctx context.Context) error
Close stops the background loop and attempts a final synchronous Flush of any remaining events. After Close the client must not be used.
func (*BufferedClient) Enqueue ¶
func (b *BufferedClient) Enqueue(events ...*Event)
Enqueue adds events to the queue without blocking. When the queue is at capacity the oldest events are dropped. A flush is triggered early once a full batch is queued.
func (*BufferedClient) Flush ¶
func (b *BufferedClient) Flush(ctx context.Context) error
Flush synchronously drains the queue, sending batch after batch. It stops and returns the underlying error on the first retryable failure (the queue is kept for the background loop to retry); permanently rejected batches are dropped as per the retry contract.
func (*BufferedClient) Len ¶
func (b *BufferedClient) Len() int
Len returns the number of events currently queued.
type BufferedOption ¶
type BufferedOption func(*BufferedClient)
BufferedOption configures a BufferedClient.
func WithFlushBatchSize ¶
func WithFlushBatchSize(n int) BufferedOption
WithFlushBatchSize sets how many events are sent per flush request (clamped to MaxTrackBatch). Defaults to DefaultFlushBatchSize.
func WithFlushInterval ¶
func WithFlushInterval(d time.Duration) BufferedOption
WithFlushInterval sets how often the background loop flushes the queue. Defaults to DefaultFlushInterval.
func WithQueueCapacity ¶
func WithQueueCapacity(n int) BufferedOption
WithQueueCapacity sets the maximum number of queued events; when the queue is full the oldest events are dropped. Defaults to DefaultQueueCapacity.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a synchronous Cohorly API client. It is safe for concurrent use.
func NewClient ¶
NewClient creates a Cohorly API client authenticated by the given project token.
With WithFlagSecret the client owns a background definitions poller and must be closed with Close; without it, Close is a no-op and optional.
func (*Client) Alias ¶
Alias links a new identifier to an existing distinct_id so events tracked under either resolve to the same user.
func (*Client) Close ¶ added in v0.2.0
Close stops the background flag-definitions poller and waits for it and any in-flight exposure sends to finish (both are bounded by contexts canceled here, so Close never blocks on an unresponsive host). It is idempotent and safe to call on a client that never started a poller. Close does not invalidate the client: flag reads fall back to remote evaluation afterwards.
func (*Client) GetAllFlags ¶ added in v0.2.0
GetAllFlags evaluates every flag in the project for distinctID, keyed by flag key. Without a flag secret this is a single /flags/evaluate request. With one it evaluates every locally-evaluable flag in-process and makes at most one request, for the cohort-targeted flags that cannot be evaluated locally.
A failure of that one request is reported as a partial result: the locally evaluated flags are returned along with a non-nil error, and the flags that needed the server are absent from the map rather than reported disabled. Check the error before treating a missing key as "off".
Exposure events are never sent by GetAllFlags.
func (*Client) GetFeatureFlag ¶ added in v0.2.0
func (c *Client) GetFeatureFlag(ctx context.Context, key string, distinctID string, opts ...FlagOption) (variant string, enabled bool, err error)
GetFeatureFlag returns the served variant and enabled state of a flag for distinctID. The variant wins when set: a non-empty variant is the value to branch on, and enabled is only meaningful for boolean flags (variant ""). An unknown flag yields ("", false, nil).
func (*Client) GetFeatureFlagPayload ¶ added in v0.2.0
func (c *Client) GetFeatureFlagPayload(ctx context.Context, key string, distinctID string, opts ...FlagOption) (json.RawMessage, error)
GetFeatureFlagPayload returns the raw JSON payload attached to the flag's served variant, or nil when the flag is unknown or carries no payload. Unmarshal it into whatever shape the payload was authored as.
func (*Client) IsFeatureEnabled ¶ added in v0.2.0
func (c *Client) IsFeatureEnabled(ctx context.Context, key string, distinctID string, opts ...FlagOption) (bool, error)
IsFeatureEnabled reports whether the flag is enabled for distinctID. An unknown flag is reported as disabled with no error; only transport and HTTP failures return an error.
func (*Client) NewEvent ¶
NewEvent creates an event with the Cohorly default properties stamped:
- distinct_id: distinctID (skipped when EmptyDistinctID is passed)
- time: current unix time in milliseconds (unless already present in properties; override with AddTime)
- $insert_id: a random UUID v4 for server-side deduplication (unless already present; override with AddInsertID)
- $lib: "go"
- $lib_version: Version
The properties map is copied; the caller's map is not mutated.
func (*Client) PeopleDelete ¶
PeopleDelete deletes the user profile ($delete). Event history is kept.
func (*Client) PeopleIncrement ¶
func (c *Client) PeopleIncrement(ctx context.Context, distinctID string, add map[string]float64) error
PeopleIncrement adds the given numeric deltas to profile properties ($add).
func (*Client) PeopleSet ¶
func (c *Client) PeopleSet(ctx context.Context, people []*PeopleProperties) error
PeopleSet sets profile properties on the given users ($set), overwriting existing values.
func (*Client) PeopleSetOnce ¶
func (c *Client) PeopleSetOnce(ctx context.Context, people []*PeopleProperties) error
PeopleSetOnce sets profile properties on the given users only if they are not already set ($set_once).
func (*Client) PeopleUnset ¶
PeopleUnset removes the given properties from a profile ($unset).
func (*Client) Track ¶
Track sends events to the Cohorly /track endpoint. Slices larger than MaxTrackBatch are chunked into sequential requests; if a chunk fails its error is returned and the remaining chunks are not sent. The server applies batches atomically, so a failed batch inserted nothing and is safe to retry as-is.
type Event ¶
type Event struct {
// Name is the event name.
Name string `json:"event"`
// Properties holds the event properties, including the reserved keys
// stamped by NewEvent (distinct_id, time, $insert_id, $lib,
// $lib_version).
Properties map[string]any `json:"properties"`
}
Event is a single analytics event sent to the /track endpoint.
func (*Event) AddInsertID ¶
AddInsertID overrides the $insert_id used for server-side deduplication.
type FlagDefinition ¶ added in v0.2.0
type FlagDefinition struct {
Key string `json:"key"`
Name string `json:"name"`
Active bool `json:"active"`
Variants []FlagVariant `json:"variants"`
Rules []FlagRule `json:"rules"`
LocalEvaluable bool `json:"localEvaluable"`
}
FlagDefinition is one flag as served by GET /flags/local-evaluation: the fields an SDK needs to evaluate in-process, and nothing else. LocalEvaluable is false when any rule references a cohort; the SDK must fall back to POST /flags/evaluate for such a flag rather than silently not matching.
type FlagOption ¶ added in v0.2.0
type FlagOption func(*flagCallOptions)
FlagOption customizes a single flag read.
func WithExposureEvent ¶ added in v0.2.0
func WithExposureEvent() FlagOption
WithExposureEvent tracks a "$feature_flag_called" event for this read, so the flag exposure shows up in analysis (funnels, retention) alongside the rest of the project's events. It is opt-in per call: flag reads are often in hot paths where an extra event per call is not wanted.
The event goes through the normal Track path and is best effort - a failure to send it never fails the flag read, and never changes the value returned. GetAllFlags never sends exposure events.
type FlagResult ¶ added in v0.2.0
type FlagResult struct {
// Enabled reports whether the flag is on for this user.
Enabled bool `json:"enabled"`
// Variant is the multivariate value, empty when the flag is a simple
// boolean rollout.
Variant string `json:"variant"`
// Payload is the raw JSON payload attached to the served variant, nil
// when the flag has none.
Payload json.RawMessage `json:"payload"`
// Reason explains the decision (e.g. "rollout", "cohort", "override").
Reason string `json:"reason"`
}
FlagResult is the evaluation of one feature flag for a distinct id, as returned by POST /flags/evaluate.
type FlagRule ¶ added in v0.2.0
type FlagRule struct {
// CohortID references a cohort. Its presence makes the whole flag
// non-locally-evaluable, so a rule carrying it is never evaluated here.
CohortID *int `json:"cohortId,omitempty"`
// DistinctIDs is the Override allow-list; nil means the rule applies to
// everyone.
DistinctIDs []string `json:"distinctIds,omitempty"`
RolloutPct float64 `json:"rolloutPct"`
// Variant names an existing variant key to serve; empty means the
// variant is picked by hash.
Variant string `json:"variant,omitempty"`
}
FlagRule is one targeting rule. Rules are ordered within a flag and the first match wins. DistinctIDs is the Override, an explicit allow-list.
type FlagVariant ¶ added in v0.2.0
type FlagVariant struct {
Key string `json:"key"`
// Payload is the raw JSON payload attached to the variant, nil when it
// has none.
Payload json.RawMessage `json:"payload,omitempty"`
RolloutPct float64 `json:"rolloutPct"`
}
FlagVariant is one variant of a multivariate flag. RolloutPct values across a flag's variants sum to exactly 100 when the list is non-empty.
type HTTPError ¶
type HTTPError struct {
// Status is the HTTP status code.
Status int
// Body is the raw response body (truncated to 64 KiB).
Body string
// RetryAfter is the parsed Retry-After header on 429 responses, or 0
// when the header is absent.
RetryAfter time.Duration
}
HTTPError is returned for any non-2xx response from the Cohorly server.
type Option ¶
type Option func(*Client)
Option configures a Client.
func WithAPIHost ¶
WithAPIHost sets the base URL of the Cohorly server, e.g. "https://analytics.example.com". Defaults to DefaultAPIHost.
func WithFlagPollInterval ¶ added in v0.2.0
WithFlagPollInterval sets how often flag definitions are refetched. Defaults to DefaultFlagPollInterval. A non-positive interval fetches once and never refreshes. A flag edit takes up to one interval to propagate; remote evaluation (no flag secret) is the option when immediacy matters.
func WithFlagSecret ¶ added in v0.2.0
WithFlagSecret enables local flag evaluation (ADR-0011). The flag secret is a per-project revocable credential, distinct from the project token, that authorizes exactly one endpoint: GET /flags/local-evaluation. When it is set the client polls flag definitions in the background and evaluates locally-evaluable flags in-process, at zero request latency. Flags whose rules reference a cohort are served with LocalEvaluable false and keep falling back to POST /flags/evaluate per call.
A client created with this option starts a goroutine; call Close when done.
func WithHTTPClient ¶
WithHTTPClient sets the *http.Client used for all requests. Defaults to http.DefaultClient.
type PeopleProperties ¶
type PeopleProperties struct {
// DistinctID identifies the user profile.
DistinctID string
// Properties are the profile properties to write.
Properties map[string]any
}
PeopleProperties is a profile update for a single user, used by PeopleSet and PeopleSetOnce.
func NewPeopleProperties ¶
func NewPeopleProperties(distinctID string, properties map[string]any) *PeopleProperties
NewPeopleProperties creates a profile update for distinctID.