zizq 0.1.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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +94 -0
  4. data/bin/profile-worker +145 -0
  5. data/bin/zizq-worker +174 -0
  6. data/lib/active_job/queue_adapters/zizq_adapter.rb +109 -0
  7. data/lib/zizq/ack_processor.rb +132 -0
  8. data/lib/zizq/active_job_config.rb +122 -0
  9. data/lib/zizq/backoff.rb +50 -0
  10. data/lib/zizq/bulk_enqueue.rb +87 -0
  11. data/lib/zizq/client.rb +982 -0
  12. data/lib/zizq/configuration.rb +164 -0
  13. data/lib/zizq/enqueue_request.rb +178 -0
  14. data/lib/zizq/enqueue_with.rb +109 -0
  15. data/lib/zizq/error.rb +43 -0
  16. data/lib/zizq/job.rb +188 -0
  17. data/lib/zizq/job_config.rb +244 -0
  18. data/lib/zizq/lifecycle.rb +58 -0
  19. data/lib/zizq/middleware.rb +79 -0
  20. data/lib/zizq/query.rb +566 -0
  21. data/lib/zizq/resources/error_enumerator.rb +241 -0
  22. data/lib/zizq/resources/error_page.rb +19 -0
  23. data/lib/zizq/resources/error_record.rb +19 -0
  24. data/lib/zizq/resources/job.rb +124 -0
  25. data/lib/zizq/resources/job_page.rb +57 -0
  26. data/lib/zizq/resources/page.rb +77 -0
  27. data/lib/zizq/resources/resource.rb +45 -0
  28. data/lib/zizq/resources.rb +16 -0
  29. data/lib/zizq/version.rb +9 -0
  30. data/lib/zizq/worker.rb +467 -0
  31. data/lib/zizq.rb +269 -0
  32. data/sig/generated/zizq/ack_processor.rbs +73 -0
  33. data/sig/generated/zizq/active_job_config.rbs +74 -0
  34. data/sig/generated/zizq/backoff.rbs +34 -0
  35. data/sig/generated/zizq/bulk_enqueue.rbs +72 -0
  36. data/sig/generated/zizq/client.rbs +419 -0
  37. data/sig/generated/zizq/configuration.rbs +95 -0
  38. data/sig/generated/zizq/enqueue_request.rbs +94 -0
  39. data/sig/generated/zizq/enqueue_with.rbs +88 -0
  40. data/sig/generated/zizq/error.rbs +41 -0
  41. data/sig/generated/zizq/job.rbs +136 -0
  42. data/sig/generated/zizq/job_config.rbs +150 -0
  43. data/sig/generated/zizq/lifecycle.rbs +34 -0
  44. data/sig/generated/zizq/middleware.rbs +50 -0
  45. data/sig/generated/zizq/query.rbs +327 -0
  46. data/sig/generated/zizq/resources/error_enumerator.rbs +148 -0
  47. data/sig/generated/zizq/resources/error_page.rbs +13 -0
  48. data/sig/generated/zizq/resources/error_record.rbs +20 -0
  49. data/sig/generated/zizq/resources/job.rbs +89 -0
  50. data/sig/generated/zizq/resources/job_page.rbs +33 -0
  51. data/sig/generated/zizq/resources/page.rbs +47 -0
  52. data/sig/generated/zizq/resources/resource.rbs +26 -0
  53. data/sig/generated/zizq/version.rbs +5 -0
  54. data/sig/generated/zizq/worker.rbs +152 -0
  55. data/sig/generated/zizq.rbs +180 -0
  56. data/sig/zizq.rbs +111 -0
  57. metadata +134 -0
@@ -0,0 +1,241 @@
1
+ # Copyright (c) 2026 Chris Corbyn <chris@zizq.io>
2
+ # Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ # rbs_inline: enabled
5
+ # frozen_string_literal: true
6
+
7
+ module Zizq
8
+ module Resources
9
+ # Provides a lazy Enumerator across all errors on a job following the
10
+ # cursor-based pagination.
11
+ class ErrorEnumerator
12
+ # Maximum page size the server can handle.
13
+ MAX_PAGE_SIZE = 2000 #: Integer
14
+
15
+ # @rbs skip
16
+ include Enumerable
17
+
18
+ # @rbs!
19
+ # include ::Enumerable[Zizq::Resources::ErrorRecord]
20
+
21
+ # Initialize the enumerator.
22
+ #
23
+ # @rbs id: String
24
+ # @rbs order: Zizq::sort_direction?
25
+ # @rbs limit: Integer?
26
+ # @rbs page_size: Integer?
27
+ # @rbs return: void
28
+ def initialize(id,
29
+ order: nil,
30
+ limit: nil,
31
+ page_size: nil)
32
+ @id = id
33
+ @order = order
34
+ @limit = limit
35
+ @page_size = page_size
36
+ end
37
+
38
+ # Set the page size for paginated iteration.
39
+ #
40
+ # When set, `each_page` fetches pages of this size, and `each` fetches
41
+ # errors in pages of this size.
42
+ #
43
+ # @rbs page_size: Integer?
44
+ # @rbs return: ErrorEnumerator
45
+ def in_pages_of(page_size)
46
+ rebuild(page_size:)
47
+ end
48
+
49
+ # Set the sort order for iteration.
50
+ #
51
+ # @rbs order: Zizq::sort_direction?
52
+ # @rbs return: ErrorEnumerator
53
+ def order(order)
54
+ rebuild(order:)
55
+ end
56
+
57
+ # Limit the total number of errors returned.
58
+ #
59
+ # This is a total limit, imposed across potentially multiple page fetches.
60
+ #
61
+ # @rbs limit: Integer?
62
+ # @rbs return: ErrorEnumerator
63
+ def limit(limit)
64
+ rebuild(limit:)
65
+ end
66
+
67
+ # Reverse the sort order.
68
+ #
69
+ # Returns a new query with the opposite order. If no order was set,
70
+ # defaults to descending (the server default is ascending).
71
+ #
72
+ # @rbs return: ErrorEnumerator
73
+ def reverse_order
74
+ rebuild(order: @order == :desc ? :asc : :desc)
75
+ end
76
+
77
+ # Returns true if there are no errors.
78
+ #
79
+ # Optimised: fetches a single error to check.
80
+ #
81
+ # @rbs return: bool
82
+ def empty?
83
+ first.nil?
84
+ end
85
+
86
+ # Returns true if there are any errors.
87
+ #
88
+ # Without a block, optimised to fetch a single error. With a block,
89
+ # falls back to Enumerable (tests each error against the block).
90
+ #
91
+ # @rbs &block: ?(Resources::ErrorRecord) -> bool
92
+ # @rbs return: bool
93
+ def any?
94
+ return super if block_given?
95
+
96
+ !first.nil?
97
+ end
98
+
99
+ # Returns true if there are no errors.
100
+ #
101
+ # Without a block, optimised to fetch a single error. With a block,
102
+ # falls back to Enumerable (tests each error against the block).
103
+ #
104
+ # @rbs &block: ?(Resources::ErrorRecord) -> bool
105
+ # @rbs return: bool
106
+ def none?
107
+ return super if block_given?
108
+
109
+ first.nil?
110
+ end
111
+
112
+ # Returns true if there is exactly one error.
113
+ #
114
+ # Without a block, optimised to fetch at most two errors. With a block,
115
+ # falls back to Enumerable.
116
+ #
117
+ # @rbs &block: ?(Resources::ErrorRecord) -> bool
118
+ # @rbs return: bool
119
+ def one?
120
+ return super if block_given?
121
+
122
+ limit(2).to_a.size == 1
123
+ end
124
+
125
+ # Iterate over errors in reverse order.
126
+ #
127
+ # Optimised: pushes the reverse ordering to the server instead of
128
+ # fetching all errors into memory and reversing.
129
+ #
130
+ # @rbs &block: ?(Resources::ErrorRecord) -> void
131
+ # @rbs return: ::Enumerator[Zizq::Resources::ErrorRecord, void]
132
+ def reverse_each(&block)
133
+ reverse_order.each(&block)
134
+ end
135
+
136
+ # Return the first error, or nil if no errors.
137
+ #
138
+ # Optimised: fetches a single error from the server (`?limit=1`).
139
+ #
140
+ # @rbs return: Resources::ErrorRecord?
141
+ def first
142
+ limit(1).each.first
143
+ end
144
+
145
+ # Return the last error, or nil if no errors.
146
+ #
147
+ # Optimised: reverses the order and fetches a single error.
148
+ #
149
+ # @rbs return: Resources::ErrorRecord?
150
+ def last
151
+ reverse_order.first
152
+ end
153
+
154
+ # Return the first `n` errors.
155
+ #
156
+ # Optimised: sets the limit to `n` so the server only returns what's
157
+ # needed.
158
+ #
159
+ # @rbs n: Integer
160
+ # @rbs return: Array[Resources::ErrorRecord]
161
+ def take(n)
162
+ limit(n).to_a
163
+ end
164
+
165
+ # Iterate over errors, lazily paginating through results.
166
+ #
167
+ # Respects `limit` if set. Without a block, returns an `Enumerator`.
168
+ #
169
+ # @rbs &block: ?(Resources::ErrorRecord) -> void
170
+ # @rbs return: ::Enumerator[Zizq::Resources::ErrorRecord, void]
171
+ def each(&block)
172
+ enumerator = enum_for(:each)
173
+
174
+ if block_given?
175
+ remaining = @limit
176
+
177
+ each_page do |page|
178
+ page.errors.each do |error|
179
+ if remaining
180
+ break if remaining <= 0
181
+ end
182
+
183
+ yield error
184
+
185
+ remaining -= 1 if remaining
186
+ end
187
+ end
188
+ end
189
+
190
+ enumerator
191
+ end
192
+
193
+ # Iterate over pages of errors.
194
+ #
195
+ # Each page is a `Resources::ErrorPage`. Without a block, returns an
196
+ # `Enumerator`.
197
+ #
198
+ # If `limit` is set, terminates after the last page is reached that
199
+ # exceeds the limit, but does not truncate the page.
200
+ #
201
+ # @rbs &block: ?(Resources::ErrorPage) -> void
202
+ # @rbs return: ::Enumerator[Zizq::Resources::ErrorPage, void]
203
+ def each_page(&block)
204
+ enumerator = enum_for(:each_page)
205
+
206
+ if block_given?
207
+ page = Zizq.client.list_errors(
208
+ @id,
209
+ limit: [@page_size, @limit, (@page_size || @limit) && MAX_PAGE_SIZE].compact.min,
210
+ order: @order,
211
+ )
212
+
213
+ remaining = @limit
214
+
215
+ while page
216
+ yield page
217
+
218
+ if remaining
219
+ remaining -= page.errors.size
220
+ break if remaining <= 0
221
+ end
222
+
223
+ page = page.next_page
224
+ end
225
+ end
226
+
227
+ enumerator
228
+ end
229
+
230
+ private
231
+
232
+ # Build a new ErrorEnumerator with the given overrides, preserving all
233
+ # other fields.
234
+ #
235
+ # @rbs return: ErrorEnumerator
236
+ def rebuild(id = @id, order: @order, limit: @limit, page_size: @page_size)
237
+ self.class.new(id, limit:, order:, page_size:)
238
+ end
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,19 @@
1
+ # Copyright (c) 2026 Chris Corbyn <chris@zizq.io>
2
+ # Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ # rbs_inline: enabled
5
+ # frozen_string_literal: true
6
+
7
+ module Zizq
8
+ module Resources
9
+ # Paginated list of error records.
10
+ # @rbs inherits Page[ErrorRecord]
11
+ class ErrorPage < Page
12
+ def items #: () -> Array[ErrorRecord]
13
+ @items ||= (@data["errors"] || []).map { |e| ErrorRecord.new(client, e) }
14
+ end
15
+
16
+ alias errors items
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # Copyright (c) 2026 Chris Corbyn <chris@zizq.io>
2
+ # Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ # rbs_inline: enabled
5
+ # frozen_string_literal: true
6
+
7
+ module Zizq
8
+ module Resources
9
+ # Typed wrapper around a single error record from the job error history.
10
+ class ErrorRecord < Resource
11
+ def attempt = @data["attempt"] #: () -> Integer
12
+ def message = @data["message"] #: () -> String
13
+ def error_type = @data["error_type"] #: () -> String?
14
+ def backtrace = @data["backtrace"] #: () -> String?
15
+ def dequeued_at = ms_to_seconds(@data["dequeued_at"]) #: () -> Float
16
+ def failed_at = ms_to_seconds(@data["failed_at"]) #: () -> Float
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,124 @@
1
+ # Copyright (c) 2026 Chris Corbyn <chris@zizq.io>
2
+ # Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ # rbs_inline: enabled
5
+ # frozen_string_literal: true
6
+
7
+ module Zizq
8
+ module Resources
9
+ # Typed wrapper around a job response hash.
10
+ #
11
+ # Exposes named accessor methods with Ruby-idiomatic types (fractional
12
+ # seconds instead of milliseconds) and link methods that follow related
13
+ # resources through the Client.
14
+ class Job < Resource
15
+ def id = @data["id"] #: () -> String
16
+ def type = @data["type"] #: () -> String
17
+ def queue = @data["queue"] #: () -> String
18
+ def priority = @data["priority"] #: () -> Integer
19
+ def status = @data["status"] #: () -> String
20
+ def ready_at = ms_to_seconds(@data["ready_at"]) #: () -> Float?
21
+ def attempts = @data["attempts"] #: () -> Integer
22
+ def payload = @data["payload"] #: () -> Hash[String, untyped]?
23
+ def dequeued_at = ms_to_seconds(@data["dequeued_at"]) #: () -> Float?
24
+ def failed_at = ms_to_seconds(@data["failed_at"]) #: () -> Float?
25
+ def completed_at = ms_to_seconds(@data["completed_at"]) #: () -> Float?
26
+ def retry_limit = @data["retry_limit"] #: () -> Integer?
27
+ def unique_key = @data["unique_key"] #: () -> String?
28
+ def unique_while = @data["unique_while"]&.to_sym #: () -> Zizq::unique_scope?
29
+ def duplicate? = @data["duplicate"] == true #: () -> bool
30
+
31
+ # Backoff configuration converted from the wire format (ms) to the
32
+ # Ruby-idiomatic format (seconds), matching the Zizq::backoff type.
33
+ def backoff #: () -> Zizq::backoff?
34
+ raw = @data["backoff"]
35
+ return nil unless raw
36
+
37
+ {
38
+ exponent: raw["exponent"].to_f,
39
+ base: raw["base_ms"] / 1000.0,
40
+ jitter: raw["jitter_ms"] / 1000.0
41
+ }
42
+ end
43
+
44
+ # Retention configuration converted from the wire format (ms) to the
45
+ # Ruby-idiomatic format (seconds), matching the Zizq::retention type.
46
+ def retention #: () -> Zizq::retention?
47
+ raw = @data["retention"]
48
+ return nil unless raw
49
+
50
+ result = {} #: Hash[Symbol, Float]
51
+ result[:completed] = raw["completed_ms"] / 1000.0 if raw["completed_ms"]
52
+ result[:dead] = raw["dead_ms"] / 1000.0 if raw["dead_ms"]
53
+ result
54
+ end
55
+
56
+ # Fetch the error history for this job.
57
+ #
58
+ # @rbs order: Zizq::sort_direction?
59
+ # @rbs limit: Integer?
60
+ # @rbs page_size: Integer?
61
+ # @rbs return: ErrorEnumerator
62
+ def errors(order: nil, limit: nil, page_size: nil)
63
+ ErrorEnumerator.new(id, order:, limit:, page_size:)
64
+ end
65
+
66
+ # Mark this job as successfully completed.
67
+ def complete! #: () -> nil
68
+ @client.report_success(id)
69
+ end
70
+
71
+ # Report this job as failed.
72
+ #
73
+ # @rbs message: String
74
+ # @rbs error_type: String?
75
+ # @rbs backtrace: String?
76
+ # @rbs retry_at: Float?
77
+ # @rbs kill: bool
78
+ # @rbs return: Job
79
+ def fail!(message:, error_type: nil, backtrace: nil, retry_at: nil, kill: false)
80
+ @client.report_failure(id, message:, error_type:, backtrace:, retry_at:, kill:)
81
+ end
82
+
83
+ # Delete this job.
84
+ #
85
+ # @rbs return: void
86
+ def delete
87
+ @client.delete_job(id)
88
+ end
89
+
90
+ # Update this job's mutable fields.
91
+ #
92
+ # Returns the updated job.
93
+ #
94
+ # @rbs queue: (String | singleton(Zizq::UNCHANGED))?
95
+ # @rbs priority: (Integer | singleton(Zizq::UNCHANGED))?
96
+ # @rbs ready_at: (Zizq::to_f | singleton(Zizq::RESET) | singleton(Zizq::UNCHANGED))?
97
+ # @rbs retry_limit: (Integer | singleton(Zizq::RESET) | singleton(Zizq::UNCHANGED))?
98
+ # @rbs backoff: (Zizq::backoff | singleton(Zizq::RESET) | singleton(Zizq::UNCHANGED))?
99
+ # @rbs retention: (Zizq::retention | singleton(Zizq::RESET) | singleton(Zizq::UNCHANGED))?
100
+ # @rbs return: Job
101
+ def update(queue: Zizq::UNCHANGED,
102
+ priority: Zizq::UNCHANGED,
103
+ ready_at: Zizq::UNCHANGED,
104
+ retry_limit: Zizq::UNCHANGED,
105
+ backoff: Zizq::UNCHANGED,
106
+ retention: Zizq::UNCHANGED)
107
+ job = @client.update_job(
108
+ id,
109
+ queue:,
110
+ priority:,
111
+ ready_at:,
112
+ retry_limit:,
113
+ backoff:,
114
+ retention:
115
+ )
116
+
117
+ # Make sure this job's fields are updated.
118
+ @data.merge!(job.to_h)
119
+
120
+ job
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,57 @@
1
+ # Copyright (c) 2026 Chris Corbyn <chris@zizq.io>
2
+ # Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ # rbs_inline: enabled
5
+ # frozen_string_literal: true
6
+
7
+ module Zizq
8
+ module Resources
9
+ # Paginated list of jobs.
10
+ # @rbs inherits Page[Job]
11
+ class JobPage < Page
12
+ def items #: () -> Array[Job]
13
+ @items ||= (@data["jobs"] || []).map { |j| Job.new(client, j) }
14
+ end
15
+
16
+ alias jobs items
17
+
18
+ # Delete all jobs on this page.
19
+ #
20
+ # Returns the number of deleted jobs.
21
+ #
22
+ # @rbs return: Integer
23
+ def delete_all
24
+ ids = jobs.map(&:id)
25
+ return 0 if ids.empty?
26
+
27
+ client.delete_all_jobs(where: { id: ids })
28
+ end
29
+
30
+ # Update all jobs on this page with the given field values.
31
+ #
32
+ # Returns the number of updated jobs.
33
+ #
34
+ # @rbs queue: (String | singleton(Zizq::UNCHANGED))?
35
+ # @rbs priority: (Integer | singleton(Zizq::UNCHANGED))?
36
+ # @rbs ready_at: (Zizq::to_f | singleton(Zizq::RESET) | singleton(Zizq::UNCHANGED))?
37
+ # @rbs retry_limit: (Integer | singleton(Zizq::RESET) | singleton(Zizq::UNCHANGED))?
38
+ # @rbs backoff: (Zizq::backoff | singleton(Zizq::RESET) | singleton(Zizq::UNCHANGED))?
39
+ # @rbs retention: (Zizq::retention | singleton(Zizq::RESET) | singleton(Zizq::UNCHANGED))?
40
+ # @rbs return: Integer
41
+ def update_all(queue: Zizq::UNCHANGED,
42
+ priority: Zizq::UNCHANGED,
43
+ ready_at: Zizq::UNCHANGED,
44
+ retry_limit: Zizq::UNCHANGED,
45
+ backoff: Zizq::UNCHANGED,
46
+ retention: Zizq::UNCHANGED)
47
+ ids = jobs.map(&:id)
48
+ return 0 if ids.empty?
49
+
50
+ client.update_all_jobs(
51
+ where: { id: ids },
52
+ apply: { queue:, priority:, ready_at:, retry_limit:, backoff:, retention: },
53
+ )
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,77 @@
1
+ # Copyright (c) 2026 Chris Corbyn <chris@zizq.io>
2
+ # Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ # rbs_inline: enabled
5
+ # frozen_string_literal: true
6
+
7
+ module Zizq
8
+ module Resources
9
+ # Base class for paginated list responses.
10
+ #
11
+ # Stores the raw response data and provides navigation helpers that
12
+ # follow pagination links through the Client.
13
+ #
14
+ # @rbs generic T < Resource
15
+ class Page < Resource
16
+ # @rbs skip
17
+ include Enumerable
18
+
19
+ # @rbs!
20
+ # include ::Enumerable[T]
21
+
22
+ # Wrapped resource objects for this page.
23
+ def items #: () -> Array[T]
24
+ raise NotImplementedError, "#{self.class.name}#items must be implemented"
25
+ end
26
+
27
+ # Returns the underlying raw response hash.
28
+ #
29
+ # Re-declared here because Enumerable#to_h would otherwise shadow
30
+ # the Resource#to_h definition.
31
+ def to_h #: () -> Hash[String, untyped]
32
+ @data
33
+ end
34
+
35
+ # Yields each item on this page. Required by Enumerable.
36
+ #
37
+ # @rbs &block: (T) -> void
38
+ # @rbs return: Enumerator[T, void] | void
39
+ def each(&block)
40
+ items.each(&block)
41
+ end
42
+
43
+ # Returns true if there is a next page that can be fetched.
44
+ def has_next? #: () -> bool
45
+ !!@data.dig("pages", "next")
46
+ end
47
+
48
+ # Returns true if there is a previous page that can be fetched.
49
+ def has_prev? #: () -> bool
50
+ !!@data.dig("pages", "prev")
51
+ end
52
+
53
+ # Fetch the next page, or nil if there isn't one.
54
+ def next_page #: () -> Page?
55
+ path = @data.dig("pages", "next")
56
+ return nil unless path
57
+
58
+ wrap_page(client.get_path(path))
59
+ end
60
+
61
+ # Fetch the previous page, or nil if there isn't one.
62
+ def prev_page #: () -> Page?
63
+ path = @data.dig("pages", "prev")
64
+ return nil unless path
65
+
66
+ wrap_page(client.get_path(path))
67
+ end
68
+
69
+ private
70
+
71
+ # Subclasses override to wrap the raw page data in the correct Page type.
72
+ def wrap_page(data) #: (Hash[String, untyped]) -> Page
73
+ self.class.new(client, data)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,45 @@
1
+ # Copyright (c) 2026 Chris Corbyn <chris@zizq.io>
2
+ # Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ # rbs_inline: enabled
5
+ # frozen_string_literal: true
6
+
7
+ module Zizq
8
+ module Resources
9
+ # Base class for all typed response wrappers. Holds a reference to the
10
+ # Client (for following links) and the raw response hash.
11
+ class Resource
12
+ # Returns the client instance that returned this resource.
13
+ attr_reader :client #: Client
14
+
15
+ # @rbs client: Zizq::Client
16
+ # @rbs data: Hash[String, untyped]
17
+ # @rbs return: void
18
+ def initialize(client, data)
19
+ @client = client
20
+ @data = data
21
+ end
22
+
23
+ # Returns the underlying raw response hash.
24
+ def to_h #: () -> Hash[String, untyped]
25
+ @data
26
+ end
27
+
28
+ # Omit the client from inspect output to reduce noise.
29
+ def inspect #: () -> String
30
+ ivars = instance_variables
31
+ .reject { |v| v == :@client }
32
+ .map { |v| " #{v}=#{instance_variable_get(v).inspect}" }
33
+ .join
34
+ "#<#{self.class}#{ivars}>"
35
+ end
36
+
37
+ protected
38
+
39
+ # Convert a millisecond timestamp to fractional seconds, nil-safe.
40
+ def ms_to_seconds(value) #: (Integer?) -> Float?
41
+ value&./(1000.0)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,16 @@
1
+ # Copyright (c) 2026 Chris Corbyn <chris@zizq.io>
2
+ # Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ # frozen_string_literal: true
5
+
6
+ module Zizq
7
+ module Resources
8
+ autoload :Resource, "zizq/resources/resource"
9
+ autoload :Job, "zizq/resources/job"
10
+ autoload :ErrorRecord, "zizq/resources/error_record"
11
+ autoload :Page, "zizq/resources/page"
12
+ autoload :JobPage, "zizq/resources/job_page"
13
+ autoload :ErrorPage, "zizq/resources/error_page"
14
+ autoload :ErrorEnumerator, "zizq/resources/error_enumerator"
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ # Copyright (c) 2026 Chris Corbyn <chris@zizq.io>
2
+ # Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ # rbs_inline: enabled
5
+ # frozen_string_literal: true
6
+
7
+ module Zizq
8
+ VERSION = "0.1.0" #: String
9
+ end