firehose-rb 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: ef4dac25083ab9471245c47df41233e04e19e12282153e43e630603195d7e1cc
4
- data.tar.gz: 36db7c5b6534a76cbdd88158792fc614524ce7fa3ccce0ed396eb608fa478484
3
+ metadata.gz: 6fa8dc1d77ce7ec26e1db82f13a26094970b1294388bf2e0c0b4c7aa266f9c5d
4
+ data.tar.gz: b2f8784af1ad95c67cb5467607ba546ad6bd183a243367b124576865705eb398
5
5
  SHA512:
6
- metadata.gz: 9335a0468cb38b470b67edde432d05e6b96ac3e6209c2b66a51667754f9888eeccb9f8bf8944dcb91c175f15dbe8528343f01bc3c2e93429db55ccbb5003a2ac
7
- data.tar.gz: cec5635b29ca8b3a592fc79b4333baf6e1ae472f47bf2858d0cd11e17849ebe5b55233cae584f87fae1039b8a6c8d282dd7561b8a66cfb5d962a8fcb8a974dcc
6
+ metadata.gz: 58c1f5933095b7f2ab7a82c3559643241398aa3524e477340f0a62199b9a4478229fccdc66d928570c59e4f054082adb25775bbbd313fc0725dccafdc8996304
7
+ data.tar.gz: 7bf960e0a49fb20b142126bafcbc538565e5e1fa91bc170e38994835bbd0d550c07c35d7b38f8b106452c0b243b31742ac1021aaedabf8941dbba6d78c6cd8c7
data/README.md CHANGED
@@ -1,27 +1,33 @@
1
1
  # firehose-rb
2
2
 
3
- Ruby client for the Firehose real-time web monitoring API. SSE streaming with auto-reconnect, rules CRUD, and offset tracking.
3
+ [![Gem Version](https://badge.fury.io/rb/firehose-rb.svg)](https://rubygems.org/gems/firehose-rb)
4
+
5
+ Ruby client for the [Firehose](https://firehose.dev) real-time web monitoring API. Define rules, stream matching pages as they're discovered, and build content pipelines on top of the live web.
4
6
 
5
7
  ## Installation
6
8
 
7
9
  ```ruby
8
- gem "firehose-rb", path: "../firehose-rb"
10
+ gem "firehose-rb", "~> 0.1"
9
11
  ```
10
12
 
13
+ Then `bundle install`.
14
+
11
15
  ## Configuration
12
16
 
13
17
  ```ruby
14
18
  Firehose.configure do |c|
15
- c.management_key = "fhm_..."
16
- c.tap_token = "fh_..."
17
- c.base_url = "https://api.firehose.dev" # default
18
- c.timeout = 300 # SSE timeout in seconds
19
+ c.management_key = ENV["FIREHOSE_MANAGEMENT_KEY"] # fhm_...
20
+ c.tap_token = ENV["FIREHOSE_TAP_TOKEN"] # fh_...
21
+ c.base_url = "https://api.firehose.dev" # default
22
+ c.timeout = 300 # SSE timeout in seconds
19
23
  end
20
24
  ```
21
25
 
22
26
  ## Usage
23
27
 
24
- ### Rules CRUD
28
+ ### Rules
29
+
30
+ Rules tell Firehose what to watch for. They use Lucene query syntax.
25
31
 
26
32
  ```ruby
27
33
  client = Firehose.client
@@ -33,7 +39,7 @@ rule = client.create_rule(
33
39
  quality: true
34
40
  )
35
41
 
36
- # List rules
42
+ # List all rules
37
43
  rules = client.list_rules
38
44
 
39
45
  # Delete a rule
@@ -42,38 +48,65 @@ client.delete_rule(rule.id)
42
48
 
43
49
  ### Streaming
44
50
 
51
+ Connect to the SSE stream and process matching pages in real time.
52
+
45
53
  ```ruby
46
54
  client = Firehose.client
47
55
 
48
- # Track offsets for resume
56
+ # Persist offsets so you can resume after restart
49
57
  client.on_offset { |offset| save_offset(offset) }
50
58
 
51
59
  # Stream events (auto-reconnects with exponential backoff)
52
60
  client.stream(since: "1h") do |event|
53
- event.id # String
54
- event.document.url # String
55
- event.document.title # String
56
- event.document.markdown # String (full page content)
57
- event.matched_rule # String (tag)
58
- event.matched_at # Time
61
+ event.id # String — unique event ID
62
+ event.document.url # String — page URL
63
+ event.document.title # String — page title
64
+ event.document.markdown # String full page content as markdown
65
+ event.document.categories # Array — page categories
66
+ event.document.types # Array — page types (article, blog, etc.)
67
+ event.document.language # String — detected language
68
+ event.document.publish_time # Time — when the page was published
69
+ event.matched_rule # String — which rule tag matched
70
+ event.matched_at # Time — when the match occurred
59
71
  end
60
72
 
61
- # Stop streaming
73
+ # Stop streaming gracefully
62
74
  client.stop_stream
63
75
  ```
64
76
 
77
+ ### Resilience
78
+
79
+ - Auto-reconnect with exponential backoff (1s, 2s, 4s, ... max 30s)
80
+ - `Last-Event-ID` header sent on reconnect for automatic resume
81
+ - `on_offset` callback for persisting stream position
82
+ - Authentication errors (`401/403`) are not retried
83
+
65
84
  ## Data Structures
66
85
 
67
- - `Firehose::Rule` id, value, tag, quality, nsfw
68
- - `Firehose::Event` — id, document, matched_rule, matched_at
69
- - `Firehose::Document` url, title, markdown, categories, types, language, publish_time
86
+ | Struct | Fields |
87
+ |--------|--------|
88
+ | `Firehose::Rule` | id, value, tag, quality, nsfw |
89
+ | `Firehose::Event` | id, document, matched_rule, matched_at |
90
+ | `Firehose::Document` | url, title, markdown, categories, types, language, publish_time |
91
+
92
+ ## Errors
93
+
94
+ | Error | Cause |
95
+ |-------|-------|
96
+ | `Firehose::AuthenticationError` | Invalid management_key or tap_token |
97
+ | `Firehose::RateLimitError` | Too many requests (429) |
98
+ | `Firehose::ConnectionError` | Network or HTTP errors |
99
+ | `Firehose::TimeoutError` | Stream or request timeout |
100
+
101
+ ## Requirements
102
+
103
+ - Ruby >= 3.1
104
+ - [Faraday](https://github.com/lostisland/faraday) ~> 2.0
105
+
106
+ ## Used by
70
107
 
71
- ## Error Handling
108
+ Built for [InventList](https://inventlist.com) — a home for indie builders that turns the live web into weekly signals for makers and their agents.
72
109
 
73
- - `Firehose::AuthenticationError` — invalid API keys
74
- - `Firehose::RateLimitError` — rate limited
75
- - `Firehose::ConnectionError` — connection failures
76
- - `Firehose::TimeoutError` — request timeout
110
+ ## License
77
111
 
78
- SSE streaming auto-reconnects with exponential backoff (1s → 2s → 4s → max 30s).
79
- Authentication errors are not retried.
112
+ MIT
@@ -10,28 +10,83 @@ module Firehose
10
10
  @stream = Stream.new(config: config)
11
11
  end
12
12
 
13
- # Rules CRUD
13
+ # Tap management (requires management key)
14
14
 
15
- def create_rule(value:, tag: nil, quality: false, nsfw: false)
16
- body = { value: value, tag: tag, quality: quality, nsfw: nsfw }.compact
17
- response = management_connection.post("/rules", body.to_json)
15
+ def list_taps
16
+ response = management_connection.get("/v1/taps")
17
+ handle_response(response)
18
+ parsed = JSON.parse(response.body)
19
+ Array(parsed["data"]).map { |t| Tap.from_hash(t) }
20
+ end
21
+
22
+ def create_tap(name:)
23
+ response = management_connection.post("/v1/taps", { name: name }.to_json)
24
+ handle_response(response)
25
+ parsed = JSON.parse(response.body)
26
+ tap_data = parsed["data"] || parsed
27
+ tap_data["token"] = parsed["token"] if parsed["token"]
28
+ Tap.from_hash(tap_data)
29
+ end
30
+
31
+ def get_tap(tap_id)
32
+ response = management_connection.get("/v1/taps/#{tap_id}")
33
+ handle_response(response)
34
+ parsed = JSON.parse(response.body)
35
+ Tap.from_hash(parsed["data"] || parsed)
36
+ end
37
+
38
+ def update_tap(tap_id, name:)
39
+ response = management_connection.put("/v1/taps/#{tap_id}", { name: name }.to_json)
40
+ handle_response(response)
41
+ parsed = JSON.parse(response.body)
42
+ Tap.from_hash(parsed["data"] || parsed)
43
+ end
44
+
45
+ def revoke_tap(tap_id)
46
+ response = management_connection.delete("/v1/taps/#{tap_id}")
18
47
  handle_response(response)
19
- Rule.from_hash(JSON.parse(response.body))
48
+ true
20
49
  end
21
50
 
51
+ # Rules CRUD (requires tap token)
52
+
22
53
  def list_rules
23
- response = management_connection.get("/rules")
54
+ response = tap_connection.get("/v1/rules")
24
55
  handle_response(response)
25
- JSON.parse(response.body).map { |r| Rule.from_hash(r) }
56
+ parsed = JSON.parse(response.body)
57
+ Array(parsed["data"] || parsed).map { |r| Rule.from_hash(r) }
58
+ end
59
+
60
+ def create_rule(value:, tag: nil, quality: true, nsfw: false)
61
+ body = { value: value, tag: tag, quality: quality, nsfw: nsfw }.compact
62
+ response = tap_connection.post("/v1/rules", body.to_json)
63
+ handle_response(response)
64
+ parsed = JSON.parse(response.body)
65
+ Rule.from_hash(parsed["data"] || parsed)
66
+ end
67
+
68
+ def get_rule(rule_id)
69
+ response = tap_connection.get("/v1/rules/#{rule_id}")
70
+ handle_response(response)
71
+ parsed = JSON.parse(response.body)
72
+ Rule.from_hash(parsed["data"] || parsed)
73
+ end
74
+
75
+ def update_rule(rule_id, value: nil, tag: nil, quality: nil, nsfw: nil)
76
+ body = { value: value, tag: tag, quality: quality, nsfw: nsfw }.compact
77
+ response = tap_connection.put("/v1/rules/#{rule_id}", body.to_json)
78
+ handle_response(response)
79
+ parsed = JSON.parse(response.body)
80
+ Rule.from_hash(parsed["data"] || parsed)
26
81
  end
27
82
 
28
83
  def delete_rule(rule_id)
29
- response = management_connection.delete("/rules/#{rule_id}")
84
+ response = tap_connection.delete("/v1/rules/#{rule_id}")
30
85
  handle_response(response)
31
86
  true
32
87
  end
33
88
 
34
- # Streaming
89
+ # Streaming (requires tap token)
35
90
 
36
91
  def stream(since: nil, &block)
37
92
  raise ArgumentError, "block required" unless block_given?
@@ -57,10 +112,18 @@ module Firehose
57
112
  end
58
113
  end
59
114
 
115
+ def tap_connection
116
+ @tap_connection ||= Faraday.new(url: @config.base_url) do |f|
117
+ f.headers["Authorization"] = "Bearer #{@config.tap_token}"
118
+ f.headers["Content-Type"] = "application/json"
119
+ f.adapter Faraday.default_adapter
120
+ end
121
+ end
122
+
60
123
  def handle_response(response)
61
124
  case response.status
62
125
  when 200..299 then nil
63
- when 401, 403 then raise AuthenticationError, "Invalid management key"
126
+ when 401, 403 then raise AuthenticationError, "Invalid API key"
64
127
  when 429 then raise RateLimitError, "Rate limited"
65
128
  else raise Error, "HTTP #{response.status}: #{response.body}"
66
129
  end
@@ -77,7 +77,7 @@ module Firehose
77
77
  end
78
78
 
79
79
  def build_uri(since: nil)
80
- uri = URI.join(@config.base_url, "/stream")
80
+ uri = URI.join(@config.base_url, "/v1/stream")
81
81
  params = {}
82
82
  params["since"] = since if since
83
83
  uri.query = URI.encode_www_form(params) if params.any?
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Firehose
4
+ Tap = Data.define(:id, :name, :token, :token_prefix, :rules_count, :last_used_at, :created_at) do
5
+ def initialize(id:, name:, token: nil, token_prefix: nil, rules_count: 0, last_used_at: nil, created_at: nil)
6
+ super
7
+ end
8
+
9
+ def self.from_hash(hash)
10
+ new(
11
+ id: hash["id"] || hash[:id],
12
+ name: hash["name"] || hash[:name],
13
+ token: hash["token"] || hash[:token],
14
+ token_prefix: hash["token_prefix"] || hash[:token_prefix],
15
+ rules_count: hash["rules_count"] || hash[:rules_count] || 0,
16
+ last_used_at: hash["last_used_at"] || hash[:last_used_at],
17
+ created_at: hash["created_at"] || hash[:created_at]
18
+ )
19
+ end
20
+ end
21
+ end
data/lib/firehose.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "firehose/error"
4
4
  require_relative "firehose/rule"
5
+ require_relative "firehose/tap"
5
6
  require_relative "firehose/document"
6
7
  require_relative "firehose/event"
7
8
  require_relative "firehose/stream"
@@ -12,7 +13,7 @@ module Firehose
12
13
  attr_accessor :management_key, :tap_token, :base_url, :timeout
13
14
 
14
15
  def initialize
15
- @base_url = "https://api.firehose.dev"
16
+ @base_url = "https://api.firehose.com"
16
17
  @timeout = 300
17
18
  end
18
19
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: firehose-rb
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
  - Nauman Tariq
@@ -51,8 +51,9 @@ dependencies:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
53
  version: '3.18'
54
- description: SSE streaming client with rules CRUD, auto-reconnect, and offset tracking
55
- for the Firehose API.
54
+ description: Full-featured Ruby client for the Firehose API tap management, rules
55
+ CRUD (create, read, update, delete), SSE streaming with auto-reconnect and offset
56
+ tracking.
56
57
  email:
57
58
  - nauman@intellecta.co
58
59
  executables: []
@@ -67,6 +68,7 @@ files:
67
68
  - lib/firehose/event.rb
68
69
  - lib/firehose/rule.rb
69
70
  - lib/firehose/stream.rb
71
+ - lib/firehose/tap.rb
70
72
  homepage: https://github.com/nauman/firehose-rb
71
73
  licenses:
72
74
  - MIT