smplkit 3.0.95 → 3.0.97

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/lib/smplkit/account/client.rb +128 -0
  3. data/lib/smplkit/account/models.rb +71 -0
  4. data/lib/smplkit/api_support.rb +91 -0
  5. data/lib/smplkit/audit/buffer.rb +3 -1
  6. data/lib/smplkit/audit/categories.rb +21 -10
  7. data/lib/smplkit/audit/client.rb +18 -9
  8. data/lib/smplkit/audit/event_types.rb +26 -10
  9. data/lib/smplkit/audit/events.rb +93 -17
  10. data/lib/smplkit/{management/audit.rb → audit/forwarders.rb} +93 -85
  11. data/lib/smplkit/audit/models.rb +86 -32
  12. data/lib/smplkit/audit/resource_types.rb +21 -9
  13. data/lib/smplkit/buffers.rb +250 -0
  14. data/lib/smplkit/client.rb +161 -70
  15. data/lib/smplkit/config/client.rb +874 -186
  16. data/lib/smplkit/config/helpers.rb +44 -6
  17. data/lib/smplkit/config/models.rb +114 -7
  18. data/lib/smplkit/config_resolution.rb +17 -9
  19. data/lib/smplkit/errors.rb +14 -3
  20. data/lib/smplkit/flags/client.rb +602 -116
  21. data/lib/smplkit/flags/models.rb +110 -8
  22. data/lib/smplkit/flags/types.rb +8 -9
  23. data/lib/smplkit/jobs/client.rb +306 -0
  24. data/lib/smplkit/jobs/models.rb +47 -18
  25. data/lib/smplkit/logging/client.rb +755 -191
  26. data/lib/smplkit/logging/helpers.rb +5 -1
  27. data/lib/smplkit/logging/levels.rb +3 -1
  28. data/lib/smplkit/logging/models.rb +163 -6
  29. data/lib/smplkit/logging/normalize.rb +3 -1
  30. data/lib/smplkit/logging/resolution.rb +4 -4
  31. data/lib/smplkit/logging/sources.rb +1 -1
  32. data/lib/smplkit/platform/client.rb +597 -0
  33. data/lib/smplkit/platform/models.rb +282 -0
  34. data/lib/smplkit/{management → platform}/types.rb +21 -4
  35. data/lib/smplkit/transport.rb +103 -0
  36. data/lib/smplkit/ws.rb +1 -1
  37. data/lib/smplkit.rb +18 -6
  38. metadata +11 -7
  39. data/lib/smplkit/management/buffer.rb +0 -198
  40. data/lib/smplkit/management/client.rb +0 -1074
  41. data/lib/smplkit/management/jobs.rb +0 -226
  42. data/lib/smplkit/management/models.rb +0 -178
@@ -99,11 +99,21 @@ module Smplkit
99
99
  end
100
100
 
101
101
  # Read-only view of constrained values. +nil+ means unconstrained.
102
+ #
103
+ # Mutate via +add_value+ / +remove_value+ / +clear_values+.
104
+ #
105
+ # @return [Array<FlagValue>, nil] the constrained values, or +nil+ when
106
+ # the flag is unconstrained.
102
107
  def values
103
108
  @values&.dup
104
109
  end
105
110
 
106
111
  # Read-only view of per-environment configuration.
112
+ #
113
+ # Mutate via +add_rule+ / +enable_rules+ / +disable_rules+ /
114
+ # +set_default+ (with +environment:+) / +clear_rules+.
115
+ #
116
+ # @return [Hash{String => FlagEnvironment}] the per-environment configuration.
107
117
  def environments
108
118
  @environments.dup
109
119
  end
@@ -111,8 +121,10 @@ module Smplkit
111
121
  # Persist this flag to the server.
112
122
  #
113
123
  # Creates a new flag if unsaved, or updates the existing one. Requires a
114
- # management client (i.e. the flag was constructed via +mgmt.flags.new_*+
115
- # or returned from +mgmt.flags.get/list+).
124
+ # client (i.e. the flag was constructed via +client.flags.new_*+ or
125
+ # returned from +client.flags.get+ / +client.flags.list+).
126
+ #
127
+ # @return [self] this flag, with server-assigned fields applied.
116
128
  def save
117
129
  raise "Flag was constructed without a client; cannot save" if @client.nil?
118
130
 
@@ -127,6 +139,10 @@ module Smplkit
127
139
  end
128
140
  alias save! save
129
141
 
142
+ # Delete this flag from the server.
143
+ #
144
+ # @return [void]
145
+ # @raise [Smplkit::NotFoundError] no flag with this id exists for the account.
130
146
  def delete
131
147
  raise "Flag was constructed without a client or id; cannot delete" if @client.nil? || @id.nil?
132
148
 
@@ -138,6 +154,12 @@ module Smplkit
138
154
  #
139
155
  # The +built_rule+ Hash must include an +"environment"+ key.
140
156
  # Call +save+ to persist.
157
+ #
158
+ # @param built_rule [Hash] the Hash produced by
159
+ # +Smplkit::Rule.new(..., environment: ...).when(...).serve(...)+; must
160
+ # include an +"environment"+ key naming the target environment.
161
+ # @return [self] this flag, so calls can be chained.
162
+ # @raise [ArgumentError] the +built_rule+ Hash has no +"environment"+ key.
141
163
  def add_rule(built_rule)
142
164
  env_key = built_rule["environment"]
143
165
  if env_key.nil?
@@ -156,16 +178,37 @@ module Smplkit
156
178
  self
157
179
  end
158
180
 
181
+ # Enable rule evaluation. Call +save+ to persist.
182
+ #
183
+ # @param environment [String, nil] name of the environment to enable; when
184
+ # +nil+ (the default), enables rules in every environment configured on
185
+ # this flag.
186
+ # @return [self] this flag, so calls can be chained.
159
187
  def enable_rules(environment: nil)
160
188
  scoped(environment) { |k| @environments[k] = (@environments[k] || FlagEnvironment.new).with(enabled: true) }
161
189
  self
162
190
  end
163
191
 
192
+ # Disable rule evaluation (kill switch). Call +save+ to persist.
193
+ #
194
+ # When disabled, +get+ skips rules and returns the env-specific default
195
+ # (or the flag's base default).
196
+ #
197
+ # @param environment [String, nil] name of the environment to disable; when
198
+ # +nil+ (the default), disables rules in every environment configured on
199
+ # this flag.
200
+ # @return [self] this flag, so calls can be chained.
164
201
  def disable_rules(environment: nil)
165
202
  scoped(environment) { |k| @environments[k] = (@environments[k] || FlagEnvironment.new).with(enabled: false) }
166
203
  self
167
204
  end
168
205
 
206
+ # Remove rules. Call +save+ to persist.
207
+ #
208
+ # @param environment [String, nil] name of the environment whose rules to
209
+ # remove; when +nil+ (the default), removes rules from every environment
210
+ # configured on this flag.
211
+ # @return [self] this flag, so calls can be chained.
169
212
  def clear_rules(environment: nil)
170
213
  scoped(environment) do |k|
171
214
  @environments[k] = (@environments[k] || FlagEnvironment.new).with(rules: [].freeze)
@@ -173,22 +216,44 @@ module Smplkit
173
216
  self
174
217
  end
175
218
 
219
+ # Set the flag's per-environment default served value. Call +save+ to persist.
220
+ #
221
+ # @param value [Object] the default value to serve when no rule matches.
222
+ # @param environment [String] name of the environment whose default to set.
223
+ # @return [self] this flag, so calls can be chained.
176
224
  def set_default(value, environment:)
177
225
  @environments[environment] = (@environments[environment] || FlagEnvironment.new).with(default: value)
178
226
  self
179
227
  end
180
228
 
229
+ # Clear the per-environment default override on +environment+.
230
+ #
231
+ # After clearing, the environment falls back to the flag's base default
232
+ # when no rule matches. Call +save+ to persist.
233
+ #
234
+ # @param environment [String] name of the environment whose default
235
+ # override to clear.
236
+ # @return [self] this flag, so calls can be chained.
181
237
  def clear_default(environment:)
182
238
  @environments[environment] = (@environments[environment] || FlagEnvironment.new).with(default: nil)
183
239
  self
184
240
  end
185
241
 
242
+ # Append a constrained value to the flag's values list. Call +save+ to persist.
243
+ #
244
+ # @param flag_value [FlagValue] the value entry to allow the flag to serve.
245
+ # @return [self] this flag, so calls can be chained.
186
246
  def add_value(flag_value)
187
247
  @values ||= []
188
248
  @values << flag_value
189
249
  self
190
250
  end
191
251
 
252
+ # Remove the first values entry whose +name+ matches.
253
+ #
254
+ # @param name [String] the value-entry name to remove; the first match is
255
+ # removed and others are left in place.
256
+ # @return [self] this flag, so calls can be chained.
192
257
  def remove_value(name)
193
258
  return self unless @values
194
259
 
@@ -196,6 +261,9 @@ module Smplkit
196
261
  self
197
262
  end
198
263
 
264
+ # Clear the constrained values list (unconstrained). Call +save+ to persist.
265
+ #
266
+ # @return [self] this flag, so calls can be chained.
199
267
  def clear_values
200
268
  @values = []
201
269
  self
@@ -225,28 +293,62 @@ module Smplkit
225
293
  end
226
294
 
227
295
  class BooleanFlag < Flag
296
+ # Evaluate this flag and return its current boolean value.
297
+ #
298
+ # @param context [Array<Smplkit::Context>, nil] optional entities to
299
+ # evaluate targeting rules against; when omitted the ambient request
300
+ # context (if any) is used.
301
+ # @return [Boolean] the evaluated boolean value, or this flag's default
302
+ # when no environment override or rule applies (or the evaluated value
303
+ # is not a boolean).
228
304
  def get(context: nil)
229
- raw = @client._evaluate_handle(@id, @default, context)
230
- !!raw
305
+ value = @client._evaluate_handle(@id, @default, context)
306
+ [true, false].include?(value) ? value : @default
231
307
  end
232
308
  end
233
309
 
234
310
  class StringFlag < Flag
311
+ # Evaluate this flag and return its current string value.
312
+ #
313
+ # @param context [Array<Smplkit::Context>, nil] optional entities to
314
+ # evaluate targeting rules against; when omitted the ambient request
315
+ # context (if any) is used.
316
+ # @return [String] the evaluated string value, or this flag's default
317
+ # when no environment override or rule applies (or the evaluated value
318
+ # is not a String).
235
319
  def get(context: nil)
236
- raw = @client._evaluate_handle(@id, @default, context)
237
- raw.to_s
320
+ value = @client._evaluate_handle(@id, @default, context)
321
+ value.is_a?(String) ? value : @default
238
322
  end
239
323
  end
240
324
 
241
325
  class NumberFlag < Flag
326
+ # Evaluate this flag and return its current numeric value.
327
+ #
328
+ # @param context [Array<Smplkit::Context>, nil] optional entities to
329
+ # evaluate targeting rules against; when omitted the ambient request
330
+ # context (if any) is used.
331
+ # @return [Numeric] the evaluated numeric value, or this flag's default
332
+ # when no environment override or rule applies (or the evaluated value
333
+ # is not a Numeric).
242
334
  def get(context: nil)
243
- @client._evaluate_handle(@id, @default, context)
335
+ value = @client._evaluate_handle(@id, @default, context)
336
+ value.is_a?(Numeric) ? value : @default
244
337
  end
245
338
  end
246
339
 
247
340
  class JsonFlag < Flag
341
+ # Evaluate this flag and return its current JSON value.
342
+ #
343
+ # @param context [Array<Smplkit::Context>, nil] optional entities to
344
+ # evaluate targeting rules against; when omitted the ambient request
345
+ # context (if any) is used.
346
+ # @return [Hash] the evaluated JSON object, or this flag's default when no
347
+ # environment override or rule applies (or the evaluated value is not a
348
+ # Hash).
248
349
  def get(context: nil)
249
- @client._evaluate_handle(@id, @default, context)
350
+ value = @client._evaluate_handle(@id, @default, context)
351
+ value.is_a?(Hash) ? value : @default
250
352
  end
251
353
  end
252
354
  end
@@ -26,9 +26,9 @@ module Smplkit
26
26
  # arguments) carry the data that targeting rules evaluate against.
27
27
  #
28
28
  # Used for both authoring (+flag.get(context: [...])+,
29
- # +client.set_context([...])+, +mgmt.contexts.register([...])+) and reading
30
- # (+mgmt.contexts.list/get+ return populated +Context+ instances with
31
- # +save+ / +delete+ ready to call).
29
+ # +client.set_context([...])+, +client.platform.contexts.register([...])+) and
30
+ # reading (+client.platform.contexts.list/get+ return populated +Context+
31
+ # instances with +save+ / +delete+ ready to call).
32
32
  #
33
33
  # Examples:
34
34
  #
@@ -67,8 +67,8 @@ module Smplkit
67
67
  @attributes = stringify_keys(new_attrs || {})
68
68
  end
69
69
 
70
- # Internal: associate a management client with this context so save/delete
71
- # can route through it.
70
+ # @api private — associate a client with this context so save/delete can
71
+ # route through it.
72
72
  def _bind_client(client)
73
73
  @client = client
74
74
  self
@@ -141,10 +141,9 @@ module Smplkit
141
141
 
142
142
  # Describes a flag declaration for buffered registration.
143
143
  #
144
- # Used by +Smplkit::ManagementClient#flags#register+ to queue declarations
145
- # for bulk registration. +service+ and +environment+ default to +nil+; the
146
- # runtime client fills them from the active +Smplkit::Client+ when it
147
- # forwards declarations.
144
+ # Used by +client.flags.register+ to queue declarations for bulk
145
+ # registration. +service+ and +environment+ default to +nil+; the client
146
+ # fills them from the active +Smplkit::Client+ when it forwards declarations.
148
147
  class FlagDeclaration
149
148
  attr_reader :id, :type, :default, :service, :environment
150
149
 
@@ -0,0 +1,306 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The Smpl Jobs client — one unified +JobsClient+.
4
+ #
5
+ # Smpl Jobs schedules HTTP calls (cron-style +schedule+ + +http+ configuration)
6
+ # and records their run history. Unlike Config/Flags/Logging it installs no
7
+ # in-process machinery, so it has no runtime/management split: a single
8
+ # +JobsClient+ exposes the full surface and is reachable as +client.jobs+ on
9
+ # +Smplkit::Client+ or constructed directly.
10
+ #
11
+ # client.jobs.{new,get,list,delete,run,usage}
12
+ # client.jobs.runs.{list,get,cancel,rerun}
13
+ # Job#{save,delete}
14
+ #
15
+ # The shared model classes (+Job+, +Run+, +Usage+, +HttpConfig+) live in
16
+ # +lib/smplkit/jobs/models.rb+.
17
+ module Smplkit
18
+ module Jobs
19
+ # +client.jobs.runs.*+ — read-only run history plus the cancel / rerun run
20
+ # actions.
21
+ class RunsClient
22
+ def initialize(api)
23
+ @api = api
24
+ end
25
+
26
+ # List past runs, most recent first. Cursor paginated: pass +page_size+
27
+ # and the +after+ cursor from the prior page. Pass +job+ to scope to a
28
+ # single job's history.
29
+ #
30
+ # @param job [String, nil] Return only runs of the job with this id.
31
+ # +nil+ lists runs across all jobs in the account.
32
+ # @param page_size [Integer, nil] Maximum number of runs to return in this
33
+ # page. +nil+ uses the server default.
34
+ # @param after [String, nil] Opaque cursor from a previous page; returns
35
+ # the runs that follow it. +nil+ starts from the first page.
36
+ # @return [Array<Smplkit::Jobs::Run>] The runs in this page.
37
+ def list(job: nil, page_size: nil, after: nil)
38
+ opts = {}
39
+ opts[:filter_job] = job unless job.nil?
40
+ opts[:page_size] = page_size unless page_size.nil?
41
+ opts[:page_after] = after unless after.nil?
42
+
43
+ resp = Jobs.call_api { @api.list_runs(opts) }
44
+ (resp.data || []).map { |r| Run.from_resource(r) }
45
+ end
46
+
47
+ # Fetch a single run by its id.
48
+ #
49
+ # @param run_id [String] Identifier of the run to fetch.
50
+ # @return [Smplkit::Jobs::Run] The matching run.
51
+ # @raise [Smplkit::NotFoundError] when no run with this id exists.
52
+ def get(run_id)
53
+ resp = Jobs.call_api { @api.get_run(run_id) }
54
+ Run.from_resource(resp.data)
55
+ end
56
+
57
+ # Cancel a run that has not finished yet.
58
+ #
59
+ # @param run_id [String] Identifier of the run to cancel.
60
+ # @return [Smplkit::Jobs::Run] The updated run reflecting the cancellation.
61
+ def cancel(run_id)
62
+ resp = Jobs.call_api { @api.cancel_run(run_id) }
63
+ Run.from_resource(resp.data)
64
+ end
65
+
66
+ # Start a new run that repeats a previous one.
67
+ #
68
+ # @param run_id [String] Identifier of the run to repeat.
69
+ # @return [Smplkit::Jobs::Run] The new run, with +rerun_of+ set to the
70
+ # source +run_id+.
71
+ def rerun(run_id)
72
+ resp = Jobs.call_api { @api.rerun_run(run_id) }
73
+ Run.from_resource(resp.data)
74
+ end
75
+ end
76
+
77
+ # The Smpl Jobs client — accessed via +client.jobs+.
78
+ #
79
+ # Unlike Config/Flags/Logging, Jobs has no live "phone-home" agent — no
80
+ # environment registration, no WebSocket — so its entire surface lives on
81
+ # one client. Defining a job, triggering a run, and reading run history are
82
+ # all plain request/response calls here:
83
+ #
84
+ # client.jobs.{new,get,list,delete,run,usage}
85
+ # client.jobs.runs.{list,get,cancel,rerun}
86
+ # Job#{save,delete}
87
+ #
88
+ # Build a standalone Smpl Jobs transport from resolved config.
89
+ #
90
+ # Reuses the config resolver (jobs is account-global and never
91
+ # environment-scoped) so a standalone jobs client resolves
92
+ # credentials/base-domain from +~/.smplkit+ / env vars / constructor args
93
+ # exactly like the top-level clients do. Smpl Jobs is JSON:API, so the
94
+ # transport carries the +application/vnd.api+json+ Accept header.
95
+ def self.jobs_transport(api_key:, profile:, base_domain:, scheme:, debug:, extra_headers:)
96
+ cfg = ConfigResolution.resolve_client_config(
97
+ profile: profile, api_key: api_key, base_domain: base_domain, scheme: scheme, debug: debug
98
+ )
99
+ merged = {}
100
+ merged.merge!(cfg.extra_headers || {})
101
+ merged.merge!(extra_headers || {})
102
+ tcfg = ConfigResolution::ResolvedClientConfig.new(
103
+ api_key: cfg.api_key, base_domain: cfg.base_domain, scheme: cfg.scheme,
104
+ debug: cfg.debug, extra_headers: merged
105
+ )
106
+ Transport.build_api_client(SmplkitGeneratedClient::Jobs, "jobs", tcfg, accept: "application/vnd.api+json")
107
+ end
108
+
109
+ # The active-record entry point is {#new}: instantiate a draft, mutate
110
+ # fields, then call {Smplkit::Jobs::Job#save}. Run history and the cancel /
111
+ # rerun run actions live on {#runs}.
112
+ #
113
+ # Reachable as +client.jobs+ (+Smplkit::Client+) or constructed directly —
114
+ # +JobsClient.new+ resolves credentials from +~/.smplkit+ / env vars.
115
+ class JobsClient
116
+ # @return [RunsClient] Run history and run actions (+client.jobs.runs+).
117
+ attr_reader :runs
118
+
119
+ # @param api_key [String, nil] API key. When omitted, resolved from
120
+ # +SMPLKIT_API_KEY+ or +~/.smplkit+.
121
+ # @param profile [String, nil] Named +~/.smplkit+ profile section.
122
+ # @param base_domain [String, nil] Base domain for API requests
123
+ # (default +"smplkit.com"+).
124
+ # @param scheme [String, nil] URL scheme (default +"https"+).
125
+ # @param debug [Boolean, nil] Enable SDK debug logging.
126
+ # @param extra_headers [Hash, nil] Extra headers attached to every request.
127
+ # @param auth_client [Object, nil] Internal — a pre-built transport
128
+ # supplied by a top-level client so the jobs surface shares one
129
+ # connection pool. Not for direct use.
130
+ def initialize(api_key = nil, profile: nil, base_domain: nil, scheme: nil,
131
+ debug: nil, extra_headers: nil, auth_client: nil)
132
+ auth = auth_client || Jobs.jobs_transport(
133
+ api_key: api_key, profile: profile, base_domain: base_domain,
134
+ scheme: scheme, debug: debug, extra_headers: extra_headers
135
+ )
136
+ @api = SmplkitGeneratedClient::Jobs::JobsApi.new(auth)
137
+ @runs = RunsClient.new(SmplkitGeneratedClient::Jobs::RunsApi.new(auth))
138
+ @usage_api = SmplkitGeneratedClient::Jobs::UsageApi.new(auth)
139
+ end
140
+
141
+ # The generated ApiClient owns Faraday connections that release on GC;
142
+ # there is no explicit shutdown to call.
143
+ def close
144
+ nil
145
+ end
146
+
147
+ # Construct, yield to the block, and close on exit.
148
+ def self.open(*args, **kwargs)
149
+ client = new(*args, **kwargs)
150
+ begin
151
+ yield client
152
+ ensure
153
+ client.close
154
+ end
155
+ end
156
+
157
+ # Construct an unsaved {Smplkit::Jobs::Job} bound to this client. Call
158
+ # +#save+ on the returned instance to create it.
159
+ #
160
+ # @param id [String] Caller-supplied unique identifier for the job. Unique
161
+ # within the account and immutable; the service returns 409 if another
162
+ # live job already uses this id.
163
+ # @param name [String] Human-readable name for the job.
164
+ # @param schedule [String] An ISO-8601 datetime, a 5-field UTC cron
165
+ # expression, or the literal +"now"+.
166
+ # @param configuration [Smplkit::Jobs::HttpConfig] The HTTP request the
167
+ # job performs.
168
+ # @param description [String, nil] Optional free-text description.
169
+ # @param enabled [Boolean] Whether the job schedules runs. Defaults +true+.
170
+ # @param concurrency_policy [String] How overlapping runs are handled.
171
+ # Defaults to +"ALLOW"+.
172
+ # @return [Smplkit::Jobs::Job]
173
+ def new(id, name:, schedule:, configuration:, description: nil,
174
+ enabled: true, concurrency_policy: "ALLOW")
175
+ Job.new(
176
+ self,
177
+ id: id,
178
+ name: name,
179
+ schedule: schedule,
180
+ configuration: configuration,
181
+ description: description,
182
+ enabled: enabled,
183
+ concurrency_policy: concurrency_policy
184
+ )
185
+ end
186
+
187
+ # List jobs for the authenticated account.
188
+ #
189
+ # @param enabled [Boolean, nil] Filter to jobs matching this enabled state.
190
+ # @param page_number [Integer, nil] 1-based page number to return.
191
+ # @param page_size [Integer, nil] Items per page.
192
+ # @return [Array<Smplkit::Jobs::Job>]
193
+ def list(enabled: nil, page_number: nil, page_size: nil)
194
+ opts = {}
195
+ opts[:filter_enabled] = enabled unless enabled.nil?
196
+ opts[:page_number] = page_number unless page_number.nil?
197
+ opts[:page_size] = page_size unless page_size.nil?
198
+
199
+ resp = Jobs.call_api { @api.list_jobs(opts) }
200
+ (resp.data || []).map { |r| Job.from_resource(r, client: self) }
201
+ end
202
+
203
+ # Fetch a single job by id. The returned instance is bound to this client,
204
+ # so +job.save+ and +job.delete+ work.
205
+ #
206
+ # @param id [String]
207
+ # @return [Smplkit::Jobs::Job]
208
+ def get(id)
209
+ resp = Jobs.call_api { @api.get_job(id) }
210
+ Job.from_resource(resp.data, client: self)
211
+ end
212
+
213
+ # Delete a job by its id.
214
+ #
215
+ # @param id [String] Identifier of the job to delete.
216
+ # @return [nil]
217
+ def delete(id)
218
+ Jobs.call_api { @api.delete_job(id) }
219
+ nil
220
+ end
221
+
222
+ # Trigger one immediate, manual run of a job, ignoring its schedule.
223
+ #
224
+ # This starts an ad-hoc run right now in addition to any scheduled runs;
225
+ # it does not alter the job's schedule. To read or act on existing runs,
226
+ # use +client.jobs.runs+.
227
+ #
228
+ # @param id [String] Identifier of the job to run.
229
+ # @return [Smplkit::Jobs::Run] The run that was started, with +trigger+
230
+ # set to +MANUAL+.
231
+ def run(id)
232
+ resp = Jobs.call_api { @api.run_job_now(id) }
233
+ Run.from_resource(resp.data)
234
+ end
235
+
236
+ # Current-period usage counters for the account.
237
+ #
238
+ # @return [Smplkit::Jobs::Usage]
239
+ def usage
240
+ resp = Jobs.call_api { @usage_api.get_usage }
241
+ Usage.from_resource(resp.data)
242
+ end
243
+
244
+ # @api private — POST a new job. Called by {Smplkit::Jobs::Job#save} on
245
+ # unsaved instances. The jobs service requires a caller-supplied
246
+ # +data.id+ on create and 409s on conflict.
247
+ def _create_job(job)
248
+ raise ArgumentError, "Job.id is required on create (caller-supplied key)" if job.id.nil? || job.id.empty?
249
+
250
+ resp = Jobs.call_api { @api.create_job(build_create_body(job)) }
251
+ Job.from_resource(resp.data, client: self)
252
+ end
253
+
254
+ # @api private — Full-replace PUT for an existing job. Called by
255
+ # {Smplkit::Jobs::Job#save} on instances with +created_at+.
256
+ #
257
+ # Header values come back in plaintext on the GET path, so a fetched job
258
+ # round-trips through this full-replace PUT with its header values intact
259
+ # — no need to re-enter secrets.
260
+ def _update_job(job)
261
+ raise ArgumentError, "cannot update a Job with no id" if job.id.nil?
262
+
263
+ resp = Jobs.call_api { @api.update_job(job.id, build_body(job)) }
264
+ Job.from_resource(resp.data, client: self)
265
+ end
266
+
267
+ private
268
+
269
+ def build_attrs(job)
270
+ SmplkitGeneratedClient::Jobs::Job.new(
271
+ name: job.name,
272
+ description: job.description,
273
+ enabled: job.enabled,
274
+ type: job.type,
275
+ schedule: job.schedule,
276
+ configuration: HttpConfig.to_wire(job.configuration),
277
+ concurrency_policy: job.concurrency_policy
278
+ )
279
+ end
280
+
281
+ def build_create_body(job)
282
+ # Create uses the distinct JobCreateRequest envelope; the jobs service
283
+ # requires data.id (the caller-supplied key) on create and 409s on
284
+ # conflict.
285
+ resource = SmplkitGeneratedClient::Jobs::JobCreateResource.new(
286
+ id: job.id.to_s,
287
+ type: "job",
288
+ attributes: build_attrs(job)
289
+ )
290
+ SmplkitGeneratedClient::Jobs::JobCreateRequest.new(data: resource)
291
+ end
292
+
293
+ def build_body(job)
294
+ # Update path uses the generic JobRequest envelope.
295
+ resource = SmplkitGeneratedClient::Jobs::JobResource.new(
296
+ id: job.id.to_s,
297
+ type: "job",
298
+ attributes: build_attrs(job)
299
+ )
300
+ SmplkitGeneratedClient::Jobs::JobRequest.new(data: resource)
301
+ end
302
+ end
303
+ end
304
+
305
+ JobsClient = Jobs::JobsClient
306
+ end