Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ruby SDK #600

Draft
wants to merge 50 commits into
base: develop
Choose a base branch
from
Draft

Ruby SDK #600

wants to merge 50 commits into from

Conversation

ismasan
Copy link
Contributor

@ismasan ismasan commented Feb 4, 2025

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

Installation

For now, 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
# In a "raw" Rack handler you will need to create them
#  request = Rack::Request.new(env)
#  response = Rack::Response.new([], 200)

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

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.
Then their updates are linearized and sent to the browser.

# 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

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' } }

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

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

@bencroker
Copy link
Collaborator

Thanks for the PR, looks like a great start!

Can you please follow the remaining tasks as outlined in the Contribution Guidelines?

I’ll put this PR into draft mode until it’s ready for review again.

@bencroker bencroker marked this pull request as draft February 4, 2025 18:21
@ismasan
Copy link
Contributor Author

ismasan commented Feb 4, 2025

I pushed changes to code-get constants, and a few code snippets.

I don't think existing SDKs have working examples for all code snippets. Is that a requirement for new SDKs going forward?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants