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 +4 -4
- data/README.md +56 -4
- data/lib/datastar/configuration.rb +3 -1
- data/lib/datastar/dispatcher.rb +32 -8
- data/lib/datastar/server_sent_event_generator.rb +7 -0
- data/lib/datastar/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 23de694fa3da7ad6bd9cc78a02851ec22ab16f5d16e475beb9bc175569fbce02
|
4
|
+
data.tar.gz: eab01bb873a350c595dcdaddddb722ea2a69aef584b86f1bab5309f61764a699
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
7
|
+
Add this gem to your `Gemfile`
|
8
8
|
|
9
9
|
```bash
|
10
|
-
|
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
|
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)
|
data/lib/datastar/dispatcher.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 ==
|
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
|
data/lib/datastar/version.rb
CHANGED
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.
|
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-
|
10
|
+
date: 2025-02-12 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: rack
|