hookbridge 1.0.0 → 1.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.
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "date"
3
4
  require "time"
4
5
 
5
6
  module HookBridge
6
- # Message status constants
7
7
  module MessageStatus
8
8
  QUEUED = "queued"
9
9
  DELIVERING = "delivering"
@@ -14,7 +14,13 @@ module HookBridge
14
14
  ALL = [QUEUED, DELIVERING, SUCCEEDED, PENDING_RETRY, FAILED_PERMANENT].freeze
15
15
  end
16
16
 
17
- # API key mode constants
17
+ module ReplayableMessageStatus
18
+ FAILED_PERMANENT = "failed_permanent"
19
+ PENDING_RETRY = "pending_retry"
20
+
21
+ ALL = [FAILED_PERMANENT, PENDING_RETRY].freeze
22
+ end
23
+
18
24
  module APIKeyMode
19
25
  LIVE = "live"
20
26
  TEST = "test"
@@ -22,7 +28,6 @@ module HookBridge
22
28
  ALL = [LIVE, TEST].freeze
23
29
  end
24
30
 
25
- # Metrics time window constants
26
31
  module MetricsWindow
27
32
  ONE_HOUR = "1h"
28
33
  TWENTY_FOUR_HOURS = "24h"
@@ -32,324 +37,353 @@ module HookBridge
32
37
  ALL = [ONE_HOUR, TWENTY_FOUR_HOURS, SEVEN_DAYS, THIRTY_DAYS].freeze
33
38
  end
34
39
 
35
- # Full message details
36
- class Message
37
- attr_reader :id, :status, :project_id, :endpoint_id, :attempt_count, :replay_count,
38
- :payload_sha256, :content_type, :size_bytes, :idempotency_key,
39
- :next_attempt_at, :last_error, :response_status, :response_latency_ms,
40
- :created_at, :updated_at
41
-
42
- def initialize(data)
43
- @id = data["id"]
44
- @status = data["status"]
45
- @project_id = data["project_id"]
46
- @endpoint_id = data["endpoint_id"]
47
- @attempt_count = data["attempt_count"]
48
- @replay_count = data["replay_count"]
49
- @payload_sha256 = data["payload_sha256"]
50
- @content_type = data["content_type"]
51
- @size_bytes = data["size_bytes"]
52
- @idempotency_key = data["idempotency_key"]
53
- @next_attempt_at = parse_time(data["next_attempt_at"])
54
- @last_error = data["last_error"]
55
- @response_status = data["response_status"]
56
- @response_latency_ms = data["response_latency_ms"]
57
- @created_at = parse_time(data["created_at"])
58
- @updated_at = parse_time(data["updated_at"])
40
+ class BaseModel
41
+ def initialize(data = {}, time_fields: [], date_fields: [])
42
+ @attributes = {}
43
+ (data || {}).each do |key, value|
44
+ @attributes[key.to_s] = convert_value(key.to_s, value, time_fields, date_fields)
45
+ end
59
46
  end
60
47
 
61
48
  def to_h
62
- {
63
- id: @id,
64
- status: @status,
65
- project_id: @project_id,
66
- endpoint_id: @endpoint_id,
67
- attempt_count: @attempt_count,
68
- replay_count: @replay_count,
69
- payload_sha256: @payload_sha256,
70
- content_type: @content_type,
71
- size_bytes: @size_bytes,
72
- idempotency_key: @idempotency_key,
73
- next_attempt_at: @next_attempt_at,
74
- last_error: @last_error,
75
- response_status: @response_status,
76
- response_latency_ms: @response_latency_ms,
77
- created_at: @created_at,
78
- updated_at: @updated_at
79
- }
49
+ @attributes.transform_keys(&:to_sym)
50
+ end
51
+
52
+ def method_missing(name, *args, &block)
53
+ return super unless args.empty? && block.nil?
54
+
55
+ key = name.to_s
56
+ return @attributes[key] if @attributes.key?(key)
57
+
58
+ super
59
+ end
60
+
61
+ def respond_to_missing?(name, include_private = false)
62
+ @attributes.key?(name.to_s) || super
80
63
  end
81
64
 
82
65
  private
83
66
 
84
- def parse_time(value)
67
+ def convert_value(key, value, time_fields, date_fields)
85
68
  return nil if value.nil?
69
+ return Time.parse(value) if time_fields.include?(key)
70
+ return Date.parse(value) if date_fields.include?(key)
86
71
 
87
- Time.parse(value)
72
+ value
88
73
  end
89
74
  end
90
75
 
91
- # Lighter message summary for logs
92
- class MessageSummary
93
- attr_reader :message_id, :endpoint, :status, :attempt_count, :created_at,
94
- :delivered_at, :response_status, :response_latency_ms, :last_error
76
+ class Message < BaseModel
77
+ def initialize(data)
78
+ super(data, time_fields: %w[next_attempt_at created_at updated_at])
79
+ end
80
+ end
95
81
 
82
+ class MessageSummary < BaseModel
96
83
  def initialize(data)
97
- @message_id = data["message_id"]
98
- @endpoint = data["endpoint"]
99
- @status = data["status"]
100
- @attempt_count = data["attempt_count"]
101
- @created_at = parse_time(data["created_at"])
102
- @delivered_at = parse_time(data["delivered_at"])
103
- @response_status = data["response_status"]
104
- @response_latency_ms = data["response_latency_ms"]
105
- @last_error = data["last_error"]
84
+ super(data, time_fields: %w[created_at delivered_at next_attempt_at])
106
85
  end
86
+ end
107
87
 
108
- def to_h
109
- {
110
- message_id: @message_id,
111
- endpoint: @endpoint,
112
- status: @status,
113
- attempt_count: @attempt_count,
114
- created_at: @created_at,
115
- delivered_at: @delivered_at,
116
- response_status: @response_status,
117
- response_latency_ms: @response_latency_ms,
118
- last_error: @last_error
119
- }
88
+ class SendResponse < BaseModel
89
+ end
90
+
91
+ class ReplayResponse < BaseModel
92
+ end
93
+
94
+ class LogsResponse
95
+ attr_reader :messages, :has_more, :next_cursor
96
+
97
+ def initialize(data)
98
+ @messages = ((data["data"] || data["messages"] || data) || []).map { |entry| MessageSummary.new(entry) }
99
+ @has_more = data["has_more"] || false
100
+ @next_cursor = data["next_cursor"]
120
101
  end
102
+ end
121
103
 
122
- private
104
+ class Metrics < BaseModel
105
+ end
123
106
 
124
- def parse_time(value)
125
- return nil if value.nil?
107
+ class InboundMetrics < BaseModel
108
+ end
126
109
 
127
- Time.parse(value)
110
+ class DLQMessage < BaseModel
111
+ def initialize(data)
112
+ super(data, time_fields: %w[failed_at])
128
113
  end
129
114
  end
130
115
 
131
- # Send webhook response
132
- class SendResponse
133
- attr_reader :message_id, :status
116
+ class DLQResponse
117
+ attr_reader :messages, :has_more, :next_cursor
134
118
 
135
119
  def initialize(data)
136
- @message_id = data["message_id"]
137
- @status = data["status"]
120
+ messages = data.dig("data", "messages") || data["messages"] || data
121
+ @messages = (messages || []).map { |entry| MessageSummary.new(entry) }
122
+ @has_more = data.dig("data", "has_more") || data["has_more"] || false
123
+ @next_cursor = data.dig("data", "next_cursor") || data["next_cursor"]
138
124
  end
125
+ end
139
126
 
140
- def to_h
141
- { message_id: @message_id, status: @status }
127
+ class APIKey < BaseModel
128
+ def initialize(data)
129
+ super(data, time_fields: %w[created_at last_used_at])
130
+ end
131
+
132
+ def id
133
+ key_id
134
+ end
135
+
136
+ def name
137
+ label
142
138
  end
143
139
  end
144
140
 
145
- # Replay response
146
- class ReplayResponse
147
- attr_reader :message_id, :status, :attempt_count
141
+ class APIKeysResponse
142
+ attr_reader :keys
148
143
 
149
144
  def initialize(data)
150
- @message_id = data["message_id"]
151
- @status = data["status"]
152
- @attempt_count = data["attempt_count"]
145
+ @keys = (data["data"] || data || []).map { |entry| APIKey.new(entry) }
153
146
  end
147
+ end
154
148
 
155
- def to_h
156
- { message_id: @message_id, status: @status, attempt_count: @attempt_count }
149
+ class APIKeyCreated < BaseModel
150
+ def initialize(data)
151
+ super(data, time_fields: %w[created_at])
152
+ end
153
+
154
+ def id
155
+ key_id
156
+ end
157
+
158
+ def name
159
+ label
157
160
  end
158
161
  end
159
162
 
160
- # Paginated list of log entries
161
- class LogsResponse
162
- attr_reader :messages, :has_more, :next_cursor
163
+ class CreateEndpointResponse < BaseModel
164
+ def initialize(data)
165
+ super(data, time_fields: %w[created_at])
166
+ end
167
+ end
168
+
169
+ class Endpoint < BaseModel
170
+ def initialize(data)
171
+ super(data, time_fields: %w[created_at updated_at])
172
+ end
173
+ end
163
174
 
175
+ class EndpointSummary < BaseModel
164
176
  def initialize(data)
165
- # data is an array directly, pagination info is at top level
166
- messages_data = data["data"] || data
167
- messages_data = [] unless messages_data.is_a?(Array)
168
- @messages = messages_data.map { |m| MessageSummary.new(m) }
177
+ super(data, time_fields: %w[created_at])
178
+ end
179
+ end
180
+
181
+ class ListEndpointsResponse
182
+ attr_reader :endpoints, :has_more, :next_cursor
183
+
184
+ def initialize(data)
185
+ @endpoints = (data["data"] || data || []).map { |entry| EndpointSummary.new(entry) }
169
186
  @has_more = data["has_more"] || false
170
187
  @next_cursor = data["next_cursor"]
171
188
  end
189
+ end
172
190
 
173
- def to_h
174
- {
175
- messages: @messages.map(&:to_h),
176
- has_more: @has_more,
177
- next_cursor: @next_cursor
178
- }
191
+ class RotateSecretResponse < BaseModel
192
+ def initialize(data)
193
+ super(data, time_fields: %w[created_at])
179
194
  end
180
195
  end
181
196
 
182
- # Delivery metrics
183
- class Metrics
184
- attr_reader :window, :total_messages, :succeeded, :failed, :retries,
185
- :success_rate, :avg_latency_ms
197
+ class SigningKey < BaseModel
198
+ def initialize(data)
199
+ super(data, time_fields: %w[created_at])
200
+ end
201
+ end
202
+
203
+ class ReplayAllMessagesResponse < BaseModel
204
+ end
205
+
206
+ class ReplayBatchResult < BaseModel
207
+ end
208
+
209
+ class ReplayBatchMessagesResponse
210
+ attr_reader :replayed, :failed, :stuck, :results
186
211
 
187
212
  def initialize(data)
188
- @window = data["window"]
189
- @total_messages = data["total_messages"]
190
- @succeeded = data["succeeded"]
213
+ @replayed = data["replayed"]
191
214
  @failed = data["failed"]
192
- @retries = data["retries"]
193
- @success_rate = data["success_rate"]
194
- @avg_latency_ms = data["avg_latency_ms"]
215
+ @stuck = data["stuck"]
216
+ @results = (data["results"] || []).map { |entry| ReplayBatchResult.new(entry) }
195
217
  end
218
+ end
196
219
 
197
- def to_h
198
- {
199
- window: @window,
200
- total_messages: @total_messages,
201
- succeeded: @succeeded,
202
- failed: @failed,
203
- retries: @retries,
204
- success_rate: @success_rate,
205
- avg_latency_ms: @avg_latency_ms
206
- }
220
+ class Project < BaseModel
221
+ def initialize(data)
222
+ super(data, time_fields: %w[created_at])
207
223
  end
208
224
  end
209
225
 
210
- # Dead Letter Queue message
211
- class DLQMessage
212
- attr_reader :message_id, :endpoint, :failed_at, :reason, :attempt_count
226
+ class CheckoutSession < BaseModel
227
+ end
213
228
 
229
+ class PortalSession < BaseModel
230
+ end
231
+
232
+ class UsageHistoryRow < BaseModel
214
233
  def initialize(data)
215
- @message_id = data["message_id"]
216
- @endpoint = data["endpoint"]
217
- @failed_at = parse_time(data["failed_at"])
218
- @reason = data["reason"]
219
- @attempt_count = data["attempt_count"]
234
+ super(data, date_fields: %w[period_start period_end])
220
235
  end
236
+ end
221
237
 
222
- def to_h
223
- {
224
- message_id: @message_id,
225
- endpoint: @endpoint,
226
- failed_at: @failed_at,
227
- reason: @reason,
228
- attempt_count: @attempt_count
229
- }
238
+ class UsageHistoryResponse
239
+ attr_reader :rows, :total, :limit, :offset, :has_more
240
+
241
+ def initialize(data, meta)
242
+ @rows = (data || []).map { |entry| UsageHistoryRow.new(entry) }
243
+ @total = meta["total"]
244
+ @limit = meta["limit"]
245
+ @offset = meta["offset"]
246
+ @has_more = meta["has_more"]
230
247
  end
248
+ end
231
249
 
232
- private
250
+ class InvoiceLine < BaseModel
251
+ end
233
252
 
234
- def parse_time(value)
235
- return nil if value.nil?
253
+ class Invoice < BaseModel
254
+ attr_reader :lines
236
255
 
237
- Time.parse(value)
256
+ def initialize(data)
257
+ super(data, time_fields: %w[period_start period_end created])
258
+ @lines = (data["lines"] || []).map { |entry| InvoiceLine.new(entry) }
238
259
  end
239
260
  end
240
261
 
241
- # Paginated list of DLQ messages
242
- class DLQResponse
243
- attr_reader :messages, :has_more, :next_cursor
262
+ class InvoicesResponse
263
+ attr_reader :invoices, :has_more
244
264
 
245
- def initialize(data)
246
- # DLQ response: data can be { messages: [...] } or have messages at top level
247
- messages_data = if data.is_a?(Hash) && data["data"].is_a?(Hash)
248
- data.dig("data", "messages") || []
249
- elsif data.is_a?(Hash) && data["messages"]
250
- data["messages"]
251
- elsif data.is_a?(Array)
252
- data
253
- else
254
- []
255
- end
256
- @messages = messages_data.map { |m| MessageSummary.new(m) }
257
- @has_more = data["has_more"] || false
258
- @next_cursor = data["next_cursor"]
265
+ def initialize(data, meta)
266
+ @invoices = (data || []).map { |entry| Invoice.new(entry) }
267
+ @has_more = meta["has_more"]
259
268
  end
269
+ end
260
270
 
261
- def to_h
262
- {
263
- messages: @messages.map(&:to_h),
264
- has_more: @has_more,
265
- next_cursor: @next_cursor
266
- }
271
+ class TimeSeriesBucket < BaseModel
272
+ def initialize(data)
273
+ super(data, time_fields: %w[timestamp])
267
274
  end
268
275
  end
269
276
 
270
- # API key information
271
- class APIKey
272
- attr_reader :key_id, :label, :prefix, :created_at, :last_used_at
273
-
274
- # Aliases for backwards compatibility
275
- alias id key_id
276
- alias name label
277
+ class TimeSeriesMetrics
278
+ attr_reader :window, :buckets
277
279
 
278
280
  def initialize(data)
279
- @key_id = data["key_id"]
280
- @label = data["label"]
281
- @prefix = data["prefix"]
282
- @created_at = parse_time(data["created_at"])
283
- @last_used_at = parse_time(data["last_used_at"])
281
+ @window = data["window"]
282
+ @buckets = (data["buckets"] || []).map { |entry| TimeSeriesBucket.new(entry) }
284
283
  end
284
+ end
285
285
 
286
- def to_h
287
- {
288
- key_id: @key_id,
289
- label: @label,
290
- prefix: @prefix,
291
- created_at: @created_at,
292
- last_used_at: @last_used_at
293
- }
286
+ class SubscriptionLimits < BaseModel
287
+ end
288
+
289
+ class SubscriptionUsage < BaseModel
290
+ def initialize(data)
291
+ super(data, time_fields: %w[period_start period_end])
294
292
  end
293
+ end
295
294
 
296
- private
295
+ class Subscription
296
+ attr_reader :plan, :status, :limits, :usage, :cancel_at_period_end, :current_period_end
297
297
 
298
- def parse_time(value)
299
- return nil if value.nil?
298
+ def initialize(data)
299
+ @plan = data["plan"]
300
+ @status = data["status"]
301
+ @limits = SubscriptionLimits.new(data["limits"] || {})
302
+ @usage = SubscriptionUsage.new(data["usage"] || {})
303
+ @cancel_at_period_end = data["cancel_at_period_end"]
304
+ @current_period_end = data["current_period_end"] ? Time.parse(data["current_period_end"]) : nil
305
+ end
306
+ end
300
307
 
301
- Time.parse(value)
308
+ class CreateInboundEndpointResponse < BaseModel
309
+ def initialize(data)
310
+ super(data, time_fields: %w[created_at])
302
311
  end
303
312
  end
304
313
 
305
- # API key creation response (includes full key)
306
- class APIKeyCreated
307
- attr_reader :key_id, :label, :key, :prefix, :created_at
314
+ class InboundEndpoint < BaseModel
315
+ def initialize(data)
316
+ super(data, time_fields: %w[created_at updated_at])
317
+ end
318
+ end
308
319
 
309
- # Aliases for backwards compatibility
310
- alias id key_id
311
- alias name label
320
+ class InboundEndpointSummary < BaseModel
321
+ def initialize(data)
322
+ super(data, time_fields: %w[created_at])
323
+ end
324
+ end
325
+
326
+ class ListInboundEndpointsResponse
327
+ attr_reader :endpoints, :has_more, :next_cursor
312
328
 
313
329
  def initialize(data)
314
- @key_id = data["key_id"]
315
- @label = data["label"]
316
- @key = data["key"]
317
- @prefix = data["prefix"]
318
- @created_at = parse_time(data["created_at"])
330
+ @endpoints = (data["data"] || data || []).map { |entry| InboundEndpointSummary.new(entry) }
331
+ @has_more = data["has_more"] || false
332
+ @next_cursor = data["next_cursor"]
319
333
  end
334
+ end
320
335
 
321
- def to_h
322
- {
323
- key_id: @key_id,
324
- label: @label,
325
- key: @key,
326
- prefix: @prefix,
327
- created_at: @created_at
328
- }
336
+ class UpdateResult < BaseModel
337
+ end
338
+
339
+ class DeleteResult < BaseModel
340
+ end
341
+
342
+ class PauseState < BaseModel
343
+ end
344
+
345
+ class InboundLogEntry < BaseModel
346
+ def initialize(data)
347
+ super(data, time_fields: %w[received_at delivered_at])
329
348
  end
349
+ end
330
350
 
331
- private
351
+ class InboundLogsResponse
352
+ attr_reader :entries, :has_more, :next_cursor
332
353
 
333
- def parse_time(value)
334
- return nil if value.nil?
354
+ def initialize(data)
355
+ @entries = (data["data"] || data || []).map { |entry| InboundLogEntry.new(entry) }
356
+ @has_more = data["has_more"] || false
357
+ @next_cursor = data["next_cursor"]
358
+ end
359
+ end
335
360
 
336
- Time.parse(value)
361
+ class InboundRejection < BaseModel
362
+ def initialize(data)
363
+ super(data, time_fields: %w[received_at])
337
364
  end
338
365
  end
339
366
 
340
- # List of API keys
341
- class APIKeysResponse
342
- attr_reader :keys
367
+ class InboundRejectionsResponse
368
+ attr_reader :entries, :has_more, :next_cursor
343
369
 
344
370
  def initialize(data)
345
- # data is an array directly
346
- keys_data = data.is_a?(Array) ? data : (data["data"] || data["keys"] || [])
347
- keys_data = [] unless keys_data.is_a?(Array)
348
- @keys = keys_data.map { |k| APIKey.new(k) }
371
+ @entries = (data["data"] || data || []).map { |entry| InboundRejection.new(entry) }
372
+ @has_more = data["has_more"] || false
373
+ @next_cursor = data["next_cursor"]
349
374
  end
375
+ end
350
376
 
351
- def to_h
352
- { keys: @keys.map(&:to_h) }
377
+ class ExportRecord < BaseModel
378
+ def initialize(data)
379
+ super(data, time_fields: %w[
380
+ filter_start_time
381
+ filter_end_time
382
+ started_at
383
+ completed_at
384
+ expires_at
385
+ created_at
386
+ ])
353
387
  end
354
388
  end
355
389
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HookBridge
4
- VERSION = "1.0.0"
4
+ VERSION = "1.4.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hookbridge
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - HookBridge
@@ -180,7 +180,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
180
180
  - !ruby/object:Gem::Version
181
181
  version: '0'
182
182
  requirements: []
183
- rubygems_version: 3.7.2
183
+ rubygems_version: 4.0.3
184
184
  specification_version: 4
185
185
  summary: Ruby SDK for HookBridge - Guaranteed webhook delivery
186
186
  test_files: []