datastar/sdk/ruby
Ismael Celis 5d46096dc2
Ruby SDK (#600)
* WiP initial setup, ServerSentEventGenerator class

* WiP working merge_fragments in Rails

* #merge_fragments and #merge_signals

* Handle SSE vs Data* options

* Test that #merge_fragments works with a #call(view_context:) interface

* Test Dispatcher#stream

* #remove_fragments

* #remove_signals

* #execute_script

* execute_script with attributes Hash

* Connection: keep-alive

* Use 2 line-breaks as message end, plus last line's 1 line break (3 total)

* Connection callbacks. #on_connect, #on_disconnect, #on_error

* Dispatcher#signals

* Omit retry if using default value (1000)

* Omit defaults

* Multiline scripts

* Test Rack endpoint

* Document test Rack endpoint

* Add missing defaults

* Spawn multiple streams in threads, client_disconnect and server_disconnect handlers

* Move ThreadSpawner to configuration

* Configure a RailsThreadSpawner when Rails detected

* Move Railtie one dir up

* Global error callback

Datastar.config.on_error { |err| Sentry.notify(err) }

* Catch exception from stream threads in main thread

* Linearlize exception handling

* Refactor dispatcher to handle single stream in main thread, multi streams in separate threads

* spawner => executor. Rails Async executor using fibers.

* Support Async for fiber-based concurrency

* Finalize response for Rack and Rails

* test Rack app

* Threaded demo

* Test Dispatcher#sse?

Also do not check for SSE Accept on stream.
Leave it up to the user.

* Do not check Accept header in test app. Test scripts don't send it properly.

* Document code

* Example progress bar Rack app

* README

* Link to D* SSE docs

* See examples

* Document callbacks

* List Ruby SDK in SDKs.md

* Ruby struct in consts.go

* Document running tasks with arguments via Docker

* Code-gen Ruby constants from shared data via template

* Make test rely on constants

* Datastar.from_rack_env(env) => Datastar::Dispatcher

* Ruby example snippets

* #redirect(location)

* Ruby snippet using #redirect(new_path)

* Add X-Accel-Buffering: no header

To disable response buffering by NGinx and other proxies.

* Clarify linearisation of updates in Readme

* Tidy-up progress example

* Move examples to /examples/ruby

* Document Rails and Phlex

* Version 1.0.0.beta.1

* Version 1.0.0.beta.1

* Do not set Connection header if not HTTP/1.1

* Don't touch BUILDING.md docs in this PR

* Remove Changelog for now

* Sort Ruby alphabetically (just "ruby", not the entire line)

* Add hello world example, remove progress bar one.

* Add hello-world example to code-gen

* Typos
2025-02-05 12:02:18 -06:00
..
bin Ruby SDK (#600) 2025-02-05 12:02:18 -06:00
examples Ruby SDK (#600) 2025-02-05 12:02:18 -06:00
lib Ruby SDK (#600) 2025-02-05 12:02:18 -06:00
sig Ruby SDK (#600) 2025-02-05 12:02:18 -06:00
spec Ruby SDK (#600) 2025-02-05 12:02:18 -06:00
.gitignore Ruby SDK (#600) 2025-02-05 12:02:18 -06:00
.rspec Ruby SDK (#600) 2025-02-05 12:02:18 -06:00
Gemfile Ruby SDK (#600) 2025-02-05 12:02:18 -06:00
Gemfile.lock Ruby SDK (#600) 2025-02-05 12:02:18 -06:00
README.md Ruby SDK (#600) 2025-02-05 12:02:18 -06:00
Rakefile Ruby SDK (#600) 2025-02-05 12:02:18 -06:00
datastar.gemspec Ruby SDK (#600) 2025-02-05 12:02:18 -06:00

README.md

Datastar Ruby SDK

Implement the Datastart SSE procotocol in Ruby. It can be used in any Rack handler, and Rails controllers.

Installation

Install the gem and add to the application's Gemfile by executing:

bundle add datastar

Or point your Gemfile to the source

gem 'datastar', git: 'https://github.com/starfederation/datastar', glob: 'sdk/ruby/*.gemspec'

Usage

Initialize the Datastar dispatcher

In your Rack handler or Rails controller:

# Rails controllers, as well as Sinatra and others, 
# already have request and response objects

datastar = Datastar.new(request:, response:, view_context: self)

# In a Rack handler, you can instantiate from the Rack env
datastar = Datastar.from_rack_env(env)

Sending updates to the browser

There are two ways to use this gem in HTTP handlers:

  • One-off responses, where you want to send a single update down to the browser.
  • Streaming responses, where you want to send multiple updates down to the browser.

One-off update:

datastar.merge_fragments(%(<h1 id="title">Hello, World!</h1>))

In this mode, the response is closed after the fragment is sent.

Streaming updates

datastar.stream do |sse|
  sse.merge_fragments(%(<h1 id="title">Hello, World!</h1>))
  # Streaming multiple updates
  100.times do |i|
    sleep 1
    sse.merge_fragments(%(<h1 id="title">Hello, World #{i}!</h1>))
  end
end

In this mode, the response is kept open until stream blocks have finished.

Concurrent streaming blocks

Multiple stream blocks will be launched in threads/fibers, and will run concurrently. Their updates are linearized and sent to the browser as they are produced.

# Stream to the browser from two concurrent threads
datastar.stream do |sse|
  100.times do |i|
    sleep 1
    sse.merge_fragments(%(<h1 id="slow">#{i}!</h1>))
  end
end

datastar.stream do |sse|
  1000.times do |i|
    sleep 0.1
    sse.merge_fragments(%(<h1 id="fast">#{i}!</h1>))
  end
end

See the examples directory.

Datastar methods

All these methods are available in both the one-off and the streaming modes.

merge_fragments

See https://data-star.dev/reference/sse_events#datastar-merge-fragments

sse.merge_fragments(%(<div id="foo">\n<span>hello</span>\n</div>))

# or a Phlex view object
sse.merge_fragments(UserComponet.new)

# Or pass options
sse.merge_fragments(
  %(<div id="foo">\n<span>hello</span>\n</div>),
  merge_mode: 'append'
)

remove_fragments

See https://data-star.dev/reference/sse_events#datastar-remove-fragments

sse.remove_fragments('#users')

merge_signals

See https://data-star.dev/reference/sse_events#datastar-merge-signals

sse.merge_signals(count: 4, user: { name: 'John' })

remove_signals

See https://data-star.dev/reference/sse_events#datastar-remove-signals

sse.remove_signals(['user.name', 'user.email'])

execute_script

See https://data-star.dev/reference/sse_events#datastar-execute-script

sse.execute_scriprt(%(alert('Hello World!'))

signals

See https://data-star.dev/guide/getting_started#data-signals

Returns signals sent by the browser.

sse.signals # => { user: { name: 'John' } }

redirect

This is just a helper to send a script to update the browser's location.

sse.redirect('/new_location')

Lifecycle callbacks

on_connect

Register server-side code to run when the connection is first handled.

datastar.on_connect do
  puts 'A user has connected'
end

on_client_disconnect

Register server-side code to run when the connection is closed by the client

datastar.on_client_connect do
  puts 'A user has disconnected connected'
end

on_server_disconnect

Register server-side code to run when the connection is closed by the server. Ie when the served is done streaming without errors.

datastar.on_server_connect do
  puts 'Server is done streaming'
end

on_error

Ruby code to handle any exceptions raised by streaming blocks.

datastar.on_error do |exception|
  Sentry.notify(exception)
end

Note that this callback can be registered globally, too.

Global configuration

Datastar.configure do |config|
  config.on_error do |exception|
    Sentry.notify(exception)
  end
end

Rails

Rendering Rails templates

datastar.stream do |sse|
  10.times do |i|
    sleep 1
    tpl = render_to_string('events/user', layout: false, locals: { name: "David #{i}" })
    sse.merge_fragments tpl
  end
end

Rendering Phlex components

#merge_fragments supports Phlex component instances.

sse.merge_fragments(UserComponent.new(user: User.first))

Tests

bundle exec rspec

Running Datastar's SDK test suite

Install dependencies.

bundle install

From this library's root, run the bundled-in test Rack app:

bundle puma examples/test.ru

Now run the test bash scripts in the test directory in this repo.

./test-all.sh http://localhost:9292

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/starfederation/datastar.