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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0b116ab1581f3a4774402e520e226448ebb08e91f41472871b550fa2f5b90138
4
- data.tar.gz: 2e727e895ae664cb6bbb6f8520b550466e39c4ef68e3940b29f07f93f50f3607
3
+ metadata.gz: a785fdebe171148a4796f039e87637fd1356c8236e962298399a1c6f0a2e735a
4
+ data.tar.gz: fd356389bcc839e35de4ac7d2942f796b8e045e815a455d9ebd9fb8d3b5db64d
5
5
  SHA512:
6
- metadata.gz: a604060bfeccf108f38d1df090afcab54eaefe7bebb85320005978c7d250654df08faf26341b63e1dfe3a27306dac0207427e2bbf0d9f90871cf03ec4e0d8422
7
- data.tar.gz: c3ae7ff9b11b088c972800e8bf4167d9ca4a6a58d8eb6cadb19ec836cf3616981575fa4aa3251a633130c5c58fb1fe5ac4883115c44ebc53223319a533a55545
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
@@ -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
- # The background flusher thread drains the queue every
8
- # `flush_interval` seconds. at_exit runs a final flush.
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
- payload = { "event" => "$identify" }
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
@@ -54,6 +54,10 @@ module GetFluxly
54
54
  SecureRandom.uuid
55
55
  end
56
56
 
57
+ def self.generate_message_id
58
+ SecureRandom.uuid
59
+ end
60
+
57
61
  private
58
62
 
59
63
  def perform(uri, payload, headers)
@@ -4,5 +4,5 @@
4
4
  # run `ruby scripts/sync_version.rb` to regenerate.
5
5
 
6
6
  module GetFluxly
7
- VERSION = "0.1.0"
7
+ VERSION = "0.1.2"
8
8
  end
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.0
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: 1980-01-02 00:00:00.000000000 Z
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: 4.0.3
114
+ rubygems_version: 3.5.22
115
+ signing_key:
113
116
  specification_version: 4
114
117
  summary: GetFluxly Ruby SDK
115
118
  test_files: []