zizq 0.2.1 → 0.3.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 +19 -0
- data/lib/zizq/bulk_enqueue.rb +1 -1
- data/lib/zizq/client.rb +205 -7
- data/lib/zizq/crontab.rb +263 -0
- data/lib/zizq/crontab_builder.rb +52 -0
- data/lib/zizq/crontab_entry.rb +182 -0
- data/lib/zizq/crontab_entry_builder.rb +133 -0
- data/lib/zizq/enqueue_with.rb +1 -1
- data/lib/zizq/job.rb +6 -2
- data/lib/zizq/query.rb +2 -2
- data/lib/zizq/resources/cron_entry.rb +27 -0
- data/lib/zizq/resources/cron_group.rb +23 -0
- data/lib/zizq/resources/job.rb +4 -36
- data/lib/zizq/resources/job_template.rb +46 -0
- data/lib/zizq/resources.rb +3 -0
- data/lib/zizq/version.rb +1 -1
- data/lib/zizq.rb +106 -18
- data/sig/generated/zizq/bulk_enqueue.rbs +2 -2
- data/sig/generated/zizq/client.rbs +115 -0
- data/sig/generated/zizq/crontab.rbs +141 -0
- data/sig/generated/zizq/crontab_builder.rbs +35 -0
- data/sig/generated/zizq/crontab_entry.rbs +98 -0
- data/sig/generated/zizq/crontab_entry_builder.rbs +92 -0
- data/sig/generated/zizq/enqueue_with.rbs +2 -2
- data/sig/generated/zizq/query.rbs +4 -4
- data/sig/generated/zizq/resources/cron_entry.rbs +29 -0
- data/sig/generated/zizq/resources/cron_group.rbs +21 -0
- data/sig/generated/zizq/resources/job.rbs +4 -26
- data/sig/generated/zizq/resources/job_template.rbs +31 -0
- data/sig/generated/zizq.rbs +78 -4
- data/sig/zizq.rbs +24 -17
- metadata +20 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a94a61282aba9442ace4ca8a3a302f75a843b14a76bd7d7748f006d5c8bf0888
|
|
4
|
+
data.tar.gz: 202f0842b2a2b22d8fce753c197635ea456ac94955f6f266f806471d01d06a7f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 03cb04925c7aac0aaaee30f86e27448641ec4864b903bedeee83cd61b0d0ed803655dd69c8ed586c4b83faa3d8d910c13c589398ca3157f130c569fa3edce5e9
|
|
7
|
+
data.tar.gz: b3a2c1232195698681ae743e04db5aff883c4e82e36bab9d1735b7459c7db5fdbcb9829171fa30318cec4ac01dc34432a79c2760824f4d2db93c89fbda12da58
|
data/README.md
CHANGED
|
@@ -18,9 +18,28 @@ This is the official Zizq client library for Ruby.
|
|
|
18
18
|
* Scheduled jobs
|
|
19
19
|
* Configurable backoff policies
|
|
20
20
|
* Configurable job retention policies
|
|
21
|
+
* Recurring jobs (cron)
|
|
21
22
|
* Job introspection and management APIs, with support for `jq` query filters
|
|
22
23
|
* Unique jobs
|
|
23
24
|
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
> [!NOTE]
|
|
28
|
+
> If you have not yet installed the Zizq server, follow the
|
|
29
|
+
> [Getting Started](https://zizq.io/docs/getting-started) guide first.
|
|
30
|
+
|
|
31
|
+
Add it to your application's `Gemfile`.
|
|
32
|
+
|
|
33
|
+
``` ruby
|
|
34
|
+
gem 'zizq', '~> 0.3.0'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Or install it manually:
|
|
38
|
+
|
|
39
|
+
```shell
|
|
40
|
+
$ gem install zizq -v 0.3.0
|
|
41
|
+
```
|
|
42
|
+
|
|
24
43
|
## Example
|
|
25
44
|
|
|
26
45
|
> [!TIP]
|
data/lib/zizq/bulk_enqueue.rb
CHANGED
|
@@ -22,7 +22,7 @@ module Zizq
|
|
|
22
22
|
# Collect a job class enqueue. Accepts the same arguments as
|
|
23
23
|
# `Zizq.enqueue`.
|
|
24
24
|
#
|
|
25
|
-
# @rbs job_class: Class & Zizq::
|
|
25
|
+
# @rbs job_class: Class & Zizq::JobConfig
|
|
26
26
|
# @rbs args: Array[untyped]
|
|
27
27
|
# @rbs kwargs: Hash[Symbol, untyped]
|
|
28
28
|
# @rbs &block: ?(EnqueueRequest) -> void
|
data/lib/zizq/client.rb
CHANGED
|
@@ -204,7 +204,7 @@ module Zizq
|
|
|
204
204
|
|
|
205
205
|
# Get a single job by ID.
|
|
206
206
|
def get_job(id) #: (String) -> Resources::Job
|
|
207
|
-
response = get("/jobs/#{id}")
|
|
207
|
+
response = get("/jobs/#{enc(id)}")
|
|
208
208
|
data = handle_response!(response, expected: 200)
|
|
209
209
|
Resources::Job.new(self, data)
|
|
210
210
|
end
|
|
@@ -285,7 +285,7 @@ module Zizq
|
|
|
285
285
|
# @rbs id: String
|
|
286
286
|
# @rbs return: void
|
|
287
287
|
def delete_job(id)
|
|
288
|
-
response = delete("/jobs/#{id}")
|
|
288
|
+
response = delete("/jobs/#{enc(id)}")
|
|
289
289
|
handle_response!(response, expected: [200, 204])
|
|
290
290
|
nil
|
|
291
291
|
end
|
|
@@ -342,7 +342,7 @@ module Zizq
|
|
|
342
342
|
queue:, priority:, ready_at:,
|
|
343
343
|
retry_limit:, backoff:, retention:
|
|
344
344
|
)
|
|
345
|
-
response = patch("/jobs/#{id}", body)
|
|
345
|
+
response = patch("/jobs/#{enc(id)}", body)
|
|
346
346
|
data = handle_response!(response, expected: 200)
|
|
347
347
|
Resources::Job.new(self, data)
|
|
348
348
|
end
|
|
@@ -383,7 +383,7 @@ module Zizq
|
|
|
383
383
|
# @rbs attempt: Integer
|
|
384
384
|
# @rbs return: Resources::ErrorRecord
|
|
385
385
|
def get_error(id, attempt:)
|
|
386
|
-
response = get("/jobs/#{id}/errors/#{attempt}")
|
|
386
|
+
response = get("/jobs/#{enc(id)}/errors/#{enc(attempt.to_s)}")
|
|
387
387
|
data = handle_response!(response, expected: 200)
|
|
388
388
|
Resources::ErrorRecord.new(self, data)
|
|
389
389
|
end
|
|
@@ -397,7 +397,7 @@ module Zizq
|
|
|
397
397
|
# @rbs return: Resources::ErrorPage
|
|
398
398
|
def list_errors(id, from: nil, order: nil, limit: nil)
|
|
399
399
|
params = { from:, order:, limit: }.compact #: Hash[Symbol, untyped]
|
|
400
|
-
response = get("/jobs/#{id}/errors", params:)
|
|
400
|
+
response = get("/jobs/#{enc(id)}/errors", params:)
|
|
401
401
|
data = handle_response!(response, expected: 200)
|
|
402
402
|
Resources::ErrorPage.new(self, data)
|
|
403
403
|
end
|
|
@@ -422,6 +422,136 @@ module Zizq
|
|
|
422
422
|
data["queues"]
|
|
423
423
|
end
|
|
424
424
|
|
|
425
|
+
# List all cron group names.
|
|
426
|
+
#
|
|
427
|
+
# @rbs return: Array[String]
|
|
428
|
+
def list_cron_groups
|
|
429
|
+
response = get("/crons")
|
|
430
|
+
data = handle_response!(response, expected: 200)
|
|
431
|
+
data["crons"]
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# Fetch a cron group and all its entries.
|
|
435
|
+
#
|
|
436
|
+
# @rbs name: String
|
|
437
|
+
# @rbs return: Resources::CronGroup
|
|
438
|
+
def get_cron_group(name)
|
|
439
|
+
response = get("/crons/#{enc(name)}")
|
|
440
|
+
data = handle_response!(response, expected: 200)
|
|
441
|
+
Resources::CronGroup.new(self, data)
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
# Create or replace an entire cron group.
|
|
445
|
+
#
|
|
446
|
+
# Entries not present in the request are removed. Entries with unchanged
|
|
447
|
+
# expressions preserve their scheduling state.
|
|
448
|
+
#
|
|
449
|
+
# @rbs name: String
|
|
450
|
+
# @rbs paused: bool?
|
|
451
|
+
# @rbs entries: Array[Zizq::cron_entry_params]
|
|
452
|
+
# @rbs return: Resources::CronGroup
|
|
453
|
+
def replace_cron_group(name, paused: nil, entries: [])
|
|
454
|
+
body = {
|
|
455
|
+
paused:,
|
|
456
|
+
entries: entries.map { |entry| build_cron_entry(**entry) }
|
|
457
|
+
}.compact
|
|
458
|
+
response = put("/crons/#{enc(name)}", body)
|
|
459
|
+
data = handle_response!(response, expected: 200)
|
|
460
|
+
Resources::CronGroup.new(self, data)
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
# Update group-level fields (currently just pause/unpause).
|
|
464
|
+
#
|
|
465
|
+
# @rbs name: String
|
|
466
|
+
# @rbs paused: bool?
|
|
467
|
+
# @rbs return: Resources::CronGroup
|
|
468
|
+
def update_cron_group(name, paused: nil)
|
|
469
|
+
response = patch("/crons/#{enc(name)}", { paused: }.compact)
|
|
470
|
+
data = handle_response!(response, expected: 200)
|
|
471
|
+
Resources::CronGroup.new(self, data)
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
# Delete a cron group and all its entries.
|
|
475
|
+
#
|
|
476
|
+
# @rbs name: String
|
|
477
|
+
# @rbs return: void
|
|
478
|
+
def delete_cron_group(name)
|
|
479
|
+
response = delete("/crons/#{enc(name)}")
|
|
480
|
+
handle_response!(response, expected: 204)
|
|
481
|
+
nil
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
# Fetch a single cron entry.
|
|
485
|
+
#
|
|
486
|
+
# @rbs group: String
|
|
487
|
+
# @rbs entry: String
|
|
488
|
+
# @rbs return: Resources::CronEntry
|
|
489
|
+
def get_cron_group_entry(group, entry)
|
|
490
|
+
response = get("/crons/#{enc(group)}/entries/#{enc(entry)}")
|
|
491
|
+
data = handle_response!(response, expected: 200)
|
|
492
|
+
Resources::CronEntry.new(self, data)
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
# Add a single entry to a cron group (creates the group if needed).
|
|
496
|
+
#
|
|
497
|
+
# Raises a ClientError (409 Conflict) if an entry with the same name
|
|
498
|
+
# already exists.
|
|
499
|
+
#
|
|
500
|
+
# @rbs group: String
|
|
501
|
+
# @rbs name: String
|
|
502
|
+
# @rbs expression: String
|
|
503
|
+
# @rbs job: Zizq::cron_job_params
|
|
504
|
+
# @rbs timezone: String?
|
|
505
|
+
# @rbs paused: bool?
|
|
506
|
+
# @rbs return: Resources::CronEntry
|
|
507
|
+
def add_cron_group_entry(group, name:, expression:, job:, timezone: nil, paused: nil)
|
|
508
|
+
body = build_cron_entry(name:, expression:, job:, timezone:, paused:)
|
|
509
|
+
response = post("/crons/#{enc(group)}/entries", body)
|
|
510
|
+
data = handle_response!(response, expected: 201)
|
|
511
|
+
Resources::CronEntry.new(self, data)
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
# Create or replace a single cron entry.
|
|
515
|
+
#
|
|
516
|
+
# Preserves scheduling state if the expression is unchanged.
|
|
517
|
+
#
|
|
518
|
+
# @rbs group: String
|
|
519
|
+
# @rbs entry: String
|
|
520
|
+
# @rbs expression: String
|
|
521
|
+
# @rbs job: Zizq::cron_job_params
|
|
522
|
+
# @rbs timezone: String?
|
|
523
|
+
# @rbs paused: bool?
|
|
524
|
+
# @rbs return: Resources::CronEntry
|
|
525
|
+
def replace_cron_group_entry(group, entry, expression:, job:, timezone: nil, paused: nil)
|
|
526
|
+
body = build_cron_entry(name: entry, expression:, job:, timezone:, paused:)
|
|
527
|
+
response = put("/crons/#{enc(group)}/entries/#{enc(entry)}", body)
|
|
528
|
+
data = handle_response!(response, expected: 200)
|
|
529
|
+
Resources::CronEntry.new(self, data)
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
# Update entry-level fields (currently just pause/unpause).
|
|
533
|
+
#
|
|
534
|
+
# @rbs group: String
|
|
535
|
+
# @rbs entry: String
|
|
536
|
+
# @rbs paused: bool
|
|
537
|
+
# @rbs return: Resources::CronEntry
|
|
538
|
+
def update_cron_group_entry(group, entry, paused:)
|
|
539
|
+
response = patch("/crons/#{enc(group)}/entries/#{enc(entry)}", { paused: })
|
|
540
|
+
data = handle_response!(response, expected: 200)
|
|
541
|
+
Resources::CronEntry.new(self, data)
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
# Delete a single cron entry.
|
|
545
|
+
#
|
|
546
|
+
# @rbs group: String
|
|
547
|
+
# @rbs entry: String
|
|
548
|
+
# @rbs return: void
|
|
549
|
+
def delete_cron_group_entry(group, entry)
|
|
550
|
+
response = delete("/crons/#{enc(group)}/entries/#{enc(entry)}")
|
|
551
|
+
handle_response!(response, expected: 204)
|
|
552
|
+
nil
|
|
553
|
+
end
|
|
554
|
+
|
|
425
555
|
# Mark a job as successfully completed (ack).
|
|
426
556
|
#
|
|
427
557
|
# If this method (or [`#report_failure`]) is not called upon job
|
|
@@ -439,7 +569,7 @@ module Zizq
|
|
|
439
569
|
# The Zizq server sends heartbeat messages to connected workers so that
|
|
440
570
|
# it can quickly detect and handle disconnected clients.
|
|
441
571
|
def report_success(id) #: (String) -> nil
|
|
442
|
-
response = raw_post("/jobs/#{id}/success")
|
|
572
|
+
response = raw_post("/jobs/#{enc(id)}/success")
|
|
443
573
|
handle_response!(response, expected: 204)
|
|
444
574
|
nil
|
|
445
575
|
end
|
|
@@ -503,7 +633,7 @@ module Zizq
|
|
|
503
633
|
body[:retry_at] = (retry_at * 1000).to_i if retry_at
|
|
504
634
|
body[:kill] = kill if kill
|
|
505
635
|
|
|
506
|
-
response = post("/jobs/#{id}/failure", body)
|
|
636
|
+
response = post("/jobs/#{enc(id)}/failure", body)
|
|
507
637
|
data = handle_response!(response, expected: 200)
|
|
508
638
|
Resources::Job.new(self, data)
|
|
509
639
|
end
|
|
@@ -660,6 +790,11 @@ module Zizq
|
|
|
660
790
|
|
|
661
791
|
private
|
|
662
792
|
|
|
793
|
+
# URL-encode a single path segment.
|
|
794
|
+
def enc(value) #: (String) -> String
|
|
795
|
+
URI.encode_uri_component(value)
|
|
796
|
+
end
|
|
797
|
+
|
|
663
798
|
# Build a relative path with optional query parameters.
|
|
664
799
|
def build_path(path, params: {}) #: (String, ?params: Hash[Symbol, untyped]) -> String
|
|
665
800
|
unless params.empty?
|
|
@@ -668,6 +803,57 @@ module Zizq
|
|
|
668
803
|
path
|
|
669
804
|
end
|
|
670
805
|
|
|
806
|
+
# Validate and build a cron entry body from keyword arguments.
|
|
807
|
+
#
|
|
808
|
+
# @rbs name: String
|
|
809
|
+
# @rbs expression: String
|
|
810
|
+
# @rbs job: Zizq::cron_job_params
|
|
811
|
+
# @rbs timezone: String?
|
|
812
|
+
# @rbs paused: bool?
|
|
813
|
+
# @rbs return: Hash[Symbol, untyped]
|
|
814
|
+
def build_cron_entry(name: nil, expression: nil, job: nil, timezone: nil, paused: nil)
|
|
815
|
+
{
|
|
816
|
+
name:,
|
|
817
|
+
expression:,
|
|
818
|
+
timezone:,
|
|
819
|
+
paused:,
|
|
820
|
+
job: build_cron_job(**job),
|
|
821
|
+
}.compact
|
|
822
|
+
end
|
|
823
|
+
|
|
824
|
+
# Validate and build a cron job template from keyword arguments.
|
|
825
|
+
#
|
|
826
|
+
# Uses keyword args so that unknown keys raise ArgumentError.
|
|
827
|
+
#
|
|
828
|
+
# @rbs type: String
|
|
829
|
+
# @rbs queue: String
|
|
830
|
+
# @rbs payload: untyped
|
|
831
|
+
# @rbs priority: Integer?
|
|
832
|
+
# @rbs retry_limit: Integer?
|
|
833
|
+
# @rbs backoff: Zizq::backoff?
|
|
834
|
+
# @rbs retention: Zizq::retention?
|
|
835
|
+
# @rbs unique_key: String?
|
|
836
|
+
# @rbs unique_while: Zizq::unique_scope?
|
|
837
|
+
# @rbs return: Hash[Symbol, untyped]
|
|
838
|
+
def build_cron_job(type: nil,
|
|
839
|
+
queue: nil,
|
|
840
|
+
payload: nil,
|
|
841
|
+
priority: nil,
|
|
842
|
+
retry_limit: nil,
|
|
843
|
+
backoff: nil,
|
|
844
|
+
retention: nil,
|
|
845
|
+
unique_key: nil,
|
|
846
|
+
unique_while: nil)
|
|
847
|
+
job = { type:, queue:, payload: } #: Hash[Symbol, untyped]
|
|
848
|
+
job[:priority] = priority if priority
|
|
849
|
+
job[:retry_limit] = retry_limit if retry_limit
|
|
850
|
+
job[:backoff] = backoff if backoff
|
|
851
|
+
job[:retention] = retention if retention
|
|
852
|
+
job[:unique_key] = unique_key if unique_key
|
|
853
|
+
job[:unique_while] = unique_while.to_s if unique_while
|
|
854
|
+
job
|
|
855
|
+
end
|
|
856
|
+
|
|
671
857
|
# Validate and normalize filter parameters for bulk operations.
|
|
672
858
|
#
|
|
673
859
|
# Uses keyword arguments so that unknown keys raise ArgumentError.
|
|
@@ -942,6 +1128,18 @@ module Zizq
|
|
|
942
1128
|
end
|
|
943
1129
|
end
|
|
944
1130
|
|
|
1131
|
+
def put(path, body) #: (String, Hash[Symbol, untyped]) -> RawResponse
|
|
1132
|
+
request do |http|
|
|
1133
|
+
consume_response(
|
|
1134
|
+
http.put(
|
|
1135
|
+
build_path(path),
|
|
1136
|
+
{"content-type" => @content_type, "accept" => @content_type},
|
|
1137
|
+
Protocol::HTTP::Body::Buffered.wrap(encode_body(body))
|
|
1138
|
+
)
|
|
1139
|
+
)
|
|
1140
|
+
end
|
|
1141
|
+
end
|
|
1142
|
+
|
|
945
1143
|
def delete(path, params: {}) #: (String, ?params: Hash[Symbol, untyped]) -> RawResponse
|
|
946
1144
|
request do |http|
|
|
947
1145
|
consume_response(
|
data/lib/zizq/crontab.rb
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
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
|
+
# Represents a Crontab schedule defined on the Zizq server.
|
|
9
|
+
#
|
|
10
|
+
# This requires a Pro license on the Zizq server.
|
|
11
|
+
#
|
|
12
|
+
# The actual data is lazily fetched when first accessed.
|
|
13
|
+
#
|
|
14
|
+
# Crontabs are used to define collections of recurring jobs that run on a
|
|
15
|
+
# specified schedule, such as at 2am on every Monday. Each entry on the
|
|
16
|
+
# Crontab is a single job enqueue, which the Zizq server automatically
|
|
17
|
+
# triggers at the correct point in time. Zizq uses standard Cron expression
|
|
18
|
+
# syntax (with support for seconds via 6-fields) to define entries.
|
|
19
|
+
#
|
|
20
|
+
# Entire schedules, and individual entries on a schedule, can be paused and
|
|
21
|
+
# resumed.
|
|
22
|
+
#
|
|
23
|
+
# By default schedules operate in the system time zone of the Zizq server
|
|
24
|
+
# but an explicit IANA timezone name can be specified when defining the
|
|
25
|
+
# Crontab.
|
|
26
|
+
class Crontab
|
|
27
|
+
# The name of the cron group that this schedule is backed by.
|
|
28
|
+
attr_reader :name #: String
|
|
29
|
+
|
|
30
|
+
# True if this schedule is paused.
|
|
31
|
+
#
|
|
32
|
+
# When paused, the scheduler continues to run but does not enqueue any jobs
|
|
33
|
+
# and only advances the timer.
|
|
34
|
+
attr_writer :paused #: bool?
|
|
35
|
+
|
|
36
|
+
# Initialize the Crontab with the given group name.
|
|
37
|
+
#
|
|
38
|
+
# @rbs name: String
|
|
39
|
+
def initialize(name)
|
|
40
|
+
@building = false #: bool
|
|
41
|
+
@materialized = false #: bool
|
|
42
|
+
@name = name #: String
|
|
43
|
+
@entries = {} #: Hash[String, Zizq::CrontabEntry]
|
|
44
|
+
@paused = false #: bool?
|
|
45
|
+
@paused_at = nil #: Float?
|
|
46
|
+
@resumed_at = nil #: Float?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Fetch data from the Zizq server if not already fetched.
|
|
50
|
+
#
|
|
51
|
+
# Once fetched, this method becomes a no-op, unless #clear is called to
|
|
52
|
+
# remove the fetched data.
|
|
53
|
+
def materialize #: () -> self
|
|
54
|
+
unless @building || @materialized
|
|
55
|
+
materialize_with(Zizq.client.get_cron_group(name))
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
self
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Clear materialized data that was fetched from the Zizq server.
|
|
62
|
+
#
|
|
63
|
+
# This triggers a refetch when the data is next accessed.
|
|
64
|
+
def clear #: () -> self
|
|
65
|
+
@entries = {}
|
|
66
|
+
@paused = nil
|
|
67
|
+
@paused_at = nil
|
|
68
|
+
@resumed_at = nil
|
|
69
|
+
@materialized = false
|
|
70
|
+
|
|
71
|
+
self
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Delete this entire Crontab schedule and its entries.
|
|
75
|
+
def delete! #: () -> void
|
|
76
|
+
Zizq.client.delete_cron_group(name)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Pause this entire Crontab schedule.
|
|
80
|
+
#
|
|
81
|
+
# All entries will stop enqueueing jobs, but the server continues to
|
|
82
|
+
# advance the schedule until it is resumed.
|
|
83
|
+
def pause! #: () -> void
|
|
84
|
+
materialize_with(Zizq.client.update_cron_group(name, paused: true))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Resume this Crontab schedule if it is currently paused.
|
|
88
|
+
#
|
|
89
|
+
# Individual entries that are paused will remain paused.
|
|
90
|
+
def resume! #: () -> void
|
|
91
|
+
materialize_with(Zizq.client.update_cron_group(name, paused: false))
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Check if this schedule is currently paused.
|
|
95
|
+
def paused #: () -> bool?
|
|
96
|
+
materialize
|
|
97
|
+
@paused
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Check if this schedule is currently paused.
|
|
101
|
+
#
|
|
102
|
+
# Alias of #paused.
|
|
103
|
+
def paused? = paused #: () -> bool?
|
|
104
|
+
|
|
105
|
+
# Return the timestamp at which this Crontab schedule was last paused.
|
|
106
|
+
def paused_at #: () -> Float?
|
|
107
|
+
materialize
|
|
108
|
+
@paused_at
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Return the timestamp at which this Crontab schedule was last resumed.
|
|
112
|
+
def resumed_at #: () -> Float?
|
|
113
|
+
materialize
|
|
114
|
+
@resumed_at
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Return a Hash of Zizq::CrontabEntry instances keyed by their names.
|
|
118
|
+
#
|
|
119
|
+
# Each entry specifies the cron expression at which it executes,
|
|
120
|
+
# information about when it last/next enqueued a job, and details of the
|
|
121
|
+
# job that the entry enqueues.
|
|
122
|
+
def entries #: () -> Hash[String, Zizq::CrontabEntry]
|
|
123
|
+
materialize
|
|
124
|
+
@entries
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Redefine (replace) this Crontab schedule with another.
|
|
128
|
+
#
|
|
129
|
+
# This is equivalent to calling `Zizq.define_crontab` and is idempotent
|
|
130
|
+
# when given the same schedule more than once.
|
|
131
|
+
#
|
|
132
|
+
# @rbs ?timezone: String?
|
|
133
|
+
# @rbs ?paused: bool?
|
|
134
|
+
# @rbs &block: (Zizq::CrontabBuilder) -> void
|
|
135
|
+
# @rbs return: self
|
|
136
|
+
def redefine(timezone: nil, paused: nil, &block)
|
|
137
|
+
@building = true
|
|
138
|
+
|
|
139
|
+
yield CrontabBuilder.new(self, timezone:, paused:)
|
|
140
|
+
|
|
141
|
+
materialize_with(
|
|
142
|
+
Zizq.client.replace_cron_group(
|
|
143
|
+
name,
|
|
144
|
+
paused:,
|
|
145
|
+
entries: entries.values.map(&:to_params),
|
|
146
|
+
),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
@building = false
|
|
150
|
+
|
|
151
|
+
self
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Return a handle for the specified Zizq::CrontabEntry.
|
|
155
|
+
#
|
|
156
|
+
# The entry can be paused or resumed is isolation, can be deleted entirely
|
|
157
|
+
# or can be redefined (replaced) with another entry.
|
|
158
|
+
#
|
|
159
|
+
# @rbs name: String
|
|
160
|
+
# @rbs return: Zizq::CrontabEntry
|
|
161
|
+
def entry(name)
|
|
162
|
+
materialize
|
|
163
|
+
entries.fetch(name) do
|
|
164
|
+
entry = materialize_entry_with(
|
|
165
|
+
Zizq.client.get_cron_group_entry(self.name, name),
|
|
166
|
+
)
|
|
167
|
+
entries[name] = entry
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Define (or redefine) an entry with this Crontab schedule.
|
|
172
|
+
#
|
|
173
|
+
# Defining the same entry more than once is idempotent. If the entry does
|
|
174
|
+
# not exist, it is added to the schedule. If the entry already exists, it
|
|
175
|
+
# replaces the current entry.
|
|
176
|
+
#
|
|
177
|
+
# The return value is a Zizq::CrontabEntryBuilder instance, on which the
|
|
178
|
+
# caller must call one of the enqueue methods (`enqueue`, `enqueue_raw`,
|
|
179
|
+
# optionally chained onto `enqueue_with`, exactly the same as a regular job
|
|
180
|
+
# enqueue).
|
|
181
|
+
#
|
|
182
|
+
# All enqueue options are supported *except* `delay` and `ready_at` which
|
|
183
|
+
# make no sense for recurring jobs.
|
|
184
|
+
#
|
|
185
|
+
# Bulk enqueues are not supported.
|
|
186
|
+
#
|
|
187
|
+
# crontab.define_entry(
|
|
188
|
+
# "refresh_data_warehose",
|
|
189
|
+
# "*/15 * * * *",
|
|
190
|
+
# ).enqueue(RefreshDataWarehoseJob, incremental: true)
|
|
191
|
+
#
|
|
192
|
+
# @rbs name: String
|
|
193
|
+
# @rbs expression: String
|
|
194
|
+
# @rbs timezone: String?
|
|
195
|
+
# @rbs paused: bool?
|
|
196
|
+
# @rbs return: Zizq::CrontabEntryBuilder
|
|
197
|
+
def define_entry(name, expression, timezone: nil, paused: nil)
|
|
198
|
+
CrontabEntryBuilder.new(self, name, expression, timezone:, paused:) do |e|
|
|
199
|
+
entry = materialize_entry_with(
|
|
200
|
+
Zizq.client.replace_cron_group_entry(
|
|
201
|
+
self.name,
|
|
202
|
+
name,
|
|
203
|
+
expression: e.expression,
|
|
204
|
+
job: e.job.to_enqueue_params,
|
|
205
|
+
timezone: e.timezone,
|
|
206
|
+
paused: e.paused,
|
|
207
|
+
),
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
materialize # in case this was the first entry operation
|
|
211
|
+
|
|
212
|
+
entry
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
private
|
|
217
|
+
|
|
218
|
+
# @rbs result: Zizq::Resources::CronGroup
|
|
219
|
+
# @rbs return: self
|
|
220
|
+
def materialize_with(result)
|
|
221
|
+
@paused = result.paused?
|
|
222
|
+
@paused_at = result.paused_at
|
|
223
|
+
@resumed_at = result.resumed_at
|
|
224
|
+
|
|
225
|
+
@entries = result.entries.map do |entry|
|
|
226
|
+
[
|
|
227
|
+
entry.name,
|
|
228
|
+
materialize_entry_with(entry),
|
|
229
|
+
]
|
|
230
|
+
end.to_h
|
|
231
|
+
|
|
232
|
+
@materialized = true
|
|
233
|
+
|
|
234
|
+
self
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# @rbs result: Zizq::Resources::CronEntry
|
|
238
|
+
# @rbs return: Zizq::CrontabEntry
|
|
239
|
+
def materialize_entry_with(result)
|
|
240
|
+
CrontabEntry.new(
|
|
241
|
+
self,
|
|
242
|
+
result.name,
|
|
243
|
+
result.expression,
|
|
244
|
+
job: EnqueueRequest.new(
|
|
245
|
+
type: result.job.type,
|
|
246
|
+
queue: result.job.queue,
|
|
247
|
+
priority: result.job.priority,
|
|
248
|
+
payload: result.job.payload,
|
|
249
|
+
retry_limit: result.job.retry_limit,
|
|
250
|
+
backoff: result.job.backoff,
|
|
251
|
+
retention: result.job.retention,
|
|
252
|
+
unique_key: result.job.unique_key,
|
|
253
|
+
unique_while: result.job.unique_while,
|
|
254
|
+
),
|
|
255
|
+
paused: result.paused?,
|
|
256
|
+
paused_at: result.paused_at,
|
|
257
|
+
resumed_at: result.resumed_at,
|
|
258
|
+
last_enqueue_at: result.last_enqueue_at,
|
|
259
|
+
next_enqueue_at: result.next_enqueue_at,
|
|
260
|
+
)
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
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
|
+
# Builder used to configure a Zizq::Crontab instance.
|
|
9
|
+
#
|
|
10
|
+
# Instances of this class are returned from `Zizq.define_crontab`. See
|
|
11
|
+
# documentation for that method for usage.
|
|
12
|
+
class CrontabBuilder
|
|
13
|
+
# The Crontab instance that this builder configures.
|
|
14
|
+
attr_reader :target #: Zizq::Crontab
|
|
15
|
+
|
|
16
|
+
# Optional timezone to be applied to all entries by default.
|
|
17
|
+
attr_accessor :timezone #: String?
|
|
18
|
+
|
|
19
|
+
# Initialize the builder with the given Crontab instance.
|
|
20
|
+
#
|
|
21
|
+
# @rbs target: Zizq::Crontab
|
|
22
|
+
# @rbs timezone: String?
|
|
23
|
+
# @rbs paused: bool?
|
|
24
|
+
def initialize(target, timezone: nil, paused: nil)
|
|
25
|
+
@target = target.clear
|
|
26
|
+
@timezone = timezone
|
|
27
|
+
|
|
28
|
+
target.paused = paused
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Add or replace an entry on the schedule
|
|
32
|
+
#
|
|
33
|
+
# If no entry with the given name exists, it is added to schedule. If an
|
|
34
|
+
# entry with the same name exist, this entry replaces that entry. If the
|
|
35
|
+
# entry is the same as the original, the result is idempotent.
|
|
36
|
+
#
|
|
37
|
+
# @rbs name: String
|
|
38
|
+
# @rbs expression: String
|
|
39
|
+
# @rbs timezone: String?
|
|
40
|
+
# @rbs paused: bool?
|
|
41
|
+
# @rbs return: Zizq::CrontabEntryBuilder
|
|
42
|
+
def define_entry(name, expression, timezone: self.timezone, paused: nil)
|
|
43
|
+
CrontabEntryBuilder.new(
|
|
44
|
+
target,
|
|
45
|
+
name,
|
|
46
|
+
expression,
|
|
47
|
+
timezone:,
|
|
48
|
+
paused:,
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|