durable_streams-rails 0.1.0 → 0.2.0

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: ee437ac73b405cba8114f0a9b38a69f20a70fceb86aa80998b82396af577b133
4
- data.tar.gz: ce7306811c5b50cab5490102353399a2e14ca1bf23564c93931c0d5344f656b5
3
+ metadata.gz: 486bb79705018c3e6bfbdaa56f2b072b0ac3781c54e07a9cc16c62e9834bfed9
4
+ data.tar.gz: 9f73ab9352ba33d19eb079503d04a865567eb0cddb4810acba6e223d8b9de461
5
5
  SHA512:
6
- metadata.gz: 7dba804eeefe0a329b0cc314cfcd595982a0d3eaf5b7e359fd5816998f14668ff28453017c826e7f3cc693c5065e20a81ab68b4cb995c7424da27cbc3956b50f
7
- data.tar.gz: 71f865a9adec22871977e3e97b7cf2ce4ac7a3d5902f6e0583737c3388c93b257b98ee96240613ca2a9e1829aadb8bcb22ed27796e90508a5f9a9b93415e7d25
6
+ metadata.gz: 86aae6ff003e0f17253c8efa06780b0cabb18a58f6c0aad1abd0373c85797db95ce08ecc42785d72ffb3a46c756a0702324df9f0fa4f50dc751e1f1ff375a5ae
7
+ data.tar.gz: 44c1c3aac3c5a9d0d96e76cd39e80471d2796dae296cbb60c1fc73bc46b8f63fdcea1d2cfb4d1a270639b3c8318b6716f98e322cfce289a7ee0bc22099903cd0
data/README.md ADDED
@@ -0,0 +1,274 @@
1
+ # Durable Streams Rails
2
+
3
+ Durable Streams integration for Rails. Stream State Protocol events from your models to clients
4
+ over SSE with the same developer experience as Turbo Broadcasts — but with offset-based
5
+ resumability, persistent event logs, and no WebSocket infrastructure.
6
+
7
+ ```ruby
8
+ class Comment < ApplicationRecord
9
+ belongs_to :post
10
+ streams_to :post
11
+ end
12
+ ```
13
+
14
+ That's it. Creates, updates, and destroys are broadcast as State Protocol events to all clients
15
+ subscribed to the post's stream. No manual stream management, no JSON serialization, no offset
16
+ tracking.
17
+
18
+ ## Installation
19
+
20
+ Add to your Gemfile:
21
+
22
+ ```ruby
23
+ gem "durable_streams-rails"
24
+ ```
25
+
26
+ Run the install generator:
27
+
28
+ ```bash
29
+ bin/rails generate durable_streams:install
30
+ ```
31
+
32
+ This creates:
33
+
34
+ - `config/initializers/durable_streams.rb` — sets the stream server URL
35
+ - `config/durable_streams.yml` — server configuration (per environment)
36
+ - `bin/durable-streams` — binstub that auto-downloads and runs the server binary
37
+ - Updates `Procfile.dev` with a `streams:` entry
38
+ - Updates `.gitignore` to exclude `bin/dist/`
39
+
40
+ ### Pin a server version
41
+
42
+ ```bash
43
+ bin/rails generate durable_streams:install --version=0.1.0
44
+ ```
45
+
46
+ ### Disable route auto-mounting
47
+
48
+ The gem auto-mounts the auth verification endpoint at `/durable_streams/auth/verify`.
49
+ To disable:
50
+
51
+ ```ruby
52
+ # config/initializers/durable_streams.rb
53
+ Rails.application.config.durable_streams.draw_routes = false
54
+ ```
55
+
56
+ ## Configuration
57
+
58
+ The server reads `config/durable_streams.yml`:
59
+
60
+ ```yaml
61
+ default: &default
62
+ port: 4437
63
+ # route: /v1/streams/*
64
+ auth:
65
+ url: http://localhost:3000
66
+ path: /durable_streams/auth/verify
67
+ copy_headers:
68
+ - Cookie
69
+
70
+ development:
71
+ <<: *default
72
+
73
+ production:
74
+ <<: *default
75
+ domain: streams.example.com
76
+ auth:
77
+ url: https://app.example.com
78
+ path: /durable_streams/auth/verify
79
+ copy_headers:
80
+ - Cookie
81
+ - Authorization
82
+ storage:
83
+ data_dir: /var/data/durable-streams
84
+ ```
85
+
86
+ Power users can bypass the YAML entirely:
87
+
88
+ ```bash
89
+ DURABLE_STREAMS_CONFIG=/path/to/Caddyfile bin/durable-streams
90
+ ```
91
+
92
+ ## Deployment with Kamal
93
+
94
+ The server binary runs as a separate role in the same Docker image — same pattern as Solid Queue:
95
+
96
+ ```yaml
97
+ # config/deploy.yml
98
+ servers:
99
+ web:
100
+ cmd: bin/rails server
101
+ jobs:
102
+ cmd: bin/jobs
103
+ streams:
104
+ cmd: bin/durable-streams
105
+ ```
106
+
107
+ Pre-download the binary during Docker build:
108
+
109
+ ```dockerfile
110
+ RUN bin/rails durable_streams:download
111
+ ```
112
+
113
+ Or set a specific version:
114
+
115
+ ```dockerfile
116
+ RUN DURABLE_STREAMS_VERSION=0.1.0 bin/rails durable_streams:download
117
+ ```
118
+
119
+ ## Usage
120
+
121
+ ### Declarative streaming
122
+
123
+ Stream to an association:
124
+
125
+ ```ruby
126
+ class Comment < ApplicationRecord
127
+ belongs_to :post
128
+ streams_to :post
129
+ end
130
+ ```
131
+
132
+ Stream to self:
133
+
134
+ ```ruby
135
+ class Board < ApplicationRecord
136
+ streams
137
+ end
138
+ ```
139
+
140
+ With a proc for custom stream targets:
141
+
142
+ ```ruby
143
+ class Comment < ApplicationRecord
144
+ belongs_to :post
145
+ streams_to ->(comment) { [comment.post, :comments] }
146
+ end
147
+ ```
148
+
149
+ ### Instance methods
150
+
151
+ Synchronous (returns `txid` for optimistic update confirmation):
152
+
153
+ ```ruby
154
+ txid = comment.stream_insert_to post
155
+ txid = comment.stream_update_to post
156
+ txid = comment.stream_upsert_to post
157
+ comment.stream_delete_to post
158
+ ```
159
+
160
+ Asynchronous (via `DurableStreams::BroadcastJob`):
161
+
162
+ ```ruby
163
+ comment.stream_insert_later_to post
164
+ comment.stream_update_later_to post
165
+ comment.stream_upsert_later_to post
166
+ comment.stream_delete_later_to post
167
+ ```
168
+
169
+ Self-targeting (streams to the model itself):
170
+
171
+ ```ruby
172
+ comment.stream_insert # same as stream_insert_to(self)
173
+ comment.stream_update_later # same as stream_update_later_to(self)
174
+ ```
175
+
176
+ ### Direct broadcasting
177
+
178
+ For non-ActiveRecord use cases (presence, typing indicators):
179
+
180
+ ```ruby
181
+ DurableStreams.broadcast_to(room, :presence,
182
+ type: "presence",
183
+ key: user.id.to_s,
184
+ value: { id: user.id, name: user.name },
185
+ headers: { operation: "insert" }
186
+ )
187
+ ```
188
+
189
+ ### Signed stream URLs
190
+
191
+ Generate signed, expirable URLs for client SSE connections:
192
+
193
+ ```ruby
194
+ url = DurableStreams.signed_stream_url(room, :messages)
195
+ # => "http://localhost:4437/v1/streams/gid://app/Room/1/messages?token=eyJ..."
196
+
197
+ url = DurableStreams.signed_stream_url(room, :messages, expires_in: 1.hour)
198
+ ```
199
+
200
+ ### Suppressing broadcasts
201
+
202
+ ```ruby
203
+ Comment.suppressing_streams do
204
+ Comment.create!(post: post) # no broadcast
205
+ end
206
+ ```
207
+
208
+ ## Testing
209
+
210
+ The gem provides test helpers that mirror Turbo's `assert_turbo_stream_broadcasts`. They are
211
+ automatically included in `ActiveSupport::TestCase` via the engine.
212
+
213
+ During tests, `DurableStreams::Testing` intercepts all broadcasts at the transport layer —
214
+ events are captured in-memory instead of being sent to the stream server. This means tests
215
+ verify the Rails integration (DSL wiring, event shape, callbacks, jobs, suppression) without
216
+ requiring a running Durable Streams server. Same pattern as Turbo, where `ActionCable::TestHelper`
217
+ captures broadcasts without a WebSocket connection.
218
+
219
+ ```ruby
220
+ class CommentTest < ActiveSupport::TestCase
221
+ test "creating comment streams to post" do
222
+ assert_stream_broadcasts @post, count: 1 do
223
+ @post.comments.create!(body: "Hello")
224
+ end
225
+ end
226
+
227
+ test "capture stream events" do
228
+ events = capture_stream_broadcasts @post do
229
+ @post.comments.create!(body: "Hello")
230
+ end
231
+
232
+ assert_equal "insert", events.first["headers"]["operation"]
233
+ assert_equal "Hello", events.first["value"]["body"]
234
+ end
235
+
236
+ test "no broadcasts when suppressed" do
237
+ assert_no_stream_broadcasts @post do
238
+ Comment.suppressing_streams do
239
+ @post.comments.create!(body: "Silent")
240
+ end
241
+ end
242
+ end
243
+ end
244
+ ```
245
+
246
+ ## Architecture
247
+
248
+ This gem mirrors `turbo-rails` 1:1 in structure and design:
249
+
250
+ | Turbo Rails | Durable Streams Rails | Role |
251
+ |---|---|---|
252
+ | `Turbo::Streams::StreamName` | `DurableStreams::StreamName` | Name generation (private `stream_name_from`) |
253
+ | `Turbo::Streams::Broadcasts` | `DurableStreams::Broadcasts` | Flatten/compact guards, serialization, transport |
254
+ | `Turbo::StreamsChannel` | `DurableStreams` module | Entry point (extends StreamName + Broadcasts) |
255
+ | `Turbo::Broadcastable` | `DurableStreams::Broadcastable` | Model concern, delegates to module |
256
+ | `Turbo::Streams::ActionBroadcastJob` | `DurableStreams::BroadcastJob` | Async job, delegates to module |
257
+ | `ActionCable::TestHelper` | `DurableStreams::Testing` | Test transport interception |
258
+ | `Turbo::Broadcastable::TestHelper` | `DurableStreams::Broadcastable::TestHelper` | Test assertions |
259
+
260
+ The key architectural difference: Turbo broadcasts HTML fragments over WebSockets (Action Cable).
261
+ This gem broadcasts JSON State Protocol events over HTTP/SSE (Durable Streams).
262
+
263
+ ## Dependencies
264
+
265
+ - [`durable_streams`](https://github.com/tokimonki/durable_streams) — Ruby client for the Durable Streams protocol (resolved automatically via RubyGems)
266
+ - `railties` >= 8.0
267
+
268
+ ## Future
269
+
270
+ Brewing secretly — an async-backed durable streams server inside Rails.
271
+
272
+ ## License
273
+
274
+ MIT
data/Rakefile CHANGED
@@ -1,10 +1,19 @@
1
1
  require "bundler/setup"
2
+ require "bundler/gem_tasks"
2
3
  require "rake/testtask"
3
4
 
5
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
6
+ load "rails/tasks/engine.rake"
7
+
4
8
  Rake::TestTask.new(:test) do |t|
5
9
  t.libs << "test"
6
- t.pattern = "test/**/*_test.rb"
10
+ t.test_files = FileList["test/**/*_test.rb"]
7
11
  t.verbose = false
8
12
  end
9
13
 
10
- task default: :test
14
+ task :test_prereq do
15
+ puts "Preparing test database"
16
+ `cd test/dummy && RAILS_ENV=test bin/rails db:migrate`
17
+ end
18
+
19
+ task default: [ :test_prereq, :test ]
@@ -0,0 +1,24 @@
1
+ module DurableStreams
2
+ class AuthController < ActionController::API
3
+ def verify
4
+ if stream_name
5
+ head :ok
6
+ else
7
+ head :unauthorized
8
+ end
9
+ end
10
+
11
+ private
12
+ def stream_name
13
+ if token = extract_token
14
+ DurableStreams.verified_stream_name(token)
15
+ end
16
+ end
17
+
18
+ def extract_token
19
+ if uri = request.headers["X-Forwarded-Uri"]
20
+ DurableStreams.extract_token_from_url(uri)
21
+ end
22
+ end
23
+ end
24
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ Rails.application.routes.draw do
2
+ get "durable_streams/auth/verify", to: "durable_streams/auth#verify", as: :durable_streams_auth_verify
3
+ end if DurableStreams.draw_routes
@@ -6,11 +6,14 @@ module DurableStreams
6
6
  config.eager_load_namespaces << DurableStreams
7
7
  config.durable_streams = ActiveSupport::OrderedOptions.new
8
8
  config.autoload_once_paths = %W(
9
+ #{root}/app/controllers
9
10
  #{root}/app/models
10
11
  #{root}/app/models/concerns
11
12
  #{root}/app/jobs
12
13
  )
13
14
 
15
+ # If the parent application does not use Active Job, app/jobs cannot
16
+ # be eager loaded, because it references the ActiveJob constant.
14
17
  initializer "durable_streams.no_active_job", before: :set_eager_load_paths do
15
18
  unless defined?(ActiveJob)
16
19
  Rails.autoloaders.once.do_not_eager_load("#{root}/app/jobs")
@@ -25,14 +28,21 @@ module DurableStreams
25
28
  end
26
29
  end
27
30
 
31
+ initializer "durable_streams.configs" do
32
+ config.after_initialize do |app|
33
+ DurableStreams.draw_routes = app.config.durable_streams.draw_routes != false
34
+ end
35
+ end
36
+
28
37
  initializer "durable_streams.signed_stream_verifier_key" do
29
38
  config.after_initialize do
30
39
  DurableStreams.signed_stream_verifier_key =
31
- config.durable_streams&.signed_stream_verifier_key ||
40
+ config.durable_streams.signed_stream_verifier_key ||
32
41
  Rails.application.key_generator.generate_key("durable_streams/signed_stream_verifier_key")
33
42
  end
34
43
  end
35
44
 
45
+ # No Action Cable dependency -- load test helpers directly.
36
46
  initializer "durable_streams.test_assertions" do
37
47
  ActiveSupport.on_load(:active_support_test_case) do
38
48
  if defined?(ActiveJob)
@@ -0,0 +1,62 @@
1
+ require "yaml"
2
+ require "erb"
3
+
4
+ module DurableStreams
5
+ class ServerConfig
6
+ TEMPLATE = ERB.new(<<~'CADDYFILE', trim_mode: "-")
7
+ {
8
+ admin off
9
+ <%- unless @config["domain"] -%>
10
+ auto_https off
11
+ <%- end -%>
12
+ }
13
+
14
+ <%= address %> {
15
+ route <%= route_path %> {
16
+ forward_auth <%= auth["url"] %> {
17
+ uri <%= auth["path"] %>
18
+ <%- copy_headers.each do |header| -%>
19
+ copy_headers <%= header %>
20
+ <%- end -%>
21
+ }
22
+ durable_streams<%= storage_block %>
23
+ }
24
+ }
25
+ CADDYFILE
26
+
27
+ def self.generate(config_path, environment)
28
+ new(config_path, environment).generate
29
+ end
30
+
31
+ def initialize(config_path, environment)
32
+ @config = YAML.load_file(config_path, aliases: true).fetch(environment)
33
+ end
34
+
35
+ def generate
36
+ TEMPLATE.result(binding)
37
+ end
38
+
39
+ private
40
+ def address
41
+ @config["domain"] || ":#{@config.fetch("port", 4437)}"
42
+ end
43
+
44
+ def route_path
45
+ @config.fetch("route", "/v1/streams/*")
46
+ end
47
+
48
+ def auth
49
+ @config.fetch("auth")
50
+ end
51
+
52
+ def copy_headers
53
+ auth.fetch("copy_headers", [ "Cookie" ])
54
+ end
55
+
56
+ def storage_block
57
+ if dir = @config.dig("storage", "data_dir")
58
+ " {\n\t\t\tdata_dir #{dir}\n\t\t}"
59
+ end
60
+ end
61
+ end
62
+ end
@@ -19,7 +19,7 @@ module DurableStreams::Testing
19
19
  end
20
20
 
21
21
  def recording?
22
- messages.present?
22
+ !messages.nil?
23
23
  end
24
24
 
25
25
  def record(stream_name, message)
@@ -1,3 +1,3 @@
1
1
  module DurableStreams
2
- RAILS_VERSION = "0.1.0"
2
+ RAILS_VERSION = "0.2.0"
3
3
  end
@@ -5,10 +5,12 @@ require "durable_streams/engine"
5
5
  require "active_support/core_ext/module/attribute_accessors_per_thread"
6
6
 
7
7
  module DurableStreams
8
+ extend ActiveSupport::Autoload
8
9
  extend DurableStreams::StreamName
9
10
  extend DurableStreams::Broadcasts
10
11
 
11
12
  mattr_accessor :base_url
13
+ mattr_accessor :draw_routes, default: true
12
14
 
13
15
  class << self
14
16
  attr_writer :signed_stream_verifier_key
@@ -26,5 +28,19 @@ module DurableStreams
26
28
  token = signed_stream_verifier.generate(path, expires_in: expires_in)
27
29
  "#{base_url}/#{path}?token=#{token}"
28
30
  end
31
+
32
+ # Verifies the signed token embedded in a stream URL. Used by the auth controller
33
+ # to validate forward_auth requests from the Durable Streams server.
34
+ def verify_signed_url(url)
35
+ if token = extract_token_from_url(url)
36
+ verified_stream_name(token)
37
+ end
38
+ end
39
+
40
+ def extract_token_from_url(url)
41
+ if query = URI(url).query
42
+ Rack::Utils.parse_query(query)["token"]
43
+ end
44
+ end
29
45
  end
30
46
  end
@@ -0,0 +1,16 @@
1
+ Description:
2
+ Sets up Durable Streams for your Rails application.
3
+
4
+ Creates the server config (config/durable_streams.yml), binstub
5
+ (bin/durable-streams), initializer, and updates Procfile.dev.
6
+ The server binary downloads automatically on first run.
7
+
8
+ Set DURABLE_STREAMS_CONFIG to a file path to use a raw server
9
+ config instead of the YAML-based configuration.
10
+
11
+ Examples:
12
+ bin/rails generate durable_streams:install
13
+
14
+ bin/rails generate durable_streams:install --version=0.1.0
15
+
16
+ bin/rails generate durable_streams:install --skip-procfile
@@ -0,0 +1,62 @@
1
+ module DurableStreams
2
+ module Generators
3
+ class InstallGenerator < ::Rails::Generators::Base
4
+ source_root File.expand_path("templates", __dir__)
5
+
6
+ class_option :version,
7
+ type: :string,
8
+ desc: "Durable Streams server version (default: latest)",
9
+ default: "latest"
10
+ class_option :skip_procfile,
11
+ type: :boolean,
12
+ desc: "Skip Procfile.dev update",
13
+ default: false
14
+
15
+ def create_initializer
16
+ template "initializer.rb", "config/initializers/durable_streams.rb"
17
+ end
18
+
19
+ def create_server_config
20
+ template "durable_streams.yml", "config/durable_streams.yml"
21
+ end
22
+
23
+ def create_binstub
24
+ template "bin/durable-streams", "bin/durable-streams"
25
+ chmod "bin/durable-streams", 0755, verbose: false
26
+ end
27
+
28
+ def update_procfile
29
+ return if options[:skip_procfile]
30
+
31
+ procfile = "Procfile.dev"
32
+ entry = "streams: bin/durable-streams\n"
33
+
34
+ if File.exist?(procfile) && !File.read(procfile).match?(/^streams:/)
35
+ append_file procfile, entry
36
+ end
37
+ end
38
+
39
+ def update_gitignore
40
+ if File.exist?(".gitignore") && !File.read(".gitignore").match?(/^bin\/dist/)
41
+ append_file ".gitignore", "\n# Durable Streams server binary\nbin/dist/\n"
42
+ end
43
+ end
44
+
45
+ def show_instructions
46
+ say ""
47
+ say " Durable Streams installed!", :green
48
+ say ""
49
+ say " Configuration: config/durable_streams.yml"
50
+ say " Server binary: bin/durable-streams (auto-downloads on first run)"
51
+ say ""
52
+ say " Start development:"
53
+ say " bin/dev"
54
+ say ""
55
+ say " Stream endpoint: http://localhost:4437/v1/streams/*"
56
+ say ""
57
+ say " Power users: set DURABLE_STREAMS_CONFIG to use a raw server config file."
58
+ say ""
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ APP_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
5
+ BIN_PATH="${APP_ROOT}/bin/dist/durable-streams-server"
6
+
7
+ # Download if not cached
8
+ if [ ! -x "$BIN_PATH" ]; then
9
+ DURABLE_STREAMS_VERSION="${DURABLE_STREAMS_VERSION:-<%= options[:version] %>}" \
10
+ bundle exec rails durable_streams:download
11
+ fi
12
+
13
+ # Power user: use raw config file directly
14
+ if [ -n "${DURABLE_STREAMS_CONFIG:-}" ]; then
15
+ exec "$BIN_PATH" run --config "$DURABLE_STREAMS_CONFIG" "$@"
16
+ fi
17
+
18
+ # Standard: generate config from YAML
19
+ RAILS_ENV="${RAILS_ENV:-development}"
20
+ CONFIG_FILE="${APP_ROOT}/tmp/durable_streams.caddyfile"
21
+
22
+ mkdir -p "${APP_ROOT}/tmp"
23
+ APP_ROOT="$APP_ROOT" RAILS_ENV="$RAILS_ENV" bundle exec ruby -r durable_streams/server_config \
24
+ -e "puts DurableStreams::ServerConfig.generate(File.join(ENV['APP_ROOT'], 'config/durable_streams.yml'), ENV['RAILS_ENV'])" \
25
+ > "$CONFIG_FILE"
26
+
27
+ exec "$BIN_PATH" run --config "$CONFIG_FILE" "$@"
@@ -0,0 +1,32 @@
1
+ # Durable Streams server configuration.
2
+ # The server binary reads this on startup via bin/durable-streams.
3
+ #
4
+ # Power users: set DURABLE_STREAMS_CONFIG=/path/to/config to use a
5
+ # custom server config file directly, bypassing this YAML.
6
+
7
+ default: &default
8
+ port: 4437
9
+ # route: /v1/streams/*
10
+ auth:
11
+ url: http://localhost:3000
12
+ path: /durable_streams/auth/verify
13
+ copy_headers:
14
+ - Cookie
15
+
16
+ development:
17
+ <<: *default
18
+
19
+ test:
20
+ <<: *default
21
+
22
+ production:
23
+ <<: *default
24
+ domain: streams.example.com
25
+ auth:
26
+ url: https://app.example.com
27
+ path: /durable_streams/auth/verify
28
+ copy_headers:
29
+ - Cookie
30
+ - Authorization
31
+ storage:
32
+ data_dir: /var/data/durable-streams
@@ -0,0 +1 @@
1
+ DurableStreams.base_url = ENV.fetch("DURABLE_STREAMS_URL", "http://localhost:4437/v1/streams")
@@ -0,0 +1,42 @@
1
+ namespace :durable_streams do
2
+ desc "Download the Durable Streams server binary for the current platform"
3
+ task :download do
4
+ version = ENV.fetch("DURABLE_STREAMS_VERSION", "latest")
5
+ install_dir = Rails.root.join("bin", "dist")
6
+ bin_path = install_dir.join("durable-streams-server")
7
+
8
+ if bin_path.exist?
9
+ puts "Durable Streams server already installed at #{bin_path}"
10
+ next
11
+ end
12
+
13
+ os = RbConfig::CONFIG["host_os"].then { |s|
14
+ if s.match?(/darwin/i) then "darwin"
15
+ elsif s.match?(/linux/i) then "linux"
16
+ else abort "Unsupported OS: #{s}"
17
+ end
18
+ }
19
+
20
+ arch = RbConfig::CONFIG["host_cpu"].then { |s|
21
+ case s
22
+ when /x86_64|amd64/ then "amd64"
23
+ when /aarch64|arm64/ then "arm64"
24
+ else abort "Unsupported architecture: #{s}"
25
+ end
26
+ }
27
+
28
+ FileUtils.mkdir_p(install_dir)
29
+
30
+ if version == "latest"
31
+ url = "https://github.com/durable-streams/durable-streams/releases/latest/download/durable-streams-server_#{os}_#{arch}.tar.gz"
32
+ else
33
+ url = "https://github.com/durable-streams/durable-streams/releases/download/caddy-v#{version}/durable-streams-server_#{version}_#{os}_#{arch}.tar.gz"
34
+ end
35
+
36
+ puts "Downloading durable-streams-server #{version} (#{os}/#{arch})..."
37
+ system("curl", "-sL", url, "-o", "-", out: IO.popen(["tar", "xz", "-C", install_dir.to_s], "w").fileno) ||
38
+ abort("Download failed")
39
+ FileUtils.chmod(0755, bin_path)
40
+ puts "Installed to #{bin_path}"
41
+ end
42
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: durable_streams-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - tokimonki
@@ -27,38 +27,45 @@ dependencies:
27
27
  name: railties
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
- - - "~>"
31
- - !ruby/object:Gem::Version
32
- version: '7.1'
33
30
  - - ">="
34
31
  - !ruby/object:Gem::Version
35
- version: 7.1.0
32
+ version: '8.0'
36
33
  type: :runtime
37
34
  prerelease: false
38
35
  version_requirements: !ruby/object:Gem::Requirement
39
36
  requirements:
40
- - - "~>"
41
- - !ruby/object:Gem::Version
42
- version: '7.1'
43
37
  - - ">="
44
38
  - !ruby/object:Gem::Version
45
- version: 7.1.0
39
+ version: '8.0'
40
+ description: Stream State Protocol events from Active Record models with the same
41
+ DX as Turbo Broadcasts — declarative streaming, automatic callbacks, and async jobs
42
+ — but with offset-based resumability and persistent event logs over SSE.
46
43
  email: opensource@tokimonki.com
47
44
  executables: []
48
45
  extensions: []
49
46
  extra_rdoc_files: []
50
47
  files:
51
48
  - MIT-LICENSE
49
+ - README.md
52
50
  - Rakefile
51
+ - app/controllers/durable_streams/auth_controller.rb
53
52
  - app/jobs/durable_streams/broadcast_job.rb
54
53
  - app/models/concerns/durable_streams/broadcastable.rb
54
+ - config/routes.rb
55
55
  - lib/durable_streams-rails.rb
56
56
  - lib/durable_streams/broadcastable/test_helper.rb
57
57
  - lib/durable_streams/broadcasts.rb
58
58
  - lib/durable_streams/engine.rb
59
+ - lib/durable_streams/server_config.rb
59
60
  - lib/durable_streams/stream_name.rb
60
61
  - lib/durable_streams/testing.rb
61
62
  - lib/durable_streams/version.rb
63
+ - lib/generators/durable_streams/install/USAGE
64
+ - lib/generators/durable_streams/install/install_generator.rb
65
+ - lib/generators/durable_streams/install/templates/bin/durable-streams.tt
66
+ - lib/generators/durable_streams/install/templates/durable_streams.yml.tt
67
+ - lib/generators/durable_streams/install/templates/initializer.rb.tt
68
+ - lib/tasks/durable_streams.rake
62
69
  homepage: https://github.com/tokimonki/durable_streams-rails
63
70
  licenses:
64
71
  - MIT
@@ -70,14 +77,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
70
77
  requirements:
71
78
  - - ">="
72
79
  - !ruby/object:Gem::Version
73
- version: '3.1'
80
+ version: '3.2'
74
81
  required_rubygems_version: !ruby/object:Gem::Requirement
75
82
  requirements:
76
83
  - - ">="
77
84
  - !ruby/object:Gem::Version
78
85
  version: '0'
79
86
  requirements: []
80
- rubygems_version: 3.6.9
87
+ rubygems_version: 4.0.6
81
88
  specification_version: 4
82
89
  summary: Durable Streams integration for Rails
83
90
  test_files: []