typed_operation 1.0.0.beta3 → 1.0.0.beta4
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/README.md +76 -724
- data/lib/generators/typed_operation/install/install_generator.rb +3 -0
- data/lib/generators/typed_operation_generator.rb +8 -4
- data/lib/typed_operation/action_policy_auth.rb +44 -24
- data/lib/typed_operation/base.rb +4 -1
- data/lib/typed_operation/callable_resolver.rb +30 -0
- data/lib/typed_operation/chains/chained_operation.rb +27 -0
- data/lib/typed_operation/chains/fallback_chain.rb +32 -0
- data/lib/typed_operation/chains/map_chain.rb +37 -0
- data/lib/typed_operation/chains/sequence_chain.rb +54 -0
- data/lib/typed_operation/chains/smart_chain.rb +161 -0
- data/lib/typed_operation/chains/splat_chain.rb +53 -0
- data/lib/typed_operation/configuration.rb +52 -0
- data/lib/typed_operation/context.rb +193 -0
- data/lib/typed_operation/curried.rb +14 -1
- data/lib/typed_operation/explainable.rb +14 -0
- data/lib/typed_operation/immutable_base.rb +4 -1
- data/lib/typed_operation/instrumentation/trace.rb +71 -0
- data/lib/typed_operation/instrumentation/tree_formatter.rb +141 -0
- data/lib/typed_operation/instrumentation.rb +214 -0
- data/lib/typed_operation/operations/composition.rb +41 -0
- data/lib/typed_operation/operations/executable.rb +27 -1
- data/lib/typed_operation/operations/introspection.rb +9 -1
- data/lib/typed_operation/operations/lifecycle.rb +4 -1
- data/lib/typed_operation/operations/parameters.rb +11 -5
- data/lib/typed_operation/operations/partial_application.rb +4 -0
- data/lib/typed_operation/operations/property_builder.rb +46 -22
- data/lib/typed_operation/partially_applied.rb +33 -10
- data/lib/typed_operation/pipeline/builder.rb +88 -0
- data/lib/typed_operation/pipeline/chainable_wrapper.rb +23 -0
- data/lib/typed_operation/pipeline/empty_pipeline_chain.rb +25 -0
- data/lib/typed_operation/pipeline/step_wrapper.rb +94 -0
- data/lib/typed_operation/pipeline.rb +176 -0
- data/lib/typed_operation/prepared.rb +13 -0
- data/lib/typed_operation/railtie.rb +4 -0
- data/lib/typed_operation/result/adapters/built_in.rb +28 -0
- data/lib/typed_operation/result/adapters/dry_monads.rb +36 -0
- data/lib/typed_operation/result/failure.rb +78 -0
- data/lib/typed_operation/result/mixin.rb +24 -0
- data/lib/typed_operation/result/success.rb +75 -0
- data/lib/typed_operation/result.rb +39 -0
- data/lib/typed_operation/version.rb +5 -1
- data/lib/typed_operation.rb +18 -1
- metadata +27 -3
- data/lib/typed_operation/operations/callable.rb +0 -23
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "pipeline/step_wrapper"
|
|
5
|
+
require_relative "pipeline/empty_pipeline_chain"
|
|
6
|
+
require_relative "pipeline/chainable_wrapper"
|
|
7
|
+
require_relative "pipeline/builder"
|
|
8
|
+
|
|
9
|
+
module TypedOperation
|
|
10
|
+
# DSL for building declarative pipelines of operations.
|
|
11
|
+
# Pipeline is syntactic sugar over the composition/chaining system.
|
|
12
|
+
# Internally builds chains, adding named steps, conditions, and error attribution.
|
|
13
|
+
class Pipeline
|
|
14
|
+
# @rbs @chain: untyped
|
|
15
|
+
|
|
16
|
+
attr_reader :steps #: Array[Hash[Symbol, untyped]]
|
|
17
|
+
attr_reader :failure_handler #: (^(untyped, Symbol) -> untyped)?
|
|
18
|
+
|
|
19
|
+
# Build a new pipeline using the DSL.
|
|
20
|
+
#: () { () -> void } -> Pipeline
|
|
21
|
+
def self.build(&block)
|
|
22
|
+
builder = Builder.new
|
|
23
|
+
builder.instance_eval(&block)
|
|
24
|
+
new(builder.steps, builder.failure_handler)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
#: (?Array[Hash[Symbol, untyped]], ?(^(untyped, Symbol) -> untyped)?) -> void
|
|
28
|
+
def initialize(steps = [], failure_handler = nil)
|
|
29
|
+
@steps = steps.freeze
|
|
30
|
+
@failure_handler = failure_handler
|
|
31
|
+
@chain = nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Execute the pipeline by delegating to the internal chain.
|
|
35
|
+
#: (*untyped, **untyped) -> untyped
|
|
36
|
+
def call(*args, **kwargs)
|
|
37
|
+
chain.call(*args, **kwargs)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Append an operation as a new step, returning a new Pipeline.
|
|
41
|
+
#: (untyped, ?name: Symbol?, ?if: (^(untyped) -> boolish)?) -> Pipeline
|
|
42
|
+
def append(operation, name: nil, if: nil)
|
|
43
|
+
condition = binding.local_variable_get(:if)
|
|
44
|
+
new_step = {
|
|
45
|
+
type: :step,
|
|
46
|
+
name: name || derive_name(operation),
|
|
47
|
+
operation: operation,
|
|
48
|
+
condition: condition
|
|
49
|
+
}
|
|
50
|
+
self.class.new(@steps + [new_step], @failure_handler)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Compose with another pipeline, merging steps.
|
|
54
|
+
#: (Pipeline, ?on_failure: (:left | :right | ^(untyped, Symbol) -> untyped)?) -> Pipeline
|
|
55
|
+
def compose(other, on_failure: nil)
|
|
56
|
+
handler = resolve_failure_handlers(other, on_failure)
|
|
57
|
+
self.class.new(@steps + other.steps, handler)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Smart composition operator.
|
|
61
|
+
#: (Pipeline | untyped) -> Pipeline
|
|
62
|
+
def +(other)
|
|
63
|
+
case other
|
|
64
|
+
when Pipeline
|
|
65
|
+
compose(other)
|
|
66
|
+
else
|
|
67
|
+
append(other)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Convert to a bare chain for full Composition flexibility.
|
|
72
|
+
#: () -> ChainableWrapper
|
|
73
|
+
def to_chain
|
|
74
|
+
ChainableWrapper.new(self)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
#: () -> untyped
|
|
80
|
+
def chain
|
|
81
|
+
@chain ||= build_chain
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
#: () -> untyped
|
|
85
|
+
def build_chain
|
|
86
|
+
return EmptyPipelineChain.new if @steps.empty?
|
|
87
|
+
|
|
88
|
+
@steps.each_with_index.reduce(nil) do |current_chain, (step, index)|
|
|
89
|
+
is_first = index == 0
|
|
90
|
+
wrapped = wrap_step(step, first_step: is_first)
|
|
91
|
+
|
|
92
|
+
if current_chain.nil?
|
|
93
|
+
wrapped
|
|
94
|
+
else
|
|
95
|
+
combine_into_chain(current_chain, step, wrapped)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
#: (untyped, Hash[Symbol, untyped], untyped) -> untyped
|
|
101
|
+
def combine_into_chain(current_chain, step, wrapped)
|
|
102
|
+
case step[:type]
|
|
103
|
+
when :fallback
|
|
104
|
+
FallbackChain.new(current_chain, wrapped)
|
|
105
|
+
when :transform
|
|
106
|
+
MapChain.new(current_chain, wrapped)
|
|
107
|
+
else
|
|
108
|
+
SequenceChain.new(current_chain, wrapped)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
#: (Hash[Symbol, untyped], ?first_step: bool) -> untyped
|
|
113
|
+
def wrap_step(step, first_step: false)
|
|
114
|
+
case step[:type]
|
|
115
|
+
when :transform
|
|
116
|
+
step[:operation]
|
|
117
|
+
when :fallback
|
|
118
|
+
wrap_fallback(step)
|
|
119
|
+
else
|
|
120
|
+
StepWrapper.new(
|
|
121
|
+
step[:operation],
|
|
122
|
+
name: step[:name],
|
|
123
|
+
condition: step[:condition],
|
|
124
|
+
failure_handler: @failure_handler,
|
|
125
|
+
first_step: first_step
|
|
126
|
+
)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
#: (Hash[Symbol, untyped]) -> untyped
|
|
131
|
+
def wrap_fallback(step)
|
|
132
|
+
if step[:operation].is_a?(Proc)
|
|
133
|
+
step[:operation]
|
|
134
|
+
else
|
|
135
|
+
StepWrapper.new(
|
|
136
|
+
step[:operation],
|
|
137
|
+
name: step[:name] || :fallback,
|
|
138
|
+
condition: nil,
|
|
139
|
+
failure_handler: @failure_handler
|
|
140
|
+
)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
#: (Pipeline, (:left | :right | ^(untyped, Symbol) -> untyped)?) -> (^(untyped, Symbol) -> untyped)?
|
|
145
|
+
def resolve_failure_handlers(other, on_failure)
|
|
146
|
+
return @failure_handler if other.failure_handler.nil?
|
|
147
|
+
return other.failure_handler if @failure_handler.nil?
|
|
148
|
+
|
|
149
|
+
case on_failure
|
|
150
|
+
when :left then @failure_handler
|
|
151
|
+
when :right then other.failure_handler
|
|
152
|
+
when Proc then on_failure
|
|
153
|
+
when nil
|
|
154
|
+
raise ArgumentError,
|
|
155
|
+
"Both pipelines have failure handlers. Specify on_failure: :left, :right, or a Proc"
|
|
156
|
+
else
|
|
157
|
+
raise ArgumentError, "Invalid on_failure option: #{on_failure.inspect}"
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
#: (untyped) -> Symbol
|
|
162
|
+
def derive_name(operation)
|
|
163
|
+
return :anonymous if operation.nil?
|
|
164
|
+
return :anonymous unless operation.respond_to?(:name) && operation.name
|
|
165
|
+
|
|
166
|
+
operation.name
|
|
167
|
+
.split("::")
|
|
168
|
+
.last
|
|
169
|
+
.gsub(/Operation$/, "")
|
|
170
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
171
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
172
|
+
.downcase
|
|
173
|
+
.to_sym
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
@@ -1,13 +1,26 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
1
2
|
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
module TypedOperation
|
|
5
|
+
# Represents an operation with all required parameters provided and ready for execution.
|
|
6
|
+
# Created when a PartiallyApplied operation receives its final required parameters.
|
|
4
7
|
class Prepared < PartiallyApplied
|
|
8
|
+
#: () -> TypedOperation::Base
|
|
5
9
|
def operation
|
|
6
10
|
operation_class.new(*@positional_args, **@keyword_args)
|
|
7
11
|
end
|
|
8
12
|
|
|
13
|
+
#: () -> true
|
|
9
14
|
def prepared?
|
|
10
15
|
true
|
|
11
16
|
end
|
|
17
|
+
|
|
18
|
+
#: () -> untyped
|
|
19
|
+
def explain
|
|
20
|
+
unless operation_class.respond_to?(:explain)
|
|
21
|
+
raise InvalidOperationError, "#{operation_class.name} does not support .explain. Include TypedOperation::Explainable in your operation class."
|
|
22
|
+
end
|
|
23
|
+
operation_class.explain(*@positional_args, **@keyword_args)
|
|
24
|
+
end
|
|
12
25
|
end
|
|
13
26
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module TypedOperation
|
|
5
|
+
module Result
|
|
6
|
+
module Adapters
|
|
7
|
+
# Default adapter using built-in Success/Failure classes.
|
|
8
|
+
class BuiltIn
|
|
9
|
+
# Create a Success result.
|
|
10
|
+
#: (untyped) -> Result::Success
|
|
11
|
+
def success(value)
|
|
12
|
+
Result::Success.new(value)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Create a Failure result.
|
|
16
|
+
#: (untyped) -> Result::Failure
|
|
17
|
+
def failure(error)
|
|
18
|
+
Result::Failure.new(error)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
#: () -> String
|
|
22
|
+
def name
|
|
23
|
+
"TypedOperation::Result (built-in)"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module TypedOperation
|
|
5
|
+
module Result
|
|
6
|
+
module Adapters
|
|
7
|
+
# Adapter for Dry::Monads Result type.
|
|
8
|
+
# Requires the dry-monads gem to be available.
|
|
9
|
+
# This is a lazy-loading adapter that only loads dry-monads when instantiated.
|
|
10
|
+
class DryMonads
|
|
11
|
+
#: () -> void
|
|
12
|
+
def initialize
|
|
13
|
+
require "dry-monads"
|
|
14
|
+
extend Dry::Monads[:result]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Create a Success result using Dry::Monads.
|
|
18
|
+
#: (untyped) -> untyped
|
|
19
|
+
def success(value)
|
|
20
|
+
Success(value)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Create a Failure result using Dry::Monads.
|
|
24
|
+
#: (untyped) -> untyped
|
|
25
|
+
def failure(error)
|
|
26
|
+
Failure(error)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
#: () -> String
|
|
30
|
+
def name
|
|
31
|
+
"Dry::Monads::Result"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module TypedOperation
|
|
5
|
+
# Error raised when trying to unwrap a Failure result.
|
|
6
|
+
class UnwrapError < StandardError; end
|
|
7
|
+
|
|
8
|
+
module Result
|
|
9
|
+
# Represents a failed result. Immutable value object.
|
|
10
|
+
class Failure
|
|
11
|
+
# @rbs @error: untyped
|
|
12
|
+
|
|
13
|
+
attr_reader :error #: untyped
|
|
14
|
+
|
|
15
|
+
#: (untyped) -> void
|
|
16
|
+
def initialize(error)
|
|
17
|
+
@error = error
|
|
18
|
+
freeze
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
#: () -> false
|
|
22
|
+
def success?
|
|
23
|
+
false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
#: () -> true
|
|
27
|
+
def failure?
|
|
28
|
+
true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Raises UnwrapError since this is a Failure.
|
|
32
|
+
#: () -> bot
|
|
33
|
+
def value!
|
|
34
|
+
raise UnwrapError, "Cannot unwrap Failure: #{@error.inspect}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Returns the wrapped error.
|
|
38
|
+
#: () -> untyped
|
|
39
|
+
def failure
|
|
40
|
+
@error
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Pattern matching support - array destructuring.
|
|
44
|
+
#: () -> Array[untyped]
|
|
45
|
+
def deconstruct
|
|
46
|
+
@error.is_a?(::Array) ? @error : [@error]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Pattern matching support - hash destructuring.
|
|
50
|
+
# Delegates to the inner error if it responds to deconstruct_keys.
|
|
51
|
+
#: (Array[Symbol]?) -> Hash[Symbol, untyped]
|
|
52
|
+
def deconstruct_keys(keys)
|
|
53
|
+
if @error.respond_to?(:deconstruct_keys)
|
|
54
|
+
@error.deconstruct_keys(keys)
|
|
55
|
+
else
|
|
56
|
+
{error: @error, failure: @error}
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
#: (untyped) -> bool
|
|
61
|
+
def ==(other)
|
|
62
|
+
other.is_a?(Failure) && other.error == @error
|
|
63
|
+
end
|
|
64
|
+
alias_method :eql?, :==
|
|
65
|
+
|
|
66
|
+
#: () -> Integer
|
|
67
|
+
def hash
|
|
68
|
+
[self.class, @error].hash
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
#: () -> String
|
|
72
|
+
def inspect
|
|
73
|
+
"Failure(#{@error.inspect})"
|
|
74
|
+
end
|
|
75
|
+
alias_method :to_s, :inspect
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module TypedOperation
|
|
5
|
+
module Result
|
|
6
|
+
# Mixin providing Success/Failure factory methods to chain classes.
|
|
7
|
+
# Include this module to call Success(value) and Failure(error) directly.
|
|
8
|
+
module Mixin
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
# Create a Success result using the configured adapter.
|
|
12
|
+
#: (untyped) -> (Result::Success | untyped)
|
|
13
|
+
def Success(value)
|
|
14
|
+
TypedOperation.configuration.result_adapter.success(value)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Create a Failure result using the configured adapter.
|
|
18
|
+
#: (untyped) -> (Result::Failure | untyped)
|
|
19
|
+
def Failure(error)
|
|
20
|
+
TypedOperation.configuration.result_adapter.failure(error)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module TypedOperation
|
|
5
|
+
module Result
|
|
6
|
+
# Represents a successful result. Immutable value object.
|
|
7
|
+
class Success
|
|
8
|
+
# @rbs @value: untyped
|
|
9
|
+
|
|
10
|
+
attr_reader :value #: untyped
|
|
11
|
+
|
|
12
|
+
#: (untyped) -> void
|
|
13
|
+
def initialize(value)
|
|
14
|
+
@value = value
|
|
15
|
+
freeze
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
#: () -> true
|
|
19
|
+
def success?
|
|
20
|
+
true
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
#: () -> false
|
|
24
|
+
def failure?
|
|
25
|
+
false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns the wrapped value.
|
|
29
|
+
#: () -> untyped
|
|
30
|
+
def value!
|
|
31
|
+
@value
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns nil for Success.
|
|
35
|
+
#: () -> nil
|
|
36
|
+
def failure
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Pattern matching support - array destructuring.
|
|
41
|
+
#: () -> Array[untyped]
|
|
42
|
+
def deconstruct
|
|
43
|
+
@value.is_a?(::Array) ? @value : [@value]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Pattern matching support - hash destructuring.
|
|
47
|
+
# Delegates to the inner value if it responds to deconstruct_keys.
|
|
48
|
+
#: (Array[Symbol]?) -> Hash[Symbol, untyped]
|
|
49
|
+
def deconstruct_keys(keys)
|
|
50
|
+
if @value.respond_to?(:deconstruct_keys)
|
|
51
|
+
@value.deconstruct_keys(keys)
|
|
52
|
+
else
|
|
53
|
+
{value: @value}
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
#: (untyped) -> bool
|
|
58
|
+
def ==(other)
|
|
59
|
+
other.is_a?(Success) && other.value == @value
|
|
60
|
+
end
|
|
61
|
+
alias_method :eql?, :==
|
|
62
|
+
|
|
63
|
+
#: () -> Integer
|
|
64
|
+
def hash
|
|
65
|
+
[self.class, @value].hash
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
#: () -> String
|
|
69
|
+
def inspect
|
|
70
|
+
"Success(#{@value.inspect})"
|
|
71
|
+
end
|
|
72
|
+
alias_method :to_s, :inspect
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "result/success"
|
|
5
|
+
require_relative "result/failure"
|
|
6
|
+
require_relative "result/adapters/built_in"
|
|
7
|
+
require_relative "result/adapters/dry_monads"
|
|
8
|
+
require_relative "result/mixin"
|
|
9
|
+
|
|
10
|
+
module TypedOperation
|
|
11
|
+
# Result types for representing success and failure outcomes.
|
|
12
|
+
#
|
|
13
|
+
# TypedOperation provides built-in Success and Failure types that work
|
|
14
|
+
# out of the box. You can also configure it to use Dry::Monads or a
|
|
15
|
+
# custom adapter.
|
|
16
|
+
#
|
|
17
|
+
# @example Default usage (built-in types)
|
|
18
|
+
# result = MyPipeline.call(input)
|
|
19
|
+
# result.success? # => true or false
|
|
20
|
+
# result.value! # => the value (raises on failure)
|
|
21
|
+
#
|
|
22
|
+
# @example Configure to use Dry::Monads
|
|
23
|
+
# TypedOperation.configure do |config|
|
|
24
|
+
# config.result_adapter = :dry_monads
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# @example Pattern matching
|
|
28
|
+
# case result
|
|
29
|
+
# in Success[value]
|
|
30
|
+
# handle_success(value)
|
|
31
|
+
# in Failure[error]
|
|
32
|
+
# handle_failure(error)
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
module Result
|
|
36
|
+
# Alias for easy access to UnwrapError from Result module
|
|
37
|
+
UnwrapError = TypedOperation::UnwrapError
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/typed_operation.rb
CHANGED
|
@@ -1,20 +1,37 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
|
|
1
3
|
require "literal"
|
|
2
4
|
|
|
3
5
|
require "typed_operation/version"
|
|
4
6
|
require "typed_operation/railtie" if defined?(Rails::Railtie)
|
|
7
|
+
require "typed_operation/result"
|
|
8
|
+
require "typed_operation/configuration"
|
|
5
9
|
require "typed_operation/operations/introspection"
|
|
6
10
|
require "typed_operation/operations/parameters"
|
|
7
11
|
require "typed_operation/operations/partial_application"
|
|
8
|
-
require "typed_operation/operations/callable"
|
|
9
12
|
require "typed_operation/operations/lifecycle"
|
|
10
13
|
require "typed_operation/operations/property_builder"
|
|
11
14
|
require "typed_operation/operations/executable"
|
|
15
|
+
require "typed_operation/operations/composition"
|
|
12
16
|
require "typed_operation/curried"
|
|
17
|
+
require "typed_operation/instrumentation"
|
|
18
|
+
require "typed_operation/explainable"
|
|
13
19
|
require "typed_operation/immutable_base"
|
|
14
20
|
require "typed_operation/base"
|
|
15
21
|
require "typed_operation/partially_applied"
|
|
16
22
|
require "typed_operation/prepared"
|
|
23
|
+
require "typed_operation/callable_resolver"
|
|
24
|
+
require "typed_operation/chains/chained_operation"
|
|
25
|
+
require "typed_operation/chains/sequence_chain"
|
|
26
|
+
require "typed_operation/chains/splat_chain"
|
|
27
|
+
require "typed_operation/chains/smart_chain"
|
|
28
|
+
require "typed_operation/chains/map_chain"
|
|
29
|
+
require "typed_operation/chains/fallback_chain"
|
|
30
|
+
require "typed_operation/context"
|
|
31
|
+
require "typed_operation/pipeline"
|
|
17
32
|
|
|
33
|
+
# TypedOperation provides a framework for defining type-safe, composable operations
|
|
34
|
+
# with support for partial application, currying, and parameter validation.
|
|
18
35
|
module TypedOperation
|
|
19
36
|
class InvalidOperationError < StandardError; end
|
|
20
37
|
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: typed_operation
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.0.
|
|
4
|
+
version: 1.0.0.beta4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stephen Ierodiaconou
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2025-
|
|
10
|
+
date: 2025-12-10 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: literal
|
|
@@ -49,9 +49,22 @@ files:
|
|
|
49
49
|
- lib/typed_operation.rb
|
|
50
50
|
- lib/typed_operation/action_policy_auth.rb
|
|
51
51
|
- lib/typed_operation/base.rb
|
|
52
|
+
- lib/typed_operation/callable_resolver.rb
|
|
53
|
+
- lib/typed_operation/chains/chained_operation.rb
|
|
54
|
+
- lib/typed_operation/chains/fallback_chain.rb
|
|
55
|
+
- lib/typed_operation/chains/map_chain.rb
|
|
56
|
+
- lib/typed_operation/chains/sequence_chain.rb
|
|
57
|
+
- lib/typed_operation/chains/smart_chain.rb
|
|
58
|
+
- lib/typed_operation/chains/splat_chain.rb
|
|
59
|
+
- lib/typed_operation/configuration.rb
|
|
60
|
+
- lib/typed_operation/context.rb
|
|
52
61
|
- lib/typed_operation/curried.rb
|
|
62
|
+
- lib/typed_operation/explainable.rb
|
|
53
63
|
- lib/typed_operation/immutable_base.rb
|
|
54
|
-
- lib/typed_operation/
|
|
64
|
+
- lib/typed_operation/instrumentation.rb
|
|
65
|
+
- lib/typed_operation/instrumentation/trace.rb
|
|
66
|
+
- lib/typed_operation/instrumentation/tree_formatter.rb
|
|
67
|
+
- lib/typed_operation/operations/composition.rb
|
|
55
68
|
- lib/typed_operation/operations/executable.rb
|
|
56
69
|
- lib/typed_operation/operations/introspection.rb
|
|
57
70
|
- lib/typed_operation/operations/lifecycle.rb
|
|
@@ -59,8 +72,19 @@ files:
|
|
|
59
72
|
- lib/typed_operation/operations/partial_application.rb
|
|
60
73
|
- lib/typed_operation/operations/property_builder.rb
|
|
61
74
|
- lib/typed_operation/partially_applied.rb
|
|
75
|
+
- lib/typed_operation/pipeline.rb
|
|
76
|
+
- lib/typed_operation/pipeline/builder.rb
|
|
77
|
+
- lib/typed_operation/pipeline/chainable_wrapper.rb
|
|
78
|
+
- lib/typed_operation/pipeline/empty_pipeline_chain.rb
|
|
79
|
+
- lib/typed_operation/pipeline/step_wrapper.rb
|
|
62
80
|
- lib/typed_operation/prepared.rb
|
|
63
81
|
- lib/typed_operation/railtie.rb
|
|
82
|
+
- lib/typed_operation/result.rb
|
|
83
|
+
- lib/typed_operation/result/adapters/built_in.rb
|
|
84
|
+
- lib/typed_operation/result/adapters/dry_monads.rb
|
|
85
|
+
- lib/typed_operation/result/failure.rb
|
|
86
|
+
- lib/typed_operation/result/mixin.rb
|
|
87
|
+
- lib/typed_operation/result/success.rb
|
|
64
88
|
- lib/typed_operation/version.rb
|
|
65
89
|
homepage: https://github.com/stevegeek/typed_operation
|
|
66
90
|
licenses:
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module TypedOperation
|
|
4
|
-
module Operations
|
|
5
|
-
module Callable
|
|
6
|
-
def self.included(base)
|
|
7
|
-
base.extend(CallableMethods)
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
module CallableMethods
|
|
11
|
-
def call(...)
|
|
12
|
-
new(...).call
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def to_proc
|
|
16
|
-
method(:call).to_proc
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
include CallableMethods
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|