hatchet-sdk 0.5.0 → 0.7.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/lib/hatchet/batch.rb +58 -0
  4. data/lib/hatchet/clients/grpc/dispatcher.rb +42 -0
  5. data/lib/hatchet/clients/rest/.openapi-generator/FILES +9 -0
  6. data/lib/hatchet/clients/rest/README.md +16 -3
  7. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/api/filter_api.rb +4 -0
  8. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/api/operator_api.rb +405 -0
  9. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/api/webhook_api.rb +2 -0
  10. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/api/worker_api.rb +3 -0
  11. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/models/api_meta.rb +14 -4
  12. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/models/create_tenant_invite_request.rb +14 -4
  13. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/models/pause_workflow_request.rb +105 -0
  14. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/models/pause_workflow_request_pause.rb +343 -0
  15. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/models/pause_workflow_request_unpause.rb +262 -0
  16. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/models/tenant_invite.rb +11 -1
  17. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/models/tenant_member.rb +11 -1
  18. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/models/tenant_member_role.rb +2 -1
  19. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/models/update_tenant_invite_request.rb +14 -4
  20. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/models/update_tenant_member_request.rb +14 -4
  21. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/models/v1_create_http_operator_request.rb +346 -0
  22. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/models/v1_event.rb +14 -4
  23. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/models/v1_http_operator.rb +372 -0
  24. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/models/v1_http_operator_list.rb +231 -0
  25. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/models/v1_task_event_type.rb +3 -1
  26. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/models/v1_task_summary.rb +14 -4
  27. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/models/v1_update_http_operator_request.rb +252 -0
  28. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/models/v1_workflow_run.rb +14 -4
  29. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/models/workflow.rb +43 -1
  30. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/models/workflow_pause_scheduled_cron_run_queue_behavior.rb +40 -0
  31. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest/models/workflow_update_request.rb +7 -8
  32. data/lib/hatchet/clients/rest/lib/hatchet-sdk-rest.rb +9 -0
  33. data/lib/hatchet/concurrency.rb +5 -1
  34. data/lib/hatchet/context.rb +42 -3
  35. data/lib/hatchet/contracts/dispatcher/dispatcher_pb.rb +4 -1
  36. data/lib/hatchet/contracts/dispatcher/dispatcher_services_pb.rb +3 -0
  37. data/lib/hatchet/contracts/v1/workflows_pb.rb +2 -1
  38. data/lib/hatchet/durable_context.rb +5 -12
  39. data/lib/hatchet/features/cel.rb +2 -2
  40. data/lib/hatchet/features/cron.rb +5 -5
  41. data/lib/hatchet/features/events.rb +7 -7
  42. data/lib/hatchet/features/filters.rb +7 -7
  43. data/lib/hatchet/features/logs.rb +2 -2
  44. data/lib/hatchet/features/metrics.rb +6 -6
  45. data/lib/hatchet/features/rate_limits.rb +2 -2
  46. data/lib/hatchet/features/runs.rb +4 -5
  47. data/lib/hatchet/features/scheduled.rb +7 -7
  48. data/lib/hatchet/features/tenant.rb +2 -2
  49. data/lib/hatchet/features/workers.rb +5 -5
  50. data/lib/hatchet/features/workflows.rb +6 -6
  51. data/lib/hatchet/task.rb +13 -0
  52. data/lib/hatchet/version.rb +1 -1
  53. data/lib/hatchet/worker/runner.rb +113 -0
  54. data/lib/hatchet/workflow.rb +77 -33
  55. data/lib/hatchet-sdk.rb +86 -28
  56. data/sig/hatchet/batch.rbs +12 -0
  57. data/sig/hatchet/task.rbs +2 -0
  58. metadata +13 -2
data/lib/hatchet/task.rb CHANGED
@@ -68,6 +68,9 @@ module Hatchet
68
68
  # @return [Hatchet::EvictionPolicy, nil] Eviction policy for durable tasks
69
69
  attr_reader :eviction_policy
70
70
 
71
+ # @return [Hatchet::BatchTaskConfig, nil] Batch configuration, if this is a batch task
72
+ attr_reader :batch
73
+
71
74
  # @return [Proc, nil] The task execution block
72
75
  attr_reader :fn
73
76
 
@@ -112,6 +115,7 @@ module Hatchet
112
115
  skip_if: [],
113
116
  durable: false,
114
117
  eviction_policy: nil,
118
+ batch: nil,
115
119
  workflow: nil,
116
120
  client: nil,
117
121
  deps: nil,
@@ -131,6 +135,7 @@ module Hatchet
131
135
  @skip_if = skip_if
132
136
  @durable = durable
133
137
  @eviction_policy = eviction_policy
138
+ @batch = batch
134
139
  @workflow = workflow
135
140
  @client = client
136
141
  @deps = deps
@@ -191,6 +196,14 @@ module Hatchet
191
196
 
192
197
  opts[:is_durable] = @durable
193
198
 
199
+ # Batch tasks buffer many concurrent runs into a single execution; per-item retry
200
+ # semantics don't apply, so retries is always forced to 0, regardless of any retries
201
+ # the caller may have supplied.
202
+ if @batch
203
+ opts[:batch] = @batch.to_proto
204
+ opts[:retries] = 0
205
+ end
206
+
194
207
  ::V1::CreateTaskOpts.new(**opts)
195
208
  end
196
209
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hatchet
4
- VERSION = "0.5.0"
4
+ VERSION = "0.7.0"
5
5
  end
@@ -226,6 +226,11 @@ module Hatchet
226
226
  end
227
227
 
228
228
  def execute_task(action)
229
+ if batch_action?(action)
230
+ execute_batch_task(action)
231
+ return
232
+ end
233
+
229
234
  action_key = nil
230
235
  prepare_action_execution(action)
231
236
 
@@ -329,6 +334,114 @@ module Hatchet
329
334
  send_result(action, result)
330
335
  end
331
336
 
337
+ def batch_action?(action)
338
+ action.respond_to?(:action_type) && action.action_type == :START_BATCH
339
+ end
340
+
341
+ # Handles a START_BATCH action: invokes the registered batch task block once with
342
+ # every buffered item, then reports completion/failure back per-member (or, for
343
+ # broadcast batch tasks, the same result for every member) via send_batch_action_event.
344
+ def execute_batch_task(action)
345
+ task_key = action.action_id.downcase
346
+ task = @task_map[task_key]
347
+
348
+ unless task
349
+ @logger.error("No task found for action: #{task_key}")
350
+ return
351
+ end
352
+
353
+ batch_items = parse_batch_items(action)
354
+ send_batch_started(action, batch_items.keys)
355
+
356
+ ctx = build_context(action, task)
357
+
358
+ begin
359
+ result = task.call(batch_items, ctx)
360
+
361
+ # If the handler cancelled the batch (ctx.cancel), a CANCELLED batch event
362
+ # covering every member was already sent from within cancel itself. Don't also
363
+ # send a COMPLETED event for the same members afterward.
364
+ send_batch_result(action, task, batch_items, result) unless ctx.cancelled?
365
+ rescue StandardError => e
366
+ @logger.error("Error in batch task #{action.action_id}: #{e.message}")
367
+ send_batch_failure_for_all(action, batch_items.keys, e)
368
+ end
369
+ ensure
370
+ ContextVars.clear
371
+ end
372
+
373
+ def send_batch_started(action, member_ids)
374
+ items = member_ids.map { |id| { task_run_external_id: id } }
375
+ @dispatcher_client.send_batch_action_event(action: action, event_type: :STEP_EVENT_TYPE_STARTED, items: items)
376
+ rescue StandardError => e
377
+ @logger.warn("Failed to send batch STARTED event: #{e.message}")
378
+ end
379
+
380
+ def send_batch_result(action, task, batch_items, result)
381
+ if task.batch.broadcast_output
382
+ payload = JSON.generate(result.nil? ? {} : result)
383
+ items = batch_items.keys.map { |id| { task_run_external_id: id, event_payload: payload } }
384
+ @dispatcher_client.send_batch_action_event(action: action, event_type: :STEP_EVENT_TYPE_COMPLETED, items: items)
385
+ return
386
+ end
387
+
388
+ unless result.is_a?(Hash)
389
+ send_batch_failure_for_all(
390
+ action, batch_items.keys,
391
+ StandardError.new("batch task handler must return a Hash keyed by batch member id"),
392
+ )
393
+ return
394
+ end
395
+
396
+ missing = batch_items.keys - result.keys
397
+ extra = result.keys - batch_items.keys
398
+
399
+ if !missing.empty? || !extra.empty?
400
+ send_batch_failure_for_all(
401
+ action, batch_items.keys,
402
+ StandardError.new("batch task handler result keys do not match batch member ids (missing=#{missing}, extra=#{extra})"),
403
+ )
404
+ return
405
+ end
406
+
407
+ completed_items = []
408
+ failed_items = []
409
+
410
+ result.each do |id, value|
411
+ completed_items << { task_run_external_id: id, event_payload: JSON.generate(value) }
412
+ rescue StandardError => e
413
+ failed_items << { task_run_external_id: id, event_payload: JSON.generate({ "error" => e.message }) }
414
+ end
415
+
416
+ unless completed_items.empty?
417
+ @dispatcher_client.send_batch_action_event(action: action, event_type: :STEP_EVENT_TYPE_COMPLETED, items: completed_items)
418
+ end
419
+
420
+ return if failed_items.empty?
421
+
422
+ @dispatcher_client.send_batch_action_event(action: action, event_type: :STEP_EVENT_TYPE_FAILED, items: failed_items)
423
+ end
424
+
425
+ def send_batch_failure_for_all(action, member_ids, error)
426
+ payload = JSON.generate({ "error" => error.message })
427
+ items = member_ids.map { |id| { task_run_external_id: id, event_payload: payload, should_not_retry: true } }
428
+ @dispatcher_client.send_batch_action_event(action: action, event_type: :STEP_EVENT_TYPE_FAILED, items: items)
429
+ end
430
+
431
+ def parse_batch_items(action)
432
+ raw = action.respond_to?(:action_payload) ? action.action_payload : nil
433
+ return {} if raw.nil? || raw.to_s.empty?
434
+
435
+ parsed = JSON.parse(raw)
436
+ return {} unless parsed.is_a?(Hash)
437
+
438
+ parsed.each_with_object({}) do |(id, item), acc|
439
+ acc[id] = item.is_a?(Hash) && item["payload"].is_a?(Hash) ? (item["payload"]["input"] || {}) : {}
440
+ end
441
+ rescue JSON::ParserError
442
+ {}
443
+ end
444
+
332
445
  def action_key_for(action)
333
446
  "#{action.task_run_external_id}/#{action.retry_count}"
334
447
  end
@@ -98,10 +98,23 @@ module Hatchet
98
98
  @id ||= resolve_workflow_id
99
99
  end
100
100
 
101
- # Define a task within this workflow
101
+ # Define a task within this workflow. The block receives the workflow input
102
+ # and a {Context} object, and its return value (a Hash) becomes the task
103
+ # output.
102
104
  #
103
- # @param name [Symbol, String] Task name
104
- # @param opts [Hash] Task options (parents:, execution_timeout:, retries:, etc.)
105
+ # @param name [Symbol, String] The name of the task
106
+ # @option opts [Array<Task, Symbol>] :parents ([]) A list of tasks that are parents of the task. Note: parents must be defined before their children
107
+ # @option opts [Integer, String, nil] :execution_timeout (nil) The maximum time to wait for the task to complete, in seconds or as a duration string (e.g. "60s")
108
+ # @option opts [Integer, String, nil] :schedule_timeout (nil) The maximum time to wait for the task to be scheduled
109
+ # @option opts [Integer, nil] :retries (nil) The number of times to retry the task before failing
110
+ # @option opts [Float, nil] :backoff_factor (nil) The backoff factor for controlling exponential backoff in retries
111
+ # @option opts [Integer, nil] :backoff_max_seconds (nil) The maximum number of seconds to allow retries with exponential backoff to continue
112
+ # @option opts [Array<RateLimit>] :rate_limits ([]) A list of rate limit configurations for the task
113
+ # @option opts [ConcurrencyExpression, Array<ConcurrencyExpression>, nil] :concurrency (nil) A concurrency expression (or list of them) controlling the concurrency settings for this task
114
+ # @option opts [Hash, nil] :desired_worker_labels (nil) A hash of desired worker labels that determine to which worker the task should be assigned
115
+ # @option opts [Array] :wait_for ([]) A list of conditions that must be met before the task can run
116
+ # @option opts [Array] :skip_if ([]) A list of conditions that, if met, will cause the task to be skipped
117
+ # @option opts [Hash, nil] :deps (nil) Dependency providers to inject into the task's context
105
118
  # @yield [input, ctx] The task execution block
106
119
  # @return [Task] The created task
107
120
  def task(name, **opts, &)
@@ -130,6 +143,25 @@ module Hatchet
130
143
  task(name, durable: true, eviction_policy: eviction_policy, **opts, &)
131
144
  end
132
145
 
146
+ # Define a batch task within this workflow.
147
+ #
148
+ # Batch tasks buffer concurrent runs until Hatchet flushes the batch (size reached or
149
+ # flush interval), then invoke the block once with all buffered inputs keyed by each
150
+ # run's task-run external id. The block must return a Hash mapping each id to its
151
+ # output, or use +broadcast_output+ on the batch config to return the same result to
152
+ # all callers. retries is always forced to 0 for batch tasks.
153
+ #
154
+ # Preview: batch tasks are in beta and may change in future releases.
155
+ #
156
+ # @param name [Symbol, String] Task name
157
+ # @param batch [Hatchet::BatchTaskConfig] Batch configuration
158
+ # @param opts [Hash] Other Task options forwarded to {#task}.
159
+ # @yield [inputs, ctx] The batch execution block, receiving a Hash of task-run external id => input
160
+ # @return [Task] The created batch task
161
+ def batch_task(name, batch:, **opts, &)
162
+ task(name, batch: batch, **opts, &)
163
+ end
164
+
133
165
  # Define an on_failure task for this workflow
134
166
  #
135
167
  # @param opts [Hash] Task options
@@ -219,55 +251,65 @@ module Hatchet
219
251
  ::V1::CreateWorkflowVersionRequest.new(**args)
220
252
  end
221
253
 
222
- # Run this workflow synchronously
254
+ # Run this workflow synchronously and wait for it to complete.
223
255
  #
224
- # @param input [Hash] Workflow input
225
- # @param options [TriggerWorkflowOptions, nil] Trigger options
226
- # @return [Hash] The workflow run output
256
+ # @param input [Hash] The input data for the workflow
257
+ # @param options [TriggerWorkflowOptions, nil] Additional options for workflow execution, such as +additional_metadata:+ and +priority:+
258
+ # @return [Hash] The workflow run output, keyed by task name (e.g. `{"step1" => {...}, "step2" => {...}}`)
259
+ # @raise [Hatchet::Error] If no client is associated with the workflow
260
+ # @raise [Hatchet::FailedRunError] If the workflow run failed
227
261
  def run(input = {}, options: nil)
228
262
  raise Error, "No client associated with workflow #{@name}" unless @client
229
263
 
230
264
  @client.admin.trigger_workflow(self, input, options: options)
231
265
  end
232
266
 
233
- # Run this workflow without waiting for the result
267
+ # Trigger a workflow run without waiting for it to complete. Useful for
268
+ # starting a run and immediately returning a reference to it without
269
+ # blocking while the workflow runs.
234
270
  #
235
- # @param input [Hash] Workflow input
236
- # @param options [TriggerWorkflowOptions, nil] Trigger options
237
- # @return [WorkflowRunRef]
271
+ # @param input [Hash] The input data for the workflow
272
+ # @param options [TriggerWorkflowOptions, nil] Additional options for workflow execution
273
+ # @return [WorkflowRunRef] A reference to the workflow run, whose +result+ method blocks until the run completes
274
+ # @raise [Hatchet::Error] If no client is associated with the workflow
238
275
  def run_no_wait(input = {}, options: nil)
239
276
  raise Error, "No client associated with workflow #{@name}" unless @client
240
277
 
241
278
  @client.admin.trigger_workflow_no_wait(self, input, options: options)
242
279
  end
243
280
 
244
- # Run many instances of this workflow in bulk
281
+ # Run this workflow in bulk and wait for all runs to complete. Runs are
282
+ # triggered via bulk gRPC triggering (batched by 1000) and results are
283
+ # collected concurrently.
245
284
  #
246
- # @param items [Array<Hash>] Bulk run items
247
- # @param return_exceptions [Boolean] Return exceptions instead of raising
248
- # @return [Array] Results
285
+ # @param items [Array<Hash>] A list of bulk run items, as created by {#create_bulk_run_item}
286
+ # @param return_exceptions [Boolean] If +true+, exceptions are returned as part of the results instead of being raised
287
+ # @return [Array] A list of results for each workflow run
288
+ # @raise [Hatchet::Error] If no client is associated with the workflow
249
289
  def run_many(items, return_exceptions: false)
250
290
  raise Error, "No client associated with workflow #{@name}" unless @client
251
291
 
252
292
  @client.admin.trigger_workflow_many(self, items, return_exceptions: return_exceptions)
253
293
  end
254
294
 
255
- # Run many instances without waiting for results
295
+ # Run this workflow in bulk without waiting for the runs to complete.
256
296
  #
257
- # @param items [Array<Hash>] Bulk run items
258
- # @return [Array<WorkflowRunRef>]
297
+ # @param items [Array<Hash>] A list of bulk run items, as created by {#create_bulk_run_item}
298
+ # @return [Array<WorkflowRunRef>] A list of references to the triggered workflow runs
299
+ # @raise [Hatchet::Error] If no client is associated with the workflow
259
300
  def run_many_no_wait(items)
260
301
  raise Error, "No client associated with workflow #{@name}" unless @client
261
302
 
262
303
  @client.admin.trigger_workflow_many_no_wait(self, items)
263
304
  end
264
305
 
265
- # Create a bulk run item for use with run_many
306
+ # Create a bulk run item for this workflow, intended to be used with the
307
+ # {#run_many} methods.
266
308
  #
267
- # @param input [Hash] Input data
268
- # @param key [String, nil] Deduplication key
269
- # @param options [TriggerWorkflowOptions, nil] Trigger options
270
- # @return [Hash] Bulk run item
309
+ # @param input [Hash] The input data for the workflow
310
+ # @param key [String, nil] The key for the workflow run, used for identification and deduplication
311
+ # @param options [TriggerWorkflowOptions, nil] Additional options for the workflow run
312
+ # @return [Hash] A bulk run item that can be passed to the +run_many+ methods
271
313
  def create_bulk_run_item(input: {}, key: nil, options: nil)
272
314
  item = { input: input }
273
315
  item[:key] = key if key
@@ -275,24 +317,26 @@ module Hatchet
275
317
  item
276
318
  end
277
319
 
278
- # Schedule this workflow for future execution
320
+ # Schedule this workflow to run at a specific time.
279
321
  #
280
- # @param time [Time] When to execute
281
- # @param input [Hash] Workflow input
282
- # @param options [ScheduleTriggerWorkflowOptions, nil] Schedule options
283
- # @return [Object] Schedule result
322
+ # @param time [Time] When to execute the workflow
323
+ # @param input [Hash] The input data for the workflow
324
+ # @param options [ScheduleTriggerWorkflowOptions, nil] Additional schedule options
325
+ # @return [Object] The schedule response from the Hatchet engine
326
+ # @raise [Hatchet::Error] If no client is associated with the workflow
284
327
  def schedule(time, input: {}, options: nil)
285
328
  raise Error, "No client associated with workflow #{@name}" unless @client
286
329
 
287
330
  @client.admin.schedule_workflow(self, time, input: input, options: options)
288
331
  end
289
332
 
290
- # Create a cron trigger for this workflow
333
+ # Create a cron trigger for this workflow.
291
334
  #
292
- # @param cron_name [String] Name for the cron
293
- # @param expression [String] Cron expression
294
- # @param input [Hash] Workflow input
295
- # @return [Object] Cron result
335
+ # @param cron_name [String] The name of the cron job
336
+ # @param expression [String] The cron expression that defines the schedule
337
+ # @param input [Hash] The input data for the workflow
338
+ # @return [Object] The created cron workflow trigger
339
+ # @raise [Hatchet::Error] If no client is associated with the workflow
296
340
  def create_cron(cron_name, expression, input: {})
297
341
  raise Error, "No client associated with workflow #{@name}" unless @client
298
342
 
data/lib/hatchet-sdk.rb CHANGED
@@ -31,6 +31,7 @@ module Hatchet
31
31
  require_relative "hatchet/conditions"
32
32
  require_relative "hatchet/condition_converter"
33
33
  require_relative "hatchet/rate_limit"
34
+ require_relative "hatchet/batch"
34
35
  require_relative "hatchet/labels"
35
36
  require_relative "hatchet/trigger_options"
36
37
  require_relative "hatchet/default_filter"
@@ -112,83 +113,103 @@ module Hatchet
112
113
  @rest_client ||= Hatchet::Clients.rest_client(@config)
113
114
  end
114
115
 
115
- # Feature Client for interacting with Hatchet events
116
+ # The events client, which you can use to push events to Hatchet to trigger
117
+ # event-driven workflows.
116
118
  # @return [Hatchet::Features::Events]
117
119
  def events
118
120
  @events ||= Hatchet::Features::Events.new(rest_client, event_grpc, @config)
119
121
  end
120
122
 
121
- # Feature Client for interacting with Hatchet workflow runs
123
+ # The runs client is a client for interacting with task and workflow runs
124
+ # within Hatchet.
122
125
  # @return [Hatchet::Features::Runs]
123
126
  def runs
124
127
  @runs ||= Hatchet::Features::Runs.new(rest_client, @config, client: self)
125
128
  end
126
129
 
127
- # Feature Client for interacting with the current tenant
130
+ # The tenant client is a client for reading information about the tenant
131
+ # you're operating in.
128
132
  # @return [Hatchet::Features::Tenant]
129
133
  def tenant
130
134
  @tenant ||= Hatchet::Features::Tenant.new(rest_client, @config)
131
135
  end
132
136
 
133
- # Feature Client for interacting with Hatchet logs
137
+ # The logs client is a client for interacting with Hatchet's logs API.
134
138
  # @return [Hatchet::Features::Logs]
135
139
  def logs
136
140
  @logs ||= Hatchet::Features::Logs.new(rest_client, @config)
137
141
  end
138
142
 
139
- # Feature Client for managing workers
143
+ # The workers client is a client for managing workers programmatically
144
+ # within Hatchet.
140
145
  # @return [Hatchet::Features::Workers]
141
146
  def workers
142
147
  @workers ||= Hatchet::Features::Workers.new(rest_client, @config)
143
148
  end
144
149
 
145
- # Feature Client for debugging CEL expressions
150
+ # The CEL client is a client for debugging CEL expressions within Hatchet.
146
151
  # @return [Hatchet::Features::CEL]
147
152
  def cel
148
153
  @cel ||= Hatchet::Features::CEL.new(rest_client, @config)
149
154
  end
150
155
 
151
- # Feature Client for managing workflow definitions
156
+ # The workflows client is a client for managing workflow declarations
157
+ # programmatically within Hatchet. Note that workflows are the declaration,
158
+ # _not_ the individual runs; if you're looking for runs, use the runs
159
+ # client instead.
152
160
  # @return [Hatchet::Features::Workflows]
153
161
  def workflows
154
162
  @workflows ||= Hatchet::Features::Workflows.new(rest_client, @config)
155
163
  end
156
164
 
157
- # Feature Client for managing filters
165
+ # The filters client is a client for managing filters within Hatchet, which
166
+ # scope event triggers to workflows using CEL expressions.
158
167
  # @return [Hatchet::Features::Filters]
159
168
  def filters
160
169
  @filters ||= Hatchet::Features::Filters.new(rest_client, @config)
161
170
  end
162
171
 
163
- # Feature Client for reading metrics
172
+ # The metrics client is a client for reading metrics out of Hatchet's
173
+ # metrics API.
164
174
  # @return [Hatchet::Features::Metrics]
165
175
  def metrics
166
176
  @metrics ||= Hatchet::Features::Metrics.new(rest_client, @config)
167
177
  end
168
178
 
169
- # Feature Client for managing rate limits
179
+ # The rate limits client is a wrapper for Hatchet's gRPC API that makes it
180
+ # easier to work with rate limits in Hatchet.
170
181
  # @return [Hatchet::Features::RateLimits]
171
182
  def rate_limits
172
183
  @rate_limits ||= Hatchet::Features::RateLimits.new(admin_grpc, @config)
173
184
  end
174
185
 
175
- # Feature Client for managing cron workflows
186
+ # The cron client is a client for managing cron workflow triggers within
187
+ # Hatchet.
176
188
  # @return [Hatchet::Features::Cron]
177
189
  def cron
178
190
  @cron ||= Hatchet::Features::Cron.new(rest_client, @config)
179
191
  end
180
192
 
181
- # Feature Client for managing scheduled workflows
193
+ # The scheduled client is a client for managing scheduled workflow runs
194
+ # within Hatchet.
182
195
  # @return [Hatchet::Features::Scheduled]
183
196
  def scheduled
184
197
  @scheduled ||= Hatchet::Features::Scheduled.new(rest_client, @config)
185
198
  end
186
199
 
187
- # Create a new workflow definition
200
+ # Define a Hatchet workflow, which can then declare tasks and be run,
201
+ # scheduled, and so on.
188
202
  #
189
- # @param name [String] Workflow name
190
- # @param opts [Hash] Workflow options (on_events:, concurrency:, idempotency:, etc.)
191
- # @return [Hatchet::Workflow]
203
+ # @param name [String] The name of the workflow
204
+ # @option opts [Array<String>] :on_events ([]) A list of event triggers for the workflow - events which cause the workflow to be run
205
+ # @option opts [Array<String>] :on_crons ([]) A list of cron triggers for the workflow
206
+ # @option opts [ConcurrencyExpression, Array<ConcurrencyExpression>, nil] :concurrency (nil) A concurrency object (or list of them) controlling the concurrency settings for this workflow
207
+ # @option opts [Integer, nil] :default_priority (nil) The default priority of the workflow. Higher values will cause runs of this workflow to have priority in scheduling over other, lower priority ones
208
+ # @option opts [Hash, nil] :task_defaults (nil) Default task settings for this workflow
209
+ # @option opts [Array<DefaultFilter>] :default_filters ([]) A list of filters to create when the workflow is created
210
+ # @option opts [Symbol, nil] :sticky (nil) A sticky strategy for the workflow, either +:soft+ or +:hard+
211
+ # @option opts [TTLBasedIdempotencyConfig, StatusBasedIdempotencyConfig, nil] :idempotency (nil) An idempotency configuration for the workflow
212
+ # @return [Hatchet::Workflow] The created workflow object, which can be used to declare tasks, run the workflow, and so on
192
213
  #
193
214
  # @example
194
215
  # wf = hatchet.workflow(name: "MyWorkflow")
@@ -197,12 +218,18 @@ module Hatchet
197
218
  Workflow.new(name: name, client: self, **opts)
198
219
  end
199
220
 
200
- # Create a standalone task (auto-wraps in a single-task workflow)
221
+ # Create a standalone Hatchet task. The task is automatically wrapped in a
222
+ # single-task workflow, so it can be run, scheduled, and registered on a
223
+ # worker just like a workflow. The block receives the run's input and a
224
+ # {Context} object.
201
225
  #
202
- # @param name [String] Task name
203
- # @param opts [Hash] Task options (on_events:, idempotency:, retries:, etc.)
226
+ # @param name [String] The name of the task
227
+ # @option opts [Array<String>] :on_events ([]) A list of event triggers for the task - events which cause the task to be run
228
+ # @option opts [Array<DefaultFilter>] :default_filters ([]) A list of filters to create when the task is created
229
+ # @option opts [TTLBasedIdempotencyConfig, StatusBasedIdempotencyConfig, nil] :idempotency (nil) An idempotency configuration for the task
230
+ # @param opts [Hash] Any other keyword arguments (+retries:+, +execution_timeout:+, +concurrency:+, and so on) are forwarded to the task declaration - see {Workflow#task} for the full list
204
231
  # @yield [input, ctx] The task execution block
205
- # @return [Hatchet::Task]
232
+ # @return [Hatchet::Task] The created task object, which can be run, scheduled, and registered on a worker
206
233
  #
207
234
  # @example
208
235
  # my_task = hatchet.task(name: "my_task") { |input, ctx| { "result" => "done" } }
@@ -214,16 +241,42 @@ module Hatchet
214
241
  wf.task(name, **opts, &block)
215
242
  end
216
243
 
217
- # Create a standalone durable task.
244
+ # Create a standalone batch task (auto-wraps in a single-task workflow).
218
245
  #
219
- # @param name [String] Task name
246
+ # Batch tasks buffer concurrent runs until Hatchet flushes the batch (size reached or
247
+ # flush interval), then invoke the block once with all buffered inputs keyed by each
248
+ # run's task-run external id. The block must return a Hash mapping each id to its
249
+ # output, or use +broadcast_output+ on the batch config to return the same result to
250
+ # all callers. retries is always forced to 0 for batch tasks.
251
+ #
252
+ # Preview: batch tasks are in beta and may change in future releases.
253
+ #
254
+ # @param name [String] The name of the task
255
+ # @param batch [Hatchet::BatchTaskConfig] The batch configuration (+max_size+, flush interval, +broadcast_output+)
256
+ # @param opts [Hash] Any other keyword arguments (+on_events:+, +idempotency:+, and so on) are forwarded to {#task}
257
+ # @yield [inputs, ctx] The batch execution block, receiving a Hash of task-run external id => input
258
+ # @return [Hatchet::Task] The created batch task object
259
+ #
260
+ # @example
261
+ # batch = hatchet.batch_task(name: "my_batch", batch: Hatchet::BatchTaskConfig.new(max_size: 3)) do |inputs, ctx|
262
+ # inputs.transform_values { |input| { "result" => input["message"].upcase } }
263
+ # end
264
+ def batch_task(name:, batch:, **opts, &block)
265
+ task(name: name, batch: batch, **opts, &block)
266
+ end
267
+
268
+ # Create a standalone _durable_ Hatchet task, which works using Hatchet's
269
+ # durable execution capabilities. Durable tasks receive a {DurableContext}
270
+ # with additional methods like +sleep_for+ and +wait_for+.
271
+ #
272
+ # @param name [String] The name of the task
220
273
  # @param eviction_policy [Hatchet::EvictionPolicy, nil] Eviction policy for this
221
274
  # durable task. Defaults to {Hatchet::DEFAULT_DURABLE_TASK_EVICTION_POLICY}
222
275
  # (15-minute TTL, capacity-eviction enabled). Pass ``nil`` to disable
223
276
  # eviction entirely for this task.
224
- # @param opts [Hash] Task options
277
+ # @param opts [Hash] Any other keyword arguments (+retries:+, +execution_timeout:+, and so on) are forwarded to the task declaration - see {Workflow#task} for the full list
225
278
  # @yield [input, ctx] The task execution block
226
- # @return [Hatchet::Task]
279
+ # @return [Hatchet::Task] The created durable task object
227
280
  def durable_task(name:, eviction_policy: Hatchet::DEFAULT_DURABLE_TASK_EVICTION_POLICY, **opts, &block)
228
281
  wf = Workflow.new(name: name, client: self,
229
282
  on_events: opts.delete(:on_events) || [],
@@ -231,11 +284,16 @@ module Hatchet
231
284
  wf.durable_task(name, eviction_policy: eviction_policy, **opts, &block)
232
285
  end
233
286
 
234
- # Create a new worker
287
+ # Create a Hatchet worker on which to run workflows.
235
288
  #
236
- # @param name [String] Worker name
237
- # @param opts [Hash] Worker options (workflows:, slots:, labels:)
238
- # @return [Hatchet::Worker]
289
+ # @param name [String] The name of the worker
290
+ # @option opts [Array<Workflow, Task>] :workflows ([]) A list of workflows (or standalone tasks) to register on the worker
291
+ # @option opts [Integer] :slots (10) Slot count for standard tasks, i.e. the number of tasks the worker can run concurrently
292
+ # @option opts [Integer, nil] :durable_slots (nil) Slot count for durable tasks; defaults to +slots+ if not provided
293
+ # @option opts [Hash] :labels ({}) A hash of labels to assign to the worker, for use with worker affinity; merged with the client's +worker_preset_labels+
294
+ # @return [Hatchet::Worker] The created worker object, which exposes an instance
295
+ # method +start+ which can be called to start the worker (blocking until
296
+ # shutdown), and +stop+ to request a graceful shutdown
239
297
  #
240
298
  # @example
241
299
  # worker = hatchet.worker("my-worker", workflows: [wf], slots: 10)
@@ -0,0 +1,12 @@
1
+ module Hatchet
2
+ class BatchTaskConfig
3
+ attr_reader max_size: Integer
4
+ attr_reader max_interval_ms: Integer?
5
+ attr_reader group_key: String?
6
+ attr_reader group_max_runs: Integer?
7
+ attr_reader broadcast_output: bool
8
+
9
+ def initialize: (max_size: Integer, ?max_interval_ms: Integer?, ?group_key: String?, ?group_max_runs: Integer?, ?broadcast_output: bool) -> void
10
+ def to_proto: () -> untyped
11
+ end
12
+ end
data/sig/hatchet/task.rbs CHANGED
@@ -14,6 +14,7 @@ module Hatchet
14
14
  attr_reader skip_if: Array[untyped]
15
15
  attr_reader durable: bool
16
16
  attr_reader eviction_policy: EvictionPolicy?
17
+ attr_reader batch: BatchTaskConfig?
17
18
  attr_reader fn: Proc?
18
19
  attr_reader workflow: Workflow?
19
20
  attr_reader client: Client?
@@ -34,6 +35,7 @@ module Hatchet
34
35
  ?skip_if: Array[untyped],
35
36
  ?durable: bool,
36
37
  ?eviction_policy: EvictionPolicy?,
38
+ ?batch: BatchTaskConfig?,
37
39
  ?workflow: Workflow?,
38
40
  ?client: Client?,
39
41
  ?deps: Hash[Symbol, Proc]?