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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de36f4f8204a5d5b0c64e77b78249ed34ea6c667ceb6b03dbdca2a291cb0f330
4
- data.tar.gz: 7cbb62aac1788627bb1047b474e5ca915c5dca059bc6f00be7a2f5adca798a7a
3
+ metadata.gz: a94a61282aba9442ace4ca8a3a302f75a843b14a76bd7d7748f006d5c8bf0888
4
+ data.tar.gz: 202f0842b2a2b22d8fce753c197635ea456ac94955f6f266f806471d01d06a7f
5
5
  SHA512:
6
- metadata.gz: 95b80cec6e0bb1704bd1244b37c4434b5d5467139e35c61bae39f342825a3e136a413aa55c0a0b7de379024bcdee3486e08cb211062a9d406011093a68de2899
7
- data.tar.gz: '09952b2563e139e9987473b53524ea993f1c803ea0320082c166132053919f3401cad65c8dc063923837c3b1fe8a505e3e27dcd9ce6a20724313c1d6f82c00e3'
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]
@@ -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::job_class
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(
@@ -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