gaskit 0.1.0 → 0.1.1

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.
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gaskit
4
+ # Gaskit::HookRegistry manages registration and retrieval of global hooks for
5
+ # operation-style lifecycles (e.g., `before`, `after`, `around`).
6
+ #
7
+ # Hooks are grouped by type and tag. They are used in classes that include `Gaskit::Hookable`.
8
+ class HookRegistry
9
+ def initialize
10
+ @hooks = {
11
+ before: Hash.new { |h, k| h[k] = [] },
12
+ after: Hash.new { |h, k| h[k] = [] },
13
+ around: Hash.new { |h, k| h[k] = [] }
14
+ }
15
+ end
16
+
17
+ # Registers a new hook under a given type and tag.
18
+ #
19
+ # @param type [Symbol] The lifecycle type: `:before`, `:after`, or `:around`
20
+ # @param tag [Symbol, String] A symbolic tag to group related hooks (e.g., :audit, :metrics)
21
+ # @param callable [#call, nil] A callable object (optional if block is given)
22
+ # @yield [hook] A block to be registered as a hook if no callable is passed
23
+ # @return [void]
24
+ # @raise [ArgumentError] If the hook is not callable or the type is invalid
25
+ def register(type, tag, callable = nil, &block)
26
+ hook = callable || block
27
+ raise ArgumentError, "Hook must respond to #call" unless hook.respond_to?(:call)
28
+ raise ArgumentError, "Unknown hook type: #{type}" unless @hooks.key?(type)
29
+
30
+ @hooks[type][tag.to_sym] << hook
31
+ end
32
+
33
+ # Checks if a hook tag is registered under a specific type.
34
+ #
35
+ # @param type [Symbol] The hook type (`:before`, `:after`, or `:around`)
36
+ # @param tag [Symbol, String] The tag to check
37
+ # @return [Boolean] Whether a hook with that tag exists for the given type
38
+ def registered?(type, tag)
39
+ @hooks[type].key?(tag.to_sym)
40
+ end
41
+
42
+ # Fetches hooks for the given type, filtered by tags.
43
+ #
44
+ # @param type [Symbol] The hook type (`:before`, `:after`, or `:around`)
45
+ # @param tags [Array<Symbol, String>, nil] One or more tags to filter hooks by. If nil or empty,
46
+ # returns all hooks of that type.
47
+ # @return [Array<#call>] An array of callable hooks
48
+ def fetch(type, tags = nil)
49
+ return @hooks[type].values.flatten if tags.nil? || tags.empty?
50
+
51
+ (tags || []).flat_map { |tag| @hooks[type][tag.to_sym] }
52
+ end
53
+
54
+ # Returns all registered tags for a given hook type.
55
+ #
56
+ # @param type [Symbol] The hook type (`:before`, `:after`, or `:around`)
57
+ # @return [Array<Symbol>] List of registered tags under that type
58
+ def registered_tags(type)
59
+ @hooks[type].keys
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent"
4
+
5
+ module Gaskit
6
+ module Hookable
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ module ClassMethods
12
+ VALID_HOOK_TYPES = %i[before after around].freeze
13
+
14
+ def use_hooks(*tags, all: tags.empty?)
15
+ registered_hooks.concat(tags.map(&:to_sym)).uniq!
16
+
17
+ @run_all_hooks = all
18
+ @enabled = true
19
+ end
20
+
21
+ def use_hook(type, proc = nil, &block)
22
+ hook = block_given? ? block : proc
23
+ type, hook = validate_hook!(type, hook)
24
+
25
+ inline_hooks[type] << hook
26
+ @enabled = true
27
+ end
28
+
29
+ def before(proc = nil, &block)
30
+ use_hook(:before, proc, &block)
31
+ end
32
+
33
+ def around(proc = nil, &block)
34
+ use_hook(:around, proc, &block)
35
+ end
36
+
37
+ def after(proc = nil, &block)
38
+ use_hook(:after, proc, &block)
39
+ end
40
+
41
+ def hooks_enabled?
42
+ @enabled || false
43
+ end
44
+
45
+ def run_all_hooks?
46
+ @run_all_hooks || false
47
+ end
48
+
49
+ def registered_hooks
50
+ @registered_hooks ||= []
51
+ end
52
+
53
+ def inline_hooks
54
+ @inline_hooks ||= Concurrent::Hash.new { |h, k| h[k] = [] }
55
+ end
56
+
57
+ private
58
+
59
+ def validate_hook!(type, hook)
60
+ type = type.to_sym
61
+
62
+ unless VALID_HOOK_TYPES.include?(type)
63
+ raise ArgumentError, "#{type} is not a valid hook type (valid types are #{VALID_HOOK_TYPES.join(", ")})"
64
+ end
65
+
66
+ raise ArgumentError, "Hook must be callable" unless hook.respond_to?(:call)
67
+
68
+ [type, hook]
69
+ end
70
+ end
71
+
72
+ def apply_hooks(*types, &block)
73
+ return block.call unless self.class.hooks_enabled?
74
+
75
+ types = types.map(&:to_sym)
76
+ result = nil
77
+
78
+ apply_type_hooks(:before) if types.include?(:before)
79
+ result = apply_type_hooks(:around, &block) if types.include?(:around)
80
+ apply_type_hooks(:after, result: result) if types.include?(:after)
81
+
82
+ result
83
+ end
84
+
85
+ def apply_before_hooks
86
+ apply_type_hooks(:before)
87
+ end
88
+
89
+ def apply_around_hooks(&block)
90
+ apply_type_hooks(:around, &block)
91
+ end
92
+
93
+ def apply_after_hooks(result)
94
+ apply_type_hooks(:after, result: result)
95
+ end
96
+
97
+ private
98
+
99
+ def collect_hooks(type)
100
+ inline = self.class.inline_hooks[type]
101
+ registered =
102
+ if self.class.run_all_hooks?
103
+ Gaskit.hooks.fetch(type)
104
+ else
105
+ Gaskit.hooks.fetch(type, self.class.registered_hooks)
106
+ end
107
+
108
+ (registered + inline).uniq
109
+ end
110
+
111
+ def apply_type_hooks(type, result: nil, &block)
112
+ return result unless self.class.hooks_enabled?
113
+
114
+ hooks = collect_hooks(type)
115
+ process_hooks(type, hooks, result, &block)
116
+ end
117
+
118
+ def process_hooks(type, hooks, result, &block)
119
+ case type
120
+ when :before
121
+ hooks.each { |hook| instance_exec(&hook) }
122
+ when :around
123
+ result = hooks.reverse.inject(block) do |acc, hook|
124
+ proc { instance_exec(acc, &hook) }
125
+ end.call
126
+ when :after
127
+ hooks.each { |hook| instance_exec(result, &hook) }
128
+ end
129
+
130
+ result
131
+ end
132
+ end
133
+ end
data/lib/gaskit/logger.rb CHANGED
@@ -123,10 +123,11 @@ module Gaskit
123
123
  lambda do |severity, time, _progname, msg|
124
124
  message, context = extract_message_and_context(msg)
125
125
  context ||= {}
126
+ class_name = context.delete(:class)
126
127
 
127
128
  tags = %W[[#{time.utc.iso8601}] [#{severity}]]
128
- tags << "[#{context[:class]}]" if context[:class]
129
- tags += context.map { |k, v| "[#{k}=#{v}]" }
129
+ tags << "[#{class_name}]" if class_name
130
+ tags += flatten_context(context).map { |k, v| "[#{k}=#{v}]" }
130
131
 
131
132
  "#{tags.join(" ")} #{message}\n"
132
133
  end
@@ -143,18 +144,43 @@ module Gaskit
143
144
 
144
145
  [msg.to_s, {}]
145
146
  end
147
+
148
+ # Recursively flattens a nested hash by concatenating keys using underscores.
149
+ #
150
+ # @example
151
+ # flatten_context({ a: { b: 1, c: { d: 2 } } })
152
+ # # => { "a_b" => 1, "a_c_d" => 2 }
153
+ #
154
+ # @param hash [Hash] The hash to flatten.
155
+ # @param prefix [String, nil] The prefix to prepend to keys (used during recursion).
156
+ # @return [Hash] A flat hash with underscore-separated keys.
157
+ def flatten_context(hash, prefix = nil)
158
+ result = {}
159
+
160
+ hash.each do |key, value|
161
+ full_key = prefix ? "#{prefix}_#{key}" : key.to_s
162
+
163
+ if value.is_a?(Hash)
164
+ result.merge!(flatten_context(value, full_key))
165
+ else
166
+ result[full_key] = value
167
+ end
168
+ end
169
+
170
+ result
171
+ end
146
172
  end
147
173
 
148
174
  attr_reader :context
149
175
 
150
176
  # Initializes a new logger instance.
151
177
  #
152
- # @param klass [Class] The name of the class being logged.
178
+ # @param klass [Class, Object, String, Symbol] The name of the class being logged.
153
179
  # @param context [Hash] Optional additional context to include in every log entry.
154
180
  def initialize(klass, context: {})
155
- @class_name = resolve_name(klass)
156
- @context = apply_context(context)
181
+ @class_name = Gaskit::Helpers.resolve_name(klass)
157
182
 
183
+ @context = apply_context(context).merge(class: @class_name)
158
184
  @logger = Gaskit.configuration.logger || ::Logger.new($stdout)
159
185
  rescue StandardError
160
186
  ::Logger.new($stdout).error "Failed to initialize logger: #{$ERROR_INFO}"
@@ -226,20 +252,6 @@ module Gaskit
226
252
 
227
253
  private
228
254
 
229
- # Resolves the class name provided when instantiating the logger.
230
- #
231
- # @return [String] The resolved class name.
232
- def resolve_name(source)
233
- case source
234
- when String, Symbol
235
- source.to_s
236
- when Class
237
- source.name
238
- else
239
- source.class.name
240
- end
241
- end
242
-
243
255
  def apply_context(context)
244
256
  default_context = Gaskit.configuration.context_provider.call
245
257
  context = default_context.merge(context)
@@ -4,16 +4,14 @@ require_relative "core"
4
4
  require_relative "operation_result"
5
5
  require_relative "operation_exit"
6
6
  require_relative "helpers"
7
+ require_relative "hookable"
7
8
 
8
9
  module Gaskit
9
10
  # The Gaskit::Operation class defines a structured and extensible pattern for building application operations.
10
11
  # It enforces consistent behavior across operations while supporting customization via contracts.
11
12
  #
12
13
  # # Features
13
- # - Pluggable contracts via `use_contract`, allowing you to define or reference a `result` and
14
- # `early_exit` class.
15
- # - A **contract** is a pairing of a result and early_exit class, either manually defined or registered
16
- # under a symbol.
14
+ # - Pluggable contracts via `use_contract`, allowing you to define or reference a `result` class.
17
15
  # - Integrated duration tracking, structured logging, and early exits.
18
16
  # - Supports `.call` (non-raising) and `.call!` (raising) styles.
19
17
  #
@@ -50,6 +48,8 @@ module Gaskit
50
48
  #
51
49
  # @abstract Subclass this and define `#call` or `#call!` to create a new operation.
52
50
  class Operation
51
+ include Gaskit::Hookable
52
+
53
53
  class << self
54
54
  def result_class
55
55
  return @result_class if defined?(@result_class) && @result_class
@@ -58,30 +58,26 @@ module Gaskit
58
58
  nil
59
59
  end
60
60
 
61
- # Defines the result and early exit classes for this operation.
62
- # Can reference a named contract registered in `Gaskit::Registry`, override either part manually,
63
- # or define both without using a named contract.
61
+ # Defines the result class for this operation.
62
+ # Can reference a named contract registered in `Gaskit::Registry`, or define one without
63
+ # using a registered contract.
64
64
  #
65
65
  # @example Use a registered contract
66
66
  # use_contract :service
67
67
  #
68
- # @example Override only result class
69
- # use_contract :service, result: CustomResult
70
- #
71
- # @example Define both without using a contract name
72
- # use_contract result: CustomResult, early_exit: CustomExit
68
+ # @example Define a contract that has not been registered
69
+ # use_contract result: CustomResult
73
70
  #
74
71
  # @param contract [Symbol, nil] A registered contract name (e.g., `:service`)
75
72
  # @param result [Class, nil] A class that inherits from `Gaskit::BaseResult`
76
73
  # @raise [ArgumentError] if contract is not a symbol or unexpected args are passed
77
74
  # @raise [ResultTypeError] if `result` is not a subclass of `Gaskit::BaseResult`
78
- # @raise [EarlyExitTypeError] if `early_exit` is not a subclass of `Gaskit::BaseExit`
79
75
  # @return [void]
80
76
  def use_contract(contract = nil, result: nil)
81
77
  if contract
82
78
  raise ArgumentError, "use_contract must be called with a symbol or keyword args" unless contract.is_a?(Symbol)
83
79
 
84
- result = Gaskit.fetch_contract(contract)
80
+ result = Gaskit.contracts.fetch(contract)
85
81
  end
86
82
 
87
83
  Gaskit::ContractRegistry.verify_result_class!(result)
@@ -98,6 +94,10 @@ module Gaskit
98
94
  # @param code [String, nil] Optional error code.
99
95
  # @return [void]
100
96
  def error(key, message, code: nil)
97
+ raise ArgumentError, "Error key must be a symbol or a string, received #{key}" unless key.is_a?(Symbol)
98
+ raise ArgumentError, "Error message must be a string" unless message.is_a?(String)
99
+ raise ArgumentError, "Error key :#{key} is already registered" if errors_registry.key?(key)
100
+
101
101
  errors_registry[key.to_sym] = { message: message, code: code }
102
102
  end
103
103
 
@@ -108,24 +108,24 @@ module Gaskit
108
108
  @errors_registry ||= {}
109
109
  end
110
110
 
111
- # Execute the operation without raising an exception on failure.
111
+ # Executes the operation with soft-failure handling
112
112
  #
113
- # @param [Array] args Positional arguments passed.
114
- # @param [Hash] kwargs Keyword arguments passed (with optional :context).
115
- # @yield [Block] Additional block logic to pass during the operation.
116
- # @return [OperationResult] The result of the operation.
117
- def call(*args, **kwargs, &block)
118
- invoke(false, *args, **kwargs, &block)
113
+ # @param args [Array] Positional arguments for the first step
114
+ # @param context [Hash] Shared context across all steps
115
+ # @param kwargs [Hash] Keyword arguments for the first step
116
+ # @return [Gaskit::OperationResult]
117
+ def call(*args, context: {}, **kwargs, &block)
118
+ invoke(false, context, *args, **kwargs, &block)
119
119
  end
120
120
 
121
- # Execute the operation with raising an exception on failure.
121
+ # Executes the operation with hard-failure handling (raises on unhandled errors)
122
122
  #
123
- # @param [Array] args Positional arguments passed.
124
- # @param [Hash] kwargs Keyword arguments passed (with optional :context).
125
- # @yield [Block] Additional block logic to pass during the operation.
126
- # @return [OperationResult] The result of the operation.
127
- def call!(*args, **kwargs, &block)
128
- invoke(true, *args, **kwargs, &block)
123
+ # @param args [Array] Positional arguments for the first step
124
+ # @param context [Hash] Shared context across all steps
125
+ # @param kwargs [Hash] Keyword arguments for the first step
126
+ # @return [Gaskit::OperationResult]
127
+ def call!(*args, context: {}, **kwargs, &block)
128
+ invoke(true, context, *args, **kwargs, &block)
129
129
  end
130
130
 
131
131
  private
@@ -138,21 +138,26 @@ module Gaskit
138
138
  # @yield [Block] Additional block logic during execution.
139
139
  # @raise [NotImplementedError] If operation type is not set in subclasses.
140
140
  # @return [OperationResult] The result of the operation.
141
- def invoke(raise_on_failure, *args, **kwargs, &block)
141
+ def invoke(raise_on_failure, context, *args, **kwargs, &block)
142
142
  unless result_class
143
143
  raise NotImplementedError, "No result_class defined for #{name} or its ancestors. " \
144
144
  "Did you forget to call `use_contract`?"
145
145
  end
146
146
 
147
- context = kwargs.delete(:context) || {}
148
147
  operation = new(raise_on_failure, context: context)
149
148
  duration, (result, error) = execute(operation, *args, **kwargs, &block)
150
149
 
151
- operation.logger.debug(context: { duration: duration }) do
152
- "Operation completed in #{duration} seconds"
150
+ result = build_result(result, error, duration, operation.context)
151
+
152
+ begin
153
+ operation.apply_after_hooks(result)
154
+ rescue StandardError => e
155
+ result = handle_after_hook_error(operation, result, e, duration)
153
156
  end
154
157
 
155
- result_class.new(error.nil?, result, error, duration: duration, context: context)
158
+ log_execution_debug(operation, duration)
159
+
160
+ result
156
161
  end
157
162
 
158
163
  # Executes the operation logic and handles potential exceptions.
@@ -164,16 +169,40 @@ module Gaskit
164
169
  # @return [Array] The execution duration, result, and error if any.
165
170
  def execute(operation, *args, **kwargs, &block)
166
171
  Helpers.time_execution do
167
- [operation.call(*args, **kwargs, &block), nil]
172
+ operation.apply_hooks(:before, :around) do
173
+ [operation.call(*args, **kwargs, &block), nil]
174
+ end
168
175
  rescue StandardError => e
169
- if e.is_a?(Gaskit::OperationExit)
170
- log_exit(operation, e)
171
- else
172
- log_exception(operation, e)
173
- raise e if operation.raise_on_failure
176
+ handle_execution_error(operation, e)
177
+ end
178
+ end
174
179
 
175
- end
176
- [nil, e]
180
+ # Builds an OperationResult instance.
181
+ #
182
+ # @param [Object, nil] result The result of the operation.
183
+ # @param [StandardError] error The error, if any.
184
+ # @param [Float] duration The duration of the operation.
185
+ # @param [Gaskit::Context] context The operation context.
186
+ # @return [OperationResult]
187
+ def build_result(result, error, duration, context)
188
+ result_class.new(
189
+ error.nil?,
190
+ result,
191
+ error,
192
+ duration: duration,
193
+ context: context
194
+ )
195
+ end
196
+
197
+ # Logs execution information about the operation if Gaskit.debug? == true.
198
+ #
199
+ # @param [Gaskit::Operation] operation The operation instance.
200
+ # @param [Float] duration The operation's duration.
201
+ def log_execution_debug(operation, duration)
202
+ return unless Gaskit.debug?
203
+
204
+ operation.logger.debug(context: { duration: duration }) do
205
+ "Operation completed in #{duration} seconds"
177
206
  end
178
207
  end
179
208
 
@@ -189,16 +218,40 @@ module Gaskit
189
218
  # Logs any unhandled exception during the operation.
190
219
  #
191
220
  # @param [Gaskit::Operation] operation The operation instance.
192
- # @param [Exception] exception The raised exception.
221
+ # @param [StandardError] exception The raised exception.
193
222
  # @return [void]
194
223
  def log_exception(operation, exception)
195
224
  operation.logger.error { "[#{exception.class}] #{exception.message}" }
196
225
  operation.logger.error { exception.backtrace&.join("\n") }
197
226
  end
198
227
 
199
- # @return [String] The name of the operation class (e.g., "MyOperation").
200
- def operation_name
201
- @operation_name ||= self.class.name.to_s
228
+ # Handles exceptions during execution.
229
+ #
230
+ # @param [Gaskit::Operation] operation The operation instance.
231
+ # @param [StandardError] error The raised error.
232
+ # @return [Array] The result and the error.
233
+ def handle_execution_error(operation, error)
234
+ if error.is_a?(Gaskit::OperationExit)
235
+ log_exit(operation, error)
236
+ else
237
+ log_exception(operation, error)
238
+ raise error if operation.raise_on_failure?
239
+ end
240
+
241
+ [nil, error]
242
+ end
243
+
244
+ # Handles errors raised after executing hooks.
245
+ #
246
+ # @param [Gaskit::Operation] operation The operation instance.
247
+ # @param [OperationResult] result The current operation result.
248
+ # @param [StandardError] error The encountered error.
249
+ # @param [Float] duration The execution duration.
250
+ def handle_after_hook_error(operation, result, error, duration)
251
+ log_exception(operation, error)
252
+ raise error if operation.raise_on_failure?
253
+
254
+ build_result(result, error, duration, operation.context)
202
255
  end
203
256
  end
204
257
 
@@ -212,16 +265,15 @@ module Gaskit
212
265
  def initialize(raise_on_failure, context: {})
213
266
  @raise_on_failure = raise_on_failure
214
267
  @context = apply_context(context)
215
- @logger = Gaskit::Logger.new(self.class, context: @context)
268
+ @logger = Gaskit::Logger.new(self, context: @context)
269
+
270
+ return unless self.class.result_class.nil?
271
+
272
+ raise Gaskit::Error, "No result_class defined for #{self.class.name} or its ancestors."
216
273
  end
217
274
 
218
- # Applies global context, if set, from Gaskit.configuration.context_provider.
219
- #
220
- # @param context [Hash] The context provided directly to the Flow.
221
- # @return [Hash] The fully applied context Hash.
222
- def apply_context(context)
223
- default_context = Gaskit.configuration.context_provider.call
224
- Helpers.deep_compact(default_context.merge(context))
275
+ def raise_on_failure?
276
+ @raise_on_failure
225
277
  end
226
278
 
227
279
  # Executes the operation logic.
@@ -239,22 +291,34 @@ module Gaskit
239
291
  # If the key was previously registered via `self.error`, it uses the declared message and code.
240
292
  # Otherwise, it uses the key as the message.
241
293
  #
242
- # @param exit_key [Symbol] The symbolic reason for exiting.
294
+ # @param error_key [Symbol] The symbolic reason for exiting.
243
295
  # @param message [String, nil] Optional message override.
296
+ # @param code [String, nil] Optional error code.
244
297
  # @raise [OperationExit] always raises an instance with message and optional code
245
- def exit(exit_key, message = nil)
246
- exit_key = exit_key.to_sym
247
- definition = self.class.errors_registry[exit_key]
298
+ def exit(error_key, message = nil, code: nil)
299
+ error_key = error_key.to_sym
300
+ definition = self.class.errors_registry.fetch(error_key, nil)
248
301
 
249
302
  if definition
250
303
  message ||= definition[:message]
251
- code = definition[:code]
304
+ code ||= definition[:code]
252
305
  end
253
306
 
254
- raise OperationExit.new(exit_key, message, code: code)
307
+ raise OperationExit.new(error_key, message, code: code)
255
308
  end
256
309
 
257
310
  # @see #exit
258
311
  alias abort exit
312
+
313
+ private
314
+
315
+ # Applies global context, if set, from Gaskit.configuration.context_provider.
316
+ #
317
+ # @param context [Hash] The context provided directly to the Flow.
318
+ # @return [Hash] The fully applied context Hash.
319
+ def apply_context(context = {})
320
+ default_context = Gaskit.configuration.context_provider.call
321
+ Helpers.deep_compact(default_context.merge(context))
322
+ end
259
323
  end
260
324
  end
@@ -31,9 +31,13 @@ module Gaskit
31
31
  # @param message [String, nil] A human-readable message (defaults to key if not provided)
32
32
  # @param code [String, nil] A structured error code for analytics or debugging
33
33
  def initialize(key, message = nil, code: nil)
34
- super(message || key.to_s)
34
+ super(message || "early exit")
35
35
  @key = key
36
36
  @code = code
37
37
  end
38
+
39
+ def inspect
40
+ "#<#{self.class} key=#{key.inspect} message=#{message.inspect} code=#{code.inspect}>"
41
+ end
38
42
  end
39
43
  end
@@ -30,11 +30,11 @@ module Gaskit
30
30
 
31
31
  # Initializes a new instance of OperationResult.
32
32
  #
33
- # @param [Boolean] success Whether the operation was successful.
34
- # @param [Object, nil] value The value obtained as a result of the operation.
35
- # @param [Exception, nil] error The error encountered during the operation.
36
- # @param [Float, String] duration The time taken to complete the operation in seconds.
37
- # @param [Hash] context Optional context metadata for this operation.
33
+ # @param success [Boolean] Whether the operation was successful.
34
+ # @param value [Object, nil] The value obtained as a result of the operation.
35
+ # @param error [StandardError, nil] The error encountered during the operation.
36
+ # @param duration [Float, String] The time taken to complete the operation in seconds.
37
+ # @param context [Hash] Optional context metadata for this operation.
38
38
  def initialize(success, value, error, duration:, context: {})
39
39
  @success = success
40
40
  @value = value
@@ -47,7 +47,7 @@ module Gaskit
47
47
  #
48
48
  # @return [String] The formatted inspection string.
49
49
  def inspect
50
- "#<#{self.class.name} success=#{success?} value=#{value.inspect} duration=#{duration}>"
50
+ "#<#{self.class.name} status=#{status} value=#{value.inspect} duration=#{duration}>"
51
51
  end
52
52
 
53
53
  # Indicates whether the operation was successful.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gaskit
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
data/lib/gaskit.rb CHANGED
@@ -27,7 +27,7 @@ require_relative "gaskit/railtie" if defined?(Rails::Railtie)
27
27
  # end
28
28
  #
29
29
  # @example Registering a contract
30
- # Gaskit.register_contract(:service, MyResultClass)
30
+ # Gaskit.contracts.register(:service, MyResultClass)
31
31
  #
32
32
  # @example Defining a service
33
33
  # class MyService < Gaskit::Service
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gaskit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Lucas
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-04-09 00:00:00.000000000 Z
11
+ date: 2025-04-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -83,6 +83,7 @@ files:
83
83
  - LICENSE.txt
84
84
  - README.md
85
85
  - gasket-0.1.0.gem
86
+ - gaskit-0.1.0.gem
86
87
  - gaskit.gemspec
87
88
  - lib/gaskit.rb
88
89
  - lib/gaskit/boot/query.rb
@@ -94,6 +95,8 @@ files:
94
95
  - lib/gaskit/flow.rb
95
96
  - lib/gaskit/flow_result.rb
96
97
  - lib/gaskit/helpers.rb
98
+ - lib/gaskit/hook_registry.rb
99
+ - lib/gaskit/hookable.rb
97
100
  - lib/gaskit/logger.rb
98
101
  - lib/gaskit/operation.rb
99
102
  - lib/gaskit/operation_exit.rb