getfluxly 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/README.md +4 -0
- data/lib/getfluxly/client.rb +63 -4
- data/lib/getfluxly/http.rb +4 -0
- data/lib/getfluxly/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a785fdebe171148a4796f039e87637fd1356c8236e962298399a1c6f0a2e735a
|
|
4
|
+
data.tar.gz: fd356389bcc839e35de4ac7d2942f796b8e045e815a455d9ebd9fb8d3b5db64d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a6fb8a5a95dbd841e4e6a2c1844a28b4702a8be0798947ad1ecaaa4c1e30c2be21a468fa88a6fd4f6304bb06a4e8a745a85c082e2f16108934397b2da84b25a4
|
|
7
|
+
data.tar.gz: c698da31e1ad2814c7f3cf3fdcb16e86ad6b9da86040c1ae49247da6a9fbe6e194d579e42952e231b21e7242cd36fc3de69817962250cd7fdb9a32c50e4ea89f
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `getfluxly` (Ruby) will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.1.2] - 2026-07-03
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- `flush_interval` now works: the client starts a background flusher thread
|
|
9
|
+
on the first enqueued event that drains the queue every `flush_interval`
|
|
10
|
+
seconds. Previously the parameter was accepted but never used, so events
|
|
11
|
+
only left the process when the queue reached `flush_at` (default 20), on an
|
|
12
|
+
explicit `flush`, or at graceful interpreter exit — low-volume producers
|
|
13
|
+
effectively never delivered. Set `flush_interval: 0` to disable the flusher
|
|
14
|
+
and restore the old behavior. The flusher survives `fork` (restarts in the
|
|
15
|
+
child) and a failed scheduled flush warns and retries on the next tick.
|
|
16
|
+
|
|
17
|
+
## [0.1.1] - 2026-06-23
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- Each event payload now carries a stable `message_id` (UUID v4, snake_case key)
|
|
21
|
+
generated at build time in `track` and `identify`. The backend derives the
|
|
22
|
+
stored row id from `(project_id + message_id)` to deduplicate duplicate
|
|
23
|
+
deliveries. The per-batch `X-Idempotency-Key` header is unchanged.
|
|
24
|
+
|
|
5
25
|
## [0.1.0] - 2026-05-16
|
|
6
26
|
|
|
7
27
|
### Added
|
data/README.md
CHANGED
|
@@ -69,6 +69,10 @@ Defaults match Node and Python.
|
|
|
69
69
|
|
|
70
70
|
Retries: 408, 425, 429, and 5xx with exponential backoff and +/- 25% jitter. `Retry-After` is honored. Each batch carries a unique `X-Idempotency-Key`.
|
|
71
71
|
|
|
72
|
+
## Event deduplication
|
|
73
|
+
|
|
74
|
+
Every event built by `track` and `identify` includes a `message_id` field (UUID v4) generated once when the payload is created. The backend derives the stored row id from `(project_id + message_id)` so retried batches are idempotent — duplicate deliveries of the same event are dropped automatically.
|
|
75
|
+
|
|
72
76
|
## Errors
|
|
73
77
|
|
|
74
78
|
```ruby
|
data/lib/getfluxly/client.rb
CHANGED
|
@@ -4,8 +4,10 @@ module GetFluxly
|
|
|
4
4
|
# Synchronous GetFluxly client.
|
|
5
5
|
#
|
|
6
6
|
# Batches events with retry, jitter, and X-Idempotency-Key.
|
|
7
|
-
#
|
|
8
|
-
#
|
|
7
|
+
# A batch goes out when the queue reaches `flush_at`, when the
|
|
8
|
+
# background flusher thread fires (every `flush_interval` seconds
|
|
9
|
+
# once the first event is enqueued; `<= 0` disables it), on an
|
|
10
|
+
# explicit `flush`, and at_exit.
|
|
9
11
|
class Client
|
|
10
12
|
DEFAULT_API_HOST = "https://api.getfluxly.com"
|
|
11
13
|
EVENTS_BATCH_PATH = "/v1/events/batch"
|
|
@@ -32,6 +34,11 @@ module GetFluxly
|
|
|
32
34
|
@http = Http.new(token: token, api_host: api_host, timeout: timeout, max_retries: max_retries)
|
|
33
35
|
@shutdown = false
|
|
34
36
|
@shutdown_mutex = Mutex.new
|
|
37
|
+
@flusher = nil
|
|
38
|
+
@flusher_pid = nil
|
|
39
|
+
@wake_mutex = Mutex.new
|
|
40
|
+
@wake = ConditionVariable.new
|
|
41
|
+
@woken = false
|
|
35
42
|
|
|
36
43
|
return unless register_atexit
|
|
37
44
|
|
|
@@ -50,7 +57,7 @@ module GetFluxly
|
|
|
50
57
|
user_id: user_id
|
|
51
58
|
)
|
|
52
59
|
|
|
53
|
-
payload = { "event" => event }
|
|
60
|
+
payload = { "event" => event, "message_id" => Http.generate_message_id }
|
|
54
61
|
payload["anonymous_id"] = anonymous_id if anonymous_id
|
|
55
62
|
payload["external_id"] = external_id if external_id
|
|
56
63
|
payload["user_id"] = user_id if user_id
|
|
@@ -68,7 +75,13 @@ module GetFluxly
|
|
|
68
75
|
user_id: user_id
|
|
69
76
|
)
|
|
70
77
|
|
|
71
|
-
|
|
78
|
+
# FIXME: identify is not wired end to end. The backend treats only
|
|
79
|
+
# event == "identify" as an identify, but this sends "$identify", so
|
|
80
|
+
# identity stitching never runs. A plain rename will not fix it: the
|
|
81
|
+
# backend identify path requires BOTH anonymous_id and external_id,
|
|
82
|
+
# which a server identify usually lacks. Needs a server side identify
|
|
83
|
+
# contract first; until then use track() (see the SDK docs callout).
|
|
84
|
+
payload = { "event" => "$identify", "message_id" => Http.generate_message_id }
|
|
72
85
|
payload["anonymous_id"] = anonymous_id if anonymous_id
|
|
73
86
|
payload["external_id"] = external_id if external_id
|
|
74
87
|
payload["user_id"] = user_id if user_id
|
|
@@ -132,11 +145,24 @@ module GetFluxly
|
|
|
132
145
|
end
|
|
133
146
|
|
|
134
147
|
def shutdown
|
|
148
|
+
flusher = nil
|
|
135
149
|
@shutdown_mutex.synchronize do
|
|
136
150
|
return Batch.empty_flush_result if @shutdown
|
|
137
151
|
|
|
138
152
|
@shutdown = true
|
|
153
|
+
flusher = @flusher
|
|
154
|
+
@flusher = nil
|
|
139
155
|
end
|
|
156
|
+
# Wake the flusher so it exits promptly, and wait for it so a
|
|
157
|
+
# scheduled flush can't race the final flush below. The join is
|
|
158
|
+
# bounded by one in-flight HTTP round-trip (timeout x retries).
|
|
159
|
+
# @woken makes the wake sticky: a broadcast that lands before the
|
|
160
|
+
# flusher reaches wait would otherwise be lost.
|
|
161
|
+
@wake_mutex.synchronize do
|
|
162
|
+
@woken = true
|
|
163
|
+
@wake.broadcast
|
|
164
|
+
end
|
|
165
|
+
flusher&.join(30) unless flusher == Thread.current
|
|
140
166
|
flush
|
|
141
167
|
end
|
|
142
168
|
|
|
@@ -146,9 +172,42 @@ module GetFluxly
|
|
|
146
172
|
|
|
147
173
|
def enqueue(payload)
|
|
148
174
|
size = @batch.enqueue(payload)
|
|
175
|
+
ensure_flusher
|
|
149
176
|
return flush if size >= @flush_at
|
|
150
177
|
|
|
151
178
|
nil
|
|
152
179
|
end
|
|
180
|
+
|
|
181
|
+
def ensure_flusher
|
|
182
|
+
return if @flush_interval <= 0
|
|
183
|
+
|
|
184
|
+
@shutdown_mutex.synchronize do
|
|
185
|
+
return if @shutdown
|
|
186
|
+
# Threads don't survive fork; the pid check restarts the
|
|
187
|
+
# flusher in forked workers (puma, sidekiq).
|
|
188
|
+
return if @flusher&.alive? && @flusher_pid == Process.pid
|
|
189
|
+
|
|
190
|
+
@flusher_pid = Process.pid
|
|
191
|
+
@flusher = Thread.new { flush_loop }
|
|
192
|
+
@flusher.name = "getfluxly-flusher"
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def flush_loop
|
|
197
|
+
loop do
|
|
198
|
+
@wake_mutex.synchronize do
|
|
199
|
+
@wake.wait(@wake_mutex, @flush_interval) unless @woken
|
|
200
|
+
end
|
|
201
|
+
return if @shutdown
|
|
202
|
+
|
|
203
|
+
begin
|
|
204
|
+
flush
|
|
205
|
+
rescue StandardError => e
|
|
206
|
+
# A failed scheduled flush must not kill the thread; flush
|
|
207
|
+
# already requeued any retryable batch.
|
|
208
|
+
warn("[GetFluxly] scheduled flush failed: #{e.class}: #{e.message}")
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
153
212
|
end
|
|
154
213
|
end
|
data/lib/getfluxly/http.rb
CHANGED
data/lib/getfluxly/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: getfluxly
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- GetFluxly
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-07-03 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: minitest
|
|
@@ -95,6 +96,7 @@ metadata:
|
|
|
95
96
|
bug_tracker_uri: https://github.com/dineshmiriyala/getfluxly/issues
|
|
96
97
|
changelog_uri: https://github.com/dineshmiriyala/getfluxly/blob/main/packages/ruby/CHANGELOG.md
|
|
97
98
|
rubygems_mfa_required: 'true'
|
|
99
|
+
post_install_message:
|
|
98
100
|
rdoc_options: []
|
|
99
101
|
require_paths:
|
|
100
102
|
- lib
|
|
@@ -109,7 +111,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
109
111
|
- !ruby/object:Gem::Version
|
|
110
112
|
version: '0'
|
|
111
113
|
requirements: []
|
|
112
|
-
rubygems_version:
|
|
114
|
+
rubygems_version: 3.5.22
|
|
115
|
+
signing_key:
|
|
113
116
|
specification_version: 4
|
|
114
117
|
summary: GetFluxly Ruby SDK
|
|
115
118
|
test_files: []
|