chrono_forge 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +254 -0
- data/export.json +59 -47
- data/export.rb +1 -1
- data/lib/chrono_forge/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 88cee304794f2d186c2103563b8f98c08a00cc1d9a351ae206c35e34bacdcf61
|
4
|
+
data.tar.gz: dee282b43b65a4846797629903141329a3f094fd009416848cb0f5b33ed3f68e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba3c5937b30c3ea0a46135a30f2cc46d59e430bb75f41bda1fc50d352a4bbe615532b1de0526eed819c824d5851cac9812b2f7de1ffb930a72a380045312692d
|
7
|
+
data.tar.gz: 3db298d79b40715bc6cbcb54031b5ff37dd549c8361e70fb1584431d62010d14cffe7ca5b9e7e36e79a2513f0524882eae6ff9295fae905b342506783160a0c0
|
data/README.md
CHANGED
@@ -0,0 +1,254 @@
|
|
1
|
+
# ChronoForge
|
2
|
+
|
3
|
+
ChronoForge is a Ruby gem that provides a robust framework for building durable, distributed workflows in Ruby on Rails applications. It offers a reliable way to handle long-running processes, state management, and error recovery.
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
- **Durable Execution**: Automatically tracks and recovers from failures during workflow execution
|
8
|
+
- **State Management**: Built-in workflow state tracking with support for custom contexts
|
9
|
+
- **Concurrency Control**: Advanced locking mechanisms to prevent concurrent execution of the same workflow
|
10
|
+
- **Error Handling**: Comprehensive error tracking and retry strategies
|
11
|
+
- **Execution Logging**: Detailed logging of workflow execution steps and errors
|
12
|
+
- **Wait States**: Support for time-based waits and condition-based waiting
|
13
|
+
- **Database-Backed**: All workflow state is persisted to the database for durability
|
14
|
+
- **ActiveJob Integration**: Seamlessly works with any ActiveJob backend
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
Add this line to your application's Gemfile:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
gem 'chrono_forge'
|
22
|
+
```
|
23
|
+
|
24
|
+
Then execute:
|
25
|
+
|
26
|
+
```bash
|
27
|
+
$ bundle install
|
28
|
+
```
|
29
|
+
|
30
|
+
Or install it directly:
|
31
|
+
|
32
|
+
```bash
|
33
|
+
$ gem install chrono_forge
|
34
|
+
```
|
35
|
+
|
36
|
+
After installation, run the generator to create the necessary database migrations:
|
37
|
+
|
38
|
+
```bash
|
39
|
+
$ rails generate chrono_forge:install
|
40
|
+
$ rails db:migrate
|
41
|
+
```
|
42
|
+
|
43
|
+
## Usage
|
44
|
+
|
45
|
+
### Basic Workflow Example
|
46
|
+
|
47
|
+
Here's a complete example of a durable order processing workflow:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
class OrderProcessingWorkflow < ApplicationJob
|
51
|
+
include ChronoForge::Executor
|
52
|
+
|
53
|
+
def perform
|
54
|
+
# Context can be used to pass and store data between executions
|
55
|
+
context["order_id"] = SecureRandom.hex
|
56
|
+
|
57
|
+
# Wait until payment is confirmed
|
58
|
+
wait_until :payment_confirmed?
|
59
|
+
|
60
|
+
# Wait for potential fraud check
|
61
|
+
wait 1.minute, :fraud_check_delay
|
62
|
+
|
63
|
+
# Durably execute order processing
|
64
|
+
durably_execute :process_order
|
65
|
+
|
66
|
+
# Final steps
|
67
|
+
complete_order
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def payment_confirmed?
|
73
|
+
PaymentService.confirmed?(context["order_id"])
|
74
|
+
end
|
75
|
+
|
76
|
+
def process_order
|
77
|
+
context["processed_at"] = Time.current.iso8601
|
78
|
+
OrderProcessor.process(context["order_id"])
|
79
|
+
end
|
80
|
+
|
81
|
+
def complete_order
|
82
|
+
context["completed_at"] = Time.current.iso8601
|
83
|
+
OrderCompletionService.complete(context["order_id"])
|
84
|
+
end
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
### Workflow Features
|
89
|
+
|
90
|
+
#### Durable Execution
|
91
|
+
|
92
|
+
The `durably_execute` method ensures operations are executed exactly once:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
# Execute a method
|
96
|
+
durably_execute(:process_payment, max_attempts: 3)
|
97
|
+
|
98
|
+
# Or with a block
|
99
|
+
durably_execute -> (ctx) {
|
100
|
+
Payment.process(ctx[:payment_id])
|
101
|
+
}
|
102
|
+
```
|
103
|
+
|
104
|
+
#### Wait States
|
105
|
+
|
106
|
+
ChronoForge supports both time-based and condition-based waits:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
# Wait for a specific duration
|
110
|
+
wait 1.hour, :cooling_period
|
111
|
+
|
112
|
+
# Wait until a condition is met
|
113
|
+
wait_until :payment_processed,
|
114
|
+
timeout: 1.hour,
|
115
|
+
check_interval: 5.minutes
|
116
|
+
```
|
117
|
+
|
118
|
+
#### Workflow Context
|
119
|
+
|
120
|
+
ChronoForge provides a persistent context that survives job restarts:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
# Set context values
|
124
|
+
context[:user_name] = "John Doe"
|
125
|
+
context[:status] = "processing"
|
126
|
+
|
127
|
+
# Read context values
|
128
|
+
user_name = context[:user_name]
|
129
|
+
```
|
130
|
+
|
131
|
+
### Error Handling
|
132
|
+
|
133
|
+
ChronoForge automatically tracks errors and provides retry capabilities:
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
class MyWorkflow < ApplicationJob
|
137
|
+
include ChronoForge::Executor
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
def should_retry?(error, attempt_count)
|
142
|
+
case error
|
143
|
+
when NetworkError
|
144
|
+
attempt_count < 5
|
145
|
+
when ValidationError
|
146
|
+
false # Don't retry validation errors
|
147
|
+
else
|
148
|
+
attempt_count < 3
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
154
|
+
### Cleanup
|
155
|
+
|
156
|
+
ChronoForge includes built-in cleanup methods for managing old workflow data:
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
# Clean up old workflows and logs
|
160
|
+
ChronoForge::Workflow.cleanup_old_logs(retention_period: 30.days)
|
161
|
+
ChronoForge::ExecutionLog.cleanup_old_logs(retention_period: 30.days)
|
162
|
+
ChronoForge::ErrorLog.cleanup_old_logs(retention_period: 30.days)
|
163
|
+
```
|
164
|
+
|
165
|
+
## Testing
|
166
|
+
|
167
|
+
ChronoForge is designed to be easily testable using [ChaoticJob](https://github.com/fractaledmind/chaotic_job), a testing framework that makes it simple to test complex job workflows. Here's how to set up your test environment:
|
168
|
+
|
169
|
+
1. Add ChaoticJob to your Gemfile's test group:
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
group :test do
|
173
|
+
gem 'chaotic_job'
|
174
|
+
end
|
175
|
+
```
|
176
|
+
|
177
|
+
2. Set up your test helper:
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
# test_helper.rb
|
181
|
+
require 'chrono_forge'
|
182
|
+
require 'minitest/autorun'
|
183
|
+
require 'chaotic_job'
|
184
|
+
```
|
185
|
+
|
186
|
+
Example test:
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
class WorkflowTest < Minitest::Test
|
190
|
+
include ChaoticJob::Helpers
|
191
|
+
|
192
|
+
def test_workflow_completion
|
193
|
+
# Enqueue the job
|
194
|
+
OrderProcessingWorkflow.perform_later("order_123")
|
195
|
+
|
196
|
+
# Perform all enqueued jobs
|
197
|
+
perform_all_jobs
|
198
|
+
|
199
|
+
# Assert workflow completed successfully
|
200
|
+
workflow = ChronoForge::Workflow.last
|
201
|
+
assert workflow.completed?
|
202
|
+
|
203
|
+
# Check workflow context
|
204
|
+
assert workflow.context["processed_at"].present?
|
205
|
+
assert workflow.context["completed_at"].present?
|
206
|
+
end
|
207
|
+
end
|
208
|
+
```
|
209
|
+
|
210
|
+
ChaoticJob provides several helpful methods for testing workflows:
|
211
|
+
|
212
|
+
- `perform_all_jobs`: Processes all enqueued jobs, including those enqueued during job execution
|
213
|
+
- `enqueued_jobs`: Returns the current number of jobs in the queue
|
214
|
+
|
215
|
+
For testing with specific job processing libraries like Sidekiq or Delayed Job, you can still use their respective testing modes, but ChaoticJob is recommended for testing ChronoForge workflows as it better handles the complexities of nested job scheduling and wait states.
|
216
|
+
|
217
|
+
|
218
|
+
## Database Schema
|
219
|
+
|
220
|
+
ChronoForge creates three main tables:
|
221
|
+
|
222
|
+
1. `chrono_forge_workflows`: Stores workflow state and context
|
223
|
+
2. `chrono_forge_execution_logs`: Tracks individual execution steps
|
224
|
+
3. `chrono_forge_error_logs`: Records detailed error information
|
225
|
+
|
226
|
+
## Development
|
227
|
+
|
228
|
+
After checking out the repo, run:
|
229
|
+
|
230
|
+
```bash
|
231
|
+
$ bin/setup # Install dependencies
|
232
|
+
$ bundle exec rake test # Run the tests
|
233
|
+
$ bin/appraise # Run the full suite of appraisals
|
234
|
+
$ bin/console # Start an interactive console
|
235
|
+
```
|
236
|
+
|
237
|
+
The test suite uses SQLite by default and includes:
|
238
|
+
- Unit tests for core functionality
|
239
|
+
- Integration tests with ActiveJob
|
240
|
+
- Example workflow implementations
|
241
|
+
|
242
|
+
## Contributing
|
243
|
+
|
244
|
+
1. Fork the repository
|
245
|
+
2. Create your feature branch (`git checkout -b feature/my-new-feature`)
|
246
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
247
|
+
4. Push to the branch (`git push origin feature/my-new-feature`)
|
248
|
+
5. Create a new Pull Request
|
249
|
+
|
250
|
+
Please include tests for any new features or bug fixes.
|
251
|
+
|
252
|
+
## License
|
253
|
+
|
254
|
+
This gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/export.json
CHANGED
@@ -1,102 +1,114 @@
|
|
1
1
|
[
|
2
2
|
{
|
3
3
|
"path": "/Users/stefan/Documents/plutonium/chrono_forge/CHANGELOG.md",
|
4
|
-
"contents": "## [Unreleased]\n\n## [0.
|
4
|
+
"contents": "## [Unreleased]\n\n## [0.1.0] - 2024-12-21\n\n- Initial release\n"
|
5
5
|
},
|
6
6
|
{
|
7
7
|
"path": "/Users/stefan/Documents/plutonium/chrono_forge/README.md",
|
8
|
-
"contents": "#
|
8
|
+
"contents": "# ChronoForge\n\nChronoForge is a Ruby gem that provides a robust framework for building durable, distributed workflows in Ruby on Rails applications. It offers a reliable way to handle long-running processes, state management, and error recovery.\n\n## Features\n\n- **Durable Execution**: Automatically tracks and recovers from failures during workflow execution\n- **State Management**: Built-in workflow state tracking with support for custom contexts\n- **Concurrency Control**: Advanced locking mechanisms to prevent concurrent execution of the same workflow\n- **Error Handling**: Comprehensive error tracking and retry strategies\n- **Execution Logging**: Detailed logging of workflow execution steps and errors\n- **Wait States**: Support for time-based waits and condition-based waiting\n- **Database-Backed**: All workflow state is persisted to the database for durability\n- **ActiveJob Integration**: Seamlessly works with any ActiveJob backend\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'chrono_forge'\n```\n\nThen execute:\n\n```bash\n$ bundle install\n```\n\nOr install it directly:\n\n```bash\n$ gem install chrono_forge\n```\n\nAfter installation, run the generator to create the necessary database migrations:\n\n```bash\n$ rails generate chrono_forge:install\n$ rails db:migrate\n```\n\n## Usage\n\n### Basic Workflow Example\n\nHere's a complete example of a durable order processing workflow:\n\n```ruby\nclass OrderProcessingWorkflow < ApplicationJob\n include ChronoForge::Executor\n\n def perform\n # Context can be used to pass and store data between executions\n context[\"order_id\"] = SecureRandom.hex\n\n # Wait until payment is confirmed\n wait_until :payment_confirmed?\n\n # Wait for potential fraud check\n wait 1.minute, :fraud_check_delay\n\n # Durably execute order processing\n durably_execute :process_order\n\n # Final steps\n complete_order\n end\n\n private\n\n def payment_confirmed?\n PaymentService.confirmed?(context[\"order_id\"])\n end\n\n def process_order\n context[\"processed_at\"] = Time.current.iso8601\n OrderProcessor.process(context[\"order_id\"])\n end\n\n def complete_order\n context[\"completed_at\"] = Time.current.iso8601\n OrderCompletionService.complete(context[\"order_id\"])\n end\nend\n```\n\n### Workflow Features\n\n#### Durable Execution\n\nThe `durably_execute` method ensures operations are executed exactly once:\n\n```ruby\n# Execute a method\ndurably_execute(:process_payment, max_attempts: 3)\n\n# Or with a block\ndurably_execute -> (ctx) {\n Payment.process(ctx[:payment_id])\n}\n```\n\n#### Wait States\n\nChronoForge supports both time-based and condition-based waits:\n\n```ruby\n# Wait for a specific duration\nwait 1.hour, :cooling_period\n\n# Wait until a condition is met\nwait_until :payment_processed, \n timeout: 1.hour,\n check_interval: 5.minutes\n```\n\n#### Workflow Context\n\nChronoForge provides a persistent context that survives job restarts:\n\n```ruby\n# Set context values\ncontext[:user_name] = \"John Doe\"\ncontext[:status] = \"processing\"\n\n# Read context values\nuser_name = context[:user_name]\n```\n\n### Error Handling\n\nChronoForge automatically tracks errors and provides retry capabilities:\n\n```ruby\nclass MyWorkflow < ApplicationJob\n include ChronoForge::Executor\n\n private\n\n def should_retry?(error, attempt_count)\n case error\n when NetworkError\n attempt_count < 5\n when ValidationError\n false # Don't retry validation errors\n else\n attempt_count < 3\n end\n end\nend\n```\n\n### Cleanup\n\nChronoForge includes built-in cleanup methods for managing old workflow data:\n\n```ruby\n# Clean up old workflows and logs\nChronoForge::Workflow.cleanup_old_logs(retention_period: 30.days)\nChronoForge::ExecutionLog.cleanup_old_logs(retention_period: 30.days)\nChronoForge::ErrorLog.cleanup_old_logs(retention_period: 30.days)\n```\n\n## Testing\n\nChronoForge is designed to be easily testable using [ChaoticJob](https://github.com/fractaledmind/chaotic_job), a testing framework that makes it simple to test complex job workflows. Here's how to set up your test environment:\n\n1. Add ChaoticJob to your Gemfile's test group:\n\n```ruby\ngroup :test do\n gem 'chaotic_job'\nend\n```\n\n2. Set up your test helper:\n\n```ruby\n# test_helper.rb\nrequire 'chrono_forge'\nrequire 'minitest/autorun'\nrequire 'chaotic_job'\n```\n\nExample test:\n\n```ruby\nclass WorkflowTest < Minitest::Test\n include ChaoticJob::Helpers\n\n def test_workflow_completion\n # Enqueue the job\n OrderProcessingWorkflow.perform_later(\"order_123\")\n \n # Perform all enqueued jobs\n perform_all_jobs\n \n # Assert workflow completed successfully\n workflow = ChronoForge::Workflow.last\n assert workflow.completed?\n \n # Check workflow context\n assert workflow.context[\"processed_at\"].present?\n assert workflow.context[\"completed_at\"].present?\n end\nend\n```\n\nChaoticJob provides several helpful methods for testing workflows:\n\n- `perform_all_jobs`: Processes all enqueued jobs, including those enqueued during job execution\n- `enqueued_jobs`: Returns the current number of jobs in the queue\n\nFor testing with specific job processing libraries like Sidekiq or Delayed Job, you can still use their respective testing modes, but ChaoticJob is recommended for testing ChronoForge workflows as it better handles the complexities of nested job scheduling and wait states.\n\n\n## Database Schema\n\nChronoForge creates three main tables:\n\n1. `chrono_forge_workflows`: Stores workflow state and context\n2. `chrono_forge_execution_logs`: Tracks individual execution steps\n3. `chrono_forge_error_logs`: Records detailed error information\n\n## Development\n\nAfter checking out the repo, run:\n\n```bash\n$ bin/setup # Install dependencies\n$ bundle exec rake test # Run the tests\n$ bin/appraise # Run the full suite of appraisals\n$ bin/console # Start an interactive console\n```\n\nThe test suite uses SQLite by default and includes:\n- Unit tests for core functionality\n- Integration tests with ActiveJob\n- Example workflow implementations\n\n## Contributing\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin feature/my-new-feature`)\n5. Create a new Pull Request\n\nPlease include tests for any new features or bug fixes.\n\n## License\n\nThis gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n"
|
9
|
+
},
|
10
|
+
{
|
11
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/chrono_forge.gemspec",
|
12
|
+
"contents": "# frozen_string_literal: true\n\nrequire_relative \"lib/chrono_forge/version\"\n\nGem::Specification.new do |spec|\n spec.name = \"chrono_forge\"\n spec.version = ChronoForge::VERSION\n spec.authors = [\"Stefan Froelich\"]\n spec.email = [\"sfroelich01@gmail.com\"]\n\n spec.summary = \"Base fields for the Phlexi libraries\"\n spec.description = \"Base fields for the Phlexi libraries\"\n spec.homepage = \"https://github.com/radioactive-labs/chrono_forge\"\n spec.license = \"MIT\"\n spec.required_ruby_version = \">= 3.2.2\"\n\n spec.metadata[\"allowed_push_host\"] = \"https://rubygems.org\"\n\n spec.metadata[\"homepage_uri\"] = spec.homepage\n spec.metadata[\"source_code_uri\"] = spec.homepage\n spec.metadata[\"changelog_uri\"] = spec.homepage\n\n # Specify which files should be added to the gem when it is released.\n # The `git ls-files -z` loads the files in the RubyGem that have been added into git.\n gemspec = File.basename(__FILE__)\n spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls|\n ls.readlines(\"\\x0\", chomp: true).reject do |f|\n (f == gemspec) ||\n f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])\n end\n end\n spec.bindir = \"exe\"\n spec.executables = spec.files.grep(%r{\\Aexe/}) { |f| File.basename(f) }\n spec.require_paths = [\"lib\"]\n\n spec.add_dependency \"activerecord\"\n spec.add_dependency \"activejob\"\n spec.add_dependency \"zeitwerk\"\n\n spec.add_development_dependency \"rake\"\n spec.add_development_dependency \"minitest\"\n spec.add_development_dependency \"minitest-reporters\"\n spec.add_development_dependency \"standard\"\n # spec.add_development_dependency \"brakeman\"\n spec.add_development_dependency \"bundle-audit\"\n spec.add_development_dependency \"appraisal\"\n spec.add_development_dependency \"combustion\"\n spec.add_development_dependency \"chaotic_job\"\nend\n"
|
9
13
|
},
|
10
14
|
{
|
11
15
|
"path": "/Users/stefan/Documents/plutonium/chrono_forge/export.rb",
|
12
|
-
"contents": "require \"json\"\nrequire \"find\"\n\ndef export_files_to_json(directory, extensions, output_file, exceptions = [])\n # Convert extensions to lowercase for case-insensitive matching\n extensions = extensions.map(&:downcase)\n\n # Array to store file data\n files_data = []\n\n # Find all files in directory and subdirectories\n Find.find(directory) do |path|\n # Skip if not a file\n next unless File.file?(path)\n next if exceptions.any? { |exception| path.include?(exception) }\n\n # Check if file extension matches any in our list\n ext = File.extname(path).downcase[1..-1] # Remove the leading dot\n next unless extensions.include?(ext)\n\n puts path\n\n begin\n # Read file contents\n contents = File.read(path)\n\n # Add to our array\n files_data << {\n \"path\" => path,\n \"contents\" => contents\n }\n rescue => e\n puts \"Error reading file #{path}: #{e.message}\"\n end\n end\n\n # Write to JSON file\n File.write(output_file, JSON.pretty_generate(files_data))\n\n puts \"Successfully exported #{files_data.length} files to #{output_file}\"\nend\n\n# Example usage (uncomment and modify as needed):\ndirectory = \"/Users/stefan/Documents/plutonium/chrono_forge\"\nexceptions = [\"/.github/\", \"/.vscode/\", \"gemfiles\", \"pkg\", \"
|
16
|
+
"contents": "require \"json\"\nrequire \"find\"\n\ndef export_files_to_json(directory, extensions, output_file, exceptions = [])\n # Convert extensions to lowercase for case-insensitive matching\n extensions = extensions.map(&:downcase)\n\n # Array to store file data\n files_data = []\n\n # Find all files in directory and subdirectories\n Find.find(directory) do |path|\n # Skip if not a file\n next unless File.file?(path)\n next if exceptions.any? { |exception| path.include?(exception) }\n\n # Check if file extension matches any in our list\n ext = File.extname(path).downcase[1..-1] # Remove the leading dot\n next unless extensions.include?(ext)\n\n puts path\n\n begin\n # Read file contents\n contents = File.read(path)\n\n # Add to our array\n files_data << {\n \"path\" => path,\n \"contents\" => contents\n }\n rescue => e\n puts \"Error reading file #{path}: #{e.message}\"\n end\n end\n\n # Write to JSON file\n File.write(output_file, JSON.pretty_generate(files_data))\n\n puts \"Successfully exported #{files_data.length} files to #{output_file}\"\nend\n\n# Example usage (uncomment and modify as needed):\ndirectory = \"/Users/stefan/Documents/plutonium/chrono_forge\"\nexceptions = [\"/.github/\", \"/.vscode/\", \"gemfiles\", \"pkg\", \"node_modules\"]\nextensions = [\"rb\", \"md\", \"yml\", \"yaml\", \"gemspec\"]\noutput_file = \"export.json\"\nexport_files_to_json(directory, extensions, output_file, exceptions)\n"
|
13
17
|
},
|
14
18
|
{
|
15
|
-
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/
|
16
|
-
"contents": "# frozen_string_literal: true\n\
|
19
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/error_log.rb",
|
20
|
+
"contents": "# frozen_string_literal: true\n\n# == Schema Information\n#\n# Table name: chrono_forge_error_logs\n#\n# id :integer not null, primary key\n# backtrace :text\n# context :json\n# error_class :string\n# error_message :text\n# created_at :datetime not null\n# updated_at :datetime not null\n# workflow_id :integer not null\n#\n# Indexes\n#\n# index_chrono_forge_error_logs_on_workflow_id (workflow_id)\n#\n# Foreign Keys\n#\n# workflow_id (workflow_id => chrono_forge_workflows.id)\n#\n\nmodule ChronoForge\n class ErrorLog < ActiveRecord::Base\n self.table_name = \"chrono_forge_error_logs\"\n\n belongs_to :workflow\n\n # Cleanup method\n def self.cleanup_old_logs(retention_period: 30.days)\n where(\"created_at < ?\", retention_period.ago).delete_all\n end\n end\nend\n"
|
17
21
|
},
|
18
22
|
{
|
19
|
-
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/
|
20
|
-
"contents": "# frozen_string_literal: true\n\
|
23
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/execution_log.rb",
|
24
|
+
"contents": "# frozen_string_literal: true\n\n# == Schema Information\n#\n# Table name: chrono_forge_execution_logs\n#\n# id :integer not null, primary key\n# attempts :integer default(0), not null\n# completed_at :datetime\n# error_class :string\n# error_message :text\n# last_executed_at :datetime\n# metadata :json\n# started_at :datetime\n# state :integer default(\"pending\"), not null\n# step_name :string not null\n# created_at :datetime not null\n# updated_at :datetime not null\n# workflow_id :integer not null\n#\n# Indexes\n#\n# idx_on_workflow_id_step_name_11bea8586e (workflow_id,step_name) UNIQUE\n# index_chrono_forge_execution_logs_on_workflow_id (workflow_id)\n#\n# Foreign Keys\n#\n# workflow_id (workflow_id => chrono_forge_workflows.id)\n#\nmodule ChronoForge\n class ExecutionLog < ActiveRecord::Base\n self.table_name = \"chrono_forge_execution_logs\"\n\n belongs_to :workflow\n\n enum :state, %i[\n pending\n completed\n failed\n ]\n\n # Cleanup method\n def self.cleanup_old_logs(retention_period: 30.days)\n where(\"created_at < ?\", retention_period.ago).delete_all\n end\n end\nend\n"
|
21
25
|
},
|
22
26
|
{
|
23
|
-
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/
|
24
|
-
"contents": "
|
27
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/executor/context.rb",
|
28
|
+
"contents": "module ChronoForge\n module Executor\n class Context\n class ValidationError < Error; end\n\n ALLOWED_TYPES = [\n String,\n Integer,\n Float,\n TrueClass,\n FalseClass,\n NilClass,\n Hash,\n Array\n ]\n\n def initialize(workflow)\n @workflow = workflow\n @context = workflow.context || {}\n @dirty = false\n end\n\n def []=(key, value)\n # Type and size validation\n validate_value!(value)\n\n @context[key.to_s] =\n if value.is_a?(Hash) || value.is_a?(Array)\n deep_dup(value)\n else\n value\n end\n\n @dirty = true\n end\n\n def [](key)\n @context[key.to_s]\n end\n\n def save!\n return unless @dirty\n\n @workflow.update_column(:context, @context)\n @dirty = false\n end\n\n private\n\n def validate_value!(value)\n unless ALLOWED_TYPES.any? { |type| value.is_a?(type) }\n raise ValidationError, \"Unsupported context value type: #{value.inspect}\"\n end\n\n # Optional: Add size constraints\n if value.is_a?(String) && value.size > 64.kilobytes\n raise ValidationError, \"Context value too large\"\n end\n end\n\n def deep_dup(obj)\n JSON.parse(JSON.generate(obj))\n rescue\n obj.dup\n end\n end\n end\nend\n"
|
25
29
|
},
|
26
30
|
{
|
27
|
-
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/
|
28
|
-
"contents": "
|
31
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/executor/execution_tracker.rb",
|
32
|
+
"contents": "module ChronoForge\n module Executor\n class ExecutionTracker\n def self.track_error(workflow, error)\n # Create a detailed error log\n ErrorLog.create!(\n workflow: workflow,\n error_class: error.class.name,\n error_message: error.message,\n backtrace: error.backtrace.join(\"\\n\"),\n context: workflow.context\n )\n end\n end\n end\nend\n"
|
29
33
|
},
|
30
34
|
{
|
31
|
-
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/
|
32
|
-
"contents": "
|
35
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/executor/lock_strategy.rb",
|
36
|
+
"contents": "module ChronoForge\n module Executor\n class LongRunningConcurrentExecutionError < Error; end\n\n class ConcurrentExecutionError < Error; end\n\n class LockStrategy\n def self.acquire_lock(job_id, workflow, max_duration:)\n ActiveRecord::Base.transaction do\n # Find the workflow with a lock, considering stale locks\n workflow = workflow.lock!\n\n # Check for active execution\n if workflow.locked_at && workflow.locked_at > max_duration.ago\n raise ConcurrentExecutionError, \"Job currently in progress\"\n end\n\n # Atomic update of lock status\n workflow.update_columns(\n locked_by: job_id,\n locked_at: Time.current,\n state: :running\n )\n\n workflow\n end\n end\n\n def self.release_lock(job_id, workflow)\n workflow = workflow.reload\n if workflow.locked_by != job_id\n raise LongRunningConcurrentExecutionError,\n \"#{self.class}(#{job_id}) executed longer than specified max_duration, \" \\\n \"allowing another instance(#{workflow.locked_by}) to acquire the lock.\"\n end\n\n columns = {locked_at: nil, locked_by: nil}\n columns[:state] = :idle if workflow.running?\n\n workflow.update_columns(columns)\n end\n end\n end\nend\n"
|
33
37
|
},
|
34
38
|
{
|
35
|
-
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/
|
36
|
-
"contents": "
|
39
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/executor/methods/durably_execute.rb",
|
40
|
+
"contents": "module ChronoForge\n module Executor\n module Methods\n module DurablyExecute\n def durably_execute(method, **options)\n # Create execution log\n step_name = \"durably_execute$#{method}\"\n execution_log = ExecutionLog.create_or_find_by!(\n workflow: @workflow,\n step_name: step_name\n ) do |log|\n log.started_at = Time.current\n end\n\n # Return if already completed\n return execution_log.metadata[\"result\"] if execution_log.completed?\n\n # Execute with error handling\n begin\n # Update execution log with attempt\n execution_log.update!(\n attempts: execution_log.attempts + 1,\n last_executed_at: Time.current\n )\n\n # Execute the method\n result = if method.is_a?(Symbol)\n send(method)\n else\n method.call(@context)\n end\n\n # Complete the execution\n execution_log.update!(\n state: :completed,\n completed_at: Time.current,\n metadata: {result: result}\n )\n result\n rescue HaltExecutionFlow\n raise\n rescue => e\n # Log the error\n Rails.logger.error { \"Error while durably executing #{method}: #{e.message}\" }\n self.class::ExecutionTracker.track_error(workflow, e)\n\n # Optional retry logic\n if execution_log.attempts < (options[:max_attempts] || 3)\n # Reschedule with exponential backoff\n backoff = (2**[execution_log.attempts || 1, 5].min).seconds\n\n self.class\n .set(wait: backoff)\n .perform_later(\n @workflow.key,\n retry_method: method\n )\n\n # Halt current execution\n halt_execution!\n else\n # Max attempts reached\n execution_log.update!(\n state: :failed,\n error_message: e.message,\n error_class: e.class.name\n )\n raise ExecutionFailedError, \"#{step_name} failed after maximum attempts\"\n end\n end\n end\n end\n end\n end\nend\n"
|
37
41
|
},
|
38
42
|
{
|
39
|
-
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/
|
40
|
-
"contents": "
|
43
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/executor/methods/wait.rb",
|
44
|
+
"contents": "module ChronoForge\n module Executor\n module Methods\n module Wait\n def wait(duration, name, **options)\n # Create execution log\n execution_log = ExecutionLog.create_or_find_by!(\n workflow: @workflow,\n step_name: \"wait$#{name}\"\n ) do |log|\n log.started_at = Time.current\n log.metadata = {\n wait_until: duration.from_now\n }\n end\n\n # Return if already completed\n return if execution_log.completed?\n\n # Check if wait period has passed\n if Time.current >= Time.parse(execution_log.metadata[\"wait_until\"])\n execution_log.update!(\n attempts: execution_log.attempts + 1,\n state: :completed,\n completed_at: Time.current,\n last_executed_at: Time.current\n )\n return\n end\n\n execution_log.update!(\n attempts: execution_log.attempts + 1,\n last_executed_at: Time.current\n )\n\n # Reschedule the job\n self.class\n .set(wait: duration)\n .perform_later(@workflow.key)\n\n # Halt current execution\n halt_execution!\n end\n end\n end\n end\nend\n"
|
41
45
|
},
|
42
46
|
{
|
43
|
-
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/
|
44
|
-
"contents": "
|
47
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/executor/methods/wait_until.rb",
|
48
|
+
"contents": "module ChronoForge\n module Executor\n class WaitConditionNotMet < ExecutionFailedError; end\n\n module Methods\n module WaitUntil\n def wait_until(condition, **options)\n # Default timeout and check interval\n timeout = options[:timeout] || 10.seconds\n check_interval = options[:check_interval] || 1.second\n\n # Find or create execution log\n step_name = \"wait_until$#{condition}\"\n execution_log = ExecutionLog.create_or_find_by!(\n workflow: @workflow,\n step_name: step_name\n ) do |log|\n log.started_at = Time.current\n log.metadata = {\n timeout_at: timeout.from_now,\n check_interval: check_interval,\n condition: condition.to_s\n }\n end\n\n # Return if already completed\n if execution_log.completed?\n return execution_log.metadata[\"result\"]\n end\n\n # Evaluate condition\n begin\n execution_log.update!(\n attempts: execution_log.attempts + 1,\n last_executed_at: Time.current\n )\n\n condition_met = if condition.is_a?(Proc)\n condition.call(@context)\n elsif condition.is_a?(Symbol)\n send(condition)\n else\n raise ArgumentError, \"Unsupported condition type\"\n end\n rescue HaltExecutionFlow\n raise\n rescue => e\n # Log the error\n Rails.logger.error { \"Error evaluating condition #{condition}: #{e.message}\" }\n self.class::ExecutionTracker.track_error(workflow, e)\n\n # Optional retry logic\n if (options[:retry_on] || []).include?(e.class)\n # Reschedule with exponential backoff\n backoff = (2**[execution_log.attempts || 1, 5].min).seconds\n\n self.class\n .set(wait: backoff)\n .perform_later(\n @workflow.key\n )\n\n # Halt current execution\n halt_execution!\n else\n execution_log.update!(\n state: :failed,\n error_message: e.message,\n error_class: e.class.name\n )\n raise ExecutionFailedError, \"#{step_name} failed with an error: #{e.message}\"\n end\n end\n\n # Handle condition met\n if condition_met\n execution_log.update!(\n state: :completed,\n completed_at: Time.current,\n metadata: execution_log.metadata.merge(\"result\" => true)\n )\n return true\n end\n\n # Check for timeout\n metadata = execution_log.metadata\n if Time.current > metadata[\"timeout_at\"]\n execution_log.update!(\n state: :failed,\n metadata: metadata.merge(\"result\" => nil)\n )\n Rails.logger.warn { \"Timeout reached for condition #{condition}. Condition not met within the timeout period.\" }\n raise WaitConditionNotMet, \"Condition not met within timeout period\"\n end\n\n # Reschedule with delay\n self.class\n .set(wait: check_interval)\n .perform_later(\n @workflow.key,\n wait_condition: condition\n )\n\n # Halt current execution\n halt_execution!\n end\n end\n end\n end\nend\n"
|
45
49
|
},
|
46
50
|
{
|
47
|
-
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/
|
48
|
-
"contents": "
|
51
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/executor/methods.rb",
|
52
|
+
"contents": "module ChronoForge\n module Executor\n module Methods\n include Methods::Wait\n include Methods::WaitUntil\n include Methods::DurablyExecute\n end\n end\nend\n"
|
49
53
|
},
|
50
54
|
{
|
51
|
-
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/
|
52
|
-
"contents": "#
|
55
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/executor/retry_strategy.rb",
|
56
|
+
"contents": "module ChronoForge\n module Executor\n class RetryStrategy\n BACKOFF_STRATEGY = [\n 1.second, # Initial retry\n 5.seconds, # Second retry\n 30.seconds, # Third retry\n 2.minutes, # Fourth retry\n 10.minutes # Final retry\n ]\n\n def self.schedule_retry(workflow, attempt: 0)\n wait_duration = BACKOFF_STRATEGY[attempt] || BACKOFF_STRATEGY.last\n\n # Schedule with exponential backoff\n workflow.job_klass.constantize\n .set(wait: wait_duration)\n .perform_later(\n workflow.key,\n attempt: attempt + 1\n )\n end\n\n def self.max_attempts\n BACKOFF_STRATEGY.length\n end\n end\n end\nend\n"
|
53
57
|
},
|
54
58
|
{
|
55
|
-
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/
|
56
|
-
"contents": "
|
59
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/executor.rb",
|
60
|
+
"contents": "module ChronoForge\n module Executor\n class Error < StandardError; end\n\n class ExecutionFailedError < Error; end\n\n class ExecutionFlowControl < Error; end\n\n class HaltExecutionFlow < ExecutionFlowControl; end\n\n include Methods\n\n def perform(key, attempt: 0, **kwargs)\n # Prevent excessive retries\n if attempt >= self.class::RetryStrategy.max_attempts\n Rails.logger.error { \"Max attempts reached for job #{key}\" }\n return\n end\n\n # Find or create job with comprehensive tracking\n setup_workflow(key, kwargs)\n\n begin\n # Skip if workflow cannot be executed\n return unless workflow.executable?\n\n # Acquire lock with advanced concurrency protection\n self.class::LockStrategy.acquire_lock(job_id, workflow, max_duration: max_duration)\n\n # Execute core job logic\n super(**workflow.kwargs.symbolize_keys)\n\n # Mark as complete\n complete_workflow!\n rescue ExecutionFailedError => e\n Rails.logger.error { \"Execution step failed for #{key}\" }\n self.class::ExecutionTracker.track_error(workflow, e)\n workflow.stalled!\n nil\n rescue HaltExecutionFlow\n # Halt execution\n Rails.logger.debug { \"Execution halted for #{key}\" }\n nil\n rescue ConcurrentExecutionError\n # Graceful handling of concurrent execution\n Rails.logger.warn { \"Concurrent execution detected for job #{key}\" }\n nil\n rescue => e\n Rails.logger.error { \"An error occurred during execution of #{key}\" }\n self.class::ExecutionTracker.track_error(workflow, e)\n\n # Retry if applicable\n if should_retry?(e, attempt)\n self.class::RetryStrategy.schedule_retry(workflow, attempt: attempt)\n else\n workflow.failed!\n end\n ensure\n context.save!\n # Always release the lock\n self.class::LockStrategy.release_lock(job_id, workflow)\n end\n end\n\n private\n\n def complete_workflow!\n # Create an execution log for workflow completion\n execution_log = ExecutionLog.create_or_find_by!(\n workflow: workflow,\n step_name: \"$workflow_completion$\"\n ) do |log|\n log.started_at = Time.current\n log.metadata = {\n workflow_id: workflow.id\n }\n end\n\n begin\n execution_log.update!(\n attempts: execution_log.attempts + 1,\n last_executed_at: Time.current\n )\n\n workflow.completed_at = Time.current\n workflow.completed!\n\n # Mark execution log as completed\n execution_log.update!(\n state: :completed,\n completed_at: Time.current\n )\n\n # Return the execution log for tracking\n execution_log\n rescue => e\n # Log any completion errors\n execution_log.update!(\n state: :failed,\n error_message: e.message,\n error_class: e.class.name\n )\n raise\n end\n end\n\n def setup_workflow(key, kwargs)\n @workflow = find_workflow(key, kwargs)\n @context = Context.new(@workflow)\n end\n\n def find_workflow(key, kwargs)\n Workflow.create_or_find_by!(key: key) do |workflow|\n workflow.job_klass = self.class.to_s\n workflow.kwargs = kwargs\n workflow.started_at = Time.current\n end\n end\n\n def should_retry?(error, attempt_count)\n attempt_count < 3\n end\n\n def halt_execution!\n raise HaltExecutionFlow\n end\n\n def workflow\n @workflow\n end\n\n def context\n @context\n end\n\n def max_duration\n 10.minutes\n end\n end\nend\n"
|
57
61
|
},
|
58
62
|
{
|
59
|
-
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/
|
60
|
-
"contents": "# frozen_string_literal: true\n\nmodule
|
63
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/version.rb",
|
64
|
+
"contents": "# frozen_string_literal: true\n\nmodule ChronoForge\n VERSION = \"0.1.0\"\nend\n"
|
61
65
|
},
|
62
66
|
{
|
63
|
-
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/
|
64
|
-
"contents": "# frozen_string_literal: true\n\
|
67
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/workflow.rb",
|
68
|
+
"contents": "# frozen_string_literal: true\n\n# == Schema Information\n#\n# Table name: chrono_forge_workflows\n#\n# id :integer not null, primary key\n# completed_at :datetime\n# context :json not null\n# job_klass :string not null\n# key :string not null\n# kwargs :json not null\n# locked_at :datetime\n# started_at :datetime\n# state :integer default(\"idle\"), not null\n# created_at :datetime not null\n# updated_at :datetime not null\n#\n# Indexes\n#\n# index_chrono_forge_workflows_on_key (key) UNIQUE\n#\nmodule ChronoForge\n class Workflow < ActiveRecord::Base\n self.table_name = \"chrono_forge_workflows\"\n\n has_many :execution_logs\n has_many :error_logs\n\n enum :state, %i[\n idle\n running\n completed\n failed\n stalled\n ]\n\n # Cleanup method\n def self.cleanup_old_logs(retention_period: 30.days)\n where(\"created_at < ?\", retention_period.ago).delete_all\n end\n\n # Serialization for metadata\n serialize :metadata, coder: JSON\n\n def executable?\n idle? || running?\n end\n end\nend\n"
|
65
69
|
},
|
66
70
|
{
|
67
|
-
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge
|
68
|
-
"contents": "# frozen_string_literal: true\n\
|
71
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge.rb",
|
72
|
+
"contents": "# frozen_string_literal: true\n\nrequire \"zeitwerk\"\nrequire \"active_record\"\nrequire \"active_job\"\n\nmodule Chronoforge\n Loader = Zeitwerk::Loader.for_gem.tap do |loader|\n loader.ignore(\"#{__dir__}/generators\")\n loader.setup\n end\n\n class Error < StandardError; end\nend\n"
|
69
73
|
},
|
70
74
|
{
|
71
|
-
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/
|
72
|
-
"contents": "# frozen_string_literal: true\n\
|
75
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/generators/chrono_forge/install/install_generator.rb",
|
76
|
+
"contents": "# frozen_string_literal: true\n\nrequire \"rails/generators/active_record/migration\"\n\nmodule ChronoForge\n class InstallGenerator < Rails::Generators::Base\n include ::ActiveRecord::Generators::Migration\n\n source_root File.expand_path(\"templates\", __dir__)\n\n def start\n install_migrations\n rescue => err\n say \"#{err.class}: #{err}\\n#{err.backtrace.join(\"\\n\")}\", :red\n exit 1\n end\n\n private\n\n def install_migrations\n migration_template \"install_chrono_forge.rb\", \"install_chrono_forge.rb\"\n end\n end\nend\n"
|
73
77
|
},
|
74
78
|
{
|
75
|
-
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/chrono_forge/
|
76
|
-
"contents": "# frozen_string_literal: true\n\
|
79
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/lib/generators/chrono_forge/install/templates/install_chrono_forge.rb",
|
80
|
+
"contents": "# frozen_string_literal: true\n\nclass InstallChronoForge < ActiveRecord::Migration[7.1]\n def change\n create_table :chrono_forge_workflows do |t|\n t.string :key, null: false, index: {unique: true}\n t.string :job_klass, null: false\n\n if t.respond_to?(:jsonb)\n t.jsonb :kwargs, null: false, default: {}\n t.jsonb :options, null: false, default: {}\n t.jsonb :context, null: false, default: {}\n else\n t.json :kwargs, null: false, default: {}\n t.json :options, null: false, default: {}\n t.json :context, null: false, default: {}\n end\n\n t.integer :state, null: false, default: 0\n t.string :locked_by\n t.datetime :locked_at\n\n t.datetime :started_at\n t.datetime :completed_at\n\n t.timestamps\n end\n\n create_table :chrono_forge_execution_logs do |t|\n t.references :workflow, null: false, foreign_key: {to_table: :chrono_forge_workflows}\n t.string :step_name, null: false\n t.integer :attempts, null: false, default: 0\n t.datetime :started_at\n t.datetime :last_executed_at\n t.datetime :completed_at\n if t.respond_to?(:jsonb)\n t.jsonb :metadata\n else\n t.json :metadata\n end\n t.integer :state, null: false, default: 0\n t.string :error_class\n t.text :error_message\n\n t.timestamps\n t.index %i[workflow_id step_name], unique: true\n end\n\n create_table :chrono_forge_error_logs do |t|\n t.references :workflow, null: false, foreign_key: {to_table: :chrono_forge_workflows}\n t.string :error_class\n t.text :error_message\n t.text :backtrace\n if t.respond_to?(:jsonb)\n t.jsonb :context\n else\n t.json :context\n end\n\n t.timestamps\n end\n end\nend\n"
|
77
81
|
},
|
78
82
|
{
|
79
|
-
"path": "/Users/stefan/Documents/plutonium/chrono_forge/
|
80
|
-
"contents": "
|
83
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/test/chrono_forge_test.rb",
|
84
|
+
"contents": "require \"test_helper\"\n\nclass ChronoForgeTest < Minitest::Test\n include ChaoticJob::Helpers\n\n def test_version\n assert ChronoForge::VERSION\n end\n\n def test_job_is_durable\n DurableJob.perform_later(:key)\n perform_all_jobs\n\n assert ChronoForge::Workflow.last.completed?\n end\nend\n"
|
81
85
|
},
|
82
86
|
{
|
83
|
-
"path": "/Users/stefan/Documents/plutonium/chrono_forge/
|
84
|
-
"contents": "
|
87
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/test/internal/app/jobs/durable_job.rb",
|
88
|
+
"contents": "class DurableJob < ActiveJob::Base\n prepend ChronoForge::Executor\n\n def perform\n # Context can be used to pass and store data between executions\n context[\"order_id\"] = SecureRandom.hex\n\n # Wait until payment is confirmed\n wait_until :payment_confirmed?\n\n # Wait for potential fraud check\n wait 1.seconds, :fraud_check_delay\n\n # Durably execute order processing\n durably_execute :process_order\n\n # Final steps\n complete_order\n end\n\n private\n\n def payment_confirmed?\n [true, false].sample\n end\n\n def process_order\n context[\"processed_at\"] = Time.current.iso8601\n end\n\n def complete_order\n context[\"completed_at\"] = Time.current.iso8601\n end\nend\n"
|
85
89
|
},
|
86
90
|
{
|
87
|
-
"path": "/Users/stefan/Documents/plutonium/chrono_forge/
|
88
|
-
"contents": "
|
91
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/test/internal/app/models/user.rb",
|
92
|
+
"contents": "class User < ActiveRecord::Base\n validates :name, presence: true\n validates :email, presence: true\nend\n"
|
89
93
|
},
|
90
94
|
{
|
91
|
-
"path": "/Users/stefan/Documents/plutonium/chrono_forge/
|
92
|
-
"contents": "
|
95
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/test/internal/config/database.yml",
|
96
|
+
"contents": "test:\n adapter: sqlite3\n database: db/combustion_test.sqlite\n"
|
93
97
|
},
|
94
98
|
{
|
95
|
-
"path": "/Users/stefan/Documents/plutonium/chrono_forge/
|
96
|
-
"contents": "
|
99
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/test/internal/config/storage.yml",
|
100
|
+
"contents": "test:\n service: Disk\n root: tmp/storage\n"
|
97
101
|
},
|
98
102
|
{
|
99
|
-
"path": "/Users/stefan/Documents/plutonium/chrono_forge/
|
100
|
-
"contents": "#
|
103
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/test/internal/db/migrate/20241217100623_install_chrono_forge.rb",
|
104
|
+
"contents": "# The template is our single source of truth.\nrequire File.expand_path(\"../../../../lib/generators/chrono_forge/install/templates/install_chrono_forge.rb\", __dir__)\n"
|
105
|
+
},
|
106
|
+
{
|
107
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/test/internal/db/schema.rb",
|
108
|
+
"contents": "# frozen_string_literal: true\n\nActiveRecord::Schema.define do\n create_table :users, force: true do |t|\n t.string :name\n t.string :email\n t.timestamps null: false\n end\nend\n"
|
109
|
+
},
|
110
|
+
{
|
111
|
+
"path": "/Users/stefan/Documents/plutonium/chrono_forge/test/test_helper.rb",
|
112
|
+
"contents": "require \"chrono_forge\"\n\nrequire \"minitest/autorun\"\nrequire \"minitest/reporters\"\nMinitest::Reporters.use!\n\nrequire \"combustion\"\nCombustion.path = \"test/internal\"\nCombustion.initialize! :active_record, :active_job\n\nrequire \"chaotic_job\"\n"
|
101
113
|
}
|
102
114
|
]
|
data/export.rb
CHANGED
@@ -42,7 +42,7 @@ end
|
|
42
42
|
|
43
43
|
# Example usage (uncomment and modify as needed):
|
44
44
|
directory = "/Users/stefan/Documents/plutonium/chrono_forge"
|
45
|
-
exceptions = ["/.github/", "/.vscode/", "gemfiles", "pkg", "
|
45
|
+
exceptions = ["/.github/", "/.vscode/", "gemfiles", "pkg", "node_modules"]
|
46
46
|
extensions = ["rb", "md", "yml", "yaml", "gemspec"]
|
47
47
|
output_file = "export.json"
|
48
48
|
export_files_to_json(directory, extensions, output_file, exceptions)
|
data/lib/chrono_forge/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chrono_forge
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefan Froelich
|
@@ -164,7 +164,10 @@ dependencies:
|
|
164
164
|
- - ">="
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: '0'
|
167
|
-
description:
|
167
|
+
description: ChronoForge provides a robust framework for building durable, distributed
|
168
|
+
workflows in Ruby on Rails applications. It offers reliable state management, error
|
169
|
+
recovery, and workflow orchestration through features like durable execution, wait
|
170
|
+
states, and comprehensive error tracking.
|
168
171
|
email:
|
169
172
|
- sfroelich01@gmail.com
|
170
173
|
executables: []
|
@@ -226,5 +229,5 @@ requirements: []
|
|
226
229
|
rubygems_version: 3.4.10
|
227
230
|
signing_key:
|
228
231
|
specification_version: 4
|
229
|
-
summary:
|
232
|
+
summary: A durable workflow engine for Ruby on Rails built on ActiveJob
|
230
233
|
test_files: []
|