durable_streams-rails 0.3.0 → 0.4.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 +4 -4
- data/README.md +59 -0
- data/Rakefile +1 -1
- data/app/controllers/durable_streams/rails/auth_controller.rb +19 -4
- data/app/models/concerns/durable_streams/rails/broadcastable.rb +17 -1
- data/lib/durable_streams/rails/broadcasts.rb +31 -3
- data/lib/durable_streams/rails/server_config.rb +14 -0
- data/lib/durable_streams/rails/stream_name.rb +20 -4
- data/lib/durable_streams/rails/stream_provisioner.rb +31 -0
- data/lib/durable_streams/rails/version.rb +1 -1
- data/lib/durable_streams-rails.rb +12 -16
- data/lib/generators/durable_streams/install/templates/bin/durable-streams.tt +2 -2
- data/lib/tasks/durable_streams.rake +10 -10
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9fca76d28cee5e469a38766c7bec549acc60f5a6eddf5b840950ce96329dc31e
|
|
4
|
+
data.tar.gz: 95b45e16b2a3d40024a71c7281b5f17da3009163145b54148dceb493db919b09
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fc8227f3d31a0bbc46028c32f23c3cbdd29e5b09aa7a3ad378b69972fb25a1ed8b5e08b84020918dbdcb071a41329dc7f052ce6cc590f7cc112e7355156d9e39
|
|
7
|
+
data.tar.gz: f8cd42dc4ec64c26415e9793646d84786e104adcfcfb292c82d5662b181a280668ea13408033742649453049e94453351fbe42a9436224ebad5262943380a9f6
|
data/README.md
CHANGED
|
@@ -265,6 +265,65 @@ This gem broadcasts JSON State Protocol events over HTTP/SSE (Durable Streams).
|
|
|
265
265
|
- [`durable_streams`](https://github.com/tokimonki/durable_streams) — Ruby client for the Durable Streams protocol (resolved automatically via RubyGems)
|
|
266
266
|
- `railties` >= 8.0
|
|
267
267
|
|
|
268
|
+
## Protocol Coverage
|
|
269
|
+
|
|
270
|
+
The [Durable Streams protocol](https://github.com/durable-streams/durable-streams/blob/main/PROTOCOL.md)
|
|
271
|
+
defines 8 operations on streams. This gem handles the **server side** — broadcasting events,
|
|
272
|
+
provisioning streams, and authorizing client access via signed URLs. Client-side reads and
|
|
273
|
+
writes are handled by the official
|
|
274
|
+
[`@durable-streams/client`](https://github.com/durable-streams/durable-streams) JavaScript
|
|
275
|
+
package, not this gem.
|
|
276
|
+
|
|
277
|
+
### Operations
|
|
278
|
+
|
|
279
|
+
| # | Operation | HTTP | This gem's role |
|
|
280
|
+
|---|---|---|---|
|
|
281
|
+
| 1 | Create | PUT | `StreamProvisioner` — automatic on first use |
|
|
282
|
+
| 2 | Append | POST | `Broadcasts` — `broadcast_event_to`, `broadcast_to` |
|
|
283
|
+
| 3 | Read (catch-up) | GET | Authorization only — `signed_stream_url` |
|
|
284
|
+
| 4 | Read (long-poll) | GET + `live=long-poll` | Authorization only — `signed_stream_url` |
|
|
285
|
+
| 5 | Read (SSE) | GET + `live=sse` | Authorization only — `signed_stream_url` |
|
|
286
|
+
| 6 | Close | POST + `Stream-Closed` | Not yet implemented |
|
|
287
|
+
| 7 | Delete | DELETE | Not yet implemented |
|
|
288
|
+
| 8 | Metadata | HEAD | Not yet implemented |
|
|
289
|
+
|
|
290
|
+
`signed_stream_url` generates a signed, expiring URL and provisions the stream. The client
|
|
291
|
+
uses this URL with `@durable-streams/client` and chooses the read mode via query parameters —
|
|
292
|
+
the gem doesn't need to know which mode the client will use.
|
|
293
|
+
|
|
294
|
+
### Access control
|
|
295
|
+
|
|
296
|
+
Only the server may create streams. The server authenticates with the Durable Streams server
|
|
297
|
+
via API key; clients authenticate via signed, expiring tokens generated by this gem.
|
|
298
|
+
|
|
299
|
+
| | Create | Append | Read | Close | Delete | Metadata |
|
|
300
|
+
|---|---|---|---|---|---|---|
|
|
301
|
+
| **Server** (this gem) | Yes | Yes | Planned | Planned | Planned | Planned |
|
|
302
|
+
| **Client** (JS package) | No | Planned | Yes | No | No | Planned |
|
|
303
|
+
|
|
304
|
+
**Yes** = this gem provides the implementation or authorization today.
|
|
305
|
+
**Planned** = permitted by design, not yet implemented.
|
|
306
|
+
**No** = not permitted (security decision).
|
|
307
|
+
|
|
308
|
+
Client append (planned) enables use cases like live cursors and typing indicators where
|
|
309
|
+
the write payload goes directly to the stream server rather than through a Rails controller.
|
|
310
|
+
The Durable Streams server still performs forward_auth to Rails on every write to verify
|
|
311
|
+
the signed token, but this is lightweight — pure HMAC verification with no database or
|
|
312
|
+
session loading. For reads (SSE), forward_auth happens once at connection time.
|
|
313
|
+
|
|
314
|
+
### Stream provisioning
|
|
315
|
+
|
|
316
|
+
The Durable Streams protocol requires explicit stream creation (PUT) before any read or write —
|
|
317
|
+
unlike Action Cable which auto-creates channels on subscribe. `StreamProvisioner` hides this
|
|
318
|
+
requirement from application code:
|
|
319
|
+
|
|
320
|
+
- `signed_stream_url` calls `ensure_stream_exists` before generating the URL
|
|
321
|
+
- `append_to_stream` calls `ensure_stream_exists` before appending
|
|
322
|
+
- A `Concurrent::Set` cache ensures the PUT only fires once per stream name per process
|
|
323
|
+
|
|
324
|
+
Whichever path touches a stream first provisions it. The application developer never thinks
|
|
325
|
+
about stream creation.
|
|
326
|
+
|
|
268
327
|
## Future
|
|
269
328
|
|
|
270
329
|
Brewing secretly — an async-backed durable streams server inside Rails.
|
data/Rakefile
CHANGED
|
@@ -2,8 +2,8 @@ module DurableStreams
|
|
|
2
2
|
module Rails
|
|
3
3
|
class AuthController < ActionController::API
|
|
4
4
|
def verify
|
|
5
|
-
if request.headers["X-Forwarded-Uri"]
|
|
6
|
-
|
|
5
|
+
if request.headers["X-Forwarded-Uri"] && authorized_by_token?
|
|
6
|
+
head :ok
|
|
7
7
|
elsif valid_server_api_key?
|
|
8
8
|
head :ok
|
|
9
9
|
else
|
|
@@ -12,8 +12,23 @@ module DurableStreams
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
private
|
|
15
|
-
def
|
|
16
|
-
DurableStreams.verify_signed_url(request.headers["X-Forwarded-Uri"])
|
|
15
|
+
def authorized_by_token?
|
|
16
|
+
if payload = DurableStreams.verify_signed_url(request.headers["X-Forwarded-Uri"])
|
|
17
|
+
permissions = Array(payload["permissions"]) & DurableStreams::Rails::StreamName::PERMISSIONS
|
|
18
|
+
permitted_method?(permissions)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Only GET and POST reach token auth. PUT and DELETE are server-only
|
|
23
|
+
# operations authenticated via API key (valid_server_api_key?), not tokens.
|
|
24
|
+
# See README.md "Access control" for the full matrix.
|
|
25
|
+
def permitted_method?(permissions)
|
|
26
|
+
case request.headers["X-Forwarded-Method"]&.upcase
|
|
27
|
+
when "POST"
|
|
28
|
+
permissions.include?("write")
|
|
29
|
+
else
|
|
30
|
+
permissions.include?("read")
|
|
31
|
+
end
|
|
17
32
|
end
|
|
18
33
|
|
|
19
34
|
def valid_server_api_key?
|
|
@@ -196,7 +196,23 @@ module DurableStreams::Rails::Broadcastable
|
|
|
196
196
|
end
|
|
197
197
|
|
|
198
198
|
private
|
|
199
|
-
def stream_event_attributes(operation:, value:
|
|
199
|
+
def stream_event_attributes(operation:, value: to_stream_value)
|
|
200
200
|
{ type: model_name.singular, key: id.to_s, value: value, operation: operation }
|
|
201
201
|
end
|
|
202
|
+
|
|
203
|
+
# Returns the value used in State Protocol broadcast events. Override this in your model
|
|
204
|
+
# to customize the serialized payload — for example, to use an Alba resource or any other
|
|
205
|
+
# serializer instead of the default +as_json+.
|
|
206
|
+
#
|
|
207
|
+
# class Message < ApplicationRecord
|
|
208
|
+
# streams_to :room
|
|
209
|
+
#
|
|
210
|
+
# private
|
|
211
|
+
# def to_stream_value
|
|
212
|
+
# MessageResource.new(self).to_h
|
|
213
|
+
# end
|
|
214
|
+
# end
|
|
215
|
+
def to_stream_value
|
|
216
|
+
as_json
|
|
217
|
+
end
|
|
202
218
|
end
|
|
@@ -2,9 +2,29 @@
|
|
|
2
2
|
# See <tt>DurableStreams::Rails::Broadcastable</tt> for the user-facing API that invokes these methods with most of the
|
|
3
3
|
# paperwork filled out already.
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
5
|
+
# All broadcasts produce JSON. Two flavors are supported:
|
|
6
|
+
#
|
|
7
|
+
# ==== State Protocol events
|
|
8
|
+
#
|
|
9
|
+
# <tt>broadcast_event_to</tt> and <tt>broadcast_event_later_to</tt> produce State Protocol events —
|
|
10
|
+
# the keyed insert/update/delete format consumed by <tt>@durable-streams/state</tt> and <tt>@tanstack/db</tt>
|
|
11
|
+
# for reactive collections on the client:
|
|
12
|
+
#
|
|
13
|
+
# DurableStreams.broadcast_event_to :room, type: "message", key: "5",
|
|
14
|
+
# value: { content: "Hello" }, operation: :insert
|
|
15
|
+
#
|
|
16
|
+
# ==== Arbitrary JSON
|
|
17
|
+
#
|
|
18
|
+
# <tt>broadcast_to</tt> sends free-form JSON with no State Protocol structure:
|
|
19
|
+
#
|
|
20
|
+
# DurableStreams.broadcast_to :room, event: "typing", user_id: 5
|
|
21
|
+
#
|
|
22
|
+
# Although the underlying Durable Streams server supports any content type (bytes, text, etc.),
|
|
23
|
+
# this gem only creates <tt>application/json</tt> streams. Non-JSON broadcasts would require
|
|
24
|
+
# extending <tt>append_to_stream</tt> to accept a configurable content type.
|
|
7
25
|
module DurableStreams::Rails::Broadcasts
|
|
26
|
+
# Broadcast a State Protocol event to one or more streamables. Returns the generated +txid+
|
|
27
|
+
# for optimistic update confirmation on the client, or +nil+ if streamables are blank.
|
|
8
28
|
def broadcast_event_to(*streamables, type:, key:, value:, operation:)
|
|
9
29
|
streamables.flatten!
|
|
10
30
|
streamables.compact_blank!
|
|
@@ -45,11 +65,19 @@ module DurableStreams::Rails::Broadcasts
|
|
|
45
65
|
end
|
|
46
66
|
|
|
47
67
|
private
|
|
68
|
+
# Single transport point for all broadcasts. Testing intercepts here.
|
|
69
|
+
#
|
|
70
|
+
# Content type is hardcoded to <tt>application/json</tt> because every public method in this
|
|
71
|
+
# module produces JSON (State Protocol events via +broadcast_event_to+, free-form JSON via
|
|
72
|
+
# +broadcast_to+). This ensures the POST body is correctly wrapped in a JSON array for the
|
|
73
|
+
# Durable Streams server. If non-JSON broadcast support is needed in the future, this method
|
|
74
|
+
# should accept a content type parameter.
|
|
48
75
|
def append_to_stream(stream_name, message)
|
|
49
76
|
if DurableStreams::Rails::Testing.recording?
|
|
50
77
|
DurableStreams::Rails::Testing.record(stream_name, message)
|
|
51
78
|
else
|
|
52
|
-
|
|
79
|
+
ensure_stream_exists(stream_name)
|
|
80
|
+
DurableStreams::Stream.new(stream_name, context: DurableStreams.default_context, content_type: "application/json").append(message)
|
|
53
81
|
end
|
|
54
82
|
end
|
|
55
83
|
end
|
|
@@ -13,6 +13,12 @@ module DurableStreams
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
<%= address %> {
|
|
16
|
+
log {
|
|
17
|
+
output stderr
|
|
18
|
+
format <%= log_format %>
|
|
19
|
+
level <%= log_level %>
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
route <%= route_path %> {
|
|
17
23
|
forward_auth <%= auth["url"] %> {
|
|
18
24
|
uri <%= auth["path"] %>
|
|
@@ -54,6 +60,14 @@ module DurableStreams
|
|
|
54
60
|
auth.fetch("copy_headers", [ "Cookie" ])
|
|
55
61
|
end
|
|
56
62
|
|
|
63
|
+
def log_format
|
|
64
|
+
@config.fetch("log_format", "console")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def log_level
|
|
68
|
+
@config.fetch("log_level", "INFO")
|
|
69
|
+
end
|
|
70
|
+
|
|
57
71
|
def storage_block
|
|
58
72
|
if dir = @config.dig("storage", "data_dir")
|
|
59
73
|
" {\n\t\t\tdata_dir #{dir}\n\t\t}"
|
|
@@ -2,15 +2,31 @@
|
|
|
2
2
|
# are exposed directly to the client via signed URL tokens, we need to ensure that the name isn't
|
|
3
3
|
# tampered with, so the names are signed upon generation and verified upon receipt. All verification
|
|
4
4
|
# happens through the <tt>DurableStreams.signed_stream_verifier</tt>.
|
|
5
|
+
#
|
|
6
|
+
# Tokens embed a permissions array (<tt>["read"]</tt>, <tt>["write"]</tt>, or
|
|
7
|
+
# <tt>["read", "write"]</tt>) that determines what operations the client can perform.
|
|
8
|
+
# The auth controller checks the permissions against the HTTP method of the incoming request.
|
|
5
9
|
module DurableStreams::Rails::StreamName
|
|
6
|
-
|
|
10
|
+
PERMISSIONS = %w[ read write ].freeze
|
|
11
|
+
|
|
12
|
+
# Verifies a signed token and returns the payload hash.
|
|
13
|
+
# Returns +nil+ if the token is invalid or expired.
|
|
14
|
+
#
|
|
15
|
+
# DurableStreams.verified_stream_name(token)
|
|
16
|
+
# # => { "stream" => "gid://app/Room/1/messages", "permissions" => ["read"] }
|
|
7
17
|
def verified_stream_name(signed_stream_name)
|
|
8
18
|
DurableStreams.signed_stream_verifier.verified signed_stream_name
|
|
9
19
|
end
|
|
10
20
|
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
21
|
+
# Generates a signed token embedding the stream name and permissions.
|
|
22
|
+
#
|
|
23
|
+
# Tokens generated without +expires_in+ never expire. Prefer
|
|
24
|
+
# <tt>DurableStreams.signed_stream_url</tt> for client-facing URLs.
|
|
25
|
+
def signed_stream_name(streamables, permissions: [ "read" ], expires_in: nil)
|
|
26
|
+
DurableStreams.signed_stream_verifier.generate(
|
|
27
|
+
{ "stream" => stream_name_from(streamables), "permissions" => permissions.map(&:to_s) },
|
|
28
|
+
expires_in: expires_in
|
|
29
|
+
)
|
|
14
30
|
end
|
|
15
31
|
|
|
16
32
|
private
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require "concurrent/set"
|
|
2
|
+
|
|
3
|
+
# Ensures streams exist on the Durable Streams server before they are used.
|
|
4
|
+
#
|
|
5
|
+
# The Durable Streams protocol requires explicit stream creation (PUT) before any read (GET/SSE)
|
|
6
|
+
# or write (POST) operation — unlike Action Cable which auto-creates channels on subscribe.
|
|
7
|
+
# This module hides that requirement from application code.
|
|
8
|
+
#
|
|
9
|
+
# Both the signing path (<tt>signed_stream_url</tt>) and the broadcast path
|
|
10
|
+
# (<tt>append_to_stream</tt>) call <tt>ensure_stream_exists</tt>. A thread-safe
|
|
11
|
+
# <tt>Concurrent::Set</tt> cache ensures the PUT only fires once per stream name per process,
|
|
12
|
+
# regardless of which path touches the stream first.
|
|
13
|
+
module DurableStreams::Rails::StreamProvisioner
|
|
14
|
+
private
|
|
15
|
+
# Creates the stream on the server if it hasn't been created by this process yet.
|
|
16
|
+
# Idempotent — a 409 means the stream already exists.
|
|
17
|
+
def ensure_stream_exists(stream_name)
|
|
18
|
+
return if DurableStreams::Rails::Testing.recording?
|
|
19
|
+
return if known_streams.include?(stream_name)
|
|
20
|
+
|
|
21
|
+
DurableStreams::Stream.new(stream_name, context: DurableStreams.default_context)
|
|
22
|
+
.create_stream(content_type: "application/json")
|
|
23
|
+
known_streams.add(stream_name)
|
|
24
|
+
rescue DurableStreams::StreamExistsError
|
|
25
|
+
known_streams.add(stream_name)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def known_streams
|
|
29
|
+
@known_streams ||= Concurrent::Set.new
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
require "durable_streams"
|
|
2
|
+
require "durable_streams/rails/engine"
|
|
2
3
|
require "durable_streams/rails/stream_name"
|
|
4
|
+
require "durable_streams/rails/stream_provisioner"
|
|
3
5
|
require "durable_streams/rails/broadcasts"
|
|
4
6
|
require "durable_streams/rails/testing"
|
|
5
|
-
require "durable_streams/rails/engine"
|
|
6
7
|
require "active_support/core_ext/module/attribute_accessors_per_thread"
|
|
7
8
|
|
|
8
9
|
module DurableStreams
|
|
9
10
|
extend ActiveSupport::Autoload
|
|
10
11
|
extend DurableStreams::Rails::StreamName
|
|
12
|
+
extend DurableStreams::Rails::StreamProvisioner
|
|
11
13
|
extend DurableStreams::Rails::Broadcasts
|
|
12
14
|
|
|
13
15
|
mattr_accessor :base_url
|
|
14
16
|
mattr_accessor :draw_routes, default: true
|
|
17
|
+
mattr_accessor :signed_stream_url_expires_in, default: 24.hours
|
|
15
18
|
|
|
16
19
|
class << self
|
|
17
20
|
attr_writer :signed_stream_verifier_key, :server_api_key
|
|
@@ -28,15 +31,21 @@ module DurableStreams
|
|
|
28
31
|
@server_api_key or raise ArgumentError, "DurableStreams requires a server_api_key"
|
|
29
32
|
end
|
|
30
33
|
|
|
31
|
-
def signed_stream_url(*streamables, expires_in:
|
|
34
|
+
def signed_stream_url(*streamables, permissions: [ :read ], expires_in: DurableStreams.signed_stream_url_expires_in)
|
|
32
35
|
path = stream_name_from(streamables)
|
|
33
36
|
ensure_stream_exists(path)
|
|
34
|
-
token = signed_stream_verifier.generate(
|
|
37
|
+
token = signed_stream_verifier.generate(
|
|
38
|
+
{ "stream" => path, "permissions" => permissions.map(&:to_s) },
|
|
39
|
+
expires_in: expires_in
|
|
40
|
+
)
|
|
35
41
|
"#{base_url}/#{path}?token=#{token}"
|
|
36
42
|
end
|
|
37
43
|
|
|
38
44
|
# Verifies the signed token embedded in a stream URL. Used by the auth controller
|
|
39
45
|
# to validate forward_auth requests from the Durable Streams server.
|
|
46
|
+
#
|
|
47
|
+
# Returns a hash with <tt>"stream"</tt> and <tt>"permissions"</tt> keys, or +nil+
|
|
48
|
+
# if the token is invalid or expired.
|
|
40
49
|
def verify_signed_url(url)
|
|
41
50
|
if token = extract_token_from_url(url)
|
|
42
51
|
verified_stream_name(token)
|
|
@@ -48,18 +57,5 @@ module DurableStreams
|
|
|
48
57
|
Rack::Utils.parse_query(query)["token"]
|
|
49
58
|
end
|
|
50
59
|
end
|
|
51
|
-
|
|
52
|
-
private
|
|
53
|
-
# Creates the stream on the server if it doesn't already exist. Called automatically
|
|
54
|
-
# by +signed_stream_url+ so clients can subscribe immediately without hitting a 404.
|
|
55
|
-
# Idempotent — the server returns 200 when the stream already exists with matching config.
|
|
56
|
-
# Skipped during tests (no server) and when base_url is not configured.
|
|
57
|
-
def ensure_stream_exists(stream_name)
|
|
58
|
-
return if DurableStreams::Rails::Testing.recording?
|
|
59
|
-
|
|
60
|
-
stream(stream_name).create_stream(content_type: "application/json")
|
|
61
|
-
rescue DurableStreams::StreamExistsError
|
|
62
|
-
# Already exists with different config — still usable
|
|
63
|
-
end
|
|
64
60
|
end
|
|
65
61
|
end
|
|
@@ -20,8 +20,8 @@ RAILS_ENV="${RAILS_ENV:-development}"
|
|
|
20
20
|
CONFIG_FILE="${APP_ROOT}/tmp/durable_streams.caddyfile"
|
|
21
21
|
|
|
22
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'])" \
|
|
23
|
+
APP_ROOT="$APP_ROOT" RAILS_ENV="$RAILS_ENV" bundle exec ruby -r durable_streams/rails/server_config \
|
|
24
|
+
-e "puts DurableStreams::Rails::ServerConfig.generate(File.join(ENV['APP_ROOT'], 'config/durable_streams.yml'), ENV['RAILS_ENV'])" \
|
|
25
25
|
> "$CONFIG_FILE"
|
|
26
26
|
|
|
27
27
|
exec "$BIN_PATH" run --config "$CONFIG_FILE" "$@"
|
|
@@ -4,6 +4,15 @@ namespace :durable_streams do
|
|
|
4
4
|
require "net/http"
|
|
5
5
|
require "json"
|
|
6
6
|
|
|
7
|
+
resolve_latest_version = lambda do
|
|
8
|
+
uri = URI("https://api.github.com/repos/durable-streams/durable-streams/releases/latest")
|
|
9
|
+
response = Net::HTTP.get(uri)
|
|
10
|
+
tag = JSON.parse(response).fetch("tag_name")
|
|
11
|
+
tag.delete_prefix("v")
|
|
12
|
+
rescue => e
|
|
13
|
+
abort "Failed to resolve latest version: #{e.message}"
|
|
14
|
+
end
|
|
15
|
+
|
|
7
16
|
version = ENV.fetch("DURABLE_STREAMS_VERSION", "latest")
|
|
8
17
|
install_dir = Rails.root.join("bin", "dist")
|
|
9
18
|
bin_path = install_dir.join("durable-streams-server")
|
|
@@ -29,7 +38,7 @@ namespace :durable_streams do
|
|
|
29
38
|
}
|
|
30
39
|
|
|
31
40
|
if version == "latest"
|
|
32
|
-
version = resolve_latest_version
|
|
41
|
+
version = resolve_latest_version.call
|
|
33
42
|
end
|
|
34
43
|
|
|
35
44
|
FileUtils.mkdir_p(install_dir)
|
|
@@ -43,12 +52,3 @@ namespace :durable_streams do
|
|
|
43
52
|
puts "Installed to #{bin_path}"
|
|
44
53
|
end
|
|
45
54
|
end
|
|
46
|
-
|
|
47
|
-
def resolve_latest_version
|
|
48
|
-
uri = URI("https://api.github.com/repos/durable-streams/durable-streams/releases/latest")
|
|
49
|
-
response = Net::HTTP.get(uri)
|
|
50
|
-
tag = JSON.parse(response).fetch("tag_name")
|
|
51
|
-
tag.delete_prefix("v")
|
|
52
|
-
rescue => e
|
|
53
|
-
abort "Failed to resolve latest version: #{e.message}"
|
|
54
|
-
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.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- tokimonki
|
|
@@ -58,6 +58,7 @@ files:
|
|
|
58
58
|
- lib/durable_streams/rails/engine.rb
|
|
59
59
|
- lib/durable_streams/rails/server_config.rb
|
|
60
60
|
- lib/durable_streams/rails/stream_name.rb
|
|
61
|
+
- lib/durable_streams/rails/stream_provisioner.rb
|
|
61
62
|
- lib/durable_streams/rails/testing.rb
|
|
62
63
|
- lib/durable_streams/rails/version.rb
|
|
63
64
|
- lib/generators/durable_streams/install/USAGE
|