job-workflow 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +1 -1
- data/guides/API_REFERENCE.md +70 -0
- data/guides/PRODUCTION_DEPLOYMENT.md +1 -1
- data/guides/README.md +1 -6
- data/lib/job_workflow/context.rb +72 -10
- data/lib/job_workflow/dsl.rb +1 -21
- data/lib/job_workflow/railtie.rb +10 -0
- data/lib/job_workflow/runner.rb +42 -14
- data/lib/job_workflow/task.rb +10 -7
- data/lib/job_workflow/version.rb +1 -1
- data/lib/job_workflow/workflow.rb +0 -11
- data/lib/job_workflow.rb +3 -2
- data/rbs_collection.lock.yaml +11 -11
- data/sig/generated/job_workflow/context.rbs +30 -6
- data/sig/generated/job_workflow/dsl.rbs +0 -8
- data/sig/generated/job_workflow/railtie.rbs +6 -0
- data/sig/generated/job_workflow/runner.rbs +10 -4
- data/sig/generated/job_workflow/task.rbs +7 -5
- data/sig/generated/job_workflow/workflow.rbs +0 -5
- metadata +3 -4
- data/guides/NAMESPACES.md +0 -75
- data/lib/job_workflow/namespace.rb +0 -36
- data/sig/generated/job_workflow/namespace.rbs +0 -24
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9474c65583cf21d485c3468675d4f23124e60e88f98c954bd38256b916d98ffe
|
|
4
|
+
data.tar.gz: 59d5e0b60a2b52e87ea4da4aab1834d02c63f88cba36f661acd8c74f89a4e6a9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4ac034e1b373b28aa52dcffd059bc5b64e5c45fc04d9b07460c24491c2514eabddf839b2dcb2c4a2cfbf36b578e2836e153e9bd6f34cd24f8af543cf06242ce9
|
|
7
|
+
data.tar.gz: 455b6913137103b6a5871562a6adc0ffca11e9e99543b94ec7e0bda9f2b78c6c76c5cee73baec30ca5b5b5e2fd388d6a4b119c08d7cf1039c20e115e255f58d7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.5.0] - 2026-05-13
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Add task-scoped cursor helpers on `Context` so tasks can persist progress, restore task-local continuation state, and delegate explicit checkpoints without exposing ActiveJob continuation internals
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Initialize the SolidQueue adapter from a Railtie after Rails boot so the `ClaimedExecution` patch is applied reliably, rescheduled `dependency_wait` jobs are not marked finished early, and Rails apps no longer need a manual initializer
|
|
12
|
+
|
|
13
|
+
## [0.4.0] - 2026-05-12
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Fix `enqueue: true` consumer tasks that read `depends_on` outputs by persisting the parent context before `perform_all_later`, so sub-jobs load the latest dependency outputs instead of stale persisted state
|
|
18
|
+
- Fix sequential `each` task resumption to persist continuation cursor progress at the workflow step level, resume from `step.cursor`, and keep the failed iteration retrying from the same `each_index` without skipping previously collected outputs
|
|
19
|
+
|
|
20
|
+
### Removed
|
|
21
|
+
|
|
22
|
+
- Remove the `namespace` DSL and `JobWorkflow::Namespace`, flatten task naming to a single task space, and delete the dedicated namespace guide/example coverage that existed for the removed feature
|
|
23
|
+
|
|
3
24
|
## [0.3.0] - 2026-03-13
|
|
4
25
|
|
|
5
26
|
### Added
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# JobWorkflow
|
|
2
2
|
|
|
3
|
-
> ⚠️ **Early Stage (v0.
|
|
3
|
+
> ⚠️ **Early Stage (v0.5.0):** This library is in active development. APIs and features may change in breaking ways without notice. Use in production at your own risk and expect potential breaking changes in future releases.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
data/guides/API_REFERENCE.md
CHANGED
|
@@ -84,6 +84,76 @@ end
|
|
|
84
84
|
|
|
85
85
|
**Map Task Output**: When `each:` is specified, outputs are automatically collected as an array.
|
|
86
86
|
|
|
87
|
+
### Task continuation helpers
|
|
88
|
+
|
|
89
|
+
Inside a task body, you can read the current task cursor, store a new cursor, and create interruption points through the task context.
|
|
90
|
+
|
|
91
|
+
#### Regular task example
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
task :sync_pages, output: { processed: "Integer" } do |ctx|
|
|
95
|
+
page = ctx.cursor || 1
|
|
96
|
+
result = ExternalAPI.fetch(page:)
|
|
97
|
+
|
|
98
|
+
ctx.set_cursor!(page + 1) if result.next_page?
|
|
99
|
+
|
|
100
|
+
{ processed: result.items.size }
|
|
101
|
+
end
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
- `ctx.cursor` returns the current task cursor, or `nil` when no cursor has been stored
|
|
105
|
+
- `ctx.set_cursor!(value)` validates that `value` is ActiveJob-serializable, stores it in the current continuation step, and checkpoints the job through Active Job continuation
|
|
106
|
+
- `ctx.checkpoint!` creates a checkpoint without changing the public cursor value
|
|
107
|
+
- Call `ctx.set_cursor!` when you want to change the public cursor value and create a checkpoint at the same time
|
|
108
|
+
- Call `ctx.checkpoint!` when you want the current task execution to become interruptible without changing the public cursor value
|
|
109
|
+
- Outside task execution, `ctx.cursor` returns `nil`, and `ctx.set_cursor!` / `ctx.checkpoint!` raise an error
|
|
110
|
+
|
|
111
|
+
For regular tasks, a cursor is only persisted when you call `ctx.set_cursor!(value)` explicitly.
|
|
112
|
+
|
|
113
|
+
#### Checkpoint without changing the cursor
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
task :publish_report do |ctx|
|
|
117
|
+
report = build_report
|
|
118
|
+
ctx.checkpoint!
|
|
119
|
+
deliver_report(report)
|
|
120
|
+
end
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### Repeating work inside a task
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
task :sync_users do |ctx|
|
|
127
|
+
start_index = ctx.cursor || 0
|
|
128
|
+
|
|
129
|
+
ctx.arguments.user_ids.drop(start_index).each_with_index do |user_id, offset|
|
|
130
|
+
sync_user(user_id)
|
|
131
|
+
ctx.set_cursor!(start_index + offset + 1)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
This pattern is useful when a single task iterates over an Enumerable internally and you want to resume from the last completed item after an interruption.
|
|
137
|
+
|
|
138
|
+
#### `each:` task example
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
task :sync_users, each: ->(ctx) { ctx.arguments.user_ids } do |ctx|
|
|
142
|
+
next_cursor = ExternalAPI.sync_user(
|
|
143
|
+
user_id: ctx.each_value,
|
|
144
|
+
cursor: ctx.cursor
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
ctx.set_cursor!(next_cursor) unless next_cursor.nil?
|
|
148
|
+
end
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
For `each:` tasks, JobWorkflow keeps the existing integer resume behavior for completed iterations.
|
|
152
|
+
|
|
153
|
+
- If an `each:` task is interrupted after calling `ctx.set_cursor!`, JobWorkflow resumes with both the current iteration index and the saved task cursor
|
|
154
|
+
- If an iteration completes normally, the resume state advances to the next integer index
|
|
155
|
+
- In other words, a custom cursor in an `each:` task is meant for resuming work inside the current iteration, not for replacing the completion index of finished iterations
|
|
156
|
+
|
|
87
157
|
### workflow_concurrency
|
|
88
158
|
|
|
89
159
|
Configure job-level concurrency limits with workflow-aware context.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Production Deployment
|
|
2
2
|
|
|
3
|
-
> ⚠️ **Early Stage (v0.
|
|
3
|
+
> ⚠️ **Early Stage (v0.5.0):** JobWorkflow is still in early development. While this section outlines potential deployment patterns, please thoroughly test in your specific environment and monitor for any issues before relying on JobWorkflow in critical production systems.
|
|
4
4
|
|
|
5
5
|
This section covers suggested settings and patterns for running JobWorkflow in production-like environments.
|
|
6
6
|
|
data/guides/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# JobWorkflow Guides
|
|
2
2
|
|
|
3
|
-
> ⚠️ **Early Stage (v0.
|
|
3
|
+
> ⚠️ **Early Stage (v0.5.0):** JobWorkflow is in active development. APIs and features may change. The following guides provide patterns and examples for building workflows, but be aware that implementations may need adjustment as the library evolves.
|
|
4
4
|
|
|
5
5
|
Welcome to the JobWorkflow documentation! This directory contains comprehensive guides to help you build robust workflows with JobWorkflow.
|
|
6
6
|
|
|
@@ -67,11 +67,6 @@ Power features for complex workflows:
|
|
|
67
67
|
- Configuration options (poll_timeout, poll_interval, reschedule_delay)
|
|
68
68
|
- SolidQueue integration
|
|
69
69
|
|
|
70
|
-
- **[NAMESPACES.md](NAMESPACES.md)** - Organizing large workflows
|
|
71
|
-
- Basic namespace usage
|
|
72
|
-
- Nested namespaces
|
|
73
|
-
- Cross-namespace dependencies
|
|
74
|
-
|
|
75
70
|
- **[THROTTLING.md](THROTTLING.md)** - Rate limiting and resource control
|
|
76
71
|
- Task-level throttling
|
|
77
72
|
- Runtime throttling
|
data/lib/job_workflow/context.rb
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module JobWorkflow
|
|
4
4
|
class Context # rubocop:disable Metrics/ClassLength
|
|
5
|
+
EACH_TASK_CURSOR_MARKER = "__job_workflow_each_cursor__"
|
|
6
|
+
|
|
5
7
|
attr_reader :workflow #: Workflow
|
|
6
8
|
attr_reader :arguments #: Arguments
|
|
7
9
|
attr_reader :output #: Output
|
|
@@ -52,7 +54,7 @@ module JobWorkflow
|
|
|
52
54
|
# job_status: JobStatus,
|
|
53
55
|
# ?job: DSL?
|
|
54
56
|
# ) -> void
|
|
55
|
-
def initialize(workflow:, arguments:, task_context:, output:, job_status:, job: nil) # rubocop:disable Metrics/ParameterLists
|
|
57
|
+
def initialize(workflow:, arguments:, task_context:, output:, job_status:, job: nil) # rubocop:disable Metrics/ParameterLists, Metrics/AbcSize, Metrics/MethodLength
|
|
56
58
|
raise "job does not match the provided workflow" if job&.then { |j| j.class._workflow != workflow }
|
|
57
59
|
|
|
58
60
|
self.job = job
|
|
@@ -64,6 +66,8 @@ module JobWorkflow
|
|
|
64
66
|
self.enabled_with_each_value = false
|
|
65
67
|
self.throttle_index = 0
|
|
66
68
|
self.skip_in_dry_run_index = 0
|
|
69
|
+
self.current_step = nil
|
|
70
|
+
self.current_cursor = nil
|
|
67
71
|
end
|
|
68
72
|
|
|
69
73
|
#: () -> Hash[String, untyped]
|
|
@@ -77,6 +81,31 @@ module JobWorkflow
|
|
|
77
81
|
self
|
|
78
82
|
end
|
|
79
83
|
|
|
84
|
+
#: () -> untyped
|
|
85
|
+
def cursor
|
|
86
|
+
return if current_step.nil?
|
|
87
|
+
|
|
88
|
+
current_cursor
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
#: (untyped) -> void
|
|
92
|
+
def set_cursor!(value)
|
|
93
|
+
step = current_step || (raise "set_cursor! can be called only in task")
|
|
94
|
+
|
|
95
|
+
ActiveJob::Arguments.serialize([value])
|
|
96
|
+
self.current_cursor = value
|
|
97
|
+
step.set!(build_step_cursor(value))
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
#: () -> void
|
|
101
|
+
def checkpoint!
|
|
102
|
+
step = current_step || (raise "checkpoint! can be called only in task")
|
|
103
|
+
|
|
104
|
+
return step.checkpoint! unless each_task?
|
|
105
|
+
|
|
106
|
+
step.set!(build_step_cursor(current_cursor))
|
|
107
|
+
end
|
|
108
|
+
|
|
80
109
|
#: (DSL) -> void
|
|
81
110
|
def _job=(job)
|
|
82
111
|
self.job = job
|
|
@@ -108,13 +137,13 @@ module JobWorkflow
|
|
|
108
137
|
[task_context.parent_job_id, task.task_name].compact.join("/")
|
|
109
138
|
end
|
|
110
139
|
|
|
111
|
-
#: (Task) -> Enumerator[Context]
|
|
112
|
-
def _with_each_value(task)
|
|
140
|
+
#: (Task, ?start_index: Integer?) -> Enumerator[Context]
|
|
141
|
+
def _with_each_value(task, start_index: nil)
|
|
113
142
|
raise "Nested _with_each_value calls are not allowed" if enabled_with_each_value
|
|
114
143
|
|
|
115
144
|
self.enabled_with_each_value = true
|
|
116
145
|
Enumerator.new do |y|
|
|
117
|
-
with_task_context(task, y)
|
|
146
|
+
with_task_context(task, y, start_index:)
|
|
118
147
|
ensure
|
|
119
148
|
self.enabled_with_each_value = false
|
|
120
149
|
end
|
|
@@ -220,6 +249,18 @@ module JobWorkflow
|
|
|
220
249
|
task_context
|
|
221
250
|
end
|
|
222
251
|
|
|
252
|
+
#: (ActiveJob::Continuation::Step, ?cursor: untyped) { () -> void } -> void
|
|
253
|
+
def _with_current_step(step, cursor: nil)
|
|
254
|
+
previous_step = current_step
|
|
255
|
+
previous_cursor = current_cursor
|
|
256
|
+
self.current_step = step
|
|
257
|
+
self.current_cursor = cursor
|
|
258
|
+
yield
|
|
259
|
+
ensure
|
|
260
|
+
self.current_step = previous_step
|
|
261
|
+
self.current_cursor = previous_cursor
|
|
262
|
+
end
|
|
263
|
+
|
|
223
264
|
#: (TaskOutput) -> void
|
|
224
265
|
def _add_task_output(task_output)
|
|
225
266
|
output.add_task_output(task_output)
|
|
@@ -245,12 +286,31 @@ module JobWorkflow
|
|
|
245
286
|
attr_accessor :enabled_with_each_value #: bool
|
|
246
287
|
attr_accessor :throttle_index #: Integer
|
|
247
288
|
attr_accessor :skip_in_dry_run_index #: Integer
|
|
289
|
+
attr_accessor :current_step #: ActiveJob::Continuation::Step?
|
|
290
|
+
attr_accessor :current_cursor #: untyped
|
|
248
291
|
|
|
249
292
|
#: () -> String
|
|
250
293
|
def parent_job_id
|
|
251
294
|
_task_context.parent_job_id || job_id
|
|
252
295
|
end
|
|
253
296
|
|
|
297
|
+
#: () -> bool
|
|
298
|
+
def each_task?
|
|
299
|
+
task_context.task.each?
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
#: (untyped) -> untyped
|
|
303
|
+
def build_step_cursor(value)
|
|
304
|
+
return value unless each_task?
|
|
305
|
+
return task_context.index if value.nil?
|
|
306
|
+
|
|
307
|
+
{
|
|
308
|
+
EACH_TASK_CURSOR_MARKER => true,
|
|
309
|
+
"index" => task_context.index,
|
|
310
|
+
"cursor" => value
|
|
311
|
+
}
|
|
312
|
+
end
|
|
313
|
+
|
|
254
314
|
#: () -> Hash[String, untyped]
|
|
255
315
|
def serialize_for_job
|
|
256
316
|
{
|
|
@@ -270,11 +330,11 @@ module JobWorkflow
|
|
|
270
330
|
}
|
|
271
331
|
end
|
|
272
332
|
|
|
273
|
-
#: (Task, Enumerator::Yielder) -> void
|
|
274
|
-
def with_task_context(task, yielder) # rubocop:disable Metrics/MethodLength
|
|
333
|
+
#: (Task, Enumerator::Yielder, ?start_index: Integer?) -> void
|
|
334
|
+
def with_task_context(task, yielder, start_index: nil) # rubocop:disable Metrics/MethodLength
|
|
275
335
|
reset_task_context_if_task_changed(task)
|
|
276
336
|
|
|
277
|
-
with_each_index_and_value(task) do |value, index|
|
|
337
|
+
with_each_index_and_value(task, start_index:) do |value, index|
|
|
278
338
|
dry_run = calculate_dry_run(task)
|
|
279
339
|
with_retry(task) do |retry_count|
|
|
280
340
|
self.task_context = TaskContext.new(task:, parent_job_id:, index:, value:, retry_count:, dry_run:)
|
|
@@ -294,10 +354,12 @@ module JobWorkflow
|
|
|
294
354
|
self.task_context = TaskContext.new if task_context.task&.task_name != task.task_name
|
|
295
355
|
end
|
|
296
356
|
|
|
297
|
-
#: (Task) { (untyped, Integer) -> void } -> void
|
|
298
|
-
def with_each_index_and_value(task)
|
|
357
|
+
#: (Task, ?start_index: Integer?) { (untyped, Integer) -> void } -> void
|
|
358
|
+
def with_each_index_and_value(task, start_index: nil)
|
|
359
|
+
resume_index = start_index || task_context.index
|
|
360
|
+
|
|
299
361
|
task.each.call(self).each.with_index do |value, index|
|
|
300
|
-
next if index <
|
|
362
|
+
next if index < resume_index
|
|
301
363
|
|
|
302
364
|
yield value, index
|
|
303
365
|
|
data/lib/job_workflow/dsl.rb
CHANGED
|
@@ -108,15 +108,9 @@ module JobWorkflow
|
|
|
108
108
|
|
|
109
109
|
#: (Symbol argument_name, String type, ?default: untyped) -> void
|
|
110
110
|
def argument(argument_name, type, default: nil)
|
|
111
|
-
validate_namespace!
|
|
112
111
|
_workflow.add_argument(ArgumentDef.new(name: argument_name, type:, default:))
|
|
113
112
|
end
|
|
114
113
|
|
|
115
|
-
#: (Symbol) { () -> void } -> void
|
|
116
|
-
def namespace(namespace_name, &)
|
|
117
|
-
_workflow.add_namespace(Namespace.new(name: namespace_name), &)
|
|
118
|
-
end
|
|
119
|
-
|
|
120
114
|
# rubocop:disable Metrics/ParameterLists
|
|
121
115
|
#
|
|
122
116
|
#: (
|
|
@@ -134,7 +128,7 @@ module JobWorkflow
|
|
|
134
128
|
# ) { (untyped) -> void } -> void
|
|
135
129
|
def task(
|
|
136
130
|
task_name,
|
|
137
|
-
each:
|
|
131
|
+
each: Task::DEFAULT_EACH,
|
|
138
132
|
enqueue: nil,
|
|
139
133
|
retry: 0,
|
|
140
134
|
output: {},
|
|
@@ -149,7 +143,6 @@ module JobWorkflow
|
|
|
149
143
|
new_task = Task.new(
|
|
150
144
|
job_name: name,
|
|
151
145
|
name: task_name,
|
|
152
|
-
namespace: _workflow.namespace,
|
|
153
146
|
block: block,
|
|
154
147
|
enqueue:,
|
|
155
148
|
each:,
|
|
@@ -172,25 +165,21 @@ module JobWorkflow
|
|
|
172
165
|
|
|
173
166
|
#: (*Symbol) { (Context) -> void } -> void
|
|
174
167
|
def before(*task_names, &block)
|
|
175
|
-
validate_namespace!
|
|
176
168
|
_workflow.add_hook(:before, task_names:, block:)
|
|
177
169
|
end
|
|
178
170
|
|
|
179
171
|
#: (*Symbol) { (Context) -> void } -> void
|
|
180
172
|
def after(*task_names, &block)
|
|
181
|
-
validate_namespace!
|
|
182
173
|
_workflow.add_hook(:after, task_names:, block:)
|
|
183
174
|
end
|
|
184
175
|
|
|
185
176
|
#: (*Symbol) { (Context, TaskCallable) -> void } -> void
|
|
186
177
|
def around(*task_names, &block)
|
|
187
|
-
validate_namespace!
|
|
188
178
|
_workflow.add_hook(:around, task_names:, block:)
|
|
189
179
|
end
|
|
190
180
|
|
|
191
181
|
#: (*Symbol) { (Context, StandardError, Task) -> void } -> void
|
|
192
182
|
def on_error(*task_names, &block)
|
|
193
|
-
validate_namespace!
|
|
194
183
|
_workflow.add_hook(:error, task_names:, block:)
|
|
195
184
|
end
|
|
196
185
|
|
|
@@ -240,7 +229,6 @@ module JobWorkflow
|
|
|
240
229
|
|
|
241
230
|
#: (?bool) ?{ (Context) -> bool } -> void
|
|
242
231
|
def dry_run(value = nil, &block)
|
|
243
|
-
validate_namespace!
|
|
244
232
|
_workflow.dry_run_config = block || value
|
|
245
233
|
end
|
|
246
234
|
|
|
@@ -254,7 +242,6 @@ module JobWorkflow
|
|
|
254
242
|
# ?description: String?
|
|
255
243
|
# ) -> void
|
|
256
244
|
def schedule(expression, key: nil, queue: nil, priority: nil, args: {}, description: nil)
|
|
257
|
-
validate_namespace!
|
|
258
245
|
_workflow.add_schedule(
|
|
259
246
|
Schedule.new(
|
|
260
247
|
expression:,
|
|
@@ -268,13 +255,6 @@ module JobWorkflow
|
|
|
268
255
|
)
|
|
269
256
|
end
|
|
270
257
|
# rubocop:enable Metrics/ParameterLists
|
|
271
|
-
|
|
272
|
-
private
|
|
273
|
-
|
|
274
|
-
#: () -> void
|
|
275
|
-
def validate_namespace!
|
|
276
|
-
raise "cannot be defined within a namespace." unless _workflow.namespace.default?
|
|
277
|
-
end
|
|
278
258
|
end
|
|
279
259
|
end
|
|
280
260
|
end
|
data/lib/job_workflow/runner.rb
CHANGED
|
@@ -14,13 +14,13 @@ module JobWorkflow
|
|
|
14
14
|
def run
|
|
15
15
|
task = context._task_context.task
|
|
16
16
|
if !task.nil? && context.sub_job?
|
|
17
|
-
run_task(task)
|
|
18
|
-
|
|
17
|
+
job.step(task.task_name) { |step| run_task(task, step:) }
|
|
18
|
+
persist_current_job_context
|
|
19
19
|
return
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
catch(:rescheduled) { run_workflow }
|
|
23
|
-
|
|
23
|
+
persist_current_job_context
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
private
|
|
@@ -50,7 +50,7 @@ module JobWorkflow
|
|
|
50
50
|
|
|
51
51
|
job.step(task.task_name) do |step|
|
|
52
52
|
wait_for_dependent_tasks(task, step)
|
|
53
|
-
task.enqueue.should_enqueue?(context) ? enqueue_task(task) : run_task(task)
|
|
53
|
+
task.enqueue.should_enqueue?(context) ? enqueue_task(task) : run_task(task, step:)
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
end
|
|
@@ -63,24 +63,46 @@ module JobWorkflow
|
|
|
63
63
|
result
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
#: (Task) -> void
|
|
67
|
-
def run_task(task)
|
|
66
|
+
#: (Task, step: ActiveJob::Continuation::Step) -> void
|
|
67
|
+
def run_task(task, step:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
68
68
|
context._load_parent_task_output
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
start_index, task_cursor = decode_task_cursor(task, step.cursor)
|
|
70
|
+
|
|
71
|
+
context._with_each_value(task, start_index:).each do |ctx|
|
|
72
|
+
iteration_cursor = task_cursor
|
|
73
|
+
iteration_cursor = nil if task.each? && (task_cursor.nil? || start_index != ctx._task_context.index)
|
|
74
|
+
|
|
75
|
+
run_each_task(task, ctx, step:, cursor: iteration_cursor)
|
|
76
|
+
step.set!(ctx._task_context.index + 1) if task.each?
|
|
71
77
|
rescue StandardError => e
|
|
72
78
|
run_error_hooks(task, ctx, e)
|
|
73
79
|
raise
|
|
74
80
|
end
|
|
75
81
|
end
|
|
76
82
|
|
|
77
|
-
#: (Task,
|
|
78
|
-
def
|
|
83
|
+
#: (Task, untyped) -> [Integer?, untyped]
|
|
84
|
+
def decode_task_cursor(task, task_cursor)
|
|
85
|
+
return [nil, task_cursor] unless task.each?
|
|
86
|
+
return [task_cursor, nil] if task_cursor.is_a?(Integer)
|
|
87
|
+
|
|
88
|
+
if task_cursor.is_a?(Hash) && task_cursor[Context::EACH_TASK_CURSOR_MARKER]
|
|
89
|
+
return [task_cursor.fetch("index"), task_cursor.fetch("cursor")]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
raise "invalid each task cursor: #{task_cursor.inspect}" unless task_cursor.nil?
|
|
93
|
+
|
|
94
|
+
[nil, nil]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
#: (Task, Context, step: ActiveJob::Continuation::Step, ?cursor: untyped) -> void
|
|
98
|
+
def run_each_task(task, ctx, step:, cursor: nil)
|
|
79
99
|
Instrumentation.instrument_task(job, task, ctx) do
|
|
80
|
-
ctx.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
100
|
+
ctx._with_current_step(step, cursor:) do
|
|
101
|
+
ctx._with_task_throttle do
|
|
102
|
+
run_hooks(task, ctx) do
|
|
103
|
+
data = task.block.call(ctx)
|
|
104
|
+
add_task_output(ctx:, task:, each_index: ctx._task_context.index, data:)
|
|
105
|
+
end
|
|
84
106
|
end
|
|
85
107
|
end
|
|
86
108
|
end
|
|
@@ -112,6 +134,7 @@ module JobWorkflow
|
|
|
112
134
|
#: (Task) -> void
|
|
113
135
|
def enqueue_task(task)
|
|
114
136
|
sub_jobs = context._with_each_value(task).map { |ctx| job.class.from_context(ctx) }
|
|
137
|
+
persist_current_job_context
|
|
115
138
|
ActiveJob.perform_all_later(sub_jobs)
|
|
116
139
|
context.job_status.update_task_job_statuses_from_jobs(task_name: task.task_name, jobs: sub_jobs)
|
|
117
140
|
Instrumentation.notify_task_enqueue(job, task, sub_jobs.size)
|
|
@@ -175,5 +198,10 @@ module JobWorkflow
|
|
|
175
198
|
context_data_list = QueueAdapter.current.fetch_job_contexts(finished_job_ids)
|
|
176
199
|
context.output.update_task_outputs_from_contexts(context_data_list, context.workflow)
|
|
177
200
|
end
|
|
201
|
+
|
|
202
|
+
#: () -> void
|
|
203
|
+
def persist_current_job_context
|
|
204
|
+
QueueAdapter.current.persist_job_context(job)
|
|
205
|
+
end
|
|
178
206
|
end
|
|
179
207
|
end
|
data/lib/job_workflow/task.rb
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module JobWorkflow
|
|
4
4
|
class Task
|
|
5
|
+
DEFAULT_EACH = ->(_ctx) { [nil] }
|
|
6
|
+
|
|
5
7
|
attr_reader :job_name #: String
|
|
6
|
-
attr_reader :namespace #: Namespace
|
|
7
8
|
attr_reader :block #: ^(untyped) -> void
|
|
8
9
|
attr_reader :each #: ^(Context) -> untyped
|
|
9
10
|
attr_reader :enqueue #: TaskEnqueue
|
|
@@ -16,11 +17,10 @@ module JobWorkflow
|
|
|
16
17
|
attr_reader :dependency_wait #: TaskDependencyWait
|
|
17
18
|
attr_reader :dry_run_config #: DryRunConfig
|
|
18
19
|
|
|
19
|
-
# rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
|
|
20
|
+
# rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
|
|
20
21
|
#: (
|
|
21
22
|
# job_name: String,
|
|
22
23
|
# name: Symbol,
|
|
23
|
-
# namespace: Namespace,
|
|
24
24
|
# block: ^(untyped) -> void,
|
|
25
25
|
# ?each: ^(Context) -> untyped,
|
|
26
26
|
# ?enqueue: true | false | ^(Context) -> bool | Hash[Symbol, untyped],
|
|
@@ -36,7 +36,6 @@ module JobWorkflow
|
|
|
36
36
|
def initialize(
|
|
37
37
|
job_name:,
|
|
38
38
|
name:,
|
|
39
|
-
namespace:,
|
|
40
39
|
block:,
|
|
41
40
|
each: nil,
|
|
42
41
|
enqueue: nil,
|
|
@@ -51,7 +50,6 @@ module JobWorkflow
|
|
|
51
50
|
)
|
|
52
51
|
@job_name = job_name
|
|
53
52
|
@name = name
|
|
54
|
-
@namespace = namespace #: Namespace
|
|
55
53
|
@block = block
|
|
56
54
|
@each = each
|
|
57
55
|
@enqueue = TaskEnqueue.from_primitive_value(enqueue)
|
|
@@ -64,11 +62,11 @@ module JobWorkflow
|
|
|
64
62
|
@dependency_wait = TaskDependencyWait.from_primitive_value(dependency_wait)
|
|
65
63
|
@dry_run_config = DryRunConfig.from_primitive_value(dry_run)
|
|
66
64
|
end
|
|
67
|
-
# rubocop:enable Metrics/ParameterLists, Metrics/MethodLength
|
|
65
|
+
# rubocop:enable Metrics/ParameterLists, Metrics/MethodLength
|
|
68
66
|
|
|
69
67
|
#: () -> Symbol
|
|
70
68
|
def task_name
|
|
71
|
-
|
|
69
|
+
name
|
|
72
70
|
end
|
|
73
71
|
|
|
74
72
|
#: () -> String
|
|
@@ -76,6 +74,11 @@ module JobWorkflow
|
|
|
76
74
|
"#{job_name}:#{task_name}"
|
|
77
75
|
end
|
|
78
76
|
|
|
77
|
+
#: () -> bool
|
|
78
|
+
def each?
|
|
79
|
+
!each.nil? && !each.equal?(DEFAULT_EACH)
|
|
80
|
+
end
|
|
81
|
+
|
|
79
82
|
private
|
|
80
83
|
|
|
81
84
|
attr_reader :name #: Symbol
|
data/lib/job_workflow/version.rb
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
module JobWorkflow
|
|
4
4
|
class Workflow
|
|
5
|
-
attr_reader :namespace #: Namespace
|
|
6
5
|
attr_reader :dry_run_config #: DryRunConfig
|
|
7
6
|
|
|
8
7
|
#: () -> void
|
|
@@ -10,7 +9,6 @@ module JobWorkflow
|
|
|
10
9
|
@task_graph = TaskGraph.new
|
|
11
10
|
@argument_defs = {} #: Hash[Symbol, ArgumentDef]
|
|
12
11
|
@hook_registry = HookRegistry.new
|
|
13
|
-
@namespace = Namespace.default #: Namespace
|
|
14
12
|
@schedules = {} #: Hash[Symbol, Schedule]
|
|
15
13
|
@dry_run_config = DryRunConfig.new
|
|
16
14
|
end
|
|
@@ -20,15 +18,6 @@ module JobWorkflow
|
|
|
20
18
|
@dry_run_config = DryRunConfig.from_primitive_value(value)
|
|
21
19
|
end
|
|
22
20
|
|
|
23
|
-
#: (Namespace) { () -> void } -> void
|
|
24
|
-
def add_namespace(namespace)
|
|
25
|
-
original_namespace = @namespace
|
|
26
|
-
@namespace = namespace.update_parent(original_namespace)
|
|
27
|
-
yield
|
|
28
|
-
ensure
|
|
29
|
-
@namespace = original_namespace
|
|
30
|
-
end
|
|
31
|
-
|
|
32
21
|
#: (Task) -> void
|
|
33
22
|
def add_task(task)
|
|
34
23
|
@task_graph.add(task)
|
data/lib/job_workflow.rb
CHANGED
|
@@ -23,7 +23,6 @@ require_relative "job_workflow/task_throttle"
|
|
|
23
23
|
require_relative "job_workflow/task_enqueue"
|
|
24
24
|
require_relative "job_workflow/task_dependency_wait"
|
|
25
25
|
require_relative "job_workflow/semaphore"
|
|
26
|
-
require_relative "job_workflow/namespace"
|
|
27
26
|
require_relative "job_workflow/hook"
|
|
28
27
|
require_relative "job_workflow/error_hook"
|
|
29
28
|
require_relative "job_workflow/hook_registry"
|
|
@@ -46,6 +45,9 @@ require_relative "job_workflow/task_output"
|
|
|
46
45
|
require_relative "job_workflow/output"
|
|
47
46
|
require_relative "job_workflow/queue"
|
|
48
47
|
require_relative "job_workflow/auto_scaling"
|
|
48
|
+
# :nocov:
|
|
49
|
+
require_relative "job_workflow/railtie" if defined?(Rails::Railtie)
|
|
50
|
+
# :nocov:
|
|
49
51
|
|
|
50
52
|
module JobWorkflow
|
|
51
53
|
class Error < StandardError; end
|
|
@@ -54,6 +56,5 @@ module JobWorkflow
|
|
|
54
56
|
|
|
55
57
|
Instrumentation::LogSubscriber.attach!
|
|
56
58
|
|
|
57
|
-
ActiveSupport.on_load(:solid_queue) { QueueAdapter.current.initialize_adapter! }
|
|
58
59
|
QueueAdapter.current.initialize_adapter!
|
|
59
60
|
end
|
data/rbs_collection.lock.yaml
CHANGED
|
@@ -6,7 +6,7 @@ gems:
|
|
|
6
6
|
source:
|
|
7
7
|
type: git
|
|
8
8
|
name: ruby/gem_rbs_collection
|
|
9
|
-
revision:
|
|
9
|
+
revision: 81fa8bd0617286078617a62b6a3229cebfd4af23
|
|
10
10
|
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
11
11
|
repo_dir: gems
|
|
12
12
|
- name: activerecord
|
|
@@ -14,7 +14,7 @@ gems:
|
|
|
14
14
|
source:
|
|
15
15
|
type: git
|
|
16
16
|
name: ruby/gem_rbs_collection
|
|
17
|
-
revision:
|
|
17
|
+
revision: 81fa8bd0617286078617a62b6a3229cebfd4af23
|
|
18
18
|
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
19
19
|
repo_dir: gems
|
|
20
20
|
- name: activesupport
|
|
@@ -22,7 +22,7 @@ gems:
|
|
|
22
22
|
source:
|
|
23
23
|
type: git
|
|
24
24
|
name: ruby/gem_rbs_collection
|
|
25
|
-
revision:
|
|
25
|
+
revision: 81fa8bd0617286078617a62b6a3229cebfd4af23
|
|
26
26
|
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
27
27
|
repo_dir: gems
|
|
28
28
|
- name: base64
|
|
@@ -34,7 +34,7 @@ gems:
|
|
|
34
34
|
source:
|
|
35
35
|
type: git
|
|
36
36
|
name: ruby/gem_rbs_collection
|
|
37
|
-
revision:
|
|
37
|
+
revision: 81fa8bd0617286078617a62b6a3229cebfd4af23
|
|
38
38
|
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
39
39
|
repo_dir: gems
|
|
40
40
|
- name: concurrent-ruby
|
|
@@ -42,7 +42,7 @@ gems:
|
|
|
42
42
|
source:
|
|
43
43
|
type: git
|
|
44
44
|
name: ruby/gem_rbs_collection
|
|
45
|
-
revision:
|
|
45
|
+
revision: 81fa8bd0617286078617a62b6a3229cebfd4af23
|
|
46
46
|
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
47
47
|
repo_dir: gems
|
|
48
48
|
- name: connection_pool
|
|
@@ -50,7 +50,7 @@ gems:
|
|
|
50
50
|
source:
|
|
51
51
|
type: git
|
|
52
52
|
name: ruby/gem_rbs_collection
|
|
53
|
-
revision:
|
|
53
|
+
revision: 81fa8bd0617286078617a62b6a3229cebfd4af23
|
|
54
54
|
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
55
55
|
repo_dir: gems
|
|
56
56
|
- name: date
|
|
@@ -74,7 +74,7 @@ gems:
|
|
|
74
74
|
source:
|
|
75
75
|
type: git
|
|
76
76
|
name: ruby/gem_rbs_collection
|
|
77
|
-
revision:
|
|
77
|
+
revision: 81fa8bd0617286078617a62b6a3229cebfd4af23
|
|
78
78
|
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
79
79
|
repo_dir: gems
|
|
80
80
|
- name: i18n
|
|
@@ -82,7 +82,7 @@ gems:
|
|
|
82
82
|
source:
|
|
83
83
|
type: git
|
|
84
84
|
name: ruby/gem_rbs_collection
|
|
85
|
-
revision:
|
|
85
|
+
revision: 81fa8bd0617286078617a62b6a3229cebfd4af23
|
|
86
86
|
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
87
87
|
repo_dir: gems
|
|
88
88
|
- name: json
|
|
@@ -98,7 +98,7 @@ gems:
|
|
|
98
98
|
source:
|
|
99
99
|
type: git
|
|
100
100
|
name: ruby/gem_rbs_collection
|
|
101
|
-
revision:
|
|
101
|
+
revision: 81fa8bd0617286078617a62b6a3229cebfd4af23
|
|
102
102
|
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
103
103
|
repo_dir: gems
|
|
104
104
|
- name: monitor
|
|
@@ -138,7 +138,7 @@ gems:
|
|
|
138
138
|
source:
|
|
139
139
|
type: git
|
|
140
140
|
name: ruby/gem_rbs_collection
|
|
141
|
-
revision:
|
|
141
|
+
revision: 81fa8bd0617286078617a62b6a3229cebfd4af23
|
|
142
142
|
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
143
143
|
repo_dir: gems
|
|
144
144
|
- name: stringio
|
|
@@ -162,7 +162,7 @@ gems:
|
|
|
162
162
|
source:
|
|
163
163
|
type: git
|
|
164
164
|
name: ruby/gem_rbs_collection
|
|
165
|
-
revision:
|
|
165
|
+
revision: 81fa8bd0617286078617a62b6a3229cebfd4af23
|
|
166
166
|
remote: https://github.com/ruby/gem_rbs_collection.git
|
|
167
167
|
repo_dir: gems
|
|
168
168
|
- name: uri
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module JobWorkflow
|
|
4
4
|
class Context
|
|
5
5
|
# rubocop:disable Metrics/ClassLength
|
|
6
|
+
EACH_TASK_CURSOR_MARKER: ::String
|
|
7
|
+
|
|
6
8
|
attr_reader workflow: Workflow
|
|
7
9
|
|
|
8
10
|
attr_reader arguments: Arguments
|
|
@@ -33,6 +35,15 @@ module JobWorkflow
|
|
|
33
35
|
# : (Hash[Symbol, untyped]) -> Context
|
|
34
36
|
def _update_arguments: (Hash[Symbol, untyped]) -> Context
|
|
35
37
|
|
|
38
|
+
# : () -> untyped
|
|
39
|
+
def cursor: () -> untyped
|
|
40
|
+
|
|
41
|
+
# : (untyped) -> void
|
|
42
|
+
def set_cursor!: (untyped) -> void
|
|
43
|
+
|
|
44
|
+
# : () -> void
|
|
45
|
+
def checkpoint!: () -> void
|
|
46
|
+
|
|
36
47
|
# : (DSL) -> void
|
|
37
48
|
def _job=: (DSL) -> void
|
|
38
49
|
|
|
@@ -48,8 +59,8 @@ module JobWorkflow
|
|
|
48
59
|
# : () -> String?
|
|
49
60
|
def concurrency_key: () -> String?
|
|
50
61
|
|
|
51
|
-
# : (Task) -> Enumerator[Context]
|
|
52
|
-
def _with_each_value: (Task) -> Enumerator[Context]
|
|
62
|
+
# : (Task, ?start_index: Integer?) -> Enumerator[Context]
|
|
63
|
+
def _with_each_value: (Task, ?start_index: Integer?) -> Enumerator[Context]
|
|
53
64
|
|
|
54
65
|
# : () { () -> void } -> void
|
|
55
66
|
def _with_task_throttle: () { () -> void } -> void
|
|
@@ -93,6 +104,9 @@ module JobWorkflow
|
|
|
93
104
|
# : () -> TaskContext
|
|
94
105
|
def _task_context: () -> TaskContext
|
|
95
106
|
|
|
107
|
+
# : (ActiveJob::Continuation::Step, ?cursor: untyped) { () -> void } -> void
|
|
108
|
+
def _with_current_step: (ActiveJob::Continuation::Step, ?cursor: untyped) { () -> void } -> void
|
|
109
|
+
|
|
96
110
|
# : (TaskOutput) -> void
|
|
97
111
|
def _add_task_output: (TaskOutput) -> void
|
|
98
112
|
|
|
@@ -119,23 +133,33 @@ module JobWorkflow
|
|
|
119
133
|
|
|
120
134
|
attr_accessor skip_in_dry_run_index: Integer
|
|
121
135
|
|
|
136
|
+
attr_accessor current_step: ActiveJob::Continuation::Step?
|
|
137
|
+
|
|
138
|
+
attr_accessor current_cursor: untyped
|
|
139
|
+
|
|
122
140
|
# : () -> String
|
|
123
141
|
def parent_job_id: () -> String
|
|
124
142
|
|
|
143
|
+
# : () -> bool
|
|
144
|
+
def each_task?: () -> bool
|
|
145
|
+
|
|
146
|
+
# : (untyped) -> untyped
|
|
147
|
+
def build_step_cursor: (untyped) -> untyped
|
|
148
|
+
|
|
125
149
|
# : () -> Hash[String, untyped]
|
|
126
150
|
def serialize_for_job: () -> Hash[String, untyped]
|
|
127
151
|
|
|
128
152
|
# : () -> Hash[String, untyped]
|
|
129
153
|
def serialize_for_sub_job: () -> Hash[String, untyped]
|
|
130
154
|
|
|
131
|
-
# : (Task, Enumerator::Yielder) -> void
|
|
132
|
-
def with_task_context: (Task, Enumerator::Yielder) -> void
|
|
155
|
+
# : (Task, Enumerator::Yielder, ?start_index: Integer?) -> void
|
|
156
|
+
def with_task_context: (Task, Enumerator::Yielder, ?start_index: Integer?) -> void
|
|
133
157
|
|
|
134
158
|
# : (Task) -> void
|
|
135
159
|
def reset_task_context_if_task_changed: (Task) -> void
|
|
136
160
|
|
|
137
|
-
# : (Task) { (untyped, Integer) -> void } -> void
|
|
138
|
-
def with_each_index_and_value: (Task) { (untyped, Integer) -> void } -> void
|
|
161
|
+
# : (Task, ?start_index: Integer?) { (untyped, Integer) -> void } -> void
|
|
162
|
+
def with_each_index_and_value: (Task, ?start_index: Integer?) { (untyped, Integer) -> void } -> void
|
|
139
163
|
|
|
140
164
|
# : () -> void
|
|
141
165
|
def clear_after_each_index_and_value: () -> void
|
|
@@ -62,9 +62,6 @@ module JobWorkflow
|
|
|
62
62
|
# : (Symbol argument_name, String type, ?default: untyped) -> void
|
|
63
63
|
def argument: (Symbol argument_name, String type, ?default: untyped) -> void
|
|
64
64
|
|
|
65
|
-
# : (Symbol) { () -> void } -> void
|
|
66
|
-
def namespace: (Symbol) { () -> void } -> void
|
|
67
|
-
|
|
68
65
|
# rubocop:disable Metrics/ParameterLists
|
|
69
66
|
#
|
|
70
67
|
# : (
|
|
@@ -139,11 +136,6 @@ module JobWorkflow
|
|
|
139
136
|
# ?description: String?
|
|
140
137
|
# ) -> void
|
|
141
138
|
def schedule: (String expression, ?key: (String | Symbol)?, ?queue: String?, ?priority: Integer?, ?args: Hash[Symbol, untyped], ?description: String?) -> void
|
|
142
|
-
|
|
143
|
-
private
|
|
144
|
-
|
|
145
|
-
# : () -> void
|
|
146
|
-
def validate_namespace!: () -> void
|
|
147
139
|
end
|
|
148
140
|
end
|
|
149
141
|
end
|
|
@@ -30,11 +30,14 @@ module JobWorkflow
|
|
|
30
30
|
# : (Task) -> bool
|
|
31
31
|
def skip_task?: (Task) -> bool
|
|
32
32
|
|
|
33
|
-
# : (Task) -> void
|
|
34
|
-
def run_task: (Task) -> void
|
|
33
|
+
# : (Task, step: ActiveJob::Continuation::Step) -> void
|
|
34
|
+
def run_task: (Task, step: ActiveJob::Continuation::Step) -> void
|
|
35
|
+
|
|
36
|
+
# : (Task, untyped) -> [Integer?, untyped]
|
|
37
|
+
def decode_task_cursor: (Task, untyped) -> [ Integer?, untyped ]
|
|
35
38
|
|
|
36
|
-
# : (Task, Context) -> void
|
|
37
|
-
def run_each_task: (Task, Context) -> void
|
|
39
|
+
# : (Task, Context, step: ActiveJob::Continuation::Step, ?cursor: untyped) -> void
|
|
40
|
+
def run_each_task: (Task, Context, step: ActiveJob::Continuation::Step, ?cursor: untyped) -> void
|
|
38
41
|
|
|
39
42
|
# : (Task, Context) { () -> void } -> void
|
|
40
43
|
def run_hooks: (Task, Context) { () -> void } -> void
|
|
@@ -62,5 +65,8 @@ module JobWorkflow
|
|
|
62
65
|
|
|
63
66
|
# : (Task) -> void
|
|
64
67
|
def update_task_outputs: (Task) -> void
|
|
68
|
+
|
|
69
|
+
# : () -> void
|
|
70
|
+
def persist_current_job_context: () -> void
|
|
65
71
|
end
|
|
66
72
|
end
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module JobWorkflow
|
|
4
4
|
class Task
|
|
5
|
-
|
|
5
|
+
DEFAULT_EACH: untyped
|
|
6
6
|
|
|
7
|
-
attr_reader
|
|
7
|
+
attr_reader job_name: String
|
|
8
8
|
|
|
9
9
|
attr_reader block: ^(untyped) -> void
|
|
10
10
|
|
|
@@ -28,11 +28,10 @@ module JobWorkflow
|
|
|
28
28
|
|
|
29
29
|
attr_reader dry_run_config: DryRunConfig
|
|
30
30
|
|
|
31
|
-
# rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
|
|
31
|
+
# rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
|
|
32
32
|
# : (
|
|
33
33
|
# job_name: String,
|
|
34
34
|
# name: Symbol,
|
|
35
|
-
# namespace: Namespace,
|
|
36
35
|
# block: ^(untyped) -> void,
|
|
37
36
|
# ?each: ^(Context) -> untyped,
|
|
38
37
|
# ?enqueue: true | false | ^(Context) -> bool | Hash[Symbol, untyped],
|
|
@@ -45,7 +44,7 @@ module JobWorkflow
|
|
|
45
44
|
# ?dependency_wait: Hash[Symbol, untyped],
|
|
46
45
|
# ?dry_run: bool | ^(Context) -> bool
|
|
47
46
|
# ) -> void
|
|
48
|
-
def initialize: (job_name: String, name: Symbol,
|
|
47
|
+
def initialize: (job_name: String, name: Symbol, block: ^(untyped) -> void, condition: ^(Context) -> bool, ?each: ^(Context) -> untyped, ?enqueue: true | false | ^(Context) -> bool | Hash[Symbol, untyped], ?output: Hash[Symbol, String], ?depends_on: Array[Symbol], ?task_retry: Integer | Hash[Symbol, untyped], ?throttle: Integer | Hash[Symbol, untyped], ?timeout: Numeric?, ?dependency_wait: Hash[Symbol, untyped], ?dry_run: bool | ^(Context) -> bool) -> void
|
|
49
48
|
|
|
50
49
|
# : () -> Symbol
|
|
51
50
|
def task_name: () -> Symbol
|
|
@@ -53,6 +52,9 @@ module JobWorkflow
|
|
|
53
52
|
# : () -> String
|
|
54
53
|
def throttle_prefix_key: () -> String
|
|
55
54
|
|
|
55
|
+
# : () -> bool
|
|
56
|
+
def each?: () -> bool
|
|
57
|
+
|
|
56
58
|
private
|
|
57
59
|
|
|
58
60
|
attr_reader name: Symbol
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
module JobWorkflow
|
|
4
4
|
class Workflow
|
|
5
|
-
attr_reader namespace: Namespace
|
|
6
|
-
|
|
7
5
|
attr_reader dry_run_config: DryRunConfig
|
|
8
6
|
|
|
9
7
|
# : () -> void
|
|
@@ -12,9 +10,6 @@ module JobWorkflow
|
|
|
12
10
|
# : (bool | ^(Context) -> bool) -> void
|
|
13
11
|
def dry_run_config=: (bool | ^(Context) -> bool) -> void
|
|
14
12
|
|
|
15
|
-
# : (Namespace) { () -> void } -> void
|
|
16
|
-
def add_namespace: (Namespace) { () -> void } -> void
|
|
17
|
-
|
|
18
13
|
# : (Task) -> void
|
|
19
14
|
def add_task: (Task) -> void
|
|
20
15
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: job-workflow
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- shoma07
|
|
@@ -49,7 +49,6 @@ files:
|
|
|
49
49
|
- guides/GETTING_STARTED.md
|
|
50
50
|
- guides/INSTRUMENTATION.md
|
|
51
51
|
- guides/LIFECYCLE_HOOKS.md
|
|
52
|
-
- guides/NAMESPACES.md
|
|
53
52
|
- guides/OPENTELEMETRY_INTEGRATION.md
|
|
54
53
|
- guides/PARALLEL_PROCESSING.md
|
|
55
54
|
- guides/PRODUCTION_DEPLOYMENT.md
|
|
@@ -84,7 +83,6 @@ files:
|
|
|
84
83
|
- lib/job_workflow/instrumentation/opentelemetry_subscriber.rb
|
|
85
84
|
- lib/job_workflow/job_status.rb
|
|
86
85
|
- lib/job_workflow/logger.rb
|
|
87
|
-
- lib/job_workflow/namespace.rb
|
|
88
86
|
- lib/job_workflow/output.rb
|
|
89
87
|
- lib/job_workflow/output_def.rb
|
|
90
88
|
- lib/job_workflow/queue.rb
|
|
@@ -92,6 +90,7 @@ files:
|
|
|
92
90
|
- lib/job_workflow/queue_adapters/abstract.rb
|
|
93
91
|
- lib/job_workflow/queue_adapters/null_adapter.rb
|
|
94
92
|
- lib/job_workflow/queue_adapters/solid_queue_adapter.rb
|
|
93
|
+
- lib/job_workflow/railtie.rb
|
|
95
94
|
- lib/job_workflow/runner.rb
|
|
96
95
|
- lib/job_workflow/schedule.rb
|
|
97
96
|
- lib/job_workflow/semaphore.rb
|
|
@@ -136,7 +135,6 @@ files:
|
|
|
136
135
|
- sig/generated/job_workflow/instrumentation/opentelemetry_subscriber.rbs
|
|
137
136
|
- sig/generated/job_workflow/job_status.rbs
|
|
138
137
|
- sig/generated/job_workflow/logger.rbs
|
|
139
|
-
- sig/generated/job_workflow/namespace.rbs
|
|
140
138
|
- sig/generated/job_workflow/output.rbs
|
|
141
139
|
- sig/generated/job_workflow/output_def.rbs
|
|
142
140
|
- sig/generated/job_workflow/queue.rbs
|
|
@@ -144,6 +142,7 @@ files:
|
|
|
144
142
|
- sig/generated/job_workflow/queue_adapters/abstract.rbs
|
|
145
143
|
- sig/generated/job_workflow/queue_adapters/null_adapter.rbs
|
|
146
144
|
- sig/generated/job_workflow/queue_adapters/solid_queue_adapter.rbs
|
|
145
|
+
- sig/generated/job_workflow/railtie.rbs
|
|
147
146
|
- sig/generated/job_workflow/runner.rbs
|
|
148
147
|
- sig/generated/job_workflow/schedule.rbs
|
|
149
148
|
- sig/generated/job_workflow/semaphore.rbs
|
data/guides/NAMESPACES.md
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# Namespaces
|
|
2
|
-
|
|
3
|
-
Logically grouping tasks improves readability and maintainability of complex workflows. JobWorkflow provides namespace functionality.
|
|
4
|
-
|
|
5
|
-
## Basic Namespaces
|
|
6
|
-
|
|
7
|
-
### namespace DSL
|
|
8
|
-
|
|
9
|
-
Group related tasks.
|
|
10
|
-
|
|
11
|
-
```ruby
|
|
12
|
-
class ECommerceOrderJob < ApplicationJob
|
|
13
|
-
include JobWorkflow::DSL
|
|
14
|
-
|
|
15
|
-
argument :order, "Order"
|
|
16
|
-
|
|
17
|
-
# Payment-related tasks
|
|
18
|
-
namespace :payment do
|
|
19
|
-
task :validate do |ctx|
|
|
20
|
-
order = ctx.arguments.order
|
|
21
|
-
PaymentValidator.validate(order)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
task :charge, depends_on: [:"payment:validate"], output: { payment_result: "Hash" } do |ctx|
|
|
25
|
-
order = ctx.arguments.order
|
|
26
|
-
{ payment_result: PaymentProcessor.charge(order) }
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
task :send_receipt, depends_on: [:"payment:charge"] do |ctx|
|
|
30
|
-
order = ctx.arguments.order
|
|
31
|
-
payment_result = ctx.output[:"payment:charge"].first.payment_result
|
|
32
|
-
ReceiptMailer.send(order, payment_result)
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Inventory-related tasks
|
|
37
|
-
namespace :inventory do
|
|
38
|
-
task :check_availability do |ctx|
|
|
39
|
-
order = ctx.arguments.order
|
|
40
|
-
InventoryService.check(order.items)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
task :reserve, depends_on: [:"inventory:check_availability"], output: { reserved: "bool" } do |ctx|
|
|
44
|
-
order = ctx.arguments.order
|
|
45
|
-
{ reserved: InventoryService.reserve(order.items) }
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Shipping-related tasks
|
|
50
|
-
namespace :shipping do
|
|
51
|
-
task :calculate_cost, output: { shipping_cost: "Float" } do |ctx|
|
|
52
|
-
order = ctx.arguments.order
|
|
53
|
-
{ shipping_cost: ShippingCalculator.calculate(order) }
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
task :create_label, depends_on: [:"shipping:calculate_cost"], output: { shipping_label: "String" } do |ctx|
|
|
57
|
-
order = ctx.arguments.order
|
|
58
|
-
{ shipping_label: ShippingService.create_label(order) }
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Tasks in namespaces are identified as `:namespace:task_name` at runtime:
|
|
65
|
-
|
|
66
|
-
```ruby
|
|
67
|
-
# Executed tasks:
|
|
68
|
-
# - :payment:validate
|
|
69
|
-
# - :payment:charge
|
|
70
|
-
# - :payment:send_receipt
|
|
71
|
-
# - :inventory:check_availability
|
|
72
|
-
# - :inventory:reserve
|
|
73
|
-
# - :shipping:calculate_cost
|
|
74
|
-
# - :shipping:create_label
|
|
75
|
-
```
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module JobWorkflow
|
|
4
|
-
class Namespace
|
|
5
|
-
attr_reader :name #: Symbol
|
|
6
|
-
attr_reader :parent #: Namespace?
|
|
7
|
-
|
|
8
|
-
class << self
|
|
9
|
-
#: () -> Namespace
|
|
10
|
-
def default
|
|
11
|
-
new(name: :"")
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
#: (name: Symbol, ?parent: Namespace?) -> void
|
|
16
|
-
def initialize(name:, parent: nil)
|
|
17
|
-
@name = name #: Symbol
|
|
18
|
-
@parent = parent #: Namespace?
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
#: () -> bool
|
|
22
|
-
def default?
|
|
23
|
-
name.empty?
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
#: (Namespace) -> Namespace
|
|
27
|
-
def update_parent(parent)
|
|
28
|
-
self.class.new(name:, parent:)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
#: () -> Symbol
|
|
32
|
-
def full_name
|
|
33
|
-
[parent&.full_name, name.to_s].compact.reject(&:empty?).join(":").to_sym
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
# Generated from lib/job_workflow/namespace.rb with RBS::Inline
|
|
2
|
-
|
|
3
|
-
module JobWorkflow
|
|
4
|
-
class Namespace
|
|
5
|
-
attr_reader name: Symbol
|
|
6
|
-
|
|
7
|
-
attr_reader parent: Namespace?
|
|
8
|
-
|
|
9
|
-
# : () -> Namespace
|
|
10
|
-
def self.default: () -> Namespace
|
|
11
|
-
|
|
12
|
-
# : (name: Symbol, ?parent: Namespace?) -> void
|
|
13
|
-
def initialize: (name: Symbol, ?parent: Namespace?) -> void
|
|
14
|
-
|
|
15
|
-
# : () -> bool
|
|
16
|
-
def default?: () -> bool
|
|
17
|
-
|
|
18
|
-
# : (Namespace) -> Namespace
|
|
19
|
-
def update_parent: (Namespace) -> Namespace
|
|
20
|
-
|
|
21
|
-
# : () -> Symbol
|
|
22
|
-
def full_name: () -> Symbol
|
|
23
|
-
end
|
|
24
|
-
end
|