conduit-sse 2.0.0 → 2.0.1

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: 214f9e890387c6debfdd5e3e603807d771ab4158dfd309c61c178e9234a8bce1
4
- data.tar.gz: a00f7a63cfe195dbcb84394d88141379b3e4d10207307b0ef0e8ec2a2e8a7e41
3
+ metadata.gz: 8ec5fd661f1b02da8107f093fd0d07ac6542aacdfd1c6d99d456a611cea81b23
4
+ data.tar.gz: 3ef67ea5be1aa63daff67184763683453a8b68f531f8539a43ebcf8ea4bbeb53
5
5
  SHA512:
6
- metadata.gz: 79bfc29256c475a2cc2c4ee424111e11e7fc8de83e9bacd4d282fb1596e418f68f188bb488b74aa90bbb03768a91a57cab00ef6f6f6b7a51df4f15cd87b0a851
7
- data.tar.gz: 95d97067c80b26444f5e72b3400444455973502efbad476552c49fb3cd5577fe61e953b51ecaea27a99b0a1a8084a9712b49a85102cf0ec6c091e2121dcab701
6
+ metadata.gz: d466f77aaae0b06d93178cef3e593e8a66152c4bbc53afd4f0b60d62a4a40172686272286adb2f8adb8791f3951d78705a1d11e4f16a9185bc7b44c69b9b4f6e
7
+ data.tar.gz: 9062d3fc81730440a13ebf579e4769b5a09dccc0f62f3a8f7d1844012445af91f64c92fa286358064c27911f9718be5173341996c55b79673267b2e2cf7c7e19
data/CHANGELOG.md CHANGED
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.0.1] - 2026-05-13
9
+
10
+ ### Changed
11
+
12
+ - README polish: added a hero illustration, rewrote the OpenAI streaming
13
+ example (the previous version double-registered `on_parsed` and
14
+ `on_field` and undersold the gem's built-in event-type filtering),
15
+ added a Config/State architecture section, replaced the ASCII pipeline
16
+ diagram with a Mermaid `flowchart TD`, and renamed the block parameter
17
+ convention from `|c|` to `|config|` in all examples for readability.
18
+ - `ConduitSSE::Config` class docstring updated to match the new
19
+ `|config|` convention.
20
+ - Gemspec now excludes `docs/` from the packaged gem so the hero image
21
+ doesn't bloat installs.
22
+ - Switched the README gem-version badge from `badge.fury.io` (cache-laggy,
23
+ semi-abandoned) to `img.shields.io`, which queries RubyGems directly and
24
+ refreshes within minutes of a release.
25
+
26
+ No code changes. Drop-in replacement for 2.0.0.
27
+
8
28
  ## [2.0.0] - 2026-05-12
9
29
 
10
30
  ### Breaking
@@ -28,19 +48,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
28
48
  a mutable `ConduitSSE::Config` instance:
29
49
 
30
50
  ```ruby
31
- ConduitSSE.new do |c|
32
- c.parser = ->(d) { JSON.parse(d) }
33
- c.stats = true
51
+ ConduitSSE.new do |config|
52
+ config.parser = ->(d) { JSON.parse(d) }
53
+ config.stats = true
34
54
  end
35
55
  ```
36
56
 
37
57
  Both forms can be mixed; kwargs seed the config and the block overrides.
38
58
 
39
- - **`ConduitSSE::Config`** new public class. Holds the seven parsing knobs,
59
+ - **`ConduitSSE::Config`**: new public class. Holds the seven parsing knobs,
40
60
  loads its own defaults, validates unknown keys, and exposes a `finalize!`
41
61
  method that runs validation, computes the derived `data_field`, and
42
62
  freezes the instance. Accessible at runtime as `stream.config`.
43
- - **`ConduitSSE::State`** new public class. Holds the per-stream mutable
63
+ - **`ConduitSSE::State`**: new public class. Holds the per-stream mutable
44
64
  runtime: input buffer, callbacks registry, last event id / retry / type,
45
65
  and (when enabled) the stats counter hash. Exposes a null-object
46
66
  `#increment_stat` / `#add_fields` so the stream has no `if @stats`
data/README.md CHANGED
@@ -1,8 +1,12 @@
1
- # ConduitSSE
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/franbach/conduit/main/docs/hero.png" alt="ConduitSSE: turns a stream of SSE chunks into the events you need, and routes them where they matter." width="780">
3
+ </p>
2
4
 
3
- ![CI](https://github.com/franbach/conduit/actions/workflows/main.yml/badge.svg)
4
- [![Gem Version](https://img.shields.io/gem/v/conduit-sse.svg)](https://rubygems.org/gems/conduit-sse)
5
- [![Downloads](https://img.shields.io/gem/dt/conduit-sse.svg)](https://rubygems.org/gems/conduit-sse)
5
+ <p align="center">
6
+ <a href="https://github.com/franbach/conduit/actions/workflows/main.yml"><img src="https://github.com/franbach/conduit/actions/workflows/main.yml/badge.svg" alt="CI"></a>
7
+ <a href="https://rubygems.org/gems/conduit-sse"><img src="https://img.shields.io/gem/v/conduit-sse.svg" alt="Gem Version"></a>
8
+ <a href="https://rubygems.org/gems/conduit-sse"><img src="https://img.shields.io/gem/dt/conduit-sse.svg" alt="Downloads"></a>
9
+ </p>
6
10
 
7
11
  ConduitSSE is a lightweight, zero-dependency Ruby gem for parsing Server-Sent Events (SSE) streams. It provides a flexible callback-based architecture for processing real-time server push data with full control over every stage of the parsing pipeline.
8
12
 
@@ -59,10 +63,10 @@ The canonical way to build a stream is with a configuration block. The block
59
63
  receives a mutable {ConduitSSE::Config} that exposes every knob as a setter:
60
64
 
61
65
  ```ruby
62
- stream = ConduitSSE.new do |c|
63
- c.parser = ->(d) { JSON.parse(d) }
64
- c.stats = true
65
- c.frame_separator = "\r\n\r\n"
66
+ stream = ConduitSSE.new do |config|
67
+ config.parser = ->(d) { JSON.parse(d) }
68
+ config.stats = true
69
+ config.frame_separator = "\r\n\r\n"
66
70
  end
67
71
  ```
68
72
 
@@ -72,12 +76,12 @@ For one- or two-knob setups, plain keyword arguments are equally fine:
72
76
  stream = ConduitSSE.new(parser: ->(d) { JSON.parse(d) }, stats: true)
73
77
  ```
74
78
 
75
- The two forms can also be combined kwargs seed the config, the block then
79
+ The two forms can also be combined. Kwargs seed the config, and the block
76
80
  mutates whatever it wants on top:
77
81
 
78
82
  ```ruby
79
- stream = ConduitSSE.new(parser: ->(d) { d }) do |c|
80
- c.stats = true
83
+ stream = ConduitSSE.new(parser: ->(d) { d }) do |config|
84
+ config.stats = true
81
85
  end
82
86
  ```
83
87
 
@@ -94,8 +98,8 @@ At its core, ConduitSSE processes SSE data chunks and emits callbacks at each st
94
98
  require "conduit_sse"
95
99
 
96
100
  # Create a stream with a parser that transforms event data
97
- stream = ConduitSSE.new do |c|
98
- c.parser = ->(data) { JSON.parse(data) }
101
+ stream = ConduitSSE.new do |config|
102
+ config.parser = ->(data) { JSON.parse(data) }
99
103
  end
100
104
 
101
105
  # Subscribe to parsed events
@@ -117,8 +121,8 @@ require "net/http"
117
121
  require "uri"
118
122
  require "json"
119
123
 
120
- stream = ConduitSSE.new do |c|
121
- c.parser = ->(d) { JSON.parse(d) rescue d }
124
+ stream = ConduitSSE.new do |config|
125
+ config.parser = ->(d) { JSON.parse(d) rescue d }
122
126
  end
123
127
 
124
128
  stream.on_parsed do |parsed|
@@ -151,37 +155,36 @@ require "json"
151
155
  api_key = "your-api-key-here"
152
156
 
153
157
  # Create the stream with a parser that extracts the delta content
154
- stream = ConduitSSE.new do |c|
155
- c.parser = ->(data) { JSON.parse(data) }
158
+ stream = ConduitSSE.new do |config|
159
+ config.parser = ->(data) { JSON.parse(data) }
156
160
  end
161
+ ```
157
162
 
158
- result = +""
163
+ **Approach 1**: Use `on_parsed` to extract delta after JSON parsing
164
+ Since OpenAI sends structured JSON in the data field, the parser converts it to a Hash,
165
+ making it easy to extract the delta content directly.
159
166
 
160
- # Approach 1: Use on_parsed to extract delta after JSON parsing
161
- # Since OpenAI sends structured JSON in the data field, the parser converts it to a Hash,
162
- # making it easy to extract the delta content directly.
167
+ ```ruby
163
168
  stream.on_parsed do |parsed_data|
164
169
  type = parsed_data["type"]
165
170
 
166
171
  if type == "response.output_text.delta"
167
172
  delta = parsed_data["delta"]
168
- if delta
169
- puts "parsed delta: #{delta}"
170
- result += delta
171
173
 
172
- # You can also emit the delta to a frontend app here if you will.
173
- # emit_to_frontend(delta)
174
- end
174
+ do_something_with(delta) if delta
175
175
  end
176
176
 
177
177
  if type == "response.completed"
178
- puts "\n\nResult: #{result}"
178
+ do_something_with(parsed_data)
179
179
  end
180
180
  end
181
+ ```
182
+
183
+ **Approach 2**: Use `on_field` for more granular control
184
+ This approach gives you access to the raw field values before JSON parsing,
185
+ useful if you need to inspect or modify the raw data field content.
181
186
 
182
- # Approach 2: Use on_field for more granular control
183
- # This approach gives you access to the raw field values before JSON parsing,
184
- # useful if you need to inspect or modify the raw data field content.
187
+ ```ruby
185
188
  stream.on_field do |name, value|
186
189
  if name == "data"
187
190
  data = JSON.parse(value)
@@ -189,22 +192,45 @@ stream.on_field do |name, value|
189
192
 
190
193
  if type == "response.output_text.delta"
191
194
  delta = data["delta"]
192
- if delta
193
- puts "delta: #{delta}"
194
- result += delta
195
195
 
196
- # You can also emit the delta to a frontend app here if you will.
197
- # emit_to_frontend(delta)
198
- end
196
+ do_something_with(delta) if delta
199
197
  end
200
198
 
201
199
  if type == "response.completed"
202
- puts "\n\nResult: #{result}"
200
+ do_something_with(data)
203
201
  end
204
202
  end
205
203
  end
204
+ ```
205
+
206
+ **Approach 3**: Use filtered parsed callbacks for maximum flexibility
207
+
208
+ ```ruby
209
+ stream.on_parsed(type: "response.output_text.delta") do |payload|
210
+ delta = payload["delta"]
211
+
212
+ do_something_with(delta) if delta
213
+ end
214
+
215
+ stream.on_parsed(type: "response.completed") do |payload|
216
+ do_something_with(payload)
217
+ end
206
218
 
207
- # Make the streaming request
219
+ stream.on_parsed(type: "response.error") do |payload|
220
+ # capture OpenAi error
221
+ do_something_with(payload)
222
+ end
223
+
224
+ # Resilience: a single malformed frame won't tear the whole stream down.
225
+ # Without on_error, a JSON.parse failure would bubble up out of `stream << chunk`.
226
+ stream.on_error do |error|
227
+ Sentry.capture_exception(error)
228
+ end
229
+ ```
230
+
231
+ **Make the streaming request**
232
+
233
+ ```ruby
208
234
  uri = URI("https://api.openai.com/v1/responses")
209
235
  http = Net::HTTP.new(uri.host, uri.port)
210
236
  http.use_ssl = true
@@ -237,7 +263,7 @@ end
237
263
  ConduitSSE provides callbacks at every stage of processing:
238
264
 
239
265
  ```ruby
240
- stream = ConduitSSE.new { |c| c.parser = ->(data) { data } }
266
+ stream = ConduitSSE.new { |config| config.parser = ->(data) { data } }
241
267
 
242
268
  # Raw chunk as it arrived (after normalization)
243
269
  stream.on_chunk do |chunk|
@@ -280,7 +306,7 @@ end
280
306
  Filter events by type directly on callback registration:
281
307
 
282
308
  ```ruby
283
- stream = ConduitSSE.new { |c| c.parser = ->(data) { data } }
309
+ stream = ConduitSSE.new { |config| config.parser = ->(data) { data } }
284
310
 
285
311
  # Only process "message" events in this callback
286
312
  stream.on_event(type: "message") do |event|
@@ -335,7 +361,7 @@ end
335
361
  **`each` / `on_parsed`** - Receives the result of your custom parser (the `parser:` lambda):
336
362
 
337
363
  ```ruby
338
- stream = ConduitSSE.new { |c| c.parser = ->(data) { JSON.parse(data) } }
364
+ stream = ConduitSSE.new { |config| config.parser = ->(data) { JSON.parse(data) } }
339
365
 
340
366
  stream.each do |parsed|
341
367
  # parsed is whatever your parser returns
@@ -419,8 +445,8 @@ end
419
445
  **Data Transformation**
420
446
 
421
447
  ```ruby
422
- stream = ConduitSSE.new do |c|
423
- c.parser = ->(data) {
448
+ stream = ConduitSSE.new do |config|
449
+ config.parser = ->(data) {
424
450
  # Transform raw data into your domain models
425
451
  raw = JSON.parse(data)
426
452
  MyDomainModel.new(raw)
@@ -476,38 +502,38 @@ end
476
502
  Every setting lives on `ConduitSSE::Config` and can be set via the block:
477
503
 
478
504
  ```ruby
479
- stream = ConduitSSE.new do |c|
505
+ stream = ConduitSSE.new do |config|
480
506
  # Required: A callable that receives the joined data field content (string)
481
507
  # and returns whatever shape your application needs (e.g., JSON.parse, YAML.load).
482
- c.parser = ->(data) { JSON.parse(data) }
508
+ config.parser = ->(data) { JSON.parse(data) }
483
509
 
484
510
  # Optional: Transforms incoming chunks before processing.
485
511
  # Default: UTF-8 conversion + CRLF→LF normalization.
486
512
  # NOTE: Replacing the default fully replaces UTF-8 handling; if you need it,
487
513
  # implement it yourself.
488
- c.chunk_normalizer = ->(chunk) { chunk.upcase }
514
+ config.chunk_normalizer = ->(chunk) { chunk.upcase }
489
515
 
490
516
  # Optional: Delimiter that separates frames in the stream.
491
517
  # Default: "\n\n"
492
- c.frame_separator = "\r\n\r\n"
518
+ config.frame_separator = "\r\n\r\n"
493
519
 
494
520
  # Optional: Prefix used to identify the data field.
495
521
  # The trailing ":" is stripped to derive the field name.
496
522
  # Default: "data:"
497
- c.payload_start = "data:"
523
+ config.payload_start = "data:"
498
524
 
499
525
  # Optional: Pattern identifying ping/comment frames.
500
526
  # Default: ":"
501
- c.ping_pattern = ":"
527
+ config.ping_pattern = ":"
502
528
 
503
529
  # Optional: Cleans or validates frame content after splitting.
504
530
  # Default: UTF-8 conversion + strip.
505
531
  # NOTE: Replacing the default fully replaces UTF-8 handling.
506
- c.sanitize_pattern = ->(frame) { frame.strip }
532
+ config.sanitize_pattern = ->(frame) { frame.strip }
507
533
 
508
534
  # Optional: Enables the per-stage counter hash exposed via #stats.
509
535
  # Default: false (off; #stats returns nil).
510
- c.stats = true
536
+ config.stats = true
511
537
  end
512
538
  ```
513
539
 
@@ -519,7 +545,7 @@ prefer a compact one-liner.
519
545
  For a simpler interface, use `each` to iterate over parsed events:
520
546
 
521
547
  ```ruby
522
- stream = ConduitSSE.new { |c| c.parser = ->(data) { data } }
548
+ stream = ConduitSSE.new { |config| config.parser = ->(data) { data } }
523
549
 
524
550
  stream.each do |parsed|
525
551
  puts "Received: #{parsed}"
@@ -537,7 +563,7 @@ ConduitSSE tracks SSE spec state that you can access directly on the stream
537
563
  [Architecture: Config and State](#architecture-config-and-state)):
538
564
 
539
565
  ```ruby
540
- stream = ConduitSSE.new { |c| c.parser = ->(data) { data } }
566
+ stream = ConduitSSE.new { |config| config.parser = ->(data) { data } }
541
567
 
542
568
  stream << "id: 123\ndata: hello\n\n"
543
569
 
@@ -550,30 +576,30 @@ puts stream.retry_ms # => nil (unless server sends retry field)
550
576
  ConduitSSE provides read-only methods for monitoring stream activity without the overhead of the Inspector:
551
577
 
552
578
  ```ruby
553
- stream = ConduitSSE.new { |c| c.parser = ->(data) { data } }
579
+ stream = ConduitSSE.new { |config| config.parser = ->(data) { data } }
554
580
 
555
- # Check buffer size always available, zero-cost (one bytesize lookup).
581
+ # Check buffer size. Always available, zero-cost (one bytesize lookup).
556
582
  stream << "data: hello"
557
583
  puts stream.buffer_size # => buffer size in bytes
558
584
  ```
559
585
 
560
586
  #### Stats are opt-in
561
587
 
562
- Per-stage counters are **disabled by default**. Set `c.stats = true` (or pass
588
+ Per-stage counters are **disabled by default**. Set `config.stats = true` (or pass
563
589
  `stats: true` as a kwarg) to enable them. When stats are off, `#stats` returns
564
- `nil` and the parser performs no counter bookkeeping per event important on
565
- hot paths at high event rates.
590
+ `nil` and the parser performs no counter bookkeeping per event. That matters
591
+ on hot paths at high event rates.
566
592
 
567
593
  ```ruby
568
594
  # Default: stats disabled
569
- stream = ConduitSSE.new { |c| c.parser = ->(data) { data } }
595
+ stream = ConduitSSE.new { |config| config.parser = ->(data) { data } }
570
596
  stream << "data: hello\n\n"
571
597
  stream.stats # => nil
572
598
 
573
599
  # Opt in:
574
- stream = ConduitSSE.new do |c|
575
- c.parser = ->(data) { data }
576
- c.stats = true
600
+ stream = ConduitSSE.new do |config|
601
+ config.parser = ->(data) { data }
602
+ config.stats = true
577
603
  end
578
604
  stream << "data: hello\n\n"
579
605
  stream.stats
@@ -596,7 +622,7 @@ stream.stats
596
622
  Use `finish` (or its alias `close`) once at the end of the stream to process any remaining data in the buffer:
597
623
 
598
624
  ```ruby
599
- stream = ConduitSSE.new { |c| c.parser = ->(data) { JSON.parse(data) } }
625
+ stream = ConduitSSE.new { |config| config.parser = ->(data) { JSON.parse(data) } }
600
626
 
601
627
  http.request(request) do |response|
602
628
  response.read_body do |chunk|
@@ -621,7 +647,7 @@ stream.finish
621
647
  Errors in callbacks are routed to the `on_error` handler, preventing stream interruption:
622
648
 
623
649
  ```ruby
624
- stream = ConduitSSE.new { |c| c.parser = ->(data) { JSON.parse(data) } }
650
+ stream = ConduitSSE.new { |config| config.parser = ->(data) { JSON.parse(data) } }
625
651
 
626
652
  stream.on_error do |error|
627
653
  puts "Caught error: #{error.message}"
@@ -645,7 +671,7 @@ require "net/http"
645
671
  require "uri"
646
672
  require "json"
647
673
 
648
- stream = ConduitSSE.new { |c| c.parser = ->(data) { JSON.parse(data) } }
674
+ stream = ConduitSSE.new { |config| config.parser = ->(data) { JSON.parse(data) } }
649
675
 
650
676
  # Attach inspector to log everything to stdout
651
677
  ConduitSSE::Inspector.attach(stream)
@@ -682,7 +708,7 @@ The inspector logs:
682
708
  You can register multiple callbacks for the same event type:
683
709
 
684
710
  ```ruby
685
- stream = ConduitSSE.new { |c| c.parser = ->(data) { data } }
711
+ stream = ConduitSSE.new { |config| config.parser = ->(data) { data } }
686
712
 
687
713
  stream.on_parsed do |parsed|
688
714
  puts "Handler 1: #{parsed}"
@@ -701,7 +727,7 @@ stream << "data: hello\n\n"
701
727
  ConduitSSE emits all SSE fields, including custom ones:
702
728
 
703
729
  ```ruby
704
- stream = ConduitSSE.new { |c| c.parser = ->(data) { data } }
730
+ stream = ConduitSSE.new { |config| config.parser = ->(data) { data } }
705
731
 
706
732
  stream.on_field do |name, value|
707
733
  case name
@@ -753,15 +779,15 @@ flowchart TD
753
779
 
754
780
  Stage-by-stage:
755
781
 
756
- - **1. Chunk Normalization** Raw chunks are normalized (UTF-8 conversion, CRLF→LF).
757
- - **2. Buffering** Chunks are buffered until frame boundaries are found.
758
- - **3. Frame Splitting** Frames are split by the separator (default: `\n\n`).
759
- - **4. Sanitization** Frames are sanitized (default: strip whitespace).
760
- - **5. Ping Detection** Ping/comment frames are identified and short-circuited.
761
- - **6. Field Parsing** SSE fields are parsed per the HTML spec.
762
- - **7. Event Construction** Events are built from parsed fields.
763
- - **8. Parser Application** Your custom parser transforms event data.
764
- - **9. Callback Emission** Callbacks are invoked at each stage.
782
+ - **1. Chunk Normalization**: Raw chunks are normalized (UTF-8 conversion, CRLF→LF).
783
+ - **2. Buffering**: Chunks are buffered until frame boundaries are found.
784
+ - **3. Frame Splitting**: Frames are split by the separator (default: `\n\n`).
785
+ - **4. Sanitization**: Frames are sanitized (default: strip whitespace).
786
+ - **5. Ping Detection**: Ping/comment frames are identified and short-circuited.
787
+ - **6. Field Parsing**: SSE fields are parsed per the HTML spec.
788
+ - **7. Event Construction**: Events are built from parsed fields.
789
+ - **8. Parser Application**: Your custom parser transforms event data.
790
+ - **9. Callback Emission**: Callbacks are invoked at each stage.
765
791
 
766
792
  ### Architecture: Config and State
767
793
 
@@ -769,15 +795,15 @@ Internally a `Stream` is the composition of two distinct objects, each with a
769
795
  different lifecycle:
770
796
 
771
797
  ```text
772
- ConduitSSE::Stream ← glues the two together
773
- ├─ #config : ConduitSSE::Config ← what to do (frozen after construction)
774
- └─ #state : ConduitSSE::State ← where we are (mutated per event)
798
+ ConduitSSE::Stream ← glues the two together
799
+ ├─ #config : ConduitSSE::Config ← what to do (frozen after construction)
800
+ └─ #state : ConduitSSE::State ← where we are (mutated per event)
775
801
  ```
776
802
 
777
803
  - **`ConduitSSE::Config`** holds the seven parsing knobs (parser, normalizers,
778
804
  separators, patterns, the `stats` flag). It's populated via kwargs and/or
779
805
  the configuration block, validated, has its derived `data_field` computed,
780
- and is then **frozen** accidental mid-stream mutation raises
806
+ and is then **frozen**. Accidental mid-stream mutation raises
781
807
  `FrozenError`. Both forms of public access work the same way:
782
808
 
783
809
  ```ruby
@@ -792,14 +818,14 @@ ConduitSSE::Stream ← glues the two together
792
818
  stats counter hash. It's accessible via `stream.state` for inspection:
793
819
 
794
820
  ```ruby
795
- stream.state.buffer_size # bytes pending in the buffer
796
- stream.state.last_event_id # last `id:` seen
797
- stream.state.stats_enabled? # true iff stats: true was passed
821
+ stream.state.buffer_size # bytes pending in the buffer
822
+ stream.state.last_event_id # last `id:` seen
823
+ stream.state.stats_enabled? # true iff stats: true was passed
798
824
  ```
799
825
 
800
826
  The `#last_event_id`, `#retry_ms`, `#buffer_size`, and `#stats` methods on
801
827
  `Stream` are thin forwarders to the underlying `State`, so you rarely need to
802
- reach into `stream.state` directly but it's there when you want to
828
+ reach into `stream.state` directly, but it's there when you want to
803
829
  introspect a stream from the outside (tests, dashboards, custom tooling).
804
830
 
805
831
  ### Performance Notes: Stats vs. Inspector
@@ -807,7 +833,7 @@ introspect a stream from the outside (tests, dashboards, custom tooling).
807
833
  The `#stats` hash and the `ConduitSSE::Inspector` are **not** the same mechanism
808
834
  and have very different performance profiles:
809
835
 
810
- - **`#stats` is opt-in** (`c.stats = true` in the block, or `stats: true` as a
836
+ - **`#stats` is opt-in** (`config.stats = true` in the block, or `stats: true` as a
811
837
  kwarg). When disabled (the default), `#stats` returns `nil` and the stream
812
838
  performs **zero** per-event counter bookkeeping. The State object exposes
813
839
  `#increment_stat` and `#add_fields` as null-object methods that return
@@ -818,8 +844,8 @@ and have very different performance profiles:
818
844
  on in production if you want the metrics; safe to leave off if you don't.
819
845
  - **`ConduitSSE::Inspector` is strictly opt-in.** It only does work after you
820
846
  call `ConduitSSE::Inspector.attach(stream)`. Attachment registers regular
821
- user callbacks (`on_chunk`, `on_frame`, ) that do `inspect`, string
822
- interpolation, and `IO#puts`. Those are **not** zero-cost keep the
847
+ user callbacks (`on_chunk`, `on_frame`, etc.) that do `inspect`, string
848
+ interpolation, and `IO#puts`. Those are **not** zero-cost. Keep the
823
849
  inspector off in production and use it for local debugging only.
824
850
  - **Unregistered callbacks are free.** `Callbacks#emit` returns immediately
825
851
  when no handler is registered for a given stage; there is no hidden
@@ -15,10 +15,10 @@ module ConduitSSE
15
15
  # ConduitSSE.new(parser: ->(d) { JSON.parse(d) }, stats: true)
16
16
  #
17
17
  # # Block form
18
- # ConduitSSE.new do |c|
19
- # c.parser = ->(d) { JSON.parse(d) }
20
- # c.stats = true
21
- # c.frame_separator = "\r\n\r\n"
18
+ # ConduitSSE.new do |config|
19
+ # config.parser = ->(d) { JSON.parse(d) }
20
+ # config.stats = true
21
+ # config.frame_separator = "\r\n\r\n"
22
22
  # end
23
23
  #
24
24
  # The two can be mixed; kwargs seed the config and the block then mutates
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ConduitSSE
4
- VERSION = "2.0.0"
4
+ VERSION = "2.0.1"
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: conduit-sse
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - franbach
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-05-13 00:00:00.000000000 Z
10
+ date: 2026-05-14 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: ConduitSSE provides a flexible callback-based architecture for processing
13
13
  real-time server push data with full control over every stage of the parsing pipeline.