zizq 0.4.0 → 0.6.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 +4 -3
- data/lib/active_job/queue_adapters/zizq_adapter.rb +42 -20
- data/lib/zizq/ack_processor.rb +17 -11
- data/lib/zizq/active_job_config.rb +30 -10
- data/lib/zizq/backoff.rb +5 -1
- data/lib/zizq/bulk_enqueue.rb +7 -1
- data/lib/zizq/client.rb +363 -167
- data/lib/zizq/configuration.rb +15 -14
- data/lib/zizq/crontab.rb +35 -33
- data/lib/zizq/crontab_builder.rb +1 -7
- data/lib/zizq/crontab_entry.rb +36 -35
- data/lib/zizq/crontab_entry_builder.rb +15 -18
- data/lib/zizq/enqueue_request.rb +51 -38
- data/lib/zizq/enqueue_with.rb +1 -3
- data/lib/zizq/error.rb +12 -6
- data/lib/zizq/job.rb +31 -13
- data/lib/zizq/job_config.rb +217 -13
- data/lib/zizq/lifecycle.rb +10 -2
- data/lib/zizq/middleware.rb +7 -6
- data/lib/zizq/query.rb +154 -53
- data/lib/zizq/resources/cron_entry.rb +7 -7
- data/lib/zizq/resources/cron_group.rb +5 -5
- data/lib/zizq/resources/error_enumerator.rb +11 -9
- data/lib/zizq/resources/error_page.rb +2 -1
- data/lib/zizq/resources/error_record.rb +5 -5
- data/lib/zizq/resources/job.rb +41 -24
- data/lib/zizq/resources/job_page.rb +19 -8
- data/lib/zizq/resources/job_template.rb +16 -8
- data/lib/zizq/resources/page.rb +2 -1
- data/lib/zizq/resources/resource.rb +5 -4
- data/lib/zizq/resources.rb +9 -9
- data/lib/zizq/router.rb +3 -2
- data/lib/zizq/test/client.rb +79 -58
- data/lib/zizq/test.rb +8 -8
- data/lib/zizq/tls_configuration.rb +7 -6
- data/lib/zizq/version.rb +1 -1
- data/lib/zizq/worker.rb +68 -50
- data/lib/zizq/worker_configuration.rb +11 -10
- data/lib/zizq.rb +48 -41
- data/sig/generated/zizq/active_job_config.rbs +16 -5
- data/sig/generated/zizq/bulk_enqueue.rbs +1 -0
- data/sig/generated/zizq/client.rbs +36 -4
- data/sig/generated/zizq/crontab_entry_builder.rbs +1 -0
- data/sig/generated/zizq/enqueue_request.rbs +7 -2
- data/sig/generated/zizq/job.rbs +6 -0
- data/sig/generated/zizq/job_config.rbs +98 -0
- data/sig/generated/zizq/query.rbs +55 -2
- data/sig/generated/zizq/resources/job.rbs +2 -0
- data/sig/generated/zizq/resources/job_template.rbs +5 -2
- data/sig/generated/zizq/resources/page.rbs +1 -1
- data/sig/generated/zizq.rbs +1 -0
- data/sig/zizq.rbs +16 -2
- metadata +2 -2
data/lib/zizq/client.rb
CHANGED
|
@@ -62,24 +62,23 @@ module Zizq
|
|
|
62
62
|
# @rbs read_timeout: Numeric
|
|
63
63
|
# @rbs stream_idle_timeout: Numeric
|
|
64
64
|
# @rbs return: void
|
|
65
|
-
def initialize(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
def initialize(
|
|
66
|
+
url:,
|
|
67
|
+
format: :msgpack,
|
|
68
|
+
ssl_context: nil,
|
|
69
|
+
read_timeout: 30,
|
|
70
|
+
stream_idle_timeout: 30
|
|
71
|
+
)
|
|
70
72
|
@url = url.chomp("/")
|
|
71
73
|
@format = format
|
|
72
74
|
|
|
73
75
|
endpoint_options = {
|
|
74
76
|
protocol: Async::HTTP::Protocol::HTTP2,
|
|
75
|
-
timeout: read_timeout
|
|
77
|
+
timeout: read_timeout
|
|
76
78
|
} #: Hash[Symbol, untyped]
|
|
77
79
|
endpoint_options[:ssl_context] = ssl_context if ssl_context
|
|
78
80
|
|
|
79
|
-
@endpoint = Async::HTTP::Endpoint.parse(
|
|
80
|
-
@url,
|
|
81
|
-
**endpoint_options,
|
|
82
|
-
)
|
|
81
|
+
@endpoint = Async::HTTP::Endpoint.parse(@url, **endpoint_options)
|
|
83
82
|
|
|
84
83
|
# Streaming take uses a dedicated HTTP/1.1 endpoint. The take
|
|
85
84
|
# connection is long-lived and carries only one request, so HTTP/2's
|
|
@@ -93,14 +92,13 @@ module Zizq
|
|
|
93
92
|
# timeout so server heartbeats (~3s) keep it alive while only
|
|
94
93
|
# genuinely dead connections (no data for the full window)
|
|
95
94
|
# trigger a reconnect.
|
|
96
|
-
stream_endpoint_options =
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
**stream_endpoint_options
|
|
103
|
-
)
|
|
95
|
+
stream_endpoint_options =
|
|
96
|
+
endpoint_options.merge(
|
|
97
|
+
protocol: Async::HTTP::Protocol::HTTP11,
|
|
98
|
+
timeout: stream_idle_timeout
|
|
99
|
+
)
|
|
100
|
+
@stream_endpoint =
|
|
101
|
+
Async::HTTP::Endpoint.parse(@url, **stream_endpoint_options)
|
|
104
102
|
|
|
105
103
|
@mutex = Mutex.new
|
|
106
104
|
|
|
@@ -166,17 +164,21 @@ module Zizq
|
|
|
166
164
|
# @rbs retention: Zizq::retention?
|
|
167
165
|
# @rbs unique_key: String?
|
|
168
166
|
# @rbs unique_while: Zizq::unique_scope?
|
|
167
|
+
# @rbs batch: Zizq::batch?
|
|
169
168
|
# @rbs return: Resources::Job
|
|
170
|
-
def enqueue(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
169
|
+
def enqueue(
|
|
170
|
+
queue:,
|
|
171
|
+
type:,
|
|
172
|
+
payload:,
|
|
173
|
+
priority: nil,
|
|
174
|
+
ready_at: nil,
|
|
175
|
+
retry_limit: nil,
|
|
176
|
+
backoff: nil,
|
|
177
|
+
retention: nil,
|
|
178
|
+
unique_key: nil,
|
|
179
|
+
unique_while: nil,
|
|
180
|
+
batch: nil
|
|
181
|
+
)
|
|
180
182
|
body = { queue:, type:, payload: } #: Hash[Symbol, untyped]
|
|
181
183
|
body[:priority] = priority if priority
|
|
182
184
|
# ready_at is fractional seconds in Ruby; the server expects ms.
|
|
@@ -186,6 +188,7 @@ module Zizq
|
|
|
186
188
|
body[:retention] = retention if retention
|
|
187
189
|
body[:unique_key] = unique_key if unique_key
|
|
188
190
|
body[:unique_while] = unique_while.to_s if unique_while
|
|
191
|
+
body[:batch] = batch if batch
|
|
189
192
|
|
|
190
193
|
response = post("/jobs", body)
|
|
191
194
|
data = handle_response!(response, expected: [200, 201])
|
|
@@ -204,18 +207,26 @@ module Zizq
|
|
|
204
207
|
# @rbs return: Array[Resources::Job]
|
|
205
208
|
def enqueue_bulk(jobs:)
|
|
206
209
|
body = {
|
|
207
|
-
jobs:
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
210
|
+
jobs:
|
|
211
|
+
jobs.map do |job|
|
|
212
|
+
wire = {
|
|
213
|
+
type: job[:type],
|
|
214
|
+
queue: job[:queue],
|
|
215
|
+
payload: job[:payload]
|
|
216
|
+
} #: Hash[Symbol, untyped]
|
|
217
|
+
wire[:priority] = job[:priority] if job[:priority]
|
|
218
|
+
# ready_at is fractional seconds in Ruby; the server expects ms.
|
|
219
|
+
wire[:ready_at] = (job[:ready_at].to_f * 1000).to_i if job[
|
|
220
|
+
:ready_at
|
|
221
|
+
]
|
|
222
|
+
wire[:retry_limit] = job[:retry_limit] if job[:retry_limit]
|
|
223
|
+
wire[:backoff] = job[:backoff] if job[:backoff]
|
|
224
|
+
wire[:retention] = job[:retention] if job[:retention]
|
|
225
|
+
wire[:unique_key] = job[:unique_key] if job[:unique_key]
|
|
226
|
+
wire[:unique_while] = job[:unique_while].to_s if job[:unique_while]
|
|
227
|
+
wire[:batch] = job[:batch] if job[:batch]
|
|
228
|
+
wire
|
|
229
|
+
end
|
|
219
230
|
}
|
|
220
231
|
|
|
221
232
|
response = post("/jobs/bulk", body)
|
|
@@ -238,31 +249,56 @@ module Zizq
|
|
|
238
249
|
# The `filter` parameter accepts a jq expression for filtering jobs by
|
|
239
250
|
# payload content (e.g. `.user_id == 42`).
|
|
240
251
|
#
|
|
252
|
+
# Range filters (`priority`, `attempts`, `ready_at`) accept exact values or
|
|
253
|
+
# inclusive ranges.
|
|
254
|
+
#
|
|
241
255
|
# @rbs id: (String | Array[String])?
|
|
242
256
|
# @rbs status: (String | Array[String])?
|
|
243
257
|
# @rbs queue: (String | Array[String])?
|
|
244
258
|
# @rbs type: (String | Array[String])?
|
|
245
259
|
# @rbs filter: String?
|
|
260
|
+
# @rbs priority: (Integer | Range[Integer?])?
|
|
261
|
+
# @rbs ready_at: (Zizq::to_f | Range[Zizq::to_f?])?
|
|
262
|
+
# @rbs attempts: (Integer | Range[Integer?])?
|
|
246
263
|
# @rbs from: String?
|
|
247
264
|
# @rbs order: Zizq::sort_direction?
|
|
248
265
|
# @rbs limit: Integer?
|
|
249
266
|
# @rbs return: Resources::JobPage
|
|
250
|
-
def list_jobs(
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
267
|
+
def list_jobs(
|
|
268
|
+
id: nil,
|
|
269
|
+
status: nil,
|
|
270
|
+
queue: nil,
|
|
271
|
+
type: nil,
|
|
272
|
+
filter: nil,
|
|
273
|
+
priority: nil,
|
|
274
|
+
ready_at: nil,
|
|
275
|
+
attempts: nil,
|
|
276
|
+
from: nil,
|
|
277
|
+
order: nil,
|
|
278
|
+
limit: nil
|
|
279
|
+
)
|
|
280
|
+
options = {
|
|
281
|
+
id:,
|
|
282
|
+
status:,
|
|
283
|
+
queue:,
|
|
284
|
+
type:,
|
|
285
|
+
filter:,
|
|
286
|
+
priority: encode_range(priority),
|
|
287
|
+
ready_at: encode_range(ready_at) { |v| (v.to_f * 1000).to_i },
|
|
288
|
+
attempts: encode_range(attempts),
|
|
289
|
+
from:,
|
|
290
|
+
order:,
|
|
291
|
+
limit:
|
|
292
|
+
}.compact #: Hash[Symbol, untyped]
|
|
259
293
|
|
|
260
294
|
multi_keys = %i[id status queue type]
|
|
261
295
|
params = build_where_params(options, multi_keys:)
|
|
262
296
|
|
|
263
297
|
# An empty filter ([] or "") matches nothing — short-circuit.
|
|
264
298
|
multi_keys.each do |key|
|
|
265
|
-
|
|
299
|
+
if params[key] == ""
|
|
300
|
+
return Resources::JobPage.new(self, { "jobs" => [], "pages" => {} })
|
|
301
|
+
end
|
|
266
302
|
end
|
|
267
303
|
|
|
268
304
|
response = get("/jobs", params:)
|
|
@@ -280,21 +316,36 @@ module Zizq
|
|
|
280
316
|
# @rbs queue: (String | Array[String])?
|
|
281
317
|
# @rbs type: (String | Array[String])?
|
|
282
318
|
# @rbs filter: String?
|
|
319
|
+
# @rbs priority: (Integer | Range[Integer?])?
|
|
320
|
+
# @rbs ready_at: (Zizq::to_f | Range[Zizq::to_f?])?
|
|
321
|
+
# @rbs attempts: (Integer | Range[Integer?])?
|
|
283
322
|
# @rbs return: Integer
|
|
284
|
-
def count_jobs(
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
323
|
+
def count_jobs(
|
|
324
|
+
id: nil,
|
|
325
|
+
status: nil,
|
|
326
|
+
queue: nil,
|
|
327
|
+
type: nil,
|
|
328
|
+
filter: nil,
|
|
329
|
+
priority: nil,
|
|
330
|
+
ready_at: nil,
|
|
331
|
+
attempts: nil
|
|
332
|
+
)
|
|
333
|
+
options = {
|
|
334
|
+
id:,
|
|
335
|
+
status:,
|
|
336
|
+
queue:,
|
|
337
|
+
type:,
|
|
338
|
+
filter:,
|
|
339
|
+
priority: encode_range(priority),
|
|
340
|
+
ready_at: encode_range(ready_at) { |v| (v.to_f * 1000).to_i },
|
|
341
|
+
attempts: encode_range(attempts)
|
|
342
|
+
}.compact #: Hash[Symbol, untyped]
|
|
290
343
|
|
|
291
344
|
multi_keys = %i[id status queue type]
|
|
292
345
|
params = build_where_params(options, multi_keys:)
|
|
293
346
|
|
|
294
347
|
# An empty filter ([] or "") matches nothing — short-circuit.
|
|
295
|
-
multi_keys.each
|
|
296
|
-
return 0 if params[key] == ""
|
|
297
|
-
end
|
|
348
|
+
multi_keys.each { |key| return 0 if params[key] == "" }
|
|
298
349
|
|
|
299
350
|
response = get("/jobs/count", params:)
|
|
300
351
|
data = handle_response!(response, expected: 200)
|
|
@@ -327,9 +378,7 @@ module Zizq
|
|
|
327
378
|
params = build_where_params(filter_params, multi_keys:)
|
|
328
379
|
|
|
329
380
|
# An empty multi-value filter matches nothing — short-circuit.
|
|
330
|
-
multi_keys.each
|
|
331
|
-
return 0 if params[key] == ""
|
|
332
|
-
end
|
|
381
|
+
multi_keys.each { |key| return 0 if params[key] == "" }
|
|
333
382
|
|
|
334
383
|
response = delete("/jobs", params:)
|
|
335
384
|
data = handle_response!(response, expected: 200)
|
|
@@ -372,17 +421,24 @@ module Zizq
|
|
|
372
421
|
# @rbs backoff: (Zizq::backoff | singleton(Zizq::RESET) | singleton(Zizq::UNCHANGED))?
|
|
373
422
|
# @rbs retention: (Zizq::retention | singleton(Zizq::RESET) | singleton(Zizq::UNCHANGED))?
|
|
374
423
|
# @rbs return: Resources::Job
|
|
375
|
-
def update_job(
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
424
|
+
def update_job(
|
|
425
|
+
id,
|
|
426
|
+
queue: UNCHANGED,
|
|
427
|
+
priority: UNCHANGED,
|
|
428
|
+
ready_at: UNCHANGED,
|
|
429
|
+
retry_limit: UNCHANGED,
|
|
430
|
+
backoff: UNCHANGED,
|
|
431
|
+
retention: UNCHANGED
|
|
432
|
+
)
|
|
433
|
+
body =
|
|
434
|
+
build_set_body(
|
|
435
|
+
queue:,
|
|
436
|
+
priority:,
|
|
437
|
+
ready_at:,
|
|
438
|
+
retry_limit:,
|
|
439
|
+
backoff:,
|
|
440
|
+
retention:
|
|
441
|
+
)
|
|
386
442
|
response = patch("/jobs/#{enc(id)}", body)
|
|
387
443
|
data = handle_response!(response, expected: 200)
|
|
388
444
|
Resources::Job.new(self, data)
|
|
@@ -408,9 +464,7 @@ module Zizq
|
|
|
408
464
|
params = build_where_params(filter_params, multi_keys:)
|
|
409
465
|
|
|
410
466
|
# An empty multi-value filter matches nothing — short-circuit.
|
|
411
|
-
multi_keys.each
|
|
412
|
-
return 0 if params[key] == ""
|
|
413
|
-
end
|
|
467
|
+
multi_keys.each { |key| return 0 if params[key] == "" }
|
|
414
468
|
|
|
415
469
|
body = validate_and_build_set(**apply)
|
|
416
470
|
response = patch("/jobs", body, params:)
|
|
@@ -562,7 +616,14 @@ module Zizq
|
|
|
562
616
|
# @rbs timezone: String?
|
|
563
617
|
# @rbs paused: bool?
|
|
564
618
|
# @rbs return: Resources::CronEntry
|
|
565
|
-
def add_cron_group_entry(
|
|
619
|
+
def add_cron_group_entry(
|
|
620
|
+
group,
|
|
621
|
+
name:,
|
|
622
|
+
expression:,
|
|
623
|
+
job:,
|
|
624
|
+
timezone: nil,
|
|
625
|
+
paused: nil
|
|
626
|
+
)
|
|
566
627
|
body = build_cron_entry(name:, expression:, job:, timezone:, paused:)
|
|
567
628
|
response = post("/crons/#{enc(group)}/entries", body)
|
|
568
629
|
data = handle_response!(response, expected: 201)
|
|
@@ -580,8 +641,16 @@ module Zizq
|
|
|
580
641
|
# @rbs timezone: String?
|
|
581
642
|
# @rbs paused: bool?
|
|
582
643
|
# @rbs return: Resources::CronEntry
|
|
583
|
-
def replace_cron_group_entry(
|
|
584
|
-
|
|
644
|
+
def replace_cron_group_entry(
|
|
645
|
+
group,
|
|
646
|
+
entry,
|
|
647
|
+
expression:,
|
|
648
|
+
job:,
|
|
649
|
+
timezone: nil,
|
|
650
|
+
paused: nil
|
|
651
|
+
)
|
|
652
|
+
body =
|
|
653
|
+
build_cron_entry(name: entry, expression:, job:, timezone:, paused:)
|
|
585
654
|
response = put("/crons/#{enc(group)}/entries/#{enc(entry)}", body)
|
|
586
655
|
data = handle_response!(response, expected: 200)
|
|
587
656
|
Resources::CronEntry.new(self, data)
|
|
@@ -594,7 +663,8 @@ module Zizq
|
|
|
594
663
|
# @rbs paused: bool
|
|
595
664
|
# @rbs return: Resources::CronEntry
|
|
596
665
|
def update_cron_group_entry(group, entry, paused:)
|
|
597
|
-
response =
|
|
666
|
+
response =
|
|
667
|
+
patch("/crons/#{enc(group)}/entries/#{enc(entry)}", { paused: })
|
|
598
668
|
data = handle_response!(response, expected: 200)
|
|
599
669
|
Resources::CronEntry.new(self, data)
|
|
600
670
|
end
|
|
@@ -683,7 +753,14 @@ module Zizq
|
|
|
683
753
|
# @rbs retry_at: Float?
|
|
684
754
|
# @rbs kill: bool
|
|
685
755
|
# @rbs return: Resources::Job
|
|
686
|
-
def report_failure(
|
|
756
|
+
def report_failure(
|
|
757
|
+
id,
|
|
758
|
+
message:,
|
|
759
|
+
error_type: nil,
|
|
760
|
+
backtrace: nil,
|
|
761
|
+
retry_at: nil,
|
|
762
|
+
kill: false
|
|
763
|
+
)
|
|
687
764
|
body = { message: } #: Hash[Symbol, untyped]
|
|
688
765
|
body[:error_type] = error_type if error_type
|
|
689
766
|
body[:backtrace] = backtrace if backtrace
|
|
@@ -732,7 +809,14 @@ module Zizq
|
|
|
732
809
|
# @rbs worker_id: String?
|
|
733
810
|
# @rbs &block: (Resources::Job) -> void
|
|
734
811
|
# @rbs return: void
|
|
735
|
-
def take_jobs(
|
|
812
|
+
def take_jobs(
|
|
813
|
+
prefetch: 1,
|
|
814
|
+
queues: [],
|
|
815
|
+
worker_id: nil,
|
|
816
|
+
on_connect: nil,
|
|
817
|
+
on_response: nil,
|
|
818
|
+
&block
|
|
819
|
+
)
|
|
736
820
|
raise ArgumentError, "take_jobs requires a block" unless block
|
|
737
821
|
|
|
738
822
|
params = { prefetch: } #: Hash[Symbol, untyped]
|
|
@@ -746,7 +830,10 @@ module Zizq
|
|
|
746
830
|
response = stream_http.get(path, headers)
|
|
747
831
|
|
|
748
832
|
begin
|
|
749
|
-
|
|
833
|
+
unless response.status == 200
|
|
834
|
+
raise StreamError,
|
|
835
|
+
"take jobs stream returned HTTP #{response.status}"
|
|
836
|
+
end
|
|
750
837
|
on_connect&.call
|
|
751
838
|
on_response&.call(response)
|
|
752
839
|
|
|
@@ -760,11 +847,17 @@ module Zizq
|
|
|
760
847
|
body = response.body || []
|
|
761
848
|
|
|
762
849
|
case @format
|
|
763
|
-
when :json
|
|
764
|
-
|
|
850
|
+
when :json
|
|
851
|
+
self.class.parse_ndjson(body, &wrapper)
|
|
852
|
+
when :msgpack
|
|
853
|
+
self.class.parse_msgpack_stream(body, &wrapper)
|
|
765
854
|
end
|
|
766
855
|
ensure
|
|
767
|
-
|
|
856
|
+
begin
|
|
857
|
+
response.close
|
|
858
|
+
rescue StandardError
|
|
859
|
+
nil
|
|
860
|
+
end
|
|
768
861
|
end
|
|
769
862
|
end
|
|
770
863
|
rescue SocketError,
|
|
@@ -847,7 +940,10 @@ module Zizq
|
|
|
847
940
|
# so that resource objects like Page can follow links without resorting
|
|
848
941
|
# to `.send`.
|
|
849
942
|
def get_path(path) #: (String) -> Hash[String, untyped]
|
|
850
|
-
response =
|
|
943
|
+
response =
|
|
944
|
+
request do |http|
|
|
945
|
+
consume_response(http.get(path, { "accept" => @content_type }))
|
|
946
|
+
end
|
|
851
947
|
handle_response!(response, expected: 200)
|
|
852
948
|
end
|
|
853
949
|
|
|
@@ -860,9 +956,7 @@ module Zizq
|
|
|
860
956
|
|
|
861
957
|
# Build a relative path with optional query parameters.
|
|
862
958
|
def build_path(path, params: {}) #: (String, ?params: Hash[Symbol, untyped]) -> String
|
|
863
|
-
unless params.empty?
|
|
864
|
-
path = "#{path}?#{URI.encode_www_form(params)}"
|
|
865
|
-
end
|
|
959
|
+
path = "#{path}?#{URI.encode_www_form(params)}" unless params.empty?
|
|
866
960
|
path
|
|
867
961
|
end
|
|
868
962
|
|
|
@@ -874,13 +968,19 @@ module Zizq
|
|
|
874
968
|
# @rbs timezone: String?
|
|
875
969
|
# @rbs paused: bool?
|
|
876
970
|
# @rbs return: Hash[Symbol, untyped]
|
|
877
|
-
def build_cron_entry(
|
|
971
|
+
def build_cron_entry(
|
|
972
|
+
name: nil,
|
|
973
|
+
expression: nil,
|
|
974
|
+
job: nil,
|
|
975
|
+
timezone: nil,
|
|
976
|
+
paused: nil
|
|
977
|
+
)
|
|
878
978
|
{
|
|
879
979
|
name:,
|
|
880
980
|
expression:,
|
|
881
981
|
timezone:,
|
|
882
982
|
paused:,
|
|
883
|
-
job: build_cron_job(**job)
|
|
983
|
+
job: build_cron_job(**job)
|
|
884
984
|
}.compact
|
|
885
985
|
end
|
|
886
986
|
|
|
@@ -898,15 +998,17 @@ module Zizq
|
|
|
898
998
|
# @rbs unique_key: String?
|
|
899
999
|
# @rbs unique_while: Zizq::unique_scope?
|
|
900
1000
|
# @rbs return: Hash[Symbol, untyped]
|
|
901
|
-
def build_cron_job(
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1001
|
+
def build_cron_job(
|
|
1002
|
+
type: nil,
|
|
1003
|
+
queue: nil,
|
|
1004
|
+
payload: nil,
|
|
1005
|
+
priority: nil,
|
|
1006
|
+
retry_limit: nil,
|
|
1007
|
+
backoff: nil,
|
|
1008
|
+
retention: nil,
|
|
1009
|
+
unique_key: nil,
|
|
1010
|
+
unique_while: nil
|
|
1011
|
+
)
|
|
910
1012
|
job = { type:, queue:, payload: } #: Hash[Symbol, untyped]
|
|
911
1013
|
job[:priority] = priority if priority
|
|
912
1014
|
job[:retry_limit] = retry_limit if retry_limit
|
|
@@ -926,9 +1028,64 @@ module Zizq
|
|
|
926
1028
|
# @rbs queue: (String | Array[String])?
|
|
927
1029
|
# @rbs type: (String | Array[String])?
|
|
928
1030
|
# @rbs filter: String?
|
|
1031
|
+
# @rbs priority: (Integer | Range[Integer?])?
|
|
1032
|
+
# @rbs ready_at: (Zizq::to_f | Range[Zizq::to_f?])?
|
|
1033
|
+
# @rbs attempts: (Integer | Range[Integer?])?
|
|
929
1034
|
# @rbs return: Hash[Symbol, untyped]
|
|
930
|
-
def validate_where(
|
|
931
|
-
|
|
1035
|
+
def validate_where(
|
|
1036
|
+
id: nil,
|
|
1037
|
+
status: nil,
|
|
1038
|
+
queue: nil,
|
|
1039
|
+
type: nil,
|
|
1040
|
+
filter: nil,
|
|
1041
|
+
priority: nil,
|
|
1042
|
+
ready_at: nil,
|
|
1043
|
+
attempts: nil
|
|
1044
|
+
)
|
|
1045
|
+
{
|
|
1046
|
+
id:,
|
|
1047
|
+
status:,
|
|
1048
|
+
queue:,
|
|
1049
|
+
type:,
|
|
1050
|
+
filter:,
|
|
1051
|
+
priority: encode_range(priority),
|
|
1052
|
+
ready_at: encode_range(ready_at) { |v| (v.to_f * 1000).to_i },
|
|
1053
|
+
attempts: encode_range(attempts)
|
|
1054
|
+
}.compact
|
|
1055
|
+
end
|
|
1056
|
+
|
|
1057
|
+
# Encode an Integer or Range filter into the server's query format.
|
|
1058
|
+
#
|
|
1059
|
+
# Accepted shapes:
|
|
1060
|
+
#
|
|
1061
|
+
# - `nil` -> `nil` (no filter sent)
|
|
1062
|
+
# - `Integer` -> `"N"` (single value)
|
|
1063
|
+
# - `(a..b)` -> `"A..B"` (inclusive both ends)
|
|
1064
|
+
# - `(a..)` -> `"A.."` (lower bound only)
|
|
1065
|
+
# - `(..b)` -> `"..B"` (upper bound only)
|
|
1066
|
+
#
|
|
1067
|
+
# Exclusive ranges (`a...b`, `a...`, `...b`) raise `ArgumentError` — the
|
|
1068
|
+
# server only supports inclusive bounds. A bound transformation block can
|
|
1069
|
+
# convert values before formatting (e.g. seconds -> ms for `ready_at`).
|
|
1070
|
+
#
|
|
1071
|
+
# @rbs value: untyped
|
|
1072
|
+
# @rbs &block: ?(untyped) -> Integer
|
|
1073
|
+
# @rbs return: String?
|
|
1074
|
+
def encode_range(value, &block)
|
|
1075
|
+
block ||= ->(v) { v.to_i }
|
|
1076
|
+
|
|
1077
|
+
case value
|
|
1078
|
+
when nil
|
|
1079
|
+
nil
|
|
1080
|
+
when Range
|
|
1081
|
+
if value.exclude_end?
|
|
1082
|
+
raise ArgumentError,
|
|
1083
|
+
"exclusive ranges are not supported by the server; use an inclusive range (a..b) instead"
|
|
1084
|
+
end
|
|
1085
|
+
"#{value.begin&.then(&block)}..#{value.end&.then(&block)}"
|
|
1086
|
+
else
|
|
1087
|
+
block.call(value).to_s
|
|
1088
|
+
end
|
|
932
1089
|
end
|
|
933
1090
|
|
|
934
1091
|
# Validate set parameters via keyword args (rejects unknown keys) and
|
|
@@ -941,13 +1098,22 @@ module Zizq
|
|
|
941
1098
|
# @rbs backoff: (Zizq::backoff | singleton(Zizq::RESET) | singleton(Zizq::UNCHANGED))?
|
|
942
1099
|
# @rbs retention: (Zizq::retention | singleton(Zizq::RESET) | singleton(Zizq::UNCHANGED))?
|
|
943
1100
|
# @rbs return: Hash[Symbol, untyped]
|
|
944
|
-
def validate_and_build_set(
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
1101
|
+
def validate_and_build_set(
|
|
1102
|
+
queue: UNCHANGED,
|
|
1103
|
+
priority: UNCHANGED,
|
|
1104
|
+
ready_at: UNCHANGED,
|
|
1105
|
+
retry_limit: UNCHANGED,
|
|
1106
|
+
backoff: UNCHANGED,
|
|
1107
|
+
retention: UNCHANGED
|
|
1108
|
+
)
|
|
1109
|
+
build_set_body(
|
|
1110
|
+
queue:,
|
|
1111
|
+
priority:,
|
|
1112
|
+
ready_at:,
|
|
1113
|
+
retry_limit:,
|
|
1114
|
+
backoff:,
|
|
1115
|
+
retention:
|
|
1116
|
+
)
|
|
951
1117
|
end
|
|
952
1118
|
|
|
953
1119
|
# Build the JSON body hash for a PATCH request from set parameters.
|
|
@@ -958,26 +1124,40 @@ module Zizq
|
|
|
958
1124
|
# - Other values are converted to their wire format.
|
|
959
1125
|
#
|
|
960
1126
|
# @rbs return: Hash[Symbol, untyped]
|
|
961
|
-
def build_set_body(
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1127
|
+
def build_set_body(
|
|
1128
|
+
queue: UNCHANGED,
|
|
1129
|
+
priority: UNCHANGED,
|
|
1130
|
+
ready_at: UNCHANGED,
|
|
1131
|
+
retry_limit: UNCHANGED,
|
|
1132
|
+
backoff: UNCHANGED,
|
|
1133
|
+
retention: UNCHANGED
|
|
1134
|
+
)
|
|
967
1135
|
body = {} #: Hash[Symbol, untyped]
|
|
968
1136
|
|
|
969
1137
|
unless queue.equal?(UNCHANGED)
|
|
970
|
-
|
|
1138
|
+
if queue.nil?
|
|
1139
|
+
raise ArgumentError,
|
|
1140
|
+
"queue cannot be nil; use Zizq::RESET to clear or Zizq::UNCHANGED to leave as-is"
|
|
1141
|
+
end
|
|
971
1142
|
body[:queue] = queue
|
|
972
1143
|
end
|
|
973
1144
|
|
|
974
1145
|
unless priority.equal?(UNCHANGED)
|
|
975
|
-
|
|
1146
|
+
if priority.nil?
|
|
1147
|
+
raise ArgumentError,
|
|
1148
|
+
"priority cannot be nil; use Zizq::RESET to clear or Zizq::UNCHANGED to leave as-is"
|
|
1149
|
+
end
|
|
976
1150
|
body[:priority] = priority
|
|
977
1151
|
end
|
|
978
1152
|
|
|
979
1153
|
unless ready_at.equal?(UNCHANGED)
|
|
980
|
-
body[:ready_at] =
|
|
1154
|
+
body[:ready_at] = (
|
|
1155
|
+
if ready_at.equal?(RESET)
|
|
1156
|
+
nil
|
|
1157
|
+
else
|
|
1158
|
+
(ready_at.to_f * 1000).to_i
|
|
1159
|
+
end
|
|
1160
|
+
)
|
|
981
1161
|
end
|
|
982
1162
|
|
|
983
1163
|
unless retry_limit.equal?(UNCHANGED)
|
|
@@ -1001,8 +1181,12 @@ module Zizq
|
|
|
1001
1181
|
nil
|
|
1002
1182
|
else
|
|
1003
1183
|
ret = {} #: Hash[Symbol, Integer]
|
|
1004
|
-
ret[:completed_ms] = (
|
|
1005
|
-
|
|
1184
|
+
ret[:completed_ms] = (
|
|
1185
|
+
retention[:completed].to_f * 1000
|
|
1186
|
+
).to_i if retention[:completed]
|
|
1187
|
+
ret[:dead_ms] = (retention[:dead].to_f * 1000).to_i if retention[
|
|
1188
|
+
:dead
|
|
1189
|
+
]
|
|
1006
1190
|
ret
|
|
1007
1191
|
end
|
|
1008
1192
|
end
|
|
@@ -1025,22 +1209,32 @@ module Zizq
|
|
|
1025
1209
|
|
|
1026
1210
|
def encode_body(body) #: (Hash[Symbol, untyped]) -> String
|
|
1027
1211
|
case @format
|
|
1028
|
-
when :msgpack
|
|
1029
|
-
|
|
1030
|
-
|
|
1212
|
+
when :msgpack
|
|
1213
|
+
MessagePack.pack(body)
|
|
1214
|
+
when :json
|
|
1215
|
+
JSON.generate(body)
|
|
1216
|
+
else
|
|
1217
|
+
raise ArgumentError, "Unknown format: #{@format}"
|
|
1031
1218
|
end
|
|
1032
1219
|
end
|
|
1033
1220
|
|
|
1034
1221
|
def decode_body(data, content_type: nil) #: (String, ?content_type: String?) -> Hash[String, untyped]
|
|
1035
|
-
format =
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1222
|
+
format =
|
|
1223
|
+
case content_type
|
|
1224
|
+
when /msgpack/
|
|
1225
|
+
:msgpack
|
|
1226
|
+
when /json/
|
|
1227
|
+
:json
|
|
1228
|
+
else
|
|
1229
|
+
@format
|
|
1230
|
+
end
|
|
1040
1231
|
case format
|
|
1041
|
-
when :msgpack
|
|
1042
|
-
|
|
1043
|
-
|
|
1232
|
+
when :msgpack
|
|
1233
|
+
MessagePack.unpack(data)
|
|
1234
|
+
when :json
|
|
1235
|
+
JSON.parse(data)
|
|
1236
|
+
else
|
|
1237
|
+
raise ArgumentError, "Unknown format: #{format}"
|
|
1044
1238
|
end
|
|
1045
1239
|
end
|
|
1046
1240
|
|
|
@@ -1060,7 +1254,11 @@ module Zizq
|
|
|
1060
1254
|
# Read the response body and close it, returning a RawResponse that is
|
|
1061
1255
|
# safe to use outside the reactor.
|
|
1062
1256
|
def consume_response(response) #: (untyped) -> RawResponse
|
|
1063
|
-
RawResponse.new(
|
|
1257
|
+
RawResponse.new(
|
|
1258
|
+
status: response.status,
|
|
1259
|
+
body: response.read,
|
|
1260
|
+
content_type: response.headers["content-type"]
|
|
1261
|
+
)
|
|
1064
1262
|
ensure
|
|
1065
1263
|
response.close
|
|
1066
1264
|
end
|
|
@@ -1146,24 +1344,22 @@ module Zizq
|
|
|
1146
1344
|
end
|
|
1147
1345
|
|
|
1148
1346
|
def thread_local_http(key, endpoint) #: (Symbol, Async::HTTP::Endpoint) -> Async::HTTP::Client
|
|
1149
|
-
Thread.current.thread_variable_get(key) ||
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
@
|
|
1153
|
-
|
|
1347
|
+
Thread.current.thread_variable_get(key) ||
|
|
1348
|
+
begin
|
|
1349
|
+
client = Async::HTTP::Client.new(endpoint)
|
|
1350
|
+
@mutex.synchronize do
|
|
1351
|
+
@http_clients.reject! { |ref| !ref.weakref_alive? }
|
|
1352
|
+
@http_clients << WeakRef.new(client)
|
|
1353
|
+
end
|
|
1354
|
+
Thread.current.thread_variable_set(key, client)
|
|
1355
|
+
client
|
|
1154
1356
|
end
|
|
1155
|
-
Thread.current.thread_variable_set(key, client)
|
|
1156
|
-
client
|
|
1157
|
-
end
|
|
1158
1357
|
end
|
|
1159
1358
|
|
|
1160
1359
|
def get(path, params: {}) #: (String, ?params: Hash[Symbol, untyped]) -> RawResponse
|
|
1161
1360
|
request do |http|
|
|
1162
1361
|
consume_response(
|
|
1163
|
-
http.get(
|
|
1164
|
-
build_path(path, params:),
|
|
1165
|
-
{"accept" => @content_type}
|
|
1166
|
-
)
|
|
1362
|
+
http.get(build_path(path, params:), { "accept" => @content_type })
|
|
1167
1363
|
)
|
|
1168
1364
|
end
|
|
1169
1365
|
end
|
|
@@ -1173,7 +1369,7 @@ module Zizq
|
|
|
1173
1369
|
consume_response(
|
|
1174
1370
|
http.post(
|
|
1175
1371
|
build_path(path),
|
|
1176
|
-
{"content-type" => @content_type, "accept" => @content_type},
|
|
1372
|
+
{ "content-type" => @content_type, "accept" => @content_type },
|
|
1177
1373
|
Protocol::HTTP::Body::Buffered.wrap(encode_body(body))
|
|
1178
1374
|
)
|
|
1179
1375
|
)
|
|
@@ -1183,10 +1379,7 @@ module Zizq
|
|
|
1183
1379
|
def raw_post(path) #: (String) -> RawResponse
|
|
1184
1380
|
request do |http|
|
|
1185
1381
|
consume_response(
|
|
1186
|
-
http.post(
|
|
1187
|
-
build_path(path),
|
|
1188
|
-
{"accept" => @content_type}
|
|
1189
|
-
)
|
|
1382
|
+
http.post(build_path(path), { "accept" => @content_type })
|
|
1190
1383
|
)
|
|
1191
1384
|
end
|
|
1192
1385
|
end
|
|
@@ -1196,7 +1389,7 @@ module Zizq
|
|
|
1196
1389
|
consume_response(
|
|
1197
1390
|
http.put(
|
|
1198
1391
|
build_path(path),
|
|
1199
|
-
{"content-type" => @content_type, "accept" => @content_type},
|
|
1392
|
+
{ "content-type" => @content_type, "accept" => @content_type },
|
|
1200
1393
|
Protocol::HTTP::Body::Buffered.wrap(encode_body(body))
|
|
1201
1394
|
)
|
|
1202
1395
|
)
|
|
@@ -1206,10 +1399,7 @@ module Zizq
|
|
|
1206
1399
|
def delete(path, params: {}) #: (String, ?params: Hash[Symbol, untyped]) -> RawResponse
|
|
1207
1400
|
request do |http|
|
|
1208
1401
|
consume_response(
|
|
1209
|
-
http.delete(
|
|
1210
|
-
build_path(path, params:),
|
|
1211
|
-
{"accept" => @content_type}
|
|
1212
|
-
)
|
|
1402
|
+
http.delete(build_path(path, params:), { "accept" => @content_type })
|
|
1213
1403
|
)
|
|
1214
1404
|
end
|
|
1215
1405
|
end
|
|
@@ -1219,7 +1409,7 @@ module Zizq
|
|
|
1219
1409
|
consume_response(
|
|
1220
1410
|
http.patch(
|
|
1221
1411
|
build_path(path, params:),
|
|
1222
|
-
{"content-type" => @content_type, "accept" => @content_type},
|
|
1412
|
+
{ "content-type" => @content_type, "accept" => @content_type },
|
|
1223
1413
|
Protocol::HTTP::Body::Buffered.wrap(encode_body(body))
|
|
1224
1414
|
)
|
|
1225
1415
|
)
|
|
@@ -1237,18 +1427,24 @@ module Zizq
|
|
|
1237
1427
|
return nil if status == 204
|
|
1238
1428
|
decode_body(response.body, content_type: ct)
|
|
1239
1429
|
else
|
|
1240
|
-
body =
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1430
|
+
body =
|
|
1431
|
+
begin
|
|
1432
|
+
decode_body(response.body, content_type: ct)
|
|
1433
|
+
rescue StandardError
|
|
1434
|
+
nil
|
|
1435
|
+
end
|
|
1245
1436
|
message = body&.fetch("error", nil) || "HTTP #{status}"
|
|
1246
|
-
error_class =
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1437
|
+
error_class =
|
|
1438
|
+
case status
|
|
1439
|
+
when 404
|
|
1440
|
+
NotFoundError
|
|
1441
|
+
when 400..499
|
|
1442
|
+
ClientError
|
|
1443
|
+
when 500..599
|
|
1444
|
+
ServerError
|
|
1445
|
+
else
|
|
1446
|
+
ResponseError
|
|
1447
|
+
end
|
|
1252
1448
|
raise error_class.new(message, status: status, body: body)
|
|
1253
1449
|
end
|
|
1254
1450
|
end
|