datastar 1.0.0.beta.1 → 1.0.0.beta.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07c2774d8c0274b50336a6163a1e22b4451bfd2ec271601d0a5a9f21c0e14fba
4
- data.tar.gz: bebe0a2d43cf8ab1e03a1693bbd2bfc206e8f1e650bf24333d39a8cfa59a4ebb
3
+ metadata.gz: 23de694fa3da7ad6bd9cc78a02851ec22ab16f5d16e475beb9bc175569fbce02
4
+ data.tar.gz: eab01bb873a350c595dcdaddddb722ea2a69aef584b86f1bab5309f61764a699
5
5
  SHA512:
6
- metadata.gz: f98e8f6b5de65c9250b2ab678970f9ecdac84edfbc5088951570bd8b1f5f08d134180216d8b92d272628cd392bdb0ce1b71bb80f7263451a18de7308c7bf24d7
7
- data.tar.gz: d179c5dd59e5a5de18d2688a721e10dc7d62042c40b59cd1bd707f7a147e942ba234ebf6c2ca101b3cbce2d8c6288e6809d77fe3c7282569786c59e21b81282d
6
+ metadata.gz: 3f386d1039d1a9fc53b698ce9d4390f595dcf1dfec1166c03d7bb1fa17ec87c062089386151cc7d1ea6117e4b8350d6d819458215b3f305bc3e756f1a691dba2
7
+ data.tar.gz: 88e7e8505d1640151bcd494aca0000b3ee5d935f0ad426b937372d6c93b8191e5a2b4838e0ed86a58eeab11e5a5e66e92a92841967812f4095564ebc39adbf36
data/README.md CHANGED
@@ -4,10 +4,10 @@ Implement the [Datastart SSE procotocol](https://data-star.dev/reference/sse_eve
4
4
 
5
5
  ## Installation
6
6
 
7
- Install the gem and add to the application's Gemfile by executing:
7
+ Add this gem to your `Gemfile`
8
8
 
9
9
  ```bash
10
- bundle add datastar
10
+ gem 'datastar'
11
11
  ```
12
12
 
13
13
  Or point your `Gemfile` to the source
@@ -170,7 +170,10 @@ datastar.on_client_connect do
170
170
  end
171
171
  ```
172
172
 
173
+ This callback's behaviour depends on the configured [heartbeat](#heartbeat)
174
+
173
175
  #### `on_server_disconnect`
176
+
174
177
  Register server-side code to run when the connection is closed by the server.
175
178
  Ie when the served is done streaming without errors.
176
179
 
@@ -188,15 +191,64 @@ datastar.on_error do |exception|
188
191
  Sentry.notify(exception)
189
192
  end
190
193
  ```
191
- Note that this callback can be registered globally, too.
194
+ Note that this callback can be [configured globally](#global-configuration), too.
195
+
196
+ ### heartbeat
197
+
198
+ By default, streaming responses (using the `#stream` block) launch a background thread/fiber to periodically check the connection.
199
+
200
+ This is because the browser could have disconnected during a long-lived, idle connection (for example waiting on an event bus).
201
+
202
+ The default heartbeat is 3 seconds, and it will close the connection and trigger [on_client_disconnect](#on_client_disconnect) callbacks if the client has disconnected.
203
+
204
+ In cases where a streaming block doesn't need a heartbeat and you want to save precious threads (for example a regular ticker update, ie non-idle), you can disable the heartbeat:
205
+
206
+ ```ruby
207
+ datastar = Datastar.new(request:, response:, view_context:, heartbeat: false)
208
+
209
+ datastar.stream do |sse|
210
+ 100.times do |i|
211
+ sleep 1
212
+ sse.merge_signals count: i
213
+ end
214
+ end
215
+ ```
216
+
217
+ You can also set it to a different number (in seconds)
218
+
219
+ ```ruby
220
+ heartbeat: 0.5
221
+ ```
222
+
223
+ #### Manual connection check
224
+
225
+ If you want to check connection status on your own, you can disable the heartbeat and use `sse.check_connection!`, which will close the connection and trigger callbacks if the client is disconnected.
226
+
227
+ ```ruby
228
+ datastar = Datastar.new(request:, response:, view_context:, heartbeat: false)
229
+
230
+ datastar.stream do |sse|
231
+ # The event bus implementaton will check connection status when idle
232
+ # by calling #check_connection! on it
233
+ EventBus.subscribe('channel', sse) do |event|
234
+ sse.merge_signals eventName: event.name
235
+ end
236
+ end
237
+ ```
192
238
 
193
239
  ### Global configuration
194
240
 
195
241
  ```ruby
196
242
  Datastar.configure do |config|
243
+ # Global on_error callback
244
+ # Can be overriden on specific instances
197
245
  config.on_error do |exception|
198
246
  Sentry.notify(exception)
199
247
  end
248
+
249
+ # Global heartbeat interval (or false, to disable)
250
+ # Can be overriden on specific instances
251
+ config.heartbeat = 0.3
200
252
  end
201
253
  ```
202
254
 
@@ -274,7 +326,7 @@ From this library's root, run the bundled-in test Rack app:
274
326
  bundle puma examples/test.ru
275
327
  ```
276
328
 
277
- Now run the test bash scripts in the `test` directory in this repo.
329
+ Now run the test bash scripts in the `sdk/test` directory in this repo.
278
330
 
279
331
  ```bash
280
332
  ./test-all.sh http://localhost:9292
@@ -32,13 +32,15 @@ module Datastar
32
32
  class Configuration
33
33
  NOOP_CALLBACK = ->(_error) {}
34
34
  RACK_FINALIZE = ->(_view_context, response) { response.finish }
35
+ DEFAULT_HEARTBEAT = 3
35
36
 
36
- attr_accessor :executor, :error_callback, :finalize
37
+ attr_accessor :executor, :error_callback, :finalize, :heartbeat
37
38
 
38
39
  def initialize
39
40
  @executor = ThreadExecutor.new
40
41
  @error_callback = NOOP_CALLBACK
41
42
  @finalize = RACK_FINALIZE
43
+ @heartbeat = DEFAULT_HEARTBEAT
42
44
  end
43
45
 
44
46
  def on_error(callable = nil, &block)
@@ -35,13 +35,15 @@ module Datastar
35
35
  # @option executor [Object] the executor object to use for managing threads and queues
36
36
  # @option error_callback [Proc] the callback to call when an error occurs
37
37
  # @option finalize [Proc] the callback to call when the response is finalized
38
+ # @option heartbeat [Integer, nil, FalseClass] the heartbeat interval in seconds
38
39
  def initialize(
39
40
  request:,
40
41
  response: nil,
41
42
  view_context: nil,
42
43
  executor: Datastar.config.executor,
43
44
  error_callback: Datastar.config.error_callback,
44
- finalize: Datastar.config.finalize
45
+ finalize: Datastar.config.finalize,
46
+ heartbeat: Datastar.config.heartbeat
45
47
  )
46
48
  @on_connect = []
47
49
  @on_client_disconnect = []
@@ -61,6 +63,10 @@ module Datastar
61
63
  @response.headers['X-Accel-Buffering'] = 'no'
62
64
  @response.delete_header 'Content-Length'
63
65
  @executor.prepare(@response)
66
+ raise ArgumentError, ':heartbeat must be a number' if heartbeat && !heartbeat.is_a?(Numeric)
67
+
68
+ @heartbeat = heartbeat
69
+ @heartbeat_on = false
64
70
  end
65
71
 
66
72
  # Check if the request accepts SSE responses
@@ -124,7 +130,7 @@ module Datastar
124
130
  # @param fragments [String, #call(view_context: Object) => Object] the HTML fragment or object
125
131
  # @param options [Hash] the options to send with the message
126
132
  def merge_fragments(fragments, options = BLANK_OPTIONS)
127
- stream do |sse|
133
+ stream_no_heartbeat do |sse|
128
134
  sse.merge_fragments(fragments, options)
129
135
  end
130
136
  end
@@ -138,7 +144,7 @@ module Datastar
138
144
  # @param selector [String] a CSS selector for the fragment to remove
139
145
  # @param options [Hash] the options to send with the message
140
146
  def remove_fragments(selector, options = BLANK_OPTIONS)
141
- stream do |sse|
147
+ stream_no_heartbeat do |sse|
142
148
  sse.remove_fragments(selector, options)
143
149
  end
144
150
  end
@@ -152,7 +158,7 @@ module Datastar
152
158
  # @param signals [Hash] signals to merge
153
159
  # @param options [Hash] the options to send with the message
154
160
  def merge_signals(signals, options = BLANK_OPTIONS)
155
- stream do |sse|
161
+ stream_no_heartbeat do |sse|
156
162
  sse.merge_signals(signals, options)
157
163
  end
158
164
  end
@@ -166,7 +172,7 @@ module Datastar
166
172
  # @param paths [Array<String>] object paths to the signals to remove
167
173
  # @param options [Hash] the options to send with the message
168
174
  def remove_signals(paths, options = BLANK_OPTIONS)
169
- stream do |sse|
175
+ stream_no_heartbeat do |sse|
170
176
  sse.remove_signals(paths, options)
171
177
  end
172
178
  end
@@ -180,7 +186,7 @@ module Datastar
180
186
  # @param script [String] the script to execute
181
187
  # @param options [Hash] the options to send with the message
182
188
  def execute_script(script, options = BLANK_OPTIONS)
183
- stream do |sse|
189
+ stream_no_heartbeat do |sse|
184
190
  sse.execute_script(script, options)
185
191
  end
186
192
  end
@@ -190,7 +196,7 @@ module Datastar
190
196
  #
191
197
  # @param url [String] the URL or path to redirect to
192
198
  def redirect(url)
193
- stream do |sse|
199
+ stream_no_heartbeat do |sse|
194
200
  sse.redirect(url)
195
201
  end
196
202
  end
@@ -237,6 +243,15 @@ module Datastar
237
243
  def stream(streamer = nil, &block)
238
244
  streamer ||= block
239
245
  @streamers << streamer
246
+ if @heartbeat && !@heartbeat_on
247
+ @heartbeat_on = true
248
+ @streamers << proc do |sse|
249
+ while true
250
+ sleep @heartbeat
251
+ sse.check_connection!
252
+ end
253
+ end
254
+ end
240
255
 
241
256
  body = if @streamers.size == 1
242
257
  stream_one(streamer)
@@ -250,6 +265,14 @@ module Datastar
250
265
 
251
266
  private
252
267
 
268
+ def stream_no_heartbeat(&block)
269
+ was = @heartbeat
270
+ @heartbeat = false
271
+ stream(&block).tap do
272
+ @heartbeat = was
273
+ end
274
+ end
275
+
253
276
  # Produce a response body for a single stream
254
277
  # In this case, the SSE generator can write directly to the socket
255
278
  #
@@ -300,11 +323,12 @@ module Datastar
300
323
 
301
324
  handling_errors(conn_generator, socket) do
302
325
  done_count = 0
326
+ threads_size = @heartbeat_on ? threads.size - 1 : threads.size
303
327
 
304
328
  while (data = @queue.pop)
305
329
  if data == :done
306
330
  done_count += 1
307
- @queue << nil if done_count == threads.size
331
+ @queue << nil if done_count == threads_size
308
332
  elsif data.is_a?(Exception)
309
333
  raise data
310
334
  else
@@ -39,6 +39,13 @@ module Datastar
39
39
  @view_context = view_context
40
40
  end
41
41
 
42
+ # Sometimes we'll want to run periodic checks to ensure the connection is still alive
43
+ # ie. the browser hasn't disconnected
44
+ # For example when idle listening on an event bus.
45
+ def check_connection!
46
+ @stream << MSG_END
47
+ end
48
+
42
49
  def merge_fragments(fragments, options = BLANK_OPTIONS)
43
50
  # Support Phlex components
44
51
  # And Rails' #render_in interface
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Datastar
4
- VERSION = '1.0.0.beta.1'
4
+ VERSION = '1.0.0.beta.2'
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: datastar
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta.1
4
+ version: 1.0.0.beta.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ismael Celis
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-02-11 00:00:00.000000000 Z
10
+ date: 2025-02-12 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rack