smplkit 3.0.119 → 3.0.120

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: 9911186cf0810c9d76cc16feb24f9a155d8c6708fce3edc406b581b5c9376f1c
4
- data.tar.gz: '009441ca636ade20f9f9a4cbec10d9ea9d2255b81549a2bc7e1274a94091e05d'
3
+ metadata.gz: f21cdd3b499aaffa45dbf92b679ed06324dd8c7f07a3e43ff72e24702be9cc30
4
+ data.tar.gz: 45a58c59df05b11c74a661f64c470e29aa7676d3e976107335d051384b8f6497
5
5
  SHA512:
6
- metadata.gz: 5558c39aa882f3bc403613ac51bf20b8f8dddbd4eb663126847a6b5f3a0c5fd961ec13f939c274e3c39c46f3cc2af8d2e44540b999c1e6e4b2239e1a1886e4ec
7
- data.tar.gz: e944872fdef51e90924897ee1173e0dcad70f36255e95a1372f99690f238478852890e54ba1c1a62bbebab2fd7b73f81cecbb4a27591cb79382a348edb46657c
6
+ metadata.gz: 2aa8129c15ef6c1e687a36b3672118c84aada54a01632816fef22d297c55424a69b12ae34c9e93757ea3115099ac8871094e67981c25c709f4c25fbdd80c7df8
7
+ data.tar.gz: c4d07ee7e073dd6d1387743aa1431dbcbc3e526c66055204f8059f8e6f3ec4433009666fea7c69ad91de1ce84c97bd34e66927b56b6b03119132c01bc57c29e3
@@ -12,8 +12,10 @@ require "time"
12
12
  #
13
13
  # client.jobs.{new_recurring_job,new_manual_job,schedule,get,list,delete,run,usage}
14
14
  # client.jobs.runs.{list,get,cancel,rerun}
15
+ # client.jobs.retry_policies.{new,list,get,delete}
15
16
  # Job#{save,delete,trigger,list_runs}
16
17
  # Run#{rerun,cancel}
18
+ # RetryPolicy#{save,delete}
17
19
  #
18
20
  # A job is enabled per environment: a recurring (cron) job may be enabled in
19
21
  # several environments at once, a manual job (no schedule) runs only when
@@ -47,16 +49,29 @@ module Smplkit
47
49
  # any of these environment keys. +nil+ falls back to the client's
48
50
  # configured environment (if any), otherwise covers every environment you
49
51
  # can access.
52
+ # @param triggers [Array<String>, nil] Restrict to runs started by any of
53
+ # these triggers (see {RunTrigger}) — e.g. +[RunTrigger::RETRY]+ for
54
+ # automatic retries. Serialized as a comma-joined +filter[trigger]+
55
+ # (any-of). +nil+ or empty covers every trigger.
56
+ # @param last_run_only [Boolean] When +true+, collapse the result to the
57
+ # last completed (succeeded / failed / canceled) run per
58
+ # job-and-environment; in-flight runs are excluded. The other filters
59
+ # apply first, then the collapse. Defaults to +false+; the query param is
60
+ # sent only when +true+.
50
61
  # @param page_size [Integer, nil] Maximum number of runs to return in this
51
62
  # page. +nil+ uses the server default.
52
63
  # @param after [String, nil] Opaque cursor from a previous page; returns
53
64
  # the runs that follow it. +nil+ starts from the first page.
54
65
  # @return [Array<Smplkit::Jobs::Run>] The runs in this page.
55
- def list(job: nil, environments: nil, page_size: nil, after: nil)
66
+ def list(job: nil, environments: nil, triggers: nil, last_run_only: false, page_size: nil, after: nil)
56
67
  opts = {}
57
68
  opts[:filter_job] = job unless job.nil?
58
69
  filter_environment = Jobs.resolve_environment_filter(environments, @environment)
59
70
  opts[:filter_environment] = filter_environment unless filter_environment.nil?
71
+ opts[:filter_trigger] = triggers.join(",") unless triggers.nil? || triggers.empty?
72
+ # The generated default would emit +last_run_only=false+ on every call;
73
+ # send the param only when explicitly requested.
74
+ opts[:last_run_only] = true if last_run_only
60
75
  opts[:page_size] = page_size unless page_size.nil?
61
76
  opts[:page_after] = after unless after.nil?
62
77
 
@@ -94,6 +109,144 @@ module Smplkit
94
109
  end
95
110
  end
96
111
 
112
+ # +client.jobs.retry_policies.*+ — manage reusable, account-global retry
113
+ # policies.
114
+ #
115
+ # A {RetryPolicy} is an active record: build one with {#new}, set fields, and
116
+ # call +#save+; then reference it from a job's +retry_policy+ (see
117
+ # {JobsClient#new_recurring_job} and {Job#set_retry_policy}). Retry policies
118
+ # are account-global — never environment-scoped.
119
+ class RetryPoliciesClient
120
+ # @param api [SmplkitGeneratedClient::Jobs::RetryPoliciesApi] The generated
121
+ # retry-policies API.
122
+ def initialize(api)
123
+ @api = api
124
+ end
125
+
126
+ # Construct an unsaved {RetryPolicy} bound to this client. Call +#save+ on
127
+ # the returned instance to create it.
128
+ #
129
+ # @param id [String] Caller-supplied unique identifier for the policy.
130
+ # Unique within the account and immutable; the service returns 409 if
131
+ # another live policy already uses this id.
132
+ # @param name [String] Human-readable name for the policy.
133
+ # @param max_retries [Integer] How many times a failed run is retried after
134
+ # the initial attempt — +3+ means up to 4 attempts total. +0+ disables
135
+ # retries. Maximum 10.
136
+ # @param backoff [String] How the wait between retries grows; one of
137
+ # {Backoff}.
138
+ # @param delay_seconds [Integer] The wait before a retry, in seconds — the
139
+ # constant wait for {Backoff::FIXED}, or the base that doubles each retry
140
+ # for {Backoff::EXPONENTIAL}.
141
+ # @param max_delay_seconds [Integer, nil] Ceiling on the wait between
142
+ # retries, for {Backoff::EXPONENTIAL} backoff only. +nil+ (the default)
143
+ # leaves it uncapped; omit it for {Backoff::FIXED}.
144
+ # @param retry_on [RetryOn, nil] Which failures to retry (see {RetryOn}).
145
+ # +nil+ (the default) retries nothing.
146
+ # @return [Smplkit::Jobs::RetryPolicy]
147
+ def new(id, name:, max_retries:, backoff:, delay_seconds:, max_delay_seconds: nil, retry_on: nil)
148
+ RetryPolicy.new(
149
+ self,
150
+ id: id, name: name, max_retries: max_retries, backoff: backoff,
151
+ delay_seconds: delay_seconds, max_delay_seconds: max_delay_seconds, retry_on: retry_on
152
+ )
153
+ end
154
+
155
+ # List retry policies in the account.
156
+ #
157
+ # @param name [String, nil] Return only policies whose name contains this
158
+ # text (case-insensitive). +nil+ lists all.
159
+ # @param page_number [Integer, nil] 1-based page to return. +nil+ returns
160
+ # the first page.
161
+ # @param page_size [Integer, nil] Maximum number of policies to return in
162
+ # this page. +nil+ uses the server default.
163
+ # @return [Array<Smplkit::Jobs::RetryPolicy>] The policies in this page.
164
+ def list(name: nil, page_number: nil, page_size: nil)
165
+ opts = {}
166
+ opts[:filter_name] = name unless name.nil?
167
+ opts[:page_number] = page_number unless page_number.nil?
168
+ opts[:page_size] = page_size unless page_size.nil?
169
+
170
+ resp = Jobs.call_api { @api.list_retry_policies(opts) }
171
+ (resp.data || []).map { |r| RetryPolicy.from_resource(r, client: self) }
172
+ end
173
+
174
+ # Fetch a single retry policy by its id.
175
+ #
176
+ # @param id [String] Identifier of the policy to fetch.
177
+ # @return [Smplkit::Jobs::RetryPolicy] The matching policy.
178
+ # @raise [Smplkit::NotFoundError] when no policy with this id exists.
179
+ def get(id)
180
+ resp = Jobs.call_api { @api.get_retry_policy(id) }
181
+ RetryPolicy.from_resource(resp.data, client: self)
182
+ end
183
+
184
+ # Delete a retry policy by its id.
185
+ #
186
+ # @param id [String] Identifier of the policy to delete.
187
+ # @return [nil]
188
+ def delete(id)
189
+ Jobs.call_api { @api.delete_retry_policy(id) }
190
+ nil
191
+ end
192
+
193
+ # @api private — POST a new retry policy. Called by {RetryPolicy#save} on
194
+ # unsaved instances. The jobs service requires a caller-supplied
195
+ # +data.id+ on create and 409s on conflict.
196
+ def _create_retry_policy(policy)
197
+ if policy.id.nil? || policy.id.empty?
198
+ raise ArgumentError, "RetryPolicy.id is required on create (caller-supplied key)"
199
+ end
200
+
201
+ resp = Jobs.call_api { @api.create_retry_policy(build_create_body(policy)) }
202
+ RetryPolicy.from_resource(resp.data, client: self)
203
+ end
204
+
205
+ # @api private — Full-replace PUT for an existing retry policy. Called by
206
+ # {RetryPolicy#save} on instances with +created_at+.
207
+ def _update_retry_policy(policy)
208
+ raise ArgumentError, "cannot update a RetryPolicy with no id" if policy.id.nil?
209
+
210
+ resp = Jobs.call_api { @api.update_retry_policy(policy.id, build_body(policy)) }
211
+ RetryPolicy.from_resource(resp.data, client: self)
212
+ end
213
+
214
+ private
215
+
216
+ # Build the generated attributes model shared by create and update. The
217
+ # +retry_on+ failure set is always sent; +max_delay_seconds+ is sent only
218
+ # when present (omitting it leaves the policy uncapped / is invalid for
219
+ # fixed backoff).
220
+ def build_retry_policy_attrs(policy)
221
+ attrs = {
222
+ name: policy.name,
223
+ max_retries: policy.max_retries,
224
+ backoff: policy.backoff,
225
+ delay_seconds: policy.delay_seconds,
226
+ retry_on: RetryOn.to_wire(policy.retry_on)
227
+ }
228
+ attrs[:max_delay_seconds] = policy.max_delay_seconds unless policy.max_delay_seconds.nil?
229
+ SmplkitGeneratedClient::Jobs::RetryPolicy.new(attrs)
230
+ end
231
+
232
+ def build_create_body(policy)
233
+ # Create uses the distinct RetryPolicyCreateRequest envelope; the jobs
234
+ # service requires data.id (the caller-supplied key) on create.
235
+ resource = SmplkitGeneratedClient::Jobs::RetryPolicyCreateResource.new(
236
+ id: policy.id.to_s, type: "retry_policy", attributes: build_retry_policy_attrs(policy)
237
+ )
238
+ SmplkitGeneratedClient::Jobs::RetryPolicyCreateRequest.new(data: resource)
239
+ end
240
+
241
+ def build_body(policy)
242
+ # Update path uses the generic RetryPolicyRequest envelope.
243
+ resource = SmplkitGeneratedClient::Jobs::RetryPolicyResource.new(
244
+ id: policy.id.to_s, type: "retry_policy", attributes: build_retry_policy_attrs(policy)
245
+ )
246
+ SmplkitGeneratedClient::Jobs::RetryPolicyRequest.new(data: resource)
247
+ end
248
+ end
249
+
97
250
  # The Smpl Jobs client — accessed via +client.jobs+.
98
251
  #
99
252
  # Unlike Config/Flags/Logging, Jobs has no live "phone-home" agent — no
@@ -103,8 +256,10 @@ module Smplkit
103
256
  #
104
257
  # client.jobs.{new_recurring_job,new_manual_job,schedule,get,list,delete,run,usage}
105
258
  # client.jobs.runs.{list,get,cancel,rerun}
259
+ # client.jobs.retry_policies.{new,list,get,delete}
106
260
  # Job#{save,delete,trigger,list_runs}
107
261
  # Run#{rerun,cancel}
262
+ # RetryPolicy#{save,delete}
108
263
  #
109
264
  # Build a standalone Smpl Jobs transport from resolved config.
110
265
  #
@@ -139,6 +294,10 @@ module Smplkit
139
294
  # @return [RunsClient] Run history and run actions (+client.jobs.runs+).
140
295
  attr_reader :runs
141
296
 
297
+ # @return [RetryPoliciesClient] Reusable retry policies
298
+ # (+client.jobs.retry_policies+).
299
+ attr_reader :retry_policies
300
+
142
301
  # @param api_key [String, nil] API key. When omitted, resolved from
143
302
  # +SMPLKIT_API_KEY+ or +~/.smplkit+.
144
303
  # @param profile [String, nil] Named +~/.smplkit+ profile section.
@@ -164,6 +323,7 @@ module Smplkit
164
323
  @environment = environment
165
324
  @api = SmplkitGeneratedClient::Jobs::JobsApi.new(auth)
166
325
  @runs = RunsClient.new(SmplkitGeneratedClient::Jobs::RunsApi.new(auth), environment: environment)
326
+ @retry_policies = RetryPoliciesClient.new(SmplkitGeneratedClient::Jobs::RetryPoliciesApi.new(auth))
167
327
  @usage_api = SmplkitGeneratedClient::Jobs::UsageApi.new(auth)
168
328
  end
169
329
 
@@ -191,8 +351,14 @@ module Smplkit
191
351
  # live job already uses this id.
192
352
  # @param name [String] Human-readable name for the job.
193
353
  # @param schedule [String] The base cadence — a 5-field cron expression
194
- # evaluated in UTC (e.g. +"0 2 * * *"+) that every environment inherits
195
- # unless it sets its own override.
354
+ # evaluated in the job's +timezone+ (UTC by default), e.g. +"0 2 * * *"+ —
355
+ # that every environment inherits unless it sets its own override.
356
+ # @param timezone [String, nil] Base IANA timezone the cron +schedule+ is
357
+ # evaluated in (e.g. +"America/New_York"+), DST-aware. +nil+ (the default)
358
+ # means UTC. Every environment inherits it unless it overrides it.
359
+ # @param retry_policy [String, nil] Base retry policy for failed runs — the
360
+ # id of a {Smplkit::Jobs::RetryPolicy}, overridable per environment. +nil+
361
+ # (the default) uses the built-in +"Default"+ policy, which never retries.
196
362
  # @param configuration [Smplkit::Jobs::HttpConfig] The HTTP request the job
197
363
  # sends each time it fires.
198
364
  # @param description [String, nil] Optional free-text description.
@@ -205,11 +371,11 @@ module Smplkit
205
371
  # @param concurrency_policy [String] How overlapping runs are handled.
206
372
  # Defaults to +"ALLOW"+.
207
373
  # @return [Smplkit::Jobs::Job]
208
- def new_recurring_job(id, name:, schedule:, configuration:, description: nil,
209
- environments: nil, concurrency_policy: "ALLOW")
374
+ def new_recurring_job(id, name:, schedule:, configuration:, timezone: nil, retry_policy: nil,
375
+ description: nil, environments: nil, concurrency_policy: "ALLOW")
210
376
  _new_job(
211
- id, name: name, schedule: schedule, configuration: configuration,
212
- description: description, environments: environments,
377
+ id, name: name, schedule: schedule, timezone: timezone, retry_policy: retry_policy,
378
+ configuration: configuration, description: description, environments: environments,
213
379
  concurrency_policy: concurrency_policy, environment: nil
214
380
  )
215
381
  end
@@ -234,12 +400,15 @@ module Smplkit
234
400
  # override). The job is triggerable only in environments enabled here.
235
401
  # @param concurrency_policy [String] How overlapping runs are handled.
236
402
  # Defaults to +"ALLOW"+.
403
+ # @param retry_policy [String, nil] Retry policy for failed runs — the id of
404
+ # a {Smplkit::Jobs::RetryPolicy}, overridable per environment. +nil+ (the
405
+ # default) uses the built-in +"Default"+ policy, which never retries.
237
406
  # @return [Smplkit::Jobs::Job]
238
407
  def new_manual_job(id, name:, configuration:, description: nil,
239
- environments: nil, concurrency_policy: "ALLOW")
408
+ environments: nil, concurrency_policy: "ALLOW", retry_policy: nil)
240
409
  _new_job(
241
- id, name: name, schedule: nil, configuration: configuration,
242
- description: description, environments: environments,
410
+ id, name: name, schedule: nil, retry_policy: retry_policy,
411
+ configuration: configuration, description: description, environments: environments,
243
412
  concurrency_policy: concurrency_policy, environment: nil
244
413
  )
245
414
  end
@@ -259,14 +428,17 @@ module Smplkit
259
428
  # @param description [String, nil] Optional free-text description.
260
429
  # @param concurrency_policy [String] How overlapping runs are handled.
261
430
  # Defaults to +"ALLOW"+.
431
+ # @param retry_policy [String, nil] Retry policy for failed runs — the id of
432
+ # a {Smplkit::Jobs::RetryPolicy}. +nil+ (the default) uses the built-in
433
+ # +"Default"+ policy, which never retries.
262
434
  # @param environment [String, nil] The environment the job is born in.
263
435
  # Defaults to the client's configured environment.
264
436
  # @return [Smplkit::Jobs::Job]
265
437
  def schedule(id, name:, schedule:, configuration:, description: nil,
266
- concurrency_policy: "ALLOW", environment: nil)
438
+ concurrency_policy: "ALLOW", retry_policy: nil, environment: nil)
267
439
  _new_job(
268
- id, name: name, schedule: schedule.iso8601, configuration: configuration,
269
- description: description, environments: nil,
440
+ id, name: name, schedule: schedule.iso8601, retry_policy: retry_policy,
441
+ configuration: configuration, description: description, environments: nil,
270
442
  concurrency_policy: concurrency_policy, environment: environment
271
443
  )
272
444
  end
@@ -377,12 +549,15 @@ module Smplkit
377
549
  # public constructors ({#new_recurring_job}, {#new_manual_job}, {#schedule})
378
550
  # funnel through here.
379
551
  def _new_job(id, name:, schedule:, configuration:, description:,
380
- environments:, concurrency_policy:, environment:)
552
+ environments:, concurrency_policy:, environment:,
553
+ timezone: nil, retry_policy: nil)
381
554
  Job.new(
382
555
  self,
383
556
  id: id,
384
557
  name: name,
385
558
  schedule: schedule,
559
+ timezone: timezone,
560
+ retry_policy: retry_policy,
386
561
  configuration: configuration,
387
562
  description: description,
388
563
  environments: Jobs.normalize_environments(environments),
@@ -394,15 +569,17 @@ module Smplkit
394
569
  # Convert the wrapper +environments+ map to the generated model hash.
395
570
  #
396
571
  # Each entry's +enabled+ is always written; a per-environment +schedule+
397
- # (cron) override, +timezone+ override, and +configuration+ override are
398
- # each sent only when present (omit to inherit the job's base +schedule+ /
399
- # +timezone+ / +configuration+). The read-only per-environment
400
- # +next_run_at+ is never written.
572
+ # (cron) override, +timezone+ override, +retry_policy+ override, and
573
+ # +configuration+ override are each sent only when present (omit to inherit
574
+ # the job's base +schedule+ / +timezone+ / +retry_policy+ /
575
+ # +configuration+). The read-only per-environment +next_run_at+ is never
576
+ # written.
401
577
  def environments_to_wire(environments)
402
578
  (environments || {}).each_with_object({}) do |(env_key, env), out|
403
579
  attrs = { enabled: env.enabled }
404
580
  attrs[:schedule] = env.schedule unless env.schedule.nil?
405
581
  attrs[:timezone] = env.timezone unless env.timezone.nil?
582
+ attrs[:retry_policy] = env.retry_policy unless env.retry_policy.nil?
406
583
  attrs[:configuration] = HttpConfig.to_wire(env.configuration) unless env.configuration.nil?
407
584
  out[env_key.to_s] = SmplkitGeneratedClient::Jobs::JobEnvironment.new(attrs)
408
585
  end
@@ -425,6 +602,9 @@ module Smplkit
425
602
  # explicit +null+ creates a manual job, omitting +timezone+ simply
426
603
  # inherits UTC — so it is sent only when present.)
427
604
  attrs[:timezone] = job.timezone unless job.timezone.nil?
605
+ # +retry_policy+ id; an unset +nil+ is omitted, leaving the server
606
+ # default (the built-in +Default+ policy, which never retries).
607
+ attrs[:retry_policy] = job.retry_policy unless job.retry_policy.nil?
428
608
  environments = job.environments
429
609
  attrs[:environments] = environments_to_wire(environments) unless environments.nil? || environments.empty?
430
610
  SmplkitGeneratedClient::Jobs::Job.new(attrs)
@@ -79,8 +79,8 @@ module Smplkit
79
79
  # (+{ enabled: true, schedule: "0 3 * * *", configuration: HttpConfig.new(...) }+)
80
80
  # so callers can use the lightweight hash form without importing the model. A
81
81
  # dict-form +configuration+ override is coerced to an {HttpConfig} so it
82
- # serializes on save; optional +schedule+ cron and +timezone+ overrides pass
83
- # through.
82
+ # serializes on save; optional +schedule+ cron, +timezone+, and
83
+ # +retry_policy+ overrides pass through.
84
84
  #
85
85
  # @api private
86
86
  def self.normalize_environments(environments)
@@ -96,6 +96,7 @@ module Smplkit
96
96
  enabled: value[:enabled] || value["enabled"] || false,
97
97
  schedule: value[:schedule] || value["schedule"],
98
98
  timezone: value[:timezone] || value["timezone"],
99
+ retry_policy: value[:retry_policy] || value["retry_policy"],
99
100
  configuration: cfg
100
101
  )
101
102
  end
@@ -120,13 +121,40 @@ module Smplkit
120
121
  #
121
122
  # - +MANUAL+: A +run+ / +trigger+ call started it on demand.
122
123
  # - +RERUN+: It repeats an earlier run.
124
+ # - +RETRY+: An automatic retry of a failed run, per the job's retry policy.
123
125
  # - +SCHEDULE+: The job's schedule fired.
124
126
  module RunTrigger
125
127
  MANUAL = "MANUAL"
126
128
  RERUN = "RERUN"
129
+ RETRY = "RETRY"
127
130
  SCHEDULE = "SCHEDULE"
128
131
 
129
- VALUES = [MANUAL, RERUN, SCHEDULE].freeze
132
+ VALUES = [MANUAL, RERUN, RETRY, SCHEDULE].freeze
133
+ end
134
+
135
+ # How the wait between retries grows (a retry policy's backoff strategy).
136
+ #
137
+ # - +EXPONENTIAL+: Double the wait each retry — +delay_seconds+, then +2×+,
138
+ # +4×+, … — capped at +max_delay_seconds+.
139
+ # - +FIXED+: Wait a constant +delay_seconds+ before every retry.
140
+ module Backoff
141
+ EXPONENTIAL = "exponential"
142
+ FIXED = "fixed"
143
+
144
+ VALUES = [EXPONENTIAL, FIXED].freeze
145
+ end
146
+
147
+ # A failure category a retry policy can retry on.
148
+ #
149
+ # - +CONNECTION_ERROR+: The endpoint could not be reached.
150
+ # - +NON_SUCCESS_STATUS+: Any non-success response, regardless of +statuses+.
151
+ # - +TIMEOUT+: The run did not complete in time.
152
+ module RetryReason
153
+ CONNECTION_ERROR = "CONNECTION_ERROR"
154
+ NON_SUCCESS_STATUS = "NON_SUCCESS_STATUS"
155
+ TIMEOUT = "TIMEOUT"
156
+
157
+ VALUES = [CONNECTION_ERROR, NON_SUCCESS_STATUS, TIMEOUT].freeze
130
158
  end
131
159
 
132
160
  # HTTP verb a job uses when it fires.
@@ -159,6 +187,46 @@ module Smplkit
159
187
  end
160
188
  end
161
189
 
190
+ # Which failures a retry policy retries. An empty {RetryOn} (both lists
191
+ # empty) retries nothing.
192
+ #
193
+ # @!attribute [rw] statuses
194
+ # @return [Array<Integer>] Response status codes to retry when a run fails
195
+ # because the response did not match the job's success status (e.g.
196
+ # +[429, 503]+ for rate-limit and unavailable). Each is a 3-digit HTTP
197
+ # code. Defaults to an empty list.
198
+ # @!attribute [rw] reasons
199
+ # @return [Array<String>] Failure categories to retry — see {RetryReason}.
200
+ # Defaults to an empty list.
201
+ RetryOn = Struct.new(:statuses, :reasons, keyword_init: true) do
202
+ def initialize(statuses: nil, reasons: nil)
203
+ super(statuses: statuses || [], reasons: reasons || [])
204
+ end
205
+
206
+ # @api private — Convert a {RetryOn} into the generated wire model.
207
+ # Reasons are serialized as their raw string values.
208
+ #
209
+ # @param src [RetryOn] The failure set to serialize.
210
+ # @return [SmplkitGeneratedClient::Jobs::RetryOn] The wire model.
211
+ def self.to_wire(src)
212
+ SmplkitGeneratedClient::Jobs::RetryOn.new(
213
+ statuses: Array(src.statuses),
214
+ reasons: Array(src.reasons).map(&:to_s)
215
+ )
216
+ end
217
+
218
+ # @api private — Build a {RetryOn} from the generated wire model. A +nil+
219
+ # wire value (the field was absent) yields an empty {RetryOn}.
220
+ #
221
+ # @param src [SmplkitGeneratedClient::Jobs::RetryOn, nil] The wire model.
222
+ # @return [RetryOn]
223
+ def self.from_wire(src)
224
+ return new if src.nil?
225
+
226
+ new(statuses: src.statuses || [], reasons: src.reasons || [])
227
+ end
228
+ end
229
+
162
230
  # A single name/value HTTP header on the request a job performs.
163
231
  #
164
232
  # @!attribute [rw] name
@@ -300,6 +368,10 @@ module Smplkit
300
368
  # present, it must be a valid IANA zone key (e.g. +"America/New_York"+);
301
369
  # it may be set on an environment that inherits the base schedule (it
302
370
  # need not also override {#schedule}). Sent on writes only when present.
371
+ # @!attribute [rw] retry_policy
372
+ # @return [String, nil] Optional per-environment retry-policy override — the
373
+ # id of a {RetryPolicy} (or +"Default"+). +nil+ (the default) inherits the
374
+ # job's base {Job#retry_policy}. Sent on writes only when present.
303
375
  # @!attribute [rw] configuration
304
376
  # @return [HttpConfig, nil] Optional per-environment request configuration
305
377
  # that fully replaces the job's base {Job#configuration} for this
@@ -310,8 +382,11 @@ module Smplkit
310
382
  # @return [String, nil] Read-only. The next scheduled fire time in this
311
383
  # environment. +nil+ when the environment is not enabled, or once a
312
384
  # one-off run has fired. Never written back on save.
313
- JobEnvironment = Struct.new(:enabled, :schedule, :timezone, :configuration, :next_run_at, keyword_init: true) do
314
- def initialize(enabled: false, schedule: nil, timezone: nil, configuration: nil, next_run_at: nil)
385
+ JobEnvironment = Struct.new(
386
+ :enabled, :schedule, :timezone, :retry_policy, :configuration, :next_run_at, keyword_init: true
387
+ ) do
388
+ def initialize(enabled: false, schedule: nil, timezone: nil, retry_policy: nil,
389
+ configuration: nil, next_run_at: nil)
315
390
  super
316
391
  end
317
392
 
@@ -328,6 +403,7 @@ module Smplkit
328
403
  enabled: src.enabled.nil? ? false : src.enabled,
329
404
  schedule: src.schedule,
330
405
  timezone: src.timezone,
406
+ retry_policy: src.retry_policy,
331
407
  configuration: cfg.nil? ? nil : HttpConfig.from_wire(cfg),
332
408
  next_run_at: src.next_run_at
333
409
  )
@@ -406,6 +482,13 @@ module Smplkit
406
482
  # present.
407
483
  attr_accessor :timezone
408
484
 
485
+ # @return [String, nil] The base retry policy for failed runs — the id of a
486
+ # {RetryPolicy}, overridable per environment via
487
+ # {JobEnvironment#retry_policy}. +nil+ (the default, omitted on the wire)
488
+ # uses the built-in +"Default"+ policy, which never retries. Set it with
489
+ # {#set_retry_policy}; sent on writes only when present.
490
+ attr_accessor :retry_policy
491
+
409
492
  # @return [HttpConfig] The base HTTP request to perform when the job fires.
410
493
  # Per-environment overrides live in {#environments}.
411
494
  attr_accessor :configuration
@@ -436,7 +519,7 @@ module Smplkit
436
519
  attr_accessor :birth_environment
437
520
 
438
521
  def initialize(client = nil, id:, name:, configuration:, schedule: nil,
439
- timezone: nil, description: nil, environments: nil,
522
+ timezone: nil, retry_policy: nil, description: nil, environments: nil,
440
523
  kind: nil, type: "http", concurrency_policy: "ALLOW",
441
524
  birth_environment: nil, created_at: nil,
442
525
  updated_at: nil, deleted_at: nil, version: nil)
@@ -449,6 +532,7 @@ module Smplkit
449
532
  @type = type
450
533
  @schedule = schedule
451
534
  @timezone = timezone
535
+ @retry_policy = retry_policy
452
536
  @configuration = configuration
453
537
  @concurrency_policy = concurrency_policy
454
538
  @birth_environment = birth_environment
@@ -588,17 +672,27 @@ module Smplkit
588
672
  # the cadence within that environment; it cannot turn a one-off job
589
673
  # recurring or vice-versa. Call {#save} to persist.
590
674
  #
675
+ # Because the timezone is an integral part of a cron cadence, a +timezone+
676
+ # may be supplied alongside the schedule; when given it sets the same
677
+ # scope's timezone too (equivalent to a follow-up {#set_timezone}). Omit it
678
+ # to leave the timezone untouched. For a timezone-only change, use
679
+ # {#set_timezone}.
680
+ #
591
681
  # @param schedule [String] An ISO-8601 datetime, a 5-field UTC cron
592
682
  # expression, or the literal +"now"+ (base); a 5-field UTC cron
593
683
  # expression (per-environment).
684
+ # @param timezone [String, nil] An optional IANA timezone to set on the
685
+ # same scope (recurring jobs only). +nil+ (the default) leaves the
686
+ # timezone untouched.
594
687
  # @param environment [String, nil] An environment key for a per-environment
595
688
  # override, or +nil+ to set the base schedule.
596
- def set_schedule(schedule, environment: nil)
689
+ def set_schedule(schedule, timezone: nil, environment: nil)
597
690
  if environment.nil?
598
691
  @schedule = schedule
599
692
  else
600
693
  _environment_override(environment).schedule = schedule
601
694
  end
695
+ set_timezone(timezone, environment: environment) unless timezone.nil?
602
696
  end
603
697
 
604
698
  # Set the IANA timezone the cron schedule is evaluated in — base
@@ -624,6 +718,31 @@ module Smplkit
624
718
  end
625
719
  end
626
720
 
721
+ # Set the retry policy for failed runs — base (+environment+ omitted) or
722
+ # per-environment.
723
+ #
724
+ # With +environment+ omitted (the default), sets the base {#retry_policy}
725
+ # every environment inherits unless it overrides it. With an +environment+
726
+ # given, sets that environment's per-environment override on
727
+ # {#environments}, creating the override entry if it doesn't exist yet
728
+ # (preserving any already-set +enabled+ / +schedule+ / +timezone+ /
729
+ # +configuration+ on it). Call {#save} to persist.
730
+ #
731
+ # Accepts either a {RetryPolicy} instance (its id is used) or a policy id
732
+ # string — pass +"Default"+ for the built-in never-retry policy.
733
+ #
734
+ # @param retry_policy [RetryPolicy, String] A {RetryPolicy} or a policy id.
735
+ # @param environment [String, nil] An environment key for a per-environment
736
+ # override, or +nil+ to set the base retry policy.
737
+ def set_retry_policy(retry_policy, environment: nil)
738
+ policy_id = retry_policy.is_a?(RetryPolicy) ? retry_policy.id : retry_policy
739
+ if environment.nil?
740
+ @retry_policy = policy_id
741
+ else
742
+ _environment_override(environment).retry_policy = policy_id
743
+ end
744
+ end
745
+
627
746
  # Trigger one immediate, manual run of this job (a +MANUAL+ run).
628
747
  #
629
748
  # @param environment [String, nil] Environment the run executes in.
@@ -641,17 +760,25 @@ module Smplkit
641
760
  #
642
761
  # @param environment [String, nil] Restrict to runs stamped with this
643
762
  # environment. +nil+ covers every environment you can access.
763
+ # @param triggers [Array<String>, nil] Restrict to runs started by any of
764
+ # these triggers (see {RunTrigger}) — e.g. +[RunTrigger::RETRY]+ for
765
+ # automatic retries. +nil+ covers every trigger.
766
+ # @param last_run_only [Boolean] When +true+, return only the last
767
+ # completed run per environment (in-flight runs excluded). Defaults to
768
+ # +false+.
644
769
  # @param page_size [Integer, nil] Maximum number of runs to return in this
645
770
  # page.
646
771
  # @param after [String, nil] Opaque cursor from a previous page.
647
772
  # @return [Array<Smplkit::Jobs::Run>] The runs in this page.
648
773
  # @raise [RuntimeError] when this job has no bound client.
649
- def list_runs(environment: nil, page_size: nil, after: nil)
774
+ def list_runs(environment: nil, triggers: nil, last_run_only: false, page_size: nil, after: nil)
650
775
  raise "Job was constructed without a client; cannot list runs" if @client.nil?
651
776
 
652
777
  @client.runs.list(
653
778
  job: @id,
654
779
  environments: environment.nil? ? nil : [environment],
780
+ triggers: triggers,
781
+ last_run_only: last_run_only,
655
782
  page_size: page_size,
656
783
  after: after
657
784
  )
@@ -678,6 +805,7 @@ module Smplkit
678
805
  @type = other.type
679
806
  @schedule = other.schedule
680
807
  @timezone = other.timezone
808
+ @retry_policy = other.retry_policy
681
809
  @configuration = other.configuration
682
810
  @concurrency_policy = other.concurrency_policy
683
811
  @created_at = other.created_at
@@ -709,6 +837,7 @@ module Smplkit
709
837
  type: a.type || "http",
710
838
  schedule: a.schedule,
711
839
  timezone: a.timezone,
840
+ retry_policy: a.retry_policy,
712
841
  configuration: HttpConfig.from_wire(a.configuration),
713
842
  concurrency_policy: a.concurrency_policy || "ALLOW",
714
843
  created_at: a.created_at,
@@ -719,6 +848,16 @@ module Smplkit
719
848
  end
720
849
  end
721
850
 
851
+ # Where a +RETRY+ run sits in its retry chain (read-only).
852
+ #
853
+ # @!attribute [rw] of
854
+ # @return [String] Id of the chain's original run — the first attempt that
855
+ # failed and started the chain.
856
+ # @!attribute [rw] attempt
857
+ # @return [Integer] Which retry this run is — +1+ for the first retry, +2+
858
+ # for the second, and so on.
859
+ RunRetry = Struct.new(:of, :attempt, keyword_init: true)
860
+
722
861
  # A single execution of a job (read-only) with +rerun+ / +cancel+ actions.
723
862
  #
724
863
  # Runs are created and mutated by the jobs service, not by clients; clients
@@ -741,6 +880,9 @@ module Smplkit
741
880
  # {RunTrigger} constants: +SCHEDULE+, +MANUAL+ (run now), or +RERUN+.
742
881
  # @!attribute [rw] rerun_of
743
882
  # @return [String, nil] The source run's id; set only when +trigger+ is +RERUN+.
883
+ # @!attribute [rw] retry
884
+ # @return [RunRetry, nil] Retry-chain position, populated only when
885
+ # +trigger+ is +RETRY+; +nil+ otherwise.
744
886
  # @!attribute [rw] scheduled_for
745
887
  # @return [String, nil] The intended fire time for a scheduled run; +nil+
746
888
  # for manual / rerun runs.
@@ -767,7 +909,7 @@ module Smplkit
767
909
  # @!attribute [rw] created_at
768
910
  # @return [String, nil] When the run was enqueued (became +PENDING+).
769
911
  Run = Struct.new(
770
- :id, :job, :job_version, :environment, :trigger, :rerun_of, :scheduled_for,
912
+ :id, :job, :job_version, :environment, :trigger, :rerun_of, :retry, :scheduled_for,
771
913
  :status, :started_at, :finished_at, :pending_duration_ms, :run_duration_ms,
772
914
  :total_duration_ms, :failure_reason, :error, :request, :result, :created_at,
773
915
  keyword_init: true
@@ -780,6 +922,12 @@ module Smplkit
780
922
  # @return [Run] The hydrated run.
781
923
  def self.from_resource(resource, runs: nil)
782
924
  a = resource.attributes
925
+ # The retry-chain position is populated only on a +RETRY+ run; the
926
+ # generated model exposes it as +_retry+ (+retry+ is a Ruby keyword).
927
+ retry_chain = a._retry
928
+ retry_pos = if a.trigger == RunTrigger::RETRY && !retry_chain.nil?
929
+ RunRetry.new(of: retry_chain.of, attempt: retry_chain.attempt)
930
+ end
783
931
  new(
784
932
  id: resource.id,
785
933
  job: a.job,
@@ -787,6 +935,7 @@ module Smplkit
787
935
  environment: a.environment,
788
936
  trigger: a.trigger,
789
937
  rerun_of: a.rerun_of,
938
+ retry: retry_pos,
790
939
  scheduled_for: a.scheduled_for,
791
940
  status: a.status,
792
941
  started_at: a.started_at,
@@ -857,5 +1006,145 @@ module Smplkit
857
1006
  )
858
1007
  end
859
1008
  end
1009
+
1010
+ # A named, reusable automatic-retry policy.
1011
+ #
1012
+ # Active-record style: instantiate via +client.jobs.retry_policies.new(...)+,
1013
+ # mutate fields directly, and call {#save} to persist or {#delete} to remove.
1014
+ # Reference a saved policy from a job's {Job#retry_policy} (see
1015
+ # {JobsClient#new_recurring_job} and {Job#set_retry_policy}). Retry policies
1016
+ # are account-global — never environment-scoped.
1017
+ #
1018
+ # A policy decides whether and how a failed run is retried. A job that
1019
+ # references nothing uses the built-in +"Default"+ policy, which never
1020
+ # retries.
1021
+ class RetryPolicy
1022
+ # @return [String] Caller-supplied unique identifier for the policy (the
1023
+ # resource +id+). Unique within the account and immutable; the service
1024
+ # returns 409 if another live policy already uses this id.
1025
+ attr_accessor :id
1026
+
1027
+ # @return [String] Human-readable name for the policy.
1028
+ attr_accessor :name
1029
+
1030
+ # @return [Integer] How many times a failed run is retried after the
1031
+ # initial attempt — +3+ means up to 4 attempts total. +0+ disables
1032
+ # retries. Maximum 10.
1033
+ attr_accessor :max_retries
1034
+
1035
+ # @return [String] How the wait between retries grows; one of {Backoff}.
1036
+ attr_accessor :backoff
1037
+
1038
+ # @return [Integer] The wait before a retry, in seconds — the constant wait
1039
+ # for {Backoff::FIXED}, or the base that doubles each retry for
1040
+ # {Backoff::EXPONENTIAL}.
1041
+ attr_accessor :delay_seconds
1042
+
1043
+ # @return [Integer, nil] Ceiling on the wait between retries, for
1044
+ # {Backoff::EXPONENTIAL} backoff only. +nil+ (the default, omitted on the
1045
+ # wire) leaves it uncapped; omit it for {Backoff::FIXED}.
1046
+ attr_accessor :max_delay_seconds
1047
+
1048
+ # @return [RetryOn] Which failures to retry; an empty {RetryOn} retries
1049
+ # nothing. Defaults to empty.
1050
+ attr_accessor :retry_on
1051
+
1052
+ # @return [String, nil] ISO-8601 timestamp of first persist. +nil+ for an
1053
+ # unsaved instance.
1054
+ attr_accessor :created_at
1055
+
1056
+ # @return [String, nil] ISO-8601 timestamp of the most recent mutation.
1057
+ attr_accessor :updated_at
1058
+
1059
+ # @return [String, nil] Timestamp when the policy was deleted; +nil+ for
1060
+ # live policies.
1061
+ attr_accessor :deleted_at
1062
+
1063
+ # @return [Integer, nil] Monotonic version counter, bumped on every
1064
+ # server-side write.
1065
+ attr_accessor :version
1066
+
1067
+ def initialize(client = nil, id:, name:, max_retries:, backoff:, delay_seconds:,
1068
+ max_delay_seconds: nil, retry_on: nil, created_at: nil,
1069
+ updated_at: nil, deleted_at: nil, version: nil)
1070
+ @client = client
1071
+ @id = id
1072
+ @name = name
1073
+ @max_retries = max_retries
1074
+ @backoff = backoff
1075
+ @delay_seconds = delay_seconds
1076
+ @max_delay_seconds = max_delay_seconds
1077
+ @retry_on = retry_on || RetryOn.new
1078
+ @created_at = created_at
1079
+ @updated_at = updated_at
1080
+ @deleted_at = deleted_at
1081
+ @version = version
1082
+ end
1083
+
1084
+ # Create this policy, or full-replace it if it already exists.
1085
+ #
1086
+ # Upsert behavior is driven by {#created_at}: a policy with no +created_at+
1087
+ # is created (POST); otherwise it's full-replace updated (PUT). After the
1088
+ # call, every field is refreshed from the server response.
1089
+ #
1090
+ # @return [self]
1091
+ def save
1092
+ raise "RetryPolicy was constructed without a client; cannot save" if @client.nil?
1093
+
1094
+ updated = @created_at.nil? ? @client._create_retry_policy(self) : @client._update_retry_policy(self)
1095
+ _apply(updated)
1096
+ self
1097
+ end
1098
+ alias save! save
1099
+
1100
+ # Delete this policy on the server.
1101
+ #
1102
+ # @return [nil]
1103
+ def delete
1104
+ raise "RetryPolicy was constructed without a client or id; cannot delete" if @client.nil? || @id.nil?
1105
+
1106
+ @client.delete(@id)
1107
+ end
1108
+ alias delete! delete
1109
+
1110
+ # @api private
1111
+ def _apply(other)
1112
+ @id = other.id
1113
+ @name = other.name
1114
+ @max_retries = other.max_retries
1115
+ @backoff = other.backoff
1116
+ @delay_seconds = other.delay_seconds
1117
+ @max_delay_seconds = other.max_delay_seconds
1118
+ @retry_on = other.retry_on
1119
+ @created_at = other.created_at
1120
+ @updated_at = other.updated_at
1121
+ @deleted_at = other.deleted_at
1122
+ @version = other.version
1123
+ end
1124
+
1125
+ # @api private — Build a {RetryPolicy} from a JSON:API resource returned by
1126
+ # the jobs service, binding it to +client+ so {#save} and {#delete} work.
1127
+ #
1128
+ # @param resource [Object] The JSON:API resource (id + attributes).
1129
+ # @param client [RetryPoliciesClient, nil] Client to bind the policy to.
1130
+ # @return [RetryPolicy] The hydrated policy.
1131
+ def self.from_resource(resource, client: nil)
1132
+ a = resource.attributes
1133
+ new(
1134
+ client,
1135
+ id: resource.id,
1136
+ name: a.name,
1137
+ max_retries: a.max_retries,
1138
+ backoff: a.backoff,
1139
+ delay_seconds: a.delay_seconds,
1140
+ max_delay_seconds: a.max_delay_seconds,
1141
+ retry_on: RetryOn.from_wire(a.retry_on),
1142
+ created_at: a.created_at,
1143
+ updated_at: a.updated_at,
1144
+ deleted_at: a.deleted_at,
1145
+ version: a.version
1146
+ )
1147
+ end
1148
+ end
860
1149
  end
861
1150
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smplkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.119
4
+ version: 3.0.120
5
5
  platform: ruby
6
6
  authors:
7
7
  - Smpl Solutions LLC