shikibu 0.2.0 → 0.3.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/README.md +39 -0
- data/lib/shikibu/app.rb +12 -0
- data/lib/shikibu/replay.rb +20 -7
- data/lib/shikibu/typed_payload.rb +189 -0
- data/lib/shikibu/version.rb +1 -1
- data/lib/shikibu/workflow.rb +97 -2
- data/lib/shikibu.rb +1 -0
- metadata +17 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3bf06e3a63cc523afe19105e1ed5debd74a949dc3faec06a35ee9d3853d9d4ae
|
|
4
|
+
data.tar.gz: 9877fbfe7dec08f504d067999bd6e83ffd483c050192e7057f29b6d50e2317c1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a9adb5bfbda37f44168434adccde9e22cc65a1c101f1cf2bf2350a003ebd848f2778493b40ade87a4c7e17efa293f949c2c2ac18ce9645bd53fa9c3a42e12a56
|
|
7
|
+
data.tar.gz: 16102137dc003ab1d8b787edd5d78770e84228529f55318d3d0a0b3a7a7cf1e7050acc3826431f48cc96483b30e300df34877b8cbb6891a869b367f4f2a2d88f
|
data/README.md
CHANGED
|
@@ -198,6 +198,45 @@ end
|
|
|
198
198
|
|
|
199
199
|
**Activity IDs**: Activities are automatically identified with IDs like `"create_user:1"` for deterministic replay.
|
|
200
200
|
|
|
201
|
+
### Typed Workflows (dry-struct)
|
|
202
|
+
|
|
203
|
+
Shikibu supports typed input/output with [dry-struct](https://dry-rb.org/gems/dry-struct/) for validation and type coercion:
|
|
204
|
+
|
|
205
|
+
```ruby
|
|
206
|
+
require 'dry-struct'
|
|
207
|
+
|
|
208
|
+
module Types
|
|
209
|
+
include Dry.Types()
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
class OrderInput < Dry::Struct
|
|
213
|
+
attribute :order_id, Types::String
|
|
214
|
+
attribute :amount, Types::Coercible::Decimal
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
class OrderResult < Dry::Struct
|
|
218
|
+
attribute :order_id, Types::String
|
|
219
|
+
attribute :status, Types::String
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
class TypedOrderSaga < Shikibu::Workflow
|
|
223
|
+
workflow_name 'typed_order'
|
|
224
|
+
input OrderInput
|
|
225
|
+
output OrderResult
|
|
226
|
+
|
|
227
|
+
def execute(order)
|
|
228
|
+
# order is an OrderInput instance with validated/coerced types
|
|
229
|
+
OrderResult.new(order_id: order.order_id, status: 'completed')
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Run with automatic type coercion
|
|
234
|
+
instance_id = app.start_workflow(TypedOrderSaga, order_id: 'ORD-123', amount: '99.99')
|
|
235
|
+
result = app.get_typed_result(instance_id, TypedOrderSaga) # Returns OrderResult
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Note**: `dry-struct` is an optional dependency. Add it to your Gemfile if you want typed workflows.
|
|
239
|
+
|
|
201
240
|
### Durable Execution
|
|
202
241
|
|
|
203
242
|
Shikibu ensures workflow progress is never lost through **deterministic replay**:
|
data/lib/shikibu/app.rb
CHANGED
|
@@ -162,6 +162,18 @@ module Shikibu
|
|
|
162
162
|
}
|
|
163
163
|
end
|
|
164
164
|
|
|
165
|
+
# Get workflow result with typed output deserialization
|
|
166
|
+
# @param instance_id [String] Instance ID
|
|
167
|
+
# @param workflow_class [Class] Workflow class with output type definition
|
|
168
|
+
# @return [Object, nil] Typed output or nil if no output
|
|
169
|
+
# @raise [WorkflowNotFoundError] If instance not found
|
|
170
|
+
def get_typed_result(instance_id, workflow_class)
|
|
171
|
+
result = get_result(instance_id)
|
|
172
|
+
return nil unless result[:output]
|
|
173
|
+
|
|
174
|
+
workflow_class.deserialize_output(result[:output])
|
|
175
|
+
end
|
|
176
|
+
|
|
165
177
|
# Get workflow status
|
|
166
178
|
# @param instance_id [String] Instance ID
|
|
167
179
|
# @return [String] Status
|
data/lib/shikibu/replay.rb
CHANGED
|
@@ -14,9 +14,12 @@ module Shikibu
|
|
|
14
14
|
# Start a new workflow instance
|
|
15
15
|
# @param workflow_class [Class] Workflow class
|
|
16
16
|
# @param instance_id [String] Instance ID
|
|
17
|
-
# @param input [Hash] Input parameters
|
|
17
|
+
# @param input [Hash, Object] Input parameters (Hash for untyped, typed object for typed workflows)
|
|
18
18
|
# @return [Object, nil] Workflow result or nil if suspended
|
|
19
19
|
def start_workflow(workflow_class, instance_id:, **input)
|
|
20
|
+
# Serialize input for storage (handles typed inputs)
|
|
21
|
+
serialized_input = workflow_class.serialize_input(input)
|
|
22
|
+
|
|
20
23
|
# Save workflow definition
|
|
21
24
|
storage.save_workflow_definition(
|
|
22
25
|
workflow_name: workflow_class.workflow_name,
|
|
@@ -30,12 +33,12 @@ module Shikibu
|
|
|
30
33
|
workflow_name: workflow_class.workflow_name,
|
|
31
34
|
source_hash: workflow_class.source_hash,
|
|
32
35
|
owner_service: 'default',
|
|
33
|
-
input_data:
|
|
36
|
+
input_data: serialized_input,
|
|
34
37
|
status: Status::RUNNING
|
|
35
38
|
)
|
|
36
39
|
|
|
37
40
|
# Execute the workflow
|
|
38
|
-
execute_workflow(instance_id, workflow_class,
|
|
41
|
+
execute_workflow(instance_id, workflow_class, serialized_input, replaying: false)
|
|
39
42
|
end
|
|
40
43
|
|
|
41
44
|
# Resume a workflow from its current state
|
|
@@ -197,12 +200,22 @@ module Shikibu
|
|
|
197
200
|
workflow.instance_variable_set(:@pending_compensations, [])
|
|
198
201
|
workflow.context = ctx
|
|
199
202
|
|
|
200
|
-
#
|
|
201
|
-
|
|
202
|
-
|
|
203
|
+
# Handle typed vs untyped input
|
|
204
|
+
result = if workflow_class.typed_input?
|
|
205
|
+
# Typed workflow: deserialize and pass single object
|
|
206
|
+
typed_input = workflow_class.deserialize_input(input)
|
|
207
|
+
workflow.execute(typed_input)
|
|
208
|
+
else
|
|
209
|
+
# Legacy: symbolize and spread as keyword args
|
|
210
|
+
symbolized_input = symbolize_keys(input)
|
|
211
|
+
workflow.execute(**symbolized_input)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Serialize output
|
|
215
|
+
serialized_output = workflow_class.serialize_output(result)
|
|
203
216
|
|
|
204
217
|
# Mark completed
|
|
205
|
-
storage.update_instance_status(instance_id, Status::COMPLETED, output_data:
|
|
218
|
+
storage.update_instance_status(instance_id, Status::COMPLETED, output_data: serialized_output)
|
|
206
219
|
storage.clear_compensations(instance_id)
|
|
207
220
|
|
|
208
221
|
# Cleanup direct subscriptions
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shikibu
|
|
4
|
+
# Module to detect and handle typed payload classes
|
|
5
|
+
# Supports dry-struct, Ruby 3.2+ Data classes, and duck-typed objects
|
|
6
|
+
#
|
|
7
|
+
# @example with dry-struct
|
|
8
|
+
# require 'dry-struct'
|
|
9
|
+
# class OrderInput < Dry::Struct
|
|
10
|
+
# attribute :order_id, Types::String
|
|
11
|
+
# attribute :amount, Types::Coercible::Decimal
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# TypedPayload.typed_class?(OrderInput) # => true
|
|
15
|
+
# TypedPayload.to_h(OrderInput.new(order_id: '123', amount: 99.99))
|
|
16
|
+
# # => { order_id: '123', amount: 99.99 }
|
|
17
|
+
#
|
|
18
|
+
# @example with Data class
|
|
19
|
+
# OrderInput = Data.define(:order_id, :amount)
|
|
20
|
+
# TypedPayload.typed_class?(OrderInput) # => true
|
|
21
|
+
#
|
|
22
|
+
module TypedPayload
|
|
23
|
+
class << self
|
|
24
|
+
# Check if dry-struct is available
|
|
25
|
+
# @return [Boolean]
|
|
26
|
+
def dry_struct_available?
|
|
27
|
+
return @dry_struct_available if defined?(@dry_struct_available)
|
|
28
|
+
|
|
29
|
+
@dry_struct_available = begin
|
|
30
|
+
require 'dry-struct'
|
|
31
|
+
true
|
|
32
|
+
rescue LoadError
|
|
33
|
+
false
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Check if an object is a typed payload class (not instance)
|
|
38
|
+
# @param obj [Object] Object to check
|
|
39
|
+
# @return [Boolean]
|
|
40
|
+
def typed_class?(obj)
|
|
41
|
+
return false unless obj.is_a?(Class)
|
|
42
|
+
|
|
43
|
+
# Check for dry-struct
|
|
44
|
+
return true if dry_struct_class?(obj)
|
|
45
|
+
|
|
46
|
+
# Check for Ruby 3.2+ Data class
|
|
47
|
+
return true if data_class?(obj)
|
|
48
|
+
|
|
49
|
+
# Check for duck-typed class with required methods
|
|
50
|
+
duck_typed_class?(obj)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Check if an object is a typed payload instance
|
|
54
|
+
# @param obj [Object] Object to check
|
|
55
|
+
# @return [Boolean]
|
|
56
|
+
def typed_instance?(obj)
|
|
57
|
+
return true if dry_struct_instance?(obj)
|
|
58
|
+
return true if data_instance?(obj)
|
|
59
|
+
|
|
60
|
+
duck_typed_instance?(obj)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Serialize a typed instance to a hash
|
|
64
|
+
# @param obj [Object] Typed instance or any object
|
|
65
|
+
# @return [Hash, Object] Serialized hash or original object
|
|
66
|
+
def to_h(obj)
|
|
67
|
+
deep_serialize(obj)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Deserialize a hash to a typed instance
|
|
71
|
+
# @param hash [Hash] Hash to deserialize
|
|
72
|
+
# @param type_class [Class] Target type class
|
|
73
|
+
# @return [Object] Typed instance or original hash
|
|
74
|
+
def from_h(hash, type_class)
|
|
75
|
+
return hash unless typed_class?(type_class)
|
|
76
|
+
return hash unless hash.is_a?(Hash)
|
|
77
|
+
|
|
78
|
+
# Symbolize keys for compatibility
|
|
79
|
+
symbolized = deep_symbolize_keys(hash)
|
|
80
|
+
|
|
81
|
+
# All typed classes support .new(**hash) interface
|
|
82
|
+
type_class.new(**symbolized)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Deserialize an array of typed objects
|
|
86
|
+
# @param array [Array<Hash>] Array of hashes
|
|
87
|
+
# @param element_type [Class] Element type class
|
|
88
|
+
# @return [Array] Array of typed instances
|
|
89
|
+
def from_array(array, element_type)
|
|
90
|
+
return array unless array.is_a?(Array)
|
|
91
|
+
return array unless typed_class?(element_type)
|
|
92
|
+
|
|
93
|
+
array.map do |item|
|
|
94
|
+
item.is_a?(Hash) ? from_h(item, element_type) : item
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
def dry_struct_class?(obj)
|
|
101
|
+
return false unless dry_struct_available?
|
|
102
|
+
|
|
103
|
+
obj.is_a?(Class) && obj < Dry::Struct
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def dry_struct_instance?(obj)
|
|
107
|
+
return false unless dry_struct_available?
|
|
108
|
+
|
|
109
|
+
obj.is_a?(Dry::Struct)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def data_class?(obj)
|
|
113
|
+
return false unless defined?(Data)
|
|
114
|
+
|
|
115
|
+
obj.is_a?(Class) && obj < Data
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def data_instance?(obj)
|
|
119
|
+
return false unless defined?(Data)
|
|
120
|
+
|
|
121
|
+
obj.is_a?(Data)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def duck_typed_class?(obj)
|
|
125
|
+
# A class is considered typed if it has new and instances have to_h
|
|
126
|
+
# but exclude Hash itself
|
|
127
|
+
return false if obj == Hash
|
|
128
|
+
|
|
129
|
+
obj.respond_to?(:new) &&
|
|
130
|
+
obj.method_defined?(:to_h)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def duck_typed_instance?(obj)
|
|
134
|
+
# An instance is typed if it has to_h but is not a Hash/Array
|
|
135
|
+
return false if obj.is_a?(Hash)
|
|
136
|
+
return false if obj.is_a?(Array)
|
|
137
|
+
return false unless obj.respond_to?(:to_h)
|
|
138
|
+
|
|
139
|
+
# Additional check: should have proper to_h implementation
|
|
140
|
+
# (not just Object#to_h which doesn't exist or returns weird things)
|
|
141
|
+
begin
|
|
142
|
+
result = obj.to_h
|
|
143
|
+
result.is_a?(Hash)
|
|
144
|
+
rescue StandardError
|
|
145
|
+
false
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Recursively serialize typed objects to hashes
|
|
150
|
+
# Note: Symbols are preserved here. They are converted to strings
|
|
151
|
+
# only during JSON serialization in the storage layer.
|
|
152
|
+
def deep_serialize(obj)
|
|
153
|
+
case obj
|
|
154
|
+
when Hash
|
|
155
|
+
obj.transform_values { |v| deep_serialize(v) }
|
|
156
|
+
when Array
|
|
157
|
+
obj.map { |v| deep_serialize(v) }
|
|
158
|
+
when ->(o) { dry_struct_instance?(o) || data_instance?(o) }
|
|
159
|
+
deep_serialize(obj.to_h)
|
|
160
|
+
when Time, DateTime
|
|
161
|
+
obj.iso8601
|
|
162
|
+
when Date
|
|
163
|
+
obj.to_s
|
|
164
|
+
when BigDecimal
|
|
165
|
+
obj.to_s('F')
|
|
166
|
+
else
|
|
167
|
+
# Symbols, Integers, Strings, etc. are preserved as-is
|
|
168
|
+
obj
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Recursively symbolize hash keys
|
|
173
|
+
def deep_symbolize_keys(hash)
|
|
174
|
+
return hash unless hash.is_a?(Hash)
|
|
175
|
+
|
|
176
|
+
symbolized = hash.transform_keys do |key|
|
|
177
|
+
key.is_a?(String) ? key.to_sym : key
|
|
178
|
+
end
|
|
179
|
+
symbolized.transform_values do |value|
|
|
180
|
+
case value
|
|
181
|
+
when Hash then deep_symbolize_keys(value)
|
|
182
|
+
when Array then value.map { |v| v.is_a?(Hash) ? deep_symbolize_keys(v) : v }
|
|
183
|
+
else value
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
data/lib/shikibu/version.rb
CHANGED
data/lib/shikibu/workflow.rb
CHANGED
|
@@ -59,6 +59,85 @@ module Shikibu
|
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
+
# Set the input type for this workflow
|
|
63
|
+
# @param type_class [Class, nil] A Dry::Struct, Data, or duck-typed class
|
|
64
|
+
# @example
|
|
65
|
+
# class OrderSaga < Shikibu::Workflow
|
|
66
|
+
# input OrderInput
|
|
67
|
+
# output OrderResult
|
|
68
|
+
# end
|
|
69
|
+
def input(type_class = nil)
|
|
70
|
+
if type_class
|
|
71
|
+
unless TypedPayload.typed_class?(type_class)
|
|
72
|
+
raise ArgumentError, "#{type_class} must be a Dry::Struct, Data, or respond to .new and #to_h"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
@input_type = type_class
|
|
76
|
+
else
|
|
77
|
+
@input_type
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Set the output type for this workflow (optional)
|
|
82
|
+
# @param type_class [Class, nil] A Dry::Struct, Data, or duck-typed class
|
|
83
|
+
def output(type_class = nil)
|
|
84
|
+
if type_class
|
|
85
|
+
unless TypedPayload.typed_class?(type_class)
|
|
86
|
+
raise ArgumentError, "#{type_class} must be a Dry::Struct, Data, or respond to .new and #to_h"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
@output_type = type_class
|
|
90
|
+
else
|
|
91
|
+
@output_type
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Check if this workflow has typed input
|
|
96
|
+
# @return [Boolean]
|
|
97
|
+
def typed_input?
|
|
98
|
+
!@input_type.nil?
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Check if this workflow has typed output
|
|
102
|
+
# @return [Boolean]
|
|
103
|
+
def typed_output?
|
|
104
|
+
!@output_type.nil?
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Serialize input for storage
|
|
108
|
+
# @param input_value [Object] Input to serialize
|
|
109
|
+
# @return [Hash] Serialized hash
|
|
110
|
+
def serialize_input(input_value)
|
|
111
|
+
TypedPayload.to_h(input_value)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Deserialize input from storage
|
|
115
|
+
# @param data [Hash] Stored data
|
|
116
|
+
# @return [Object] Typed input or hash
|
|
117
|
+
def deserialize_input(data)
|
|
118
|
+
return data unless typed_input?
|
|
119
|
+
return data unless data.is_a?(Hash)
|
|
120
|
+
|
|
121
|
+
TypedPayload.from_h(data, @input_type)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Serialize output for storage
|
|
125
|
+
# @param output_value [Object] Output to serialize
|
|
126
|
+
# @return [Hash, Object] Serialized output
|
|
127
|
+
def serialize_output(output_value)
|
|
128
|
+
TypedPayload.to_h(output_value)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Deserialize output from storage
|
|
132
|
+
# @param data [Object] Stored data
|
|
133
|
+
# @return [Object] Typed output or original data
|
|
134
|
+
def deserialize_output(data)
|
|
135
|
+
return data unless typed_output?
|
|
136
|
+
return data unless data.is_a?(Hash)
|
|
137
|
+
|
|
138
|
+
TypedPayload.from_h(data, @output_type)
|
|
139
|
+
end
|
|
140
|
+
|
|
62
141
|
# Get the source hash for this workflow
|
|
63
142
|
def source_hash
|
|
64
143
|
@source_hash ||= begin
|
|
@@ -130,9 +209,15 @@ module Shikibu
|
|
|
130
209
|
# Execute an activity with automatic retry and history tracking
|
|
131
210
|
# @param name [Symbol, String] Activity name
|
|
132
211
|
# @param retry_policy [RetryPolicy] Retry policy
|
|
212
|
+
# @param returns [Class, nil] Return type class for type restoration during replay
|
|
133
213
|
# @param block [Proc] Activity logic
|
|
134
214
|
# @return [Object] Activity result
|
|
135
|
-
|
|
215
|
+
# @raise [ArgumentError] If returns is not a valid typed class
|
|
216
|
+
def activity(name, retry_policy: nil, returns: nil, &)
|
|
217
|
+
if returns && !TypedPayload.typed_class?(returns)
|
|
218
|
+
raise ArgumentError, "returns: must be a typed class (Dry::Struct, Data, or duck-typed), got #{returns.inspect}"
|
|
219
|
+
end
|
|
220
|
+
|
|
136
221
|
activity_id = ctx.generate_activity_id(name.to_s)
|
|
137
222
|
ctx.current_activity_id = activity_id
|
|
138
223
|
|
|
@@ -141,7 +226,17 @@ module Shikibu
|
|
|
141
226
|
cached = ctx.get_cached_result(activity_id)
|
|
142
227
|
handle_cached_result(activity_id, cached)
|
|
143
228
|
ctx.record_last_activity_id(activity_id)
|
|
144
|
-
|
|
229
|
+
|
|
230
|
+
if cached[:event_type] == EventType::ACTIVITY_COMPLETED
|
|
231
|
+
result = cached[:result]
|
|
232
|
+
|
|
233
|
+
# Restore type if specified
|
|
234
|
+
if returns && TypedPayload.typed_class?(returns) && result.is_a?(Hash)
|
|
235
|
+
result = TypedPayload.from_h(result, returns)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
return result
|
|
239
|
+
end
|
|
145
240
|
|
|
146
241
|
# Re-raise cached error
|
|
147
242
|
raise reconstruct_error(cached)
|
data/lib/shikibu.rb
CHANGED
|
@@ -132,6 +132,7 @@ require_relative 'shikibu/notify/wake_event'
|
|
|
132
132
|
require_relative 'shikibu/locking'
|
|
133
133
|
require_relative 'shikibu/channels'
|
|
134
134
|
require_relative 'shikibu/context'
|
|
135
|
+
require_relative 'shikibu/typed_payload'
|
|
135
136
|
require_relative 'shikibu/workflow'
|
|
136
137
|
require_relative 'shikibu/activity'
|
|
137
138
|
require_relative 'shikibu/storage/migrations'
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: shikibu
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yasushi Itoh
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-12-
|
|
11
|
+
date: 2025-12-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: concurrent-ruby
|
|
@@ -80,6 +80,20 @@ dependencies:
|
|
|
80
80
|
- - "~>"
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: '2.0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: dry-struct
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '1.6'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '1.6'
|
|
83
97
|
- !ruby/object:Gem::Dependency
|
|
84
98
|
name: mysql2
|
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -192,6 +206,7 @@ files:
|
|
|
192
206
|
- lib/shikibu/retry_policy.rb
|
|
193
207
|
- lib/shikibu/storage/migrations.rb
|
|
194
208
|
- lib/shikibu/storage/sequel_storage.rb
|
|
209
|
+
- lib/shikibu/typed_payload.rb
|
|
195
210
|
- lib/shikibu/version.rb
|
|
196
211
|
- lib/shikibu/worker.rb
|
|
197
212
|
- lib/shikibu/workflow.rb
|