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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1f2655b7f5ea191bd4c80e36b5e6e55439ad04f8570fb29d36ce0f6dc66ce687
4
- data.tar.gz: ce5509c4fc6ee82179c6f14be7bf887f41f09ec0f344ef976ba3ac4691305725
3
+ metadata.gz: 88cee304794f2d186c2103563b8f98c08a00cc1d9a351ae206c35e34bacdcf61
4
+ data.tar.gz: dee282b43b65a4846797629903141329a3f094fd009416848cb0f5b33ed3f68e
5
5
  SHA512:
6
- metadata.gz: baca6968fedbded14682da7a4fb68b3d2f5f3da4228671358461c77245920787d0672fd4886ad000693a06bb8e22438d4f311b66dabfdbbd917dfad5d003cea2
7
- data.tar.gz: 677a8ef2549511e7e03f18c45d34897ac0ceaaa25ea9a11300ecac711f552c92c074e56ea6453d21a1b58d0ba20aabdd859941bbb1e93eec8cf3b7e9a6c69b34
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.2.0] - 2024-07-31\n\n- Initial release\n"
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": "# Phlexi::Form \n\nPhlexi::Form is a flexible and powerful form builder for Ruby applications. It provides a more customizable and extensible way to create forms compared to traditional form helpers.\n\n[![Ruby](https://github.com/radioactive-labs/phlexi-form/actions/workflows/main.yml/badge.svg)](https://github.com/radioactive-labs/phlexi-form/actions/workflows/main.yml)\n\n## Features\n\n- Customizable form components (input, select, checkbox, radio button, etc.)\n- Automatic field type and attribute inference based on model attributes\n- Built-in support for validations and error handling\n- Flexible form structure with support for nested attributes\n- Works with Phlex or erb views\n- Extract input from parameters that match your form definition. No need to strong paramters.\n- Rails compatible form inputs\n\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'phlexi-form'\n```\n\nAnd then execute:\n\n```\n$ bundle install\n```\n\nOr install it yourself as:\n\n```\n$ gem install phlexi-form\n```\n\n## Usage\n\nThere are 2 ways to use Phlexi::Form:\n\n### Direct Usage\n\n```ruby\nPhlexi::Form(User.new) do\n field(:name) do |name|\n render name.label_tag\n render name.input_tag\n end\n\n field(:email) do |email|\n render email.label_tag\n render email.input_tag\n end\n\n nest_one(:address) do |address|\n address.field(:street) do |street|\n render street.label_tag\n render street.input_tag\n end\n\n address.field(:city) do |city|\n render city.label_tag\n render city.input_tag\n end\n end\n\n render submit_button\nend\n```\n\n> **Important**\n>\n> If you are rendering your form inline e.g. \n> ```ruby\n> render Phlexi::Form(User.new) {\n> render field(:name).label_tag\n> render field(:name).input_tag\n> }\n> ```\n>\n> Make sure you use `{...}` in defining your block instead of `do...end`\n> This might be fixed in a future version.\n\n### Inherit form\n\n```ruby\nclass UserForm < Phlexi::Form::Base\n def form_template\n field(:name) do |name|\n render name.label_tag\n render name.input_tag\n end\n\n field(:email) do |email|\n render email.label_tag\n render email.input_tag\n end\n\n nest_one(:address) do |address|\n address.field(:street) do |street|\n render street.label_tag\n render street.input_tag\n end\n\n address.field(:city) do |city|\n render city.label_tag\n render city.input_tag\n end\n end\n\n render submit_button\n end\nend\n\n\n# In your view or controller\nform = UserForm.new(User.new)\n\n# Render the form\nrender form\n\n# Extract params\nform.extract_input({\n name: \"Brad Pitt\",\n email: \"brad@pitt.com\",\n address: {\n street: \"Plumbago\",\n city: \"Accra\",\n }\n})\n```\n\n## Advanced Usage\n\n### Custom Components\n\nYou can create custom form components by inheriting from `Phlexi::Form::Components::Base`:\n\n```ruby\nclass CustomInput < Phlexi::Form::Components::Base\n def template\n div(class: \"custom-input\") do\n input(**attributes)\n span(class: \"custom-icon\")\n end\n end\nend\n\n# Usage in your form\nfield(:custom_field) do |field|\n render CustomInput.new(field)\nend\n```\n\n### Theming\n\nPhlexi::Form supports theming through a flexible theming system:\n\n```ruby\nclass ThemedForm < Phlexi::Form::Base\n class FieldBuilder < FieldBuilder\n private\n \n def default_theme\n {\n input: \"border rounded px-2 py-1\",\n label: \"font-bold text-gray-700\",\n # Add more theme options here\n }\n end\n end\nend\n```\n\n<!-- ## Configuration\n\nYou can configure Phlexi::Form globally by creating an initializer:\n\n```ruby\n# config/initializers/phlexi_form.rb\nPhlexi::Form.configure do |config|\n config.default_theme = {\n # Your default theme options\n }\n # Add more configuration options here\nend\n``` -->\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/radioactive-labs/phlexi-form.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n"
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\", \"test\", \"node_modules\"]\nextensions = [\"rb\", \"md\", \"yml\", \"yaml\", \"gemspec\"]\noutput_file = \"export.json\"\nexport_files_to_json(directory, extensions, output_file, exceptions)\n"
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/builder.rb",
16
- "contents": "# frozen_string_literal: true\n\nrequire \"phlex\"\n\nmodule Phlexi\n module Field\n # Builder class is responsible for building fields with various options and components.\n #\n # @attr_reader [Structure::DOM] dom The DOM structure for the field.\n # @attr_reader [Hash] options Options for the field.\n # @attr_reader [Object] object The object associated with the field.\n # @attr_reader [Hash] attributes Attributes for the field.\n # @attr_accessor [Object] value The value of the field.\n class Builder < Structure::Node\n include Phlex::Helpers\n include Options::Validators\n include Options::InferredTypes\n include Options::Multiple\n include Options::Labels\n include Options::Placeholders\n include Options::Descriptions\n include Options::Hints\n include Options::Associations\n include Options::Attachments\n\n class DOM < Structure::DOM; end\n\n class FieldCollection < Structure::FieldCollection; end\n\n attr_reader :dom, :options, :object, :value\n\n # Initializes a new FieldBuilder instance.\n #\n # @param key [Symbol, String] The key for the field.\n # @param parent [Structure::Namespace] The parent object.\n # @param object [Object, nil] The associated object.\n # @param value [Object] The initial value for the field.\n # @param options [Hash] Additional options for the field.\n def initialize(key, parent:, object: nil, value: NIL_VALUE, **options)\n super(key, parent: parent)\n\n @object = object\n @value = determine_initial_value(value)\n @options = options\n @dom = self.class::DOM.new(field: self)\n end\n\n # Creates a repeated field collection.\n #\n # @param range [#each] The collection of items to generate displays for.\n # @yield [block] The block to be executed for each item in the collection.\n # @return [FieldCollection] The field collection.\n def repeated(collection = nil, &)\n self.class::FieldCollection.new(field: self, collection:, &)\n end\n\n def has_value?\n attachment_reflection.present? ? value.attached? : (value.present? || value == false)\n end\n\n protected\n\n def determine_initial_value(value)\n return value unless value == NIL_VALUE\n\n determine_value_from_object\n end\n\n def determine_value_from_object\n ChronoForge::Support::Value.from(object, key)\n end\n end\n end\nend\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/components/base.rb",
20
- "contents": "# frozen_string_literal: true\n\nmodule Phlexi\n module Field\n module Components\n class Base < COMPONENT_BASE\n attr_reader :field, :attributes\n\n def initialize(field, **attributes)\n @field = field\n @attributes = attributes\n\n build_attributes\n build_component_class\n end\n\n protected\n\n def build_attributes\n attributes.fetch(:id) { attributes[:id] = \"#{field.dom.id}_#{component_name}\" }\n end\n\n def build_component_class\n return if attributes[:class] == false\n\n attributes[:class] = tokens(component_name, attributes[:class])\n end\n\n def component_name\n @component_name ||= self.class.name.demodulize.underscore\n end\n end\n end\n end\nend\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/options/associations.rb",
24
- "contents": "# frozen_string_literal: true\n\nmodule Phlexi\n module Field\n module Options\n module Associations\n def association_reflection\n @association_reflection ||= find_association_reflection\n end\n\n protected\n\n def find_association_reflection\n if object.class.respond_to?(:reflect_on_association)\n object.class.reflect_on_association(key)\n end\n end\n end\n end\n end\nend\n"
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/options/attachments.rb",
28
- "contents": "# frozen_string_literal: true\n\nmodule Phlexi\n module Field\n module Options\n module Attachments\n def attachment_reflection\n @attachment_reflection ||= find_attachment_reflection\n end\n\n protected\n\n def find_attachment_reflection\n if object.class.respond_to?(:reflect_on_attachment)\n object.class.reflect_on_attachment(key)\n end\n end\n end\n end\n end\nend\n"
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/options/descriptions.rb",
32
- "contents": "# frozen_string_literal: true\n\nmodule Phlexi\n module Field\n module Options\n module Descriptions\n def description(description = nil)\n if description.nil?\n options[:description]\n else\n options[:description] = description\n self\n end\n end\n\n def has_description?\n description.present?\n end\n end\n end\n end\nend\n"
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/options/hints.rb",
36
- "contents": "# frozen_string_literal: true\n\nmodule Phlexi\n module Field\n module Options\n module Hints\n def hint(hint = nil)\n if hint.nil?\n options[:hint]\n else\n options[:hint] = hint\n self\n end\n end\n\n def has_hint?\n hint.present?\n end\n end\n end\n end\nend\n"
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/options/inferred_types.rb",
40
- "contents": "# frozen_string_literal: true\n\nrequire \"bigdecimal\"\n\nmodule Phlexi\n module Field\n module Options\n module InferredTypes\n def inferred_field_component\n @inferred_component ||= infer_field_component\n end\n\n def inferred_field_type\n @inferred_field_type ||= infer_field_type\n end\n\n def inferred_string_field_type\n @inferred_string_field_type || infer_string_field_type\n end\n\n private\n\n def infer_field_component\n inferred_field_type\n end\n\n def infer_field_type\n # Check attachments first since they are implemented as associations\n return :attachment if attachment_reflection\n\n return :association if association_reflection\n\n if object.class.respond_to?(:defined_enums)\n return :enum if object.class.defined_enums.key?(key.to_s)\n end\n\n if object.class.respond_to?(:columns_hash)\n # ActiveRecord\n column = object.class.columns_hash[key.to_s]\n return column.type if column&.type\n end\n\n if object.class.respond_to?(:attribute_types)\n # ActiveModel::Attributes\n custom_type = object.class.attribute_types[key.to_s]\n return custom_type.type if custom_type&.type\n end\n\n # Fallback to inferring type from the value\n value = ChronoForge::Support::Value.from(object, key)\n return infer_field_type_from_value(value) unless value.nil?\n\n # Default to string if we can't determine the type\n :string\n end\n\n def infer_field_type_from_value(value)\n case value\n when Integer\n :integer\n when Float\n :float\n when BigDecimal\n :decimal\n when TrueClass, FalseClass\n :boolean\n when Date\n :date\n when Time, DateTime\n :datetime\n when Hash\n :json\n else\n :string\n end\n end\n\n def infer_string_field_type\n infer_string_field_type_from_key || infer_string_field_type_from_validations\n end\n\n def infer_string_field_type_from_validations\n return unless has_validators?\n\n if attribute_validators.find { |v| v.kind == :numericality }\n :number\n elsif attribute_validators.find { |v| v.kind == :format && v.options[:with] == URI::MailTo::EMAIL_REGEXP }\n :email\n end\n end\n\n def infer_string_field_type_from_key\n key = self.key.to_s.downcase\n return :password if is_password_field?(key)\n\n custom_mappings = {\n /url$|^link|^site/ => :url,\n /^email/ => :email,\n /^search/ => :search,\n /phone|tel(ephone)?/ => :phone,\n /^time/ => :time,\n /^date/ => :date,\n /^number|_count$|_amount$/ => :number,\n /^color|_color$/ => :color\n }\n\n custom_mappings.each do |pattern, type|\n return type if key.match?(pattern)\n end\n\n nil\n end\n\n def is_password_field?(key)\n exact_matches = [\"password\"]\n prefixes = [\"encrypted_\"]\n suffixes = [\"_password\", \"_digest\", \"_hash\", \"_token\"]\n\n exact_matches.include?(key) ||\n prefixes.any? { |prefix| key.start_with?(prefix) } ||\n suffixes.any? { |suffix| key.end_with?(suffix) }\n end\n end\n end\n end\nend\n"
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/options/labels.rb",
44
- "contents": "# frozen_string_literal: true\n\nmodule Phlexi\n module Field\n module Options\n module Labels\n def label(label = nil)\n if label.nil?\n options[:label] = options.fetch(:label) { calculate_label }\n else\n options[:label] = label\n self\n end\n end\n\n private\n\n def calculate_label\n if object.class.respond_to?(:human_attribute_name)\n object.class.human_attribute_name(key.to_s, {base: object})\n else\n key.to_s.humanize\n end\n end\n end\n end\n end\nend\n"
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/options/multiple.rb",
48
- "contents": "# frozen_string_literal: true\n\nmodule Phlexi\n module Field\n module Options\n module Multiple\n def multiple?\n options[:multiple] = options.fetch(:multiple) { calculate_multiple_field_value }\n end\n\n def multiple!(multiple = true)\n options[:multiple] = multiple\n self\n end\n\n private\n\n def calculate_multiple_field_value\n return true if attachment_reflection&.macro == :has_many_attached\n return true if %i[has_many has_and_belongs_to_many].include?(association_reflection&.macro)\n return true if multiple_field_array_attribute?\n\n check_multiple_field_from_validators\n end\n\n def multiple_field_array_attribute?\n return false unless object.class.respond_to?(:columns_hash)\n\n column = object.class.columns_hash[key.to_s]\n return false unless column\n\n case object.class.connection.adapter_name.downcase\n when \"postgresql\"\n column.array? || (column.type == :string && column.sql_type.include?(\"[]\"))\n end # || object.class.attribute_types[key.to_s].is_a?(ActiveRecord::Type::Serialized)\n rescue\n # Rails.logger.warn(\"Error checking multiple field array attribute: #{e.message}\")\n false\n end\n\n def check_multiple_field_from_validators\n inclusion_validator = find_validator(:inclusion)\n length_validator = find_validator(:length)\n\n return false unless inclusion_validator || length_validator\n\n check_multiple_field_inclusion_validator(inclusion_validator) ||\n check_multiple_field_length_validator(length_validator)\n end\n\n def check_multiple_field_inclusion_validator(validator)\n return false unless validator\n in_option = validator.options[:in] || validator.options[:within]\n return false unless in_option.is_a?(Array)\n\n validator.options[:multiple] == true || (multiple_field_array_attribute? && in_option.size > 1)\n end\n\n def check_multiple_field_length_validator(validator)\n return false unless validator\n validator.options[:maximum].to_i > 1 if validator.options[:maximum]\n end\n end\n end\n end\nend\n"
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/options/placeholders.rb",
52
- "contents": "# frozen_string_literal: true\n\nmodule Phlexi\n module Field\n module Options\n module Placeholders\n def placeholder(placeholder = nil)\n if placeholder.nil?\n options[:placeholder]\n else\n\n options[:placeholder] = placeholder\n self\n end\n end\n end\n end\n end\nend\n"
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/options/validators.rb",
56
- "contents": "# frozen_string_literal: true\n\nmodule Phlexi\n module Field\n module Options\n module Validators\n private\n\n def has_validators?\n @has_validators ||= object.class.respond_to?(:validators_on)\n end\n\n def attribute_validators\n object.class.validators_on(key)\n end\n\n def association_reflection_validators\n association_reflection ? object.class.validators_on(association_reflection.name) : []\n end\n\n def valid_validator?(validator)\n !conditional_validators?(validator) && action_validator_match?(validator)\n end\n\n def conditional_validators?(validator)\n validator.options.include?(:if) || validator.options.include?(:unless)\n end\n\n def action_validator_match?(validator)\n return true unless validator.options.include?(:on)\n\n case validator.options[:on]\n when :save\n true\n when :create\n !object.persisted?\n when :update\n object.persisted?\n end\n end\n\n def find_validator(kind)\n attribute_validators.find { |v| v.kind == kind && valid_validator?(v) } if has_validators?\n end\n end\n end\n end\nend\n"
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/structure/dom.rb",
60
- "contents": "# frozen_string_literal: true\n\nmodule Phlexi\n module Field\n module Structure\n # Generates DOM IDs, names, etc. for a Field, Namespace, or Node based on\n # norms that were established by Rails. These can be used outside of or Rails in\n # other Ruby web frameworks since it has no dependencies on Rails.\n class DOM\n def initialize(field:)\n @field = field\n end\n\n # Converts the value of the field to a String, which is required to work\n # with Phlex. Assumes that `Object#to_s` emits a format suitable for display.\n def value\n @field.value.to_s\n end\n\n # Walks from the current node to the parent node, grabs the names, and separates\n # them with a `_` for a DOM ID.\n def id\n @id ||= begin\n root, *rest = lineage\n root_key = if root.respond_to?(:dom_id)\n root.dom_id\n else\n root.key\n end\n rest.map(&:key).unshift(root_key).join(\"_\")\n end\n end\n\n # The `name` attribute of a node, which is influenced by Rails.\n # All node names, except the parent node, are wrapped in a `[]` and collections\n # are left empty. For example, `user[addresses][][street]` would be created for a form with\n # data shaped like `{user: {addresses: [{street: \"Sesame Street\"}]}}`.\n def name\n @name ||= begin\n root, *names = keys\n names.map { |name| \"[#{name}]\" }.unshift(root).join\n end\n end\n\n # One-liner way of walking from the current node all the way up to the parent.\n def lineage\n @lineage ||= Enumerator.produce(@field, &:parent).take_while(&:itself).reverse\n end\n\n # Emit the id, name, and value in an HTML tag-ish that doesnt have an element.\n def inspect\n \"<#{self.class.name} id=#{id.inspect} name=#{name.inspect} value=#{value.inspect}/>\"\n end\n\n private\n\n def keys\n @keys ||= lineage.map do |node|\n # If the parent of a field is a field, the name should be nil.\n node.key unless node.parent.is_a? Builder\n end\n end\n end\n end\n end\nend\n"
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/structure/field_collection.rb",
64
- "contents": "# frozen_string_literal: true\n\nmodule Phlexi\n module Field\n module Structure\n class FieldCollection\n include Enumerable\n\n class Builder\n include Phlex::Helpers\n\n attr_reader :key, :index\n\n def initialize(key, field, index)\n @key = key.to_s\n @field = field\n @index = index\n end\n\n def field(**)\n @field.class.new(key, **, parent: @field).tap do |field|\n yield field if block_given?\n end\n end\n end\n\n def initialize(field:, collection:, &)\n @field = field\n @collection = build_collection(collection)\n each(&) if block_given?\n end\n\n def each(&)\n @collection.each.with_index do |item, index|\n yield self.class::Builder.new(item, @field, index)\n end\n end\n\n private\n\n def build_collection(collection)\n collection\n end\n end\n end\n end\nend\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/structure/namespace.rb",
68
- "contents": "# frozen_string_literal: true\n\nmodule Phlexi\n module Field\n module Structure\n # A Namespace maps an object to values, but doesn't actually have a value itself. For\n # example, a `User` object or ActiveRecord model could be passed into the `:user` namespace.\n #\n # To access single values on a Namespace, #field can be used.\n #\n # To access nested objects within a namespace, two methods are available:\n #\n # 1. #nest_one: Used for single nested objects, such as if a `User belongs_to :profile` in\n # ActiveRecord. This method returns another Namespace object.\n #\n # 2. #nest_many: Used for collections of nested objects, such as if a `User has_many :addresses` in\n # ActiveRecord. This method returns a NamespaceCollection object.\n class Namespace < Structure::Node\n include Enumerable\n\n class NamespaceCollection < Structure::NamespaceCollection; end\n\n attr_reader :builder_klass, :object\n\n def initialize(key, parent:, builder_klass:, object: nil, dom_id: nil)\n super(key, parent: parent)\n @builder_klass = builder_klass\n @object = object\n @dom_id = dom_id\n @children = {}\n yield self if block_given?\n end\n\n def field(key, template: false, **attributes)\n create_child(key, attributes.delete(:builder_klass) || builder_klass, object: object, template:, **attributes).tap do |field|\n yield field if block_given?\n end\n end\n\n # Creates a `Namespace` child instance with the parent set to the current instance, adds to\n # the `@children` Hash to ensure duplicate child namespaces aren't created, then calls the\n # method on the `@object` to get the child object to pass into that namespace.\n #\n # For example, if a `User#permission` returns a `Permission` object, we could map that to a\n # form like this:\n #\n # ```ruby\n # Phlexi::Form(User.new, as: :user) do\n # nest_one :profile do |profile|\n # render profile.field(:gender).input_tag\n # end\n # end\n # ```\n def nest_one(key, as: nil, object: nil, default: nil, template: false, &)\n object ||= object_value_for(key: key) || default\n key = as || key\n create_child(key, self.class, object:, template:, builder_klass:, &)\n end\n\n # Wraps an array of objects in Namespace classes. For example, if `User#addresses` returns\n # an enumerable or array of `Address` classes:\n #\n # ```ruby\n # Phlexi::Form(User.new) do\n # render field(:email).input_tag\n # render field(:name).input_tag\n # nest_many :addresses do |address|\n # render address.field(:street).input_tag\n # render address.field(:state).input_tag\n # render address.field(:zip).input_tag\n # end\n # end\n # ```\n # The object within the block is a `Namespace` object that maps each object within the enumerable\n # to another `Namespace` or `Field`.\n def nest_many(key, as: nil, collection: nil, default: nil, template: false, &)\n collection ||= Array(object_value_for(key: key)).presence || default\n key = as || key\n create_child(key, self.class::NamespaceCollection, collection:, template:, &)\n end\n\n # Iterates through the children of the current namespace, which could be `Namespace` or `Field`\n # objects.\n def each(&)\n @children.values.each(&)\n end\n\n def dom_id\n @dom_id ||= begin\n object_id = if object.nil?\n nil\n elsif (primary_key = ChronoForge.object_primary_key(object))\n primary_key&.to_s || :new\n end\n [key, object_id].compact.join(\"_\").underscore\n end\n end\n\n # Creates a root Namespace.\n def self.root(*, builder_klass:, **, &)\n new(*, parent: nil, builder_klass:, **, &)\n end\n\n protected\n\n def object_value_for(key:)\n ChronoForge::Support::Value.from(@object, key)\n end\n\n private\n\n # Checks if the child exists. If it does then it returns that. If it doesn't, it will\n # build the child.\n def create_child(key, child_class, template: false, **, &)\n if template\n child_class.new(key, parent: self, **, &)\n else\n @children.fetch(key) { @children[key] = child_class.new(key, parent: self, **, &) }\n end\n end\n end\n end\n end\nend\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/structure/namespace_collection.rb",
72
- "contents": "# frozen_string_literal: true\n\nmodule Phlexi\n module Field\n module Structure\n class NamespaceCollection < Node\n include Enumerable\n\n def initialize(key, parent:, collection: nil, &block)\n raise ArgumentError, \"block is required\" unless block.present?\n\n super(key, parent: parent)\n\n @collection = collection\n @block = block\n each(&block)\n end\n\n def object\n @collection\n end\n\n private\n\n def each(&)\n namespaces.each(&)\n end\n\n # Builds and memoizes namespaces for the collection.\n #\n # @return [Array<Namespace>] An array of namespace objects.\n def namespaces\n @namespaces ||= case @collection\n when Hash\n @collection.map do |key, object|\n build_namespace(key, object: object)\n end\n when Array\n @collection.map.with_index do |object, key|\n build_namespace(key, object: object)\n end\n end\n end\n\n def build_namespace(index, **)\n parent.class.new(index, parent: self, builder_klass: parent.builder_klass, **)\n end\n end\n end\n end\nend\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/structure/node.rb",
76
- "contents": "# frozen_string_literal: true\n\nmodule Phlexi\n module Field\n module Structure\n # Superclass for Namespace and Field classes. Represents a node in the field tree structure.\n #\n # @attr_reader [Symbol] key The node's key\n # @attr_reader [Node, nil] parent The node's parent in the tree structure\n class Node\n attr_reader :key, :parent\n\n # Initializes a new Node instance.\n #\n # @param key [Symbol, String] The key for the node\n # @param parent [Node, nil] The parent node\n def initialize(key, parent:)\n @key = :\"#{key}\"\n @parent = parent\n end\n\n def inspect\n \"<#{self.class.name} key=#{key.inspect} object=#{object.inspect} parent=#{parent.inspect} />\"\n end\n end\n end\n end\nend\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/lib/chrono_forge/support/value.rb",
80
- "contents": "module Phlexi\n module Field\n module Support\n module Value\n def self.from(object, key)\n return object[key] if object.is_a?(Hash)\n object.public_send(key) if object.respond_to?(key)\n end\n end\n end\n end\nend\n"
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/lib/chrono_forge/theme.rb",
84
- "contents": "require \"fiber/local\"\n\nmodule Phlexi\n module Field\n class Theme\n def self.inherited(subclass)\n super\n subclass.extend Fiber::Local\n end\n\n # Retrieves the theme hash\n #\n # This method returns a hash containing theme definitions for various display components.\n # If a theme has been explicitly set in the options, it returns that. Otherwise, it\n # initializes and returns a default theme.\n #\n # The theme hash defines CSS classes or references to other theme keys for different\n # components, allowing for a flexible and inheritance-based theming system.\n #\n # @return [Hash] A hash containing theme definitions for display components\n #\n # @example Accessing the theme\n # theme[:text]\n # # => \"text-gray-700 text-sm\"\n #\n # @example Theme inheritance\n # theme[:email] # Returns :text, indicating email inherits text's theme\n def self.theme\n raise NotImplementedError, \"#{self} must implement #self.theme\"\n end\n\n def theme\n @theme ||= self.class.theme.freeze\n end\n\n # Recursively resolves the theme for a given property, handling nested symbol references\n #\n # @param property [Symbol, String] The theme property to resolve\n # @param visited [Set] Set of already visited properties to prevent infinite recursion\n # @return [String, nil] The resolved theme value or nil if not found\n #\n # @example Basic usage\n # # Assuming the theme is: { text: \"text-gray-700\", email: :text }\n # themed(:text)\n # # => \"text-gray-700 text-sm\"\n #\n # @example Cascading themes\n # # Assuming the theme is: { text: \"text-gray-700\", email: :text }\n # resolve_theme(:email)\n # # => \"text-gray-700\"\n def resolve_theme(property, visited = Set.new)\n return nil if !property.present? || visited.include?(property)\n visited.add(property)\n\n result = theme[property]\n if result.is_a?(Symbol)\n resolve_theme(result, visited)\n else\n result\n end\n end\n end\n end\nend\n"
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/lib/chrono_forge/version.rb",
88
- "contents": "# frozen_string_literal: true\n\nmodule Phlexi\n module Field\n VERSION = \"0.0.9\"\n end\nend\n"
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/lib/chrono_forge.rb",
92
- "contents": "# frozen_string_literal: true\n\nrequire \"zeitwerk\"\nrequire \"phlex\"\nrequire \"active_support/core_ext/object/blank\"\n\nmodule Phlexi\n NIL_VALUE = :__i_phlexi_i__\n\n module Field\n Loader = Zeitwerk::Loader.new.tap do |loader|\n loader.tag = File.basename(__FILE__, \".rb\")\n loader.ignore(\"#{__dir__}/field/version.rb\")\n loader.inflector.inflect(\n \"chrono_forge\" => \"Phlexi\",\n \"phlexi\" => \"Phlexi\",\n \"dom\" => \"DOM\"\n )\n loader.push_dir(File.expand_path(\"..\", __dir__))\n loader.setup\n end\n\n COMPONENT_BASE = (defined?(::ApplicationComponent) ? ::ApplicationComponent : Phlex::HTML)\n\n class Error < StandardError; end\n\n def self.object_primary_key(object)\n if object.class.respond_to?(:primary_key)\n object.send(object.class.primary_key.to_sym)\n elsif object.respond_to?(:id)\n object.id\n end\n end\n end\nend\n"
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/lib/chrono_forge.rb",
96
- "contents": "# frozen_string_literal: true\n\nrequire_relative \"chrono_forge/version\"\nrequire_relative \"chrono_forge\"\n"
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/chrono_forge.gemspec",
100
- "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 \"phlex\", \"~> 1.11\"\n spec.add_dependency \"activesupport\"\n spec.add_dependency \"zeitwerk\"\n spec.add_dependency \"fiber-local\"\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 \"phlex-testing-capybara\"\nend\n"
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", "test", "node_modules"]
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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ChronoForge
4
- VERSION = "0.0.2"
4
+ VERSION = "0.1.0"
5
5
  end
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.2
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: Base fields for the Phlexi libraries
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: Base fields for the Phlexi libraries
232
+ summary: A durable workflow engine for Ruby on Rails built on ActiveJob
230
233
  test_files: []