opera 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d93222fbcb9fdd360cdadc340319157bc57dfe2229cb33a03ed46b1995f31a65
4
- data.tar.gz: 7e909537e4b3af6d636d0eb174180f47212e8e49a7bf76a66c47109c60c363e7
3
+ metadata.gz: d601d62d89303ad4efaa6ab156894bdcd247964977f3bb23fd43f2236cdf7655
4
+ data.tar.gz: 42c7cb409cec7f9dc66cd63dd723d39170cc5c02c5704e79d886869570b7775e
5
5
  SHA512:
6
- metadata.gz: 76e6997c8fd873844a0a91c93e03c55e7297312abfd5e8ad45471c310c2d943e222664f7bbd0439a25cf7bb287aeac92dca0dd24ea9b84d653dd6d16d6b259cd
7
- data.tar.gz: 0f2c582ef8d69e8e90010aa7c9b8db2c7e7483514dd3dc7da4da1ca521f5ae71e5f799c99744b75e8c0b4d32a220273b32e2f5e946a1a3060aa48280648674ee
6
+ metadata.gz: 3725001498c05f07eae31e1f85e9801833820a1f4a4c8175e9fff13977390c6aaad4da5d3787b669fe7ddf4f84fd65752857f16bc0c84d822a3bff97b10b7467
7
+ data.tar.gz: 50a96d11c1f419bfc82934403793814029f088ba2b48d8df9083ee3f5383937dc95cc312339c1ce8619fc1358bd9e2b23aac3124aca6e4b61269fd5efacb93ae
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Opera Changelog
2
2
 
3
+ ### 0.6.0 - Apr 15, 2026
4
+
5
+ - Add `always` executor: runs its step unconditionally after all regular steps, regardless of failure or an early finish
6
+
3
7
  ### 0.5.1 - Apr 15, 2026
4
8
 
5
9
  - Remove `Marshal.dump` from instruction execution for ~40-55% throughput improvement
@@ -14,6 +18,7 @@
14
18
  - Remove `benchmark` executor
15
19
 
16
20
  ### 0.4.1 - Feb 18, 2026
21
+
17
22
  - Add parsed errors to default `output!` exception message
18
23
 
19
24
  ### 0.4.0 - May 22, 2025
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- opera (0.5.1)
4
+ opera (0.6.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -112,16 +112,17 @@ end
112
112
 
113
113
  ## DSL Reference
114
114
 
115
- | Instruction | Description |
116
- |---|---|
117
- | `step :method` | Executes a method. Returns falsy to stop execution. |
118
- | `validate :method` | Executes a method that must return `Dry::Validation::Result` or `Opera::Operation::Result`. Errors are accumulated -- all validations run even if some fail. |
119
- | `transaction do ... end` | Wraps steps in a database transaction. Rolls back on error. |
120
- | `success :method` or `success do ... end` | Like `step`, but a falsy return does **not** stop execution. Use for side effects. |
121
- | `finish_if :method` | Stops execution (successfully) if the method returns truthy. |
122
- | `operation :method` | Calls an inner operation. Must return `Opera::Operation::Result`. Propagates errors on failure. Output stored in `context[:<method>_output]`. |
123
- | `operations :method` | Like `operation`, but the method must return an array of `Opera::Operation::Result`. |
124
- | `within :method do ... end` | Wraps nested steps with a custom method that must `yield`. If it doesn't yield, nested steps are skipped. |
115
+ | Instruction | Description |
116
+ | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
117
+ | `step :method` | Executes a method. Returns falsy to stop execution. |
118
+ | `validate :method` | Executes a method that must return `Dry::Validation::Result` or `Opera::Operation::Result`. Errors are accumulated -- all validations run even if some fail. |
119
+ | `transaction do ... end` | Wraps steps in a database transaction. Rolls back on error. |
120
+ | `success :method` or `success do ... end` | Like `step`, but a falsy return does **not** stop execution. Use for side effects. |
121
+ | `finish_if :method` | Stops execution (successfully) if the method returns truthy. |
122
+ | `operation :method` | Calls an inner operation. Must return `Opera::Operation::Result`. Propagates errors on failure. Output stored in `context[:<method>_output]`. |
123
+ | `operations :method` | Like `operation`, but the method must return an array of `Opera::Operation::Result`. |
124
+ | `within :method do ... end` | Wraps nested steps with a custom method that must `yield`. If it doesn't yield, nested steps are skipped. |
125
+ | `always :method` | Executes a step unconditionally after all regular steps, even after a failure or an early finish. Must appear at the end of the operation — only other `always` steps may follow. Cannot be used inside blocks. Use `result.success?` / `result.failure?` inside the method to branch on outcome. |
125
126
 
126
127
  ### Combining instructions
127
128
 
@@ -147,25 +148,26 @@ class MyOperation < Opera::Operation::Base
147
148
  end
148
149
 
149
150
  step :output
151
+ always :audit_trail
150
152
  end
151
153
  ```
152
154
 
153
155
  ## Result API
154
156
 
155
- | Method | Returns | Description |
156
- |---|---|---|
157
- | `success?` | `Boolean` | `true` if no errors |
158
- | `failure?` | `Boolean` | `true` if any errors |
159
- | `output` | `Object` | The operation's return value |
160
- | `output!` | `Object` | Returns output if success, raises `OutputError` if failure |
161
- | `output=` | | Sets the output |
162
- | `errors` | `Hash` | Accumulated error messages |
163
- | `failures` | `Hash` | Alias for `errors` |
164
- | `information` | `Hash` | Developer-facing metadata |
165
- | `executions` | `Array` | Ordered list of executed steps (development mode only) |
166
- | `add_error(key, value)` | | Adds a single error |
167
- | `add_errors(hash)` | | Merges multiple errors |
168
- | `add_information(hash)` | | Merges metadata |
157
+ | Method | Returns | Description |
158
+ | ----------------------- | --------- | ---------------------------------------------------------- |
159
+ | `success?` | `Boolean` | `true` if no errors |
160
+ | `failure?` | `Boolean` | `true` if any errors |
161
+ | `output` | `Object` | The operation's return value |
162
+ | `output!` | `Object` | Returns output if success, raises `OutputError` if failure |
163
+ | `output=` | | Sets the output |
164
+ | `errors` | `Hash` | Accumulated error messages |
165
+ | `failures` | `Hash` | Alias for `errors` |
166
+ | `information` | `Hash` | Developer-facing metadata |
167
+ | `executions` | `Array` | Ordered list of executed steps (development mode only) |
168
+ | `add_error(key, value)` | | Adds a single error |
169
+ | `add_errors(hash)` | | Merges multiple errors |
170
+ | `add_information(hash)` | | Merges metadata |
169
171
 
170
172
  ```ruby
171
173
  # Pre-set output (useful in specs)
@@ -174,13 +176,13 @@ Opera::Operation::Result.new(output: 'success')
174
176
 
175
177
  ## Operation Instance Methods
176
178
 
177
- | Method | Description |
178
- |---|---|
179
- | `context` | Mutable `Hash` for passing data between steps |
180
- | `params` | Immutable `Hash` received via `call` |
181
- | `dependencies` | Immutable `Hash` received via `call` |
182
- | `result` | The `Opera::Operation::Result` instance |
183
- | `finish!` | Halts step execution (operation is still successful) |
179
+ | Method | Description |
180
+ | -------------- | ---------------------------------------------------- |
181
+ | `context` | Mutable `Hash` for passing data between steps |
182
+ | `params` | Immutable `Hash` received via `call` |
183
+ | `dependencies` | Immutable `Hash` received via `call` |
184
+ | `result` | The `Opera::Operation::Result` instance |
185
+ | `finish!` | Halts step execution (operation is still successful) |
184
186
 
185
187
  ## Testing
186
188
 
@@ -204,6 +206,7 @@ Detailed examples with full input/output are available in the [`docs/examples/`]
204
206
  - [Finish If](docs/examples/finish-if.md)
205
207
  - [Inner Operations](docs/examples/inner-operations.md)
206
208
  - [Within](docs/examples/within.md)
209
+ - [Always](docs/examples/always.md)
207
210
  - [Context, Params & Dependencies](docs/examples/context-params-dependencies.md)
208
211
 
209
212
  ## Development
@@ -4,7 +4,7 @@
4
4
  #
5
5
  # Exercises the full execution path: step dispatch, instruction iteration,
6
6
  # context accessors, validate, transaction, success, finish_if, operation,
7
- # operations, and within -- with nested inner operations and loops to
7
+ # operations, within, and always -- with nested inner operations and loops to
8
8
  # simulate realistic workloads.
9
9
  #
10
10
  # Usage:
@@ -122,7 +122,9 @@ ComplexOperation = Class.new(Opera::Operation::Base) do
122
122
  step :log_audit
123
123
  end
124
124
 
125
- step :output
125
+ step :processing_error
126
+
127
+ always :output
126
128
 
127
129
  def schema
128
130
  Opera::Operation::Result.new(output: params)
@@ -165,6 +167,10 @@ ComplexOperation = Class.new(Opera::Operation::Base) do
165
167
  context[:audited] = true
166
168
  end
167
169
 
170
+ def processing_error
171
+ result.add_error(:base, 'processing failed')
172
+ end
173
+
168
174
  def output
169
175
  result.output = {
170
176
  profile: profile,
@@ -280,6 +286,43 @@ BatchOperation = Class.new(Opera::Operation::Base) do
280
286
  end
281
287
  end
282
288
 
289
+ # ---------------------------------------------------------------------------
290
+ # Always operation — exercises always steps on both success and failure paths
291
+ # ---------------------------------------------------------------------------
292
+ AlwaysOperation = Class.new(Opera::Operation::Base) do
293
+ context do
294
+ attr_accessor :log, default: -> { [] }
295
+ end
296
+
297
+ step :prepare
298
+ step :process
299
+ always :audit
300
+ always :cleanup
301
+
302
+ def prepare
303
+ log << :prepare
304
+ context[:value] = params.fetch(:value, 0)
305
+ end
306
+
307
+ def process
308
+ if params[:fail]
309
+ result.add_error(:base, 'processing failed')
310
+ else
311
+ context[:value] *= 2
312
+ log << :process
313
+ end
314
+ end
315
+
316
+ def audit
317
+ log << (result.success? ? :audit_success : :audit_failure)
318
+ result.output = { value: context[:value], log: log, success: result.success?, failure: result.failure? }
319
+ end
320
+
321
+ def cleanup
322
+ log << :cleanup
323
+ end
324
+ end
325
+
283
326
  # ---------------------------------------------------------------------------
284
327
  # Benchmark
285
328
  # ---------------------------------------------------------------------------
@@ -288,6 +331,8 @@ PARAMS = { name: 'benchmark', batch_size: 5 }.freeze
288
331
  BATCH_PARAMS = { count: 5 }.freeze
289
332
  VALIDATION_PARAMS = { first_name: 'Jane', last_name: 'Doe', email: 'jane@example.com' }.freeze
290
333
  WITHIN_PARAMS = {}.freeze
334
+ ALWAYS_SUCCESS_PARAMS = { value: 21 }.freeze
335
+ ALWAYS_FAILURE_PARAMS = { value: 21, fail: true }.freeze
291
336
 
292
337
  # Warm up
293
338
  3.times do
@@ -295,6 +340,8 @@ WITHIN_PARAMS = {}.freeze
295
340
  BatchOperation.call(params: BATCH_PARAMS)
296
341
  ValidationOperation.call(params: VALIDATION_PARAMS)
297
342
  WithinOperation.call(params: WITHIN_PARAMS)
343
+ AlwaysOperation.call(params: ALWAYS_SUCCESS_PARAMS)
344
+ AlwaysOperation.call(params: ALWAYS_FAILURE_PARAMS)
298
345
  end
299
346
 
300
347
  puts "Opera v#{Opera::VERSION} — #{ITERATIONS} iterations each"
@@ -322,6 +369,14 @@ Benchmark.bm(35) do |x|
322
369
  ITERATIONS.times { LeafOperation.call(params: { n: 42 }) }
323
370
  end
324
371
 
372
+ x.report('AlwaysOperation (success path):') do
373
+ ITERATIONS.times { AlwaysOperation.call(params: ALWAYS_SUCCESS_PARAMS) }
374
+ end
375
+
376
+ x.report('AlwaysOperation (failure path):') do
377
+ ITERATIONS.times { AlwaysOperation.call(params: ALWAYS_FAILURE_PARAMS) }
378
+ end
379
+
325
380
  # Total operations executed in ComplexOperation run:
326
381
  # 1 complex + 1 inner + 5 leaf = 7 operations per iteration
327
382
  # = 7000 total operation instantiations for ComplexOperation alone
@@ -0,0 +1,267 @@
1
+ # Always
2
+
3
+ `always` runs a step unconditionally at the end of the operation pipeline, after all regular steps have run (or been skipped). Unlike a regular `step`, it is never skipped — not when a prior step adds an error, not when `finish!` or `finish_if` DSL is called.
4
+
5
+ ## Placement rules
6
+
7
+ - `always` steps must appear **after all other instructions** at the top level of the operation.
8
+ - Once an `always` is declared, only further `always` steps may follow — any other instruction (`step`, `operation`, `transaction`, `within`, etc.) raises an `ArgumentError` at class load time.
9
+ - `always` **cannot** be used inside blocks (`transaction do`, `within do`, `success do`, `validate do`). Doing so raises an `ArgumentError` at class load time.
10
+
11
+ ```ruby
12
+ # correct
13
+ step :a
14
+ step :b
15
+ always :c
16
+ always :d
17
+
18
+ # raises ArgumentError — step follows always
19
+ step :a
20
+ always :b
21
+ step :c
22
+
23
+ # raises ArgumentError — always inside a transaction block
24
+ transaction do
25
+ step :a
26
+ always :b # not allowed here
27
+ end
28
+ ```
29
+
30
+ ## Basic usage
31
+
32
+ ```ruby
33
+ class Order::Submit < Opera::Operation::Base
34
+ context do
35
+ attr_accessor :order
36
+ end
37
+
38
+ dependencies do
39
+ attr_reader :current_account, :audit_logger
40
+ end
41
+
42
+ step :build
43
+ step :charge
44
+ step :send_confirmation
45
+ always :audit_log
46
+
47
+ def build
48
+ self.order = current_account.orders.build(params)
49
+ end
50
+
51
+ def charge
52
+ result.add_error(:base, 'card declined') unless order.charge!
53
+ end
54
+
55
+ def send_confirmation
56
+ # only reached when charge succeeds
57
+ OrderMailer.confirmation(order).deliver_later
58
+ end
59
+
60
+ def audit_log
61
+ # always runs, regardless of whether charge succeeded or failed
62
+ audit_logger.record(order: order, success: result.success?)
63
+ end
64
+ end
65
+ ```
66
+
67
+ ## Inspecting result state inside an always step
68
+
69
+ `result.success?` and `result.failure?` reflect the state of the operation **at the point `always` runs** — i.e. after all regular steps have executed (or been skipped due to failure). This lets you branch on the final outcome:
70
+
71
+ ```ruby
72
+ class Profile::Delete < Opera::Operation::Base
73
+ context do
74
+ attr_accessor :profile
75
+ end
76
+
77
+ dependencies do
78
+ attr_reader :current_account, :notifier
79
+ end
80
+
81
+ step :find
82
+ step :destroy
83
+ always :notify
84
+
85
+ def find
86
+ self.profile = current_account.profiles.find(params[:id])
87
+ end
88
+
89
+ def destroy
90
+ result.add_error(:base, 'cannot delete') unless profile.destroy
91
+ end
92
+
93
+ def notify
94
+ if result.success?
95
+ notifier.call(event: :deleted, profile_id: params[:id])
96
+ else
97
+ notifier.call(event: :delete_failed, profile_id: params[:id], errors: result.errors)
98
+ end
99
+ end
100
+ end
101
+ ```
102
+
103
+ ## Multiple always steps
104
+
105
+ Multiple `always` steps are allowed and run in the order they are declared in:
106
+
107
+ ```ruby
108
+ class Report::Generate < Opera::Operation::Base
109
+ dependencies do
110
+ attr_reader :audit_logger, :metrics
111
+ end
112
+
113
+ step :fetch_data
114
+ step :render
115
+ always :record_audit
116
+ always :record_metrics
117
+
118
+ def fetch_data
119
+ # ...
120
+ end
121
+
122
+ def render
123
+ # ...
124
+ end
125
+
126
+ def record_audit
127
+ audit_logger.call(success: result.success?, errors: result.errors)
128
+ end
129
+
130
+ def record_metrics
131
+ metrics.increment(result.success? ? 'report.success' : 'report.failure')
132
+ end
133
+ end
134
+ ```
135
+
136
+ ## Operation finishes early
137
+
138
+ ### With finish_if
139
+
140
+ `finish_if` halts execution successfully when its method returns truthy — subsequent regular steps are skipped, but `always` steps still run. Inside the always step, `result.success?` returns `true` because no errors were added:
141
+
142
+ ```ruby
143
+ class Import::Run < Opera::Operation::Base
144
+ dependencies do
145
+ attr_reader :audit_logger
146
+ end
147
+
148
+ step :check_preconditions
149
+ finish_if :already_imported?
150
+ step :import
151
+ step :output
152
+ always :record_attempt
153
+
154
+ def check_preconditions
155
+ # validate source data is present
156
+ end
157
+
158
+ def already_imported?
159
+ Import.exists?(ref: params[:ref])
160
+ end
161
+
162
+ def import
163
+ Import.create!(params)
164
+ end
165
+
166
+ def output
167
+ result.output = { imported: true }
168
+ end
169
+
170
+ def record_attempt
171
+ # called whether import ran, was skipped via finish_if, or failed
172
+ audit_logger.call(ref: params[:ref], success: result.success?)
173
+ end
174
+ end
175
+ ```
176
+
177
+ ### With finish!
178
+
179
+ Calling `finish!` inside a step halts execution immediately and marks the operation successful. `always` steps still run afterwards:
180
+
181
+ ```ruby
182
+ class Profile::Upsert < Opera::Operation::Base
183
+ context do
184
+ attr_accessor :profile
185
+ end
186
+
187
+ dependencies do
188
+ attr_reader :current_account, :audit_logger
189
+ end
190
+
191
+ step :find_existing
192
+ step :update_existing
193
+ step :create_new
194
+ step :output
195
+ always :audit_log
196
+
197
+ def find_existing
198
+ self.profile = current_account.profiles.find_by(email: params[:email])
199
+ end
200
+
201
+ def update_existing
202
+ return unless profile
203
+
204
+ profile.update!(params)
205
+ finish!
206
+ end
207
+
208
+ def create_new
209
+ self.profile = current_account.profiles.create!(params)
210
+ end
211
+
212
+ def output
213
+ result.output = { model: profile }
214
+ end
215
+
216
+ def audit_log
217
+ # runs whether the record was updated (finish! path), created, or failed
218
+ audit_logger.record(profile: profile, success: result.success?)
219
+ end
220
+ end
221
+ ```
222
+
223
+ ## Combining with DSL blocks
224
+
225
+ `always` cannot be placed inside `transaction`, `within` or `validate` blocks. Place it after those blocks at the top level:
226
+
227
+ ```ruby
228
+ class Ledger::Transfer < Opera::Operation::Base
229
+ configure do |config|
230
+ config.transaction_class = ActiveRecord::Base
231
+ end
232
+
233
+ dependencies do
234
+ attr_reader :audit_logger
235
+ end
236
+
237
+ transaction do
238
+ step :debit
239
+ step :credit
240
+ end
241
+
242
+ step :output
243
+ always :record_attempt
244
+
245
+ def debit
246
+ result.add_error(:base, 'insufficient funds') unless account.debit(params[:amount])
247
+ end
248
+
249
+ def credit
250
+ account.credit(params[:amount])
251
+ end
252
+
253
+ def output
254
+ result.output = { transferred: params[:amount] }
255
+ end
256
+
257
+ def record_attempt
258
+ # runs after the transaction (and rollback, if any) has settled.
259
+ # result.success? / result.failure? reflect the final outcome.
260
+ audit_logger.call(
261
+ params: params,
262
+ success: result.success?,
263
+ errors: result.errors
264
+ )
265
+ end
266
+ end
267
+ ```
@@ -3,7 +3,8 @@
3
3
  module Opera
4
4
  module Operation
5
5
  module Builder
6
- INSTRUCTIONS = %I[validate transaction step success finish_if operation operations within].freeze
6
+ INSTRUCTIONS = %I[validate transaction step success finish_if operation operations within always].freeze
7
+ INNER_INSTRUCTIONS = (INSTRUCTIONS - %I[always]).freeze
7
8
 
8
9
  def self.included(base)
9
10
  base.extend(ClassMethods)
@@ -14,12 +15,23 @@ module Opera
14
15
  @instructions ||= []
15
16
  end
16
17
 
17
- INSTRUCTIONS.each do |instruction|
18
+ INNER_INSTRUCTIONS.each do |instruction|
18
19
  define_method instruction do |method = nil, &blk|
20
+ if instructions.any? { |i| i[:kind] == :always }
21
+ raise ArgumentError,
22
+ "`#{instruction}` cannot appear after `always`. " \
23
+ 'All `always` steps must be at the end of the operation.'
24
+ end
25
+
19
26
  check_method_availability!(method) if method
20
27
  instructions.concat(InnerBuilder.new.send(instruction, method, &blk))
21
28
  end
22
29
  end
30
+
31
+ def always(method)
32
+ check_method_availability!(method)
33
+ instructions << { kind: :always, method: method }
34
+ end
23
35
  end
24
36
 
25
37
  class InnerBuilder
@@ -30,7 +42,7 @@ module Opera
30
42
  instance_eval(&block) if block_given?
31
43
  end
32
44
 
33
- INSTRUCTIONS.each do |instruction|
45
+ INNER_INSTRUCTIONS.each do |instruction|
34
46
  define_method instruction do |method = nil, &blk|
35
47
  instructions << if !blk.nil?
36
48
  {
@@ -46,6 +58,12 @@ module Opera
46
58
  end
47
59
  end
48
60
  end
61
+
62
+ def always(_method)
63
+ raise ArgumentError,
64
+ '`always` cannot be used inside a block (transaction, within, success, validate). ' \
65
+ 'Place `always` steps at the top level of the operation, after all other instructions.'
66
+ end
49
67
  end
50
68
  end
51
69
  end
@@ -21,8 +21,9 @@ module Opera
21
21
 
22
22
  def evaluate_instructions(instructions = [])
23
23
  instructions.each do |instruction|
24
+ next if instruction[:kind] != :always && break_condition
25
+
24
26
  evaluate_instruction(instruction)
25
- break if break_condition
26
27
  end
27
28
  end
28
29
 
@@ -57,6 +58,8 @@ module Opera
57
58
  Instructions::Executors::FinishIf.new(operation).call(instruction)
58
59
  when :within
59
60
  Instructions::Executors::Within.new(operation).call(instruction)
61
+ when :always
62
+ Instructions::Executors::Always.new(operation).call(instruction)
60
63
  else
61
64
  raise(UnknownInstructionError, "Unknown instruction #{instruction[:kind]}")
62
65
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Opera
4
+ module Operation
5
+ module Instructions
6
+ module Executors
7
+ class Always < Executor
8
+ def call(instruction)
9
+ execute_step(instruction)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -15,6 +15,7 @@ require 'opera/operation/instructions/executors/operation'
15
15
  require 'opera/operation/instructions/executors/operations'
16
16
  require 'opera/operation/instructions/executors/step'
17
17
  require 'opera/operation/instructions/executors/within'
18
+ require 'opera/operation/instructions/executors/always'
18
19
 
19
20
  module Opera
20
21
  module Operation
data/lib/opera/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Opera
4
- VERSION = '0.5.1'
4
+ VERSION = '0.6.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opera
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ProFinda Development Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-04-15 00:00:00.000000000 Z
11
+ date: 2026-04-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-validation
@@ -77,6 +77,7 @@ files:
77
77
  - bin/console
78
78
  - bin/setup
79
79
  - docker-compose.yml
80
+ - docs/examples/always.md
80
81
  - docs/examples/basic-operation.md
81
82
  - docs/examples/context-params-dependencies.md
82
83
  - docs/examples/finish-if.md
@@ -93,6 +94,7 @@ files:
93
94
  - lib/opera/operation/builder.rb
94
95
  - lib/opera/operation/config.rb
95
96
  - lib/opera/operation/executor.rb
97
+ - lib/opera/operation/instructions/executors/always.rb
96
98
  - lib/opera/operation/instructions/executors/finish_if.rb
97
99
  - lib/opera/operation/instructions/executors/operation.rb
98
100
  - lib/opera/operation/instructions/executors/operations.rb