Skip to content

Commit 3889df4

Browse files
committed
Release v1.1.0
1 parent d8a1708 commit 3889df4

8 files changed

Lines changed: 413 additions & 155 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [1.0.1] - 2026-01-28
8+
## [1.1.0] - 2026-01-28
99

10-
- README tweak
10+
- **BREAKING:** Simplified API to use application config instead of passing client reference to every function call
11+
- Configure once in `config/config.exs` with `config :klime, write_key: "..."`
12+
- Add `Klime.Client` directly to supervision tree (no options needed)
13+
- All functions now use module calls: `Klime.track("Event", %{}, user_id: "123")` instead of `Klime.track(client, "Event", %{}, user_id: "123")`
14+
- Function arities reduced: `track/4``track/3`, `identify/3``identify/2`, `group/4``group/3`, `flush/1``flush/0`, `shutdown/1``shutdown/0`
1115

1216
# Changelog
1317

README.md

Lines changed: 89 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -22,33 +22,41 @@ mix deps.get
2222

2323
## Quick Start
2424

25+
1. Configure in `config/config.exs`:
26+
2527
```elixir
26-
# Start the client (typically in your application supervision tree)
27-
{:ok, client} = Klime.Client.start_link(write_key: "your-write-key")
28+
config :klime,
29+
write_key: System.get_env("KLIME_WRITE_KEY")
30+
```
31+
32+
2. Add to your supervision tree in `application.ex`:
33+
34+
```elixir
35+
children = [
36+
Klime.Client
37+
]
38+
```
39+
40+
3. Track events anywhere in your app:
2841

42+
```elixir
2943
# Identify a user
30-
Klime.identify(client, "user_123", %{
44+
Klime.identify("user_123", %{
3145
3246
name: "Stefan"
3347
})
3448

3549
# Track an event
36-
Klime.track(client, "Button Clicked", %{
50+
Klime.track("Button Clicked", %{
3751
button_name: "Sign up",
3852
plan: "pro"
3953
}, user_id: "user_123")
4054

41-
# Associate user with a group and set group traits
42-
Klime.group(client, "org_456", %{
55+
# Associate user with a group
56+
Klime.group("org_456", %{
4357
name: "Acme Inc",
4458
plan: "enterprise"
4559
}, user_id: "user_123")
46-
47-
# Or just link the user to a group (if traits are already set)
48-
Klime.group(client, "org_456", %{}, user_id: "user_123")
49-
50-
# Shutdown gracefully
51-
Klime.shutdown(client)
5260
```
5361

5462
## Installation Prompt
@@ -70,29 +78,28 @@ KEY CONCEPTS:
7078
- group() links a user to a company AND sets company traits (only for Companies & Teams mode)
7179
- Order doesn't matter - events before identify/group still get attributed correctly
7280
81+
SETUP:
82+
1. Add to mix.exs: {:klime, "~> 1.0"}
83+
2. Run: mix deps.get
84+
3. Configure in config/config.exs:
85+
config :klime, write_key: System.get_env("KLIME_WRITE_KEY")
86+
4. Add Klime.Client to your application.ex supervision tree:
87+
children = [Klime.Client]
88+
7389
BEST PRACTICES:
74-
- Add Klime.Client to your application supervision tree
7590
- Store write key in KLIME_WRITE_KEY environment variable
7691
- Client automatically handles graceful shutdown when supervisor stops
7792
78-
Add to mix.exs: {:klime, "~> 1.0"}
79-
Then run: mix deps.get
80-
81-
# In application.ex
82-
children = [
83-
{Klime.Client, write_key: System.get_env("KLIME_WRITE_KEY"), name: Klime}
84-
]
85-
8693
# Identify users at signup/login:
87-
Klime.identify(Klime, "usr_abc123", %{email: "[email protected]", name: "Jane Smith"})
94+
Klime.identify("usr_abc123", %{email: "[email protected]", name: "Jane Smith"})
8895
8996
# Track key activities:
90-
Klime.track(Klime, "Report Generated", %{report_type: "revenue"}, user_id: "usr_abc123")
91-
Klime.track(Klime, "Feature Used", %{feature: "export", format: "csv"}, user_id: "usr_abc123")
92-
Klime.track(Klime, "Teammate Invited", %{role: "member"}, user_id: "usr_abc123")
97+
Klime.track("Report Generated", %{report_type: "revenue"}, user_id: "usr_abc123")
98+
Klime.track("Feature Used", %{feature: "export", format: "csv"}, user_id: "usr_abc123")
99+
Klime.track("Teammate Invited", %{role: "member"}, user_id: "usr_abc123")
93100
94101
# If Companies & Teams mode: link user to their company and set company traits
95-
Klime.group(Klime, "org_456", %{name: "Acme Inc", plan: "enterprise"}, user_id: "usr_abc123")
102+
Klime.group("org_456", %{name: "Acme Inc", plan: "enterprise"}, user_id: "usr_abc123")
96103
97104
INTEGRATION WORKFLOW:
98105
@@ -132,47 +139,53 @@ Report what you added:
132139

133140
## API Reference
134141

142+
### Configuration
143+
144+
Configure Klime in `config/config.exs`:
145+
146+
```elixir
147+
config :klime,
148+
write_key: System.get_env("KLIME_WRITE_KEY"), # Required
149+
endpoint: "https://i.klime.com", # Optional (default)
150+
flush_interval: 2000, # Optional: ms between flushes (default: 2000)
151+
max_batch_size: 20, # Optional: max events per batch (default: 20, max: 100)
152+
max_queue_size: 1000, # Optional: max queued events (default: 1000)
153+
retry_max_attempts: 5, # Optional: max retry attempts (default: 5)
154+
retry_initial_delay: 1000, # Optional: initial retry delay in ms (default: 1000)
155+
flush_on_shutdown: true, # Optional: auto-flush on shutdown (default: true)
156+
on_error: &MyApp.Analytics.handle_error/2, # Optional: callback for batch failures
157+
on_success: &MyApp.Analytics.handle_success/1 # Optional: callback for successful sends
158+
```
159+
135160
### Starting the Client
136161

162+
Add to your supervision tree in `application.ex`:
163+
137164
```elixir
138-
# Option 1: Start directly
139-
{:ok, client} = Klime.Client.start_link(
140-
write_key: "your-write-key", # Required
141-
endpoint: "https://i.klime.com", # Optional (default)
142-
flush_interval: 2000, # Optional: ms between flushes (default: 2000)
143-
max_batch_size: 20, # Optional: max events per batch (default: 20, max: 100)
144-
max_queue_size: 1000, # Optional: max queued events (default: 1000)
145-
retry_max_attempts: 5, # Optional: max retry attempts (default: 5)
146-
retry_initial_delay: 1000, # Optional: initial retry delay in ms (default: 1000)
147-
flush_on_shutdown: true, # Optional: auto-flush on shutdown (default: true)
148-
on_error: &handle_error/2, # Optional: callback for batch failures
149-
on_success: &handle_success/1, # Optional: callback for successful sends
150-
name: MyApp.Klime # Optional: registered name
151-
)
152-
153-
# Option 2: Add to supervision tree (recommended for Phoenix apps)
154165
children = [
155-
{Klime.Client, write_key: System.get_env("KLIME_WRITE_KEY"), name: Klime}
166+
Klime.Client
156167
]
157168
```
158169

170+
The client reads configuration from the application environment and registers itself as `:klime` by default.
171+
159172
### Methods
160173

161-
#### `track(client, event_name, properties \\ %{}, opts \\ [])`
174+
#### `track(event_name, properties \\ %{}, opts \\ [])`
162175

163176
Track an event. Events can be attributed in two ways:
164177
- **User events**: Provide `user_id:` to track user activity (most common)
165178
- **Group events**: Provide `group_id:` without `user_id:` for organization-level events
166179

167180
```elixir
168181
# User event (most common)
169-
Klime.track(client, "Button Clicked", %{
182+
Klime.track("Button Clicked", %{
170183
button_name: "Sign up",
171184
plan: "pro"
172185
}, user_id: "user_123")
173186

174187
# Group event (for webhooks, cron jobs, system events)
175-
Klime.track(client, "Events Received", %{
188+
Klime.track("Events Received", %{
176189
count: 100,
177190
source: "webhook"
178191
}, group_id: "org_456")
@@ -186,13 +199,13 @@ For cases where you need guaranteed delivery or want to handle errors explicitly
186199

187200
```elixir
188201
# Sync track - blocks until sent, returns {:ok, response} or {:error, error}
189-
{:ok, response} = Klime.track!(client, "Button Clicked", %{button: "signup"}, user_id: "user_123")
202+
{:ok, response} = Klime.track!("Button Clicked", %{button: "signup"}, user_id: "user_123")
190203

191204
# Sync identify
192-
{:ok, response} = Klime.identify!(client, "user_123", %{email: "[email protected]"})
205+
{:ok, response} = Klime.identify!("user_123", %{email: "[email protected]"})
193206

194207
# Sync group
195-
{:ok, response} = Klime.group!(client, "org_456", %{name: "Acme Inc"}, user_id: "user_123")
208+
{:ok, response} = Klime.group!("org_456", %{name: "Acme Inc"}, user_id: "user_123")
196209
```
197210

198211
These methods:
@@ -203,52 +216,52 @@ These methods:
203216

204217
Use sync methods sparingly - they add latency to your code. The async methods are preferred for most use cases.
205218

206-
#### `identify(client, user_id, traits \\ %{})`
219+
#### `identify(user_id, traits \\ %{})`
207220

208221
Identify a user with traits.
209222

210223
```elixir
211-
Klime.identify(client, "user_123", %{
224+
Klime.identify("user_123", %{
212225
213226
name: "Stefan"
214227
})
215228
```
216229

217-
#### `group(client, group_id, traits \\ %{}, opts \\ [])`
230+
#### `group(group_id, traits \\ %{}, opts \\ [])`
218231

219232
Associate a user with a group and/or set group traits.
220233

221234
```elixir
222235
# Associate user with a group and set group traits (most common)
223-
Klime.group(client, "org_456", %{
236+
Klime.group("org_456", %{
224237
name: "Acme Inc",
225238
plan: "enterprise"
226239
}, user_id: "user_123")
227240

228241
# Just link a user to a group (traits already set or not needed)
229-
Klime.group(client, "org_456", %{}, user_id: "user_123")
242+
Klime.group("org_456", %{}, user_id: "user_123")
230243

231244
# Just update group traits (e.g., from a webhook or background job)
232-
Klime.group(client, "org_456", %{
245+
Klime.group("org_456", %{
233246
plan: "enterprise",
234247
employee_count: 50
235248
})
236249
```
237250

238-
#### `flush(client)`
251+
#### `flush()`
239252

240253
Manually flush queued events immediately.
241254

242255
```elixir
243-
:ok = Klime.flush(client)
256+
:ok = Klime.flush()
244257
```
245258

246-
#### `shutdown(client)`
259+
#### `shutdown()`
247260

248261
Gracefully shutdown the client, flushing remaining events.
249262

250263
```elixir
251-
:ok = Klime.shutdown(client)
264+
:ok = Klime.shutdown()
252265
```
253266

254267
## Features
@@ -257,14 +270,15 @@ Gracefully shutdown the client, flushing remaining events.
257270
- **Automatic Retries**: Failed requests are automatically retried with exponential backoff
258271
- **Async & Sync Methods**: Use async methods for fire-and-forget, or sync (`track!`, `identify!`, `group!`) for guaranteed delivery
259272
- **OTP Supervision**: GenServer-based client integrates naturally with OTP supervision trees
273+
- **Application Config**: Configure once in `config.exs`, no need to pass client around
260274
- **Plug Middleware**: Optional `Klime.Plug` for per-request flush in Phoenix/Plug apps
261275
- **Graceful Shutdown**: Automatically flushes events when the supervisor stops (with `flush_on_shutdown: true`)
262276
- **Callbacks**: `on_error` and `on_success` callbacks for monitoring
263277
- **Minimal Dependencies**: Only requires `jason` for JSON encoding (`plug` optional for middleware)
264278

265279
## Performance
266280

267-
When you call `track/4`, `identify/3`, or `group/4`, the SDK:
281+
When you call `track/3`, `identify/2`, or `group/3`, the SDK:
268282

269283
1. Adds the event to an in-memory queue (microseconds)
270284
2. Returns immediately without waiting for network I/O
@@ -277,13 +291,13 @@ Events are sent to Klime's servers asynchronously. This means:
277291

278292
```elixir
279293
# This returns immediately - no HTTP request is made here
280-
Klime.track(client, "Button Clicked", %{button: "signup"}, user_id: "user_123")
294+
Klime.track("Button Clicked", %{button: "signup"}, user_id: "user_123")
281295

282296
# Your code continues without waiting
283297
json(conn, %{success: true})
284298
```
285299

286-
The only blocking operation is `flush/1`, which waits for all queued events to be sent. This is typically only called during graceful shutdown.
300+
The only blocking operation is `flush/0`, which waits for all queued events to be sent. This is typically only called during graceful shutdown.
287301

288302
## Configuration
289303

@@ -299,17 +313,16 @@ The only blocking operation is `flush/1`, which waits for all queued events to b
299313
### Callbacks
300314

301315
```elixir
302-
{Klime.Client,
316+
# In config/config.exs
317+
config :klime,
303318
write_key: System.get_env("KLIME_WRITE_KEY"),
304-
name: Klime,
305319
on_error: fn error, _events ->
306320
Logger.error("Klime error: #{inspect(error)}")
307321
Sentry.capture_exception(error)
308322
end,
309323
on_success: fn response ->
310324
Logger.info("Sent #{response.accepted} events")
311325
end
312-
}
313326
```
314327

315328
### Plug Middleware
@@ -318,7 +331,7 @@ For guaranteed per-request delivery, use `Klime.Plug` to flush events after each
318331

319332
```elixir
320333
# In your Phoenix endpoint.ex or router.ex
321-
plug Klime.Plug, client: Klime
334+
plug Klime.Plug, client: :klime
322335
```
323336

324337
> **Note**: This adds latency to every request as it waits for the flush.
@@ -343,6 +356,12 @@ Events exceeding these limits are rejected and logged.
343356

344357
## Phoenix Example
345358

359+
```elixir
360+
# config/config.exs
361+
config :klime,
362+
write_key: System.get_env("KLIME_WRITE_KEY")
363+
```
364+
346365
```elixir
347366
# lib/my_app/application.ex
348367
defmodule MyApp.Application do
@@ -351,7 +370,7 @@ defmodule MyApp.Application do
351370
def start(_type, _args) do
352371
children = [
353372
MyAppWeb.Endpoint,
354-
{Klime.Client, write_key: System.get_env("KLIME_WRITE_KEY"), name: Klime}
373+
Klime.Client
355374
]
356375

357376
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
@@ -368,7 +387,7 @@ defmodule MyAppWeb.ButtonController do
368387
def click(conn, %{"button_name" => button_name}) do
369388
user_id = conn.assigns[:current_user] && conn.assigns.current_user.id
370389

371-
Klime.track(Klime, "Button Clicked", %{
390+
Klime.track("Button Clicked", %{
372391
button_name: button_name
373392
}, user_id: user_id)
374393

@@ -387,15 +406,15 @@ defmodule MyAppWeb.DashboardLive do
387406
def mount(_params, _session, socket) do
388407
user = socket.assigns.current_user
389408

390-
Klime.track(Klime, "Dashboard Viewed", %{}, user_id: user.id)
409+
Klime.track("Dashboard Viewed", %{}, user_id: user.id)
391410

392411
{:ok, socket}
393412
end
394413

395414
def handle_event("export", %{"format" => format}, socket) do
396415
user = socket.assigns.current_user
397416

398-
Klime.track(Klime, "Export Clicked", %{format: format}, user_id: user.id)
417+
Klime.track("Export Clicked", %{format: format}, user_id: user.id)
399418

400419
{:noreply, socket}
401420
end

0 commit comments

Comments
 (0)