fractor 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +227 -102
  3. data/README.adoc +113 -1940
  4. data/docs/.lycheeignore +16 -0
  5. data/docs/Gemfile +24 -0
  6. data/docs/README.md +157 -0
  7. data/docs/_config.yml +151 -0
  8. data/docs/_features/error-handling.adoc +1192 -0
  9. data/docs/_features/index.adoc +80 -0
  10. data/docs/_features/monitoring.adoc +589 -0
  11. data/docs/_features/signal-handling.adoc +202 -0
  12. data/docs/_features/workflows.adoc +1235 -0
  13. data/docs/_guides/continuous-mode.adoc +736 -0
  14. data/docs/_guides/cookbook.adoc +1133 -0
  15. data/docs/_guides/index.adoc +55 -0
  16. data/docs/_guides/pipeline-mode.adoc +730 -0
  17. data/docs/_guides/troubleshooting.adoc +358 -0
  18. data/docs/_pages/architecture.adoc +1390 -0
  19. data/docs/_pages/core-concepts.adoc +1392 -0
  20. data/docs/_pages/design-principles.adoc +862 -0
  21. data/docs/_pages/getting-started.adoc +290 -0
  22. data/docs/_pages/installation.adoc +143 -0
  23. data/docs/_reference/api.adoc +1080 -0
  24. data/docs/_reference/error-reporting.adoc +670 -0
  25. data/docs/_reference/examples.adoc +181 -0
  26. data/docs/_reference/index.adoc +96 -0
  27. data/docs/_reference/troubleshooting.adoc +862 -0
  28. data/docs/_tutorials/complex-workflows.adoc +1022 -0
  29. data/docs/_tutorials/data-processing-pipeline.adoc +740 -0
  30. data/docs/_tutorials/first-application.adoc +384 -0
  31. data/docs/_tutorials/index.adoc +48 -0
  32. data/docs/_tutorials/long-running-services.adoc +931 -0
  33. data/docs/assets/images/favicon-16.png +0 -0
  34. data/docs/assets/images/favicon-32.png +0 -0
  35. data/docs/assets/images/favicon-48.png +0 -0
  36. data/docs/assets/images/favicon.ico +0 -0
  37. data/docs/assets/images/favicon.png +0 -0
  38. data/docs/assets/images/favicon.svg +45 -0
  39. data/docs/assets/images/fractor-icon.svg +49 -0
  40. data/docs/assets/images/fractor-logo.svg +61 -0
  41. data/docs/index.adoc +131 -0
  42. data/docs/lychee.toml +39 -0
  43. data/examples/api_aggregator/README.adoc +627 -0
  44. data/examples/api_aggregator/api_aggregator.rb +376 -0
  45. data/examples/auto_detection/README.adoc +407 -29
  46. data/examples/continuous_chat_common/message_protocol.rb +1 -1
  47. data/examples/error_reporting.rb +207 -0
  48. data/examples/file_processor/README.adoc +170 -0
  49. data/examples/file_processor/file_processor.rb +615 -0
  50. data/examples/file_processor/sample_files/invalid.csv +1 -0
  51. data/examples/file_processor/sample_files/orders.xml +24 -0
  52. data/examples/file_processor/sample_files/products.json +23 -0
  53. data/examples/file_processor/sample_files/users.csv +6 -0
  54. data/examples/hierarchical_hasher/README.adoc +629 -41
  55. data/examples/image_processor/README.adoc +610 -0
  56. data/examples/image_processor/image_processor.rb +349 -0
  57. data/examples/image_processor/processed_images/sample_10_processed.jpg.json +12 -0
  58. data/examples/image_processor/processed_images/sample_1_processed.jpg.json +12 -0
  59. data/examples/image_processor/processed_images/sample_2_processed.jpg.json +12 -0
  60. data/examples/image_processor/processed_images/sample_3_processed.jpg.json +12 -0
  61. data/examples/image_processor/processed_images/sample_4_processed.jpg.json +12 -0
  62. data/examples/image_processor/processed_images/sample_5_processed.jpg.json +12 -0
  63. data/examples/image_processor/processed_images/sample_6_processed.jpg.json +12 -0
  64. data/examples/image_processor/processed_images/sample_7_processed.jpg.json +12 -0
  65. data/examples/image_processor/processed_images/sample_8_processed.jpg.json +12 -0
  66. data/examples/image_processor/processed_images/sample_9_processed.jpg.json +12 -0
  67. data/examples/image_processor/test_images/sample_1.png +1 -0
  68. data/examples/image_processor/test_images/sample_10.png +1 -0
  69. data/examples/image_processor/test_images/sample_2.png +1 -0
  70. data/examples/image_processor/test_images/sample_3.png +1 -0
  71. data/examples/image_processor/test_images/sample_4.png +1 -0
  72. data/examples/image_processor/test_images/sample_5.png +1 -0
  73. data/examples/image_processor/test_images/sample_6.png +1 -0
  74. data/examples/image_processor/test_images/sample_7.png +1 -0
  75. data/examples/image_processor/test_images/sample_8.png +1 -0
  76. data/examples/image_processor/test_images/sample_9.png +1 -0
  77. data/examples/log_analyzer/README.adoc +662 -0
  78. data/examples/log_analyzer/log_analyzer.rb +579 -0
  79. data/examples/log_analyzer/sample_logs/apache.log +20 -0
  80. data/examples/log_analyzer/sample_logs/json.log +15 -0
  81. data/examples/log_analyzer/sample_logs/nginx.log +15 -0
  82. data/examples/log_analyzer/sample_logs/rails.log +29 -0
  83. data/examples/multi_work_type/README.adoc +576 -26
  84. data/examples/performance_monitoring.rb +120 -0
  85. data/examples/pipeline_processing/README.adoc +740 -26
  86. data/examples/pipeline_processing/pipeline_processing.rb +2 -2
  87. data/examples/priority_work_example.rb +155 -0
  88. data/examples/producer_subscriber/README.adoc +889 -46
  89. data/examples/scatter_gather/README.adoc +829 -27
  90. data/examples/simple/README.adoc +347 -0
  91. data/examples/specialized_workers/README.adoc +622 -26
  92. data/examples/specialized_workers/specialized_workers.rb +44 -8
  93. data/examples/stream_processor/README.adoc +206 -0
  94. data/examples/stream_processor/stream_processor.rb +284 -0
  95. data/examples/web_scraper/README.adoc +625 -0
  96. data/examples/web_scraper/web_scraper.rb +285 -0
  97. data/examples/workflow/README.adoc +406 -0
  98. data/examples/workflow/circuit_breaker/README.adoc +360 -0
  99. data/examples/workflow/circuit_breaker/circuit_breaker_workflow.rb +225 -0
  100. data/examples/workflow/conditional/README.adoc +483 -0
  101. data/examples/workflow/conditional/conditional_workflow.rb +215 -0
  102. data/examples/workflow/dead_letter_queue/README.adoc +374 -0
  103. data/examples/workflow/dead_letter_queue/dead_letter_queue_workflow.rb +217 -0
  104. data/examples/workflow/fan_out/README.adoc +381 -0
  105. data/examples/workflow/fan_out/fan_out_workflow.rb +202 -0
  106. data/examples/workflow/retry/README.adoc +248 -0
  107. data/examples/workflow/retry/retry_workflow.rb +195 -0
  108. data/examples/workflow/simple_linear/README.adoc +267 -0
  109. data/examples/workflow/simple_linear/simple_linear_workflow.rb +175 -0
  110. data/examples/workflow/simplified/README.adoc +329 -0
  111. data/examples/workflow/simplified/simplified_workflow.rb +222 -0
  112. data/exe/fractor +10 -0
  113. data/lib/fractor/cli.rb +288 -0
  114. data/lib/fractor/configuration.rb +307 -0
  115. data/lib/fractor/continuous_server.rb +60 -65
  116. data/lib/fractor/error_formatter.rb +72 -0
  117. data/lib/fractor/error_report_generator.rb +152 -0
  118. data/lib/fractor/error_reporter.rb +244 -0
  119. data/lib/fractor/error_statistics.rb +147 -0
  120. data/lib/fractor/execution_tracer.rb +162 -0
  121. data/lib/fractor/logger.rb +230 -0
  122. data/lib/fractor/main_loop_handler.rb +406 -0
  123. data/lib/fractor/main_loop_handler3.rb +135 -0
  124. data/lib/fractor/main_loop_handler4.rb +299 -0
  125. data/lib/fractor/performance_metrics_collector.rb +181 -0
  126. data/lib/fractor/performance_monitor.rb +215 -0
  127. data/lib/fractor/performance_report_generator.rb +202 -0
  128. data/lib/fractor/priority_work.rb +93 -0
  129. data/lib/fractor/priority_work_queue.rb +189 -0
  130. data/lib/fractor/result_aggregator.rb +32 -0
  131. data/lib/fractor/shutdown_handler.rb +168 -0
  132. data/lib/fractor/signal_handler.rb +80 -0
  133. data/lib/fractor/supervisor.rb +382 -269
  134. data/lib/fractor/supervisor_logger.rb +88 -0
  135. data/lib/fractor/version.rb +1 -1
  136. data/lib/fractor/work.rb +12 -0
  137. data/lib/fractor/work_distribution_manager.rb +151 -0
  138. data/lib/fractor/work_queue.rb +20 -0
  139. data/lib/fractor/work_result.rb +181 -9
  140. data/lib/fractor/worker.rb +73 -0
  141. data/lib/fractor/workflow/builder.rb +210 -0
  142. data/lib/fractor/workflow/chain_builder.rb +169 -0
  143. data/lib/fractor/workflow/circuit_breaker.rb +183 -0
  144. data/lib/fractor/workflow/circuit_breaker_orchestrator.rb +208 -0
  145. data/lib/fractor/workflow/circuit_breaker_registry.rb +112 -0
  146. data/lib/fractor/workflow/dead_letter_queue.rb +334 -0
  147. data/lib/fractor/workflow/execution_hooks.rb +39 -0
  148. data/lib/fractor/workflow/execution_strategy.rb +225 -0
  149. data/lib/fractor/workflow/execution_trace.rb +134 -0
  150. data/lib/fractor/workflow/helpers.rb +191 -0
  151. data/lib/fractor/workflow/job.rb +290 -0
  152. data/lib/fractor/workflow/job_dependency_validator.rb +120 -0
  153. data/lib/fractor/workflow/logger.rb +110 -0
  154. data/lib/fractor/workflow/pre_execution_context.rb +193 -0
  155. data/lib/fractor/workflow/retry_config.rb +156 -0
  156. data/lib/fractor/workflow/retry_orchestrator.rb +184 -0
  157. data/lib/fractor/workflow/retry_strategy.rb +93 -0
  158. data/lib/fractor/workflow/structured_logger.rb +30 -0
  159. data/lib/fractor/workflow/type_compatibility_validator.rb +222 -0
  160. data/lib/fractor/workflow/visualizer.rb +211 -0
  161. data/lib/fractor/workflow/workflow_context.rb +132 -0
  162. data/lib/fractor/workflow/workflow_executor.rb +669 -0
  163. data/lib/fractor/workflow/workflow_result.rb +55 -0
  164. data/lib/fractor/workflow/workflow_validator.rb +295 -0
  165. data/lib/fractor/workflow.rb +333 -0
  166. data/lib/fractor/wrapped_ractor.rb +66 -101
  167. data/lib/fractor/wrapped_ractor3.rb +161 -0
  168. data/lib/fractor/wrapped_ractor4.rb +242 -0
  169. data/lib/fractor.rb +92 -4
  170. metadata +179 -6
  171. data/tests/sample.rb.bak +0 -309
  172. data/tests/sample_working.rb.bak +0 -209
@@ -0,0 +1,349 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../../lib/fractor"
5
+ require "fileutils"
6
+ require "json"
7
+
8
+ module ImageProcessor
9
+ # ImageWork encapsulates an image file and the processing operations to perform
10
+ class ImageWork < Fractor::Work
11
+ def initialize(input_path, output_path, operations = {})
12
+ super({
13
+ input_path: input_path,
14
+ output_path: output_path,
15
+ operations: operations
16
+ })
17
+ end
18
+
19
+ def input_path
20
+ input[:input_path]
21
+ end
22
+
23
+ def output_path
24
+ input[:output_path]
25
+ end
26
+
27
+ def operations
28
+ input[:operations]
29
+ end
30
+
31
+ def to_s
32
+ "ImageWork(#{File.basename(input_path)} -> #{operations.keys.join(", ")})"
33
+ end
34
+ end
35
+
36
+ # ImageProcessorWorker performs image processing operations
37
+ class ImageProcessorWorker < Fractor::Worker
38
+ def process(work)
39
+ unless work.is_a?(ImageWork)
40
+ raise ArgumentError, "Expected ImageWork, got #{work.class}"
41
+ end
42
+
43
+ # Simulate image processing without requiring actual image libraries
44
+ # In real implementation, would use mini_magick or similar
45
+ process_image(work)
46
+
47
+ {
48
+ input: work.input_path,
49
+ output: work.output_path,
50
+ operations: work.operations,
51
+ status: "success",
52
+ file_size: simulate_file_size(work),
53
+ processing_time: rand(0.1..0.5)
54
+ }
55
+ rescue ArgumentError => e
56
+ # Re-raise ArgumentError for invalid work type
57
+ raise e
58
+ rescue StandardError => e
59
+ {
60
+ input: work.input_path,
61
+ output: work.output_path,
62
+ status: "error",
63
+ error: e.message
64
+ }
65
+ end
66
+
67
+ private
68
+
69
+ def process_image(work)
70
+ # Validate input file exists (in real scenario)
71
+ unless File.exist?(work.input_path)
72
+ raise "Input file not found: #{work.input_path}"
73
+ end
74
+
75
+ # Create output directory if needed
76
+ FileUtils.mkdir_p(File.dirname(work.output_path))
77
+
78
+ # Simulate processing based on operations
79
+ operations = work.operations
80
+
81
+ # Simulate different processing times based on operations
82
+ sleep_time = 0.05 # Base time
83
+
84
+ if operations[:resize]
85
+ sleep_time += 0.02
86
+ validate_resize_params(operations[:resize])
87
+ end
88
+
89
+ if operations[:convert]
90
+ sleep_time += 0.01
91
+ validate_format(operations[:convert])
92
+ end
93
+
94
+ if operations[:filter]
95
+ sleep_time += 0.03
96
+ validate_filter(operations[:filter])
97
+ end
98
+
99
+ if operations[:brightness]
100
+ sleep_time += 0.01
101
+ validate_brightness(operations[:brightness])
102
+ end
103
+
104
+ # Simulate processing time
105
+ sleep(sleep_time)
106
+
107
+ # In real implementation, would process the actual image here
108
+ # For simulation, copy the file or create a marker file
109
+ create_processed_output(work)
110
+ end
111
+
112
+ def validate_resize_params(resize_params)
113
+ width = resize_params[:width]
114
+ height = resize_params[:height]
115
+
116
+ if width && width <= 0
117
+ raise "Invalid width: #{width}"
118
+ end
119
+
120
+ if height && height <= 0
121
+ raise "Invalid height: #{height}"
122
+ end
123
+ end
124
+
125
+ def validate_format(format)
126
+ valid_formats = %w[jpg jpeg png gif bmp webp]
127
+ unless valid_formats.include?(format.to_s.downcase)
128
+ raise "Unsupported format: #{format}"
129
+ end
130
+ end
131
+
132
+ def validate_filter(filter)
133
+ valid_filters = %w[grayscale sepia blur sharpen]
134
+ unless valid_filters.include?(filter.to_s.downcase)
135
+ raise "Unknown filter: #{filter}"
136
+ end
137
+ end
138
+
139
+ def validate_brightness(brightness)
140
+ unless brightness.is_a?(Numeric) && brightness >= -100 && brightness <= 100
141
+ raise "Brightness must be between -100 and 100"
142
+ end
143
+ end
144
+
145
+ def simulate_file_size(work)
146
+ # Simulate output file size based on operations
147
+ base_size = 102400 # 100KB base
148
+
149
+ if work.operations[:resize]
150
+ width = work.operations[:resize][:width] || 1000
151
+ height = work.operations[:resize][:height] || 1000
152
+ base_size = (width * height * 3) / 10 # Rough estimate
153
+ end
154
+
155
+ if work.operations[:convert] == "jpg"
156
+ base_size = (base_size * 0.6).to_i # JPEG compression
157
+ end
158
+
159
+ base_size
160
+ end
161
+
162
+ def create_processed_output(work)
163
+ # In simulation mode, create a JSON file with processing metadata
164
+ # In real mode, this would be the actual processed image
165
+ output_dir = File.dirname(work.output_path)
166
+ FileUtils.mkdir_p(output_dir)
167
+
168
+ metadata = {
169
+ original: work.input_path,
170
+ operations: work.operations,
171
+ processed_at: Time.now.iso8601
172
+ }
173
+
174
+ # Create a marker file to show processing occurred
175
+ File.write("#{work.output_path}.json", JSON.pretty_generate(metadata))
176
+ end
177
+ end
178
+
179
+ # ProgressTracker monitors and displays processing progress
180
+ class ProgressTracker
181
+ attr_reader :total, :completed, :errors
182
+
183
+ def initialize(total)
184
+ @total = total
185
+ @completed = 0
186
+ @errors = 0
187
+ @start_time = Time.now
188
+ @lock = Mutex.new
189
+ end
190
+
191
+ def increment_completed
192
+ @lock.synchronize do
193
+ @completed += 1
194
+ print_progress
195
+ end
196
+ end
197
+
198
+ def increment_errors
199
+ @lock.synchronize do
200
+ @errors += 1
201
+ @completed += 1
202
+ print_progress
203
+ end
204
+ end
205
+
206
+ def percentage
207
+ return 0 if total.zero?
208
+
209
+ ((completed.to_f / total) * 100).round(2)
210
+ end
211
+
212
+ def elapsed_time
213
+ Time.now - @start_time
214
+ end
215
+
216
+ def estimated_remaining
217
+ return 0 if completed.zero?
218
+
219
+ rate = elapsed_time / completed
220
+ (total - completed) * rate
221
+ end
222
+
223
+ def print_progress
224
+ print "\rProcessing: #{completed}/#{total} (#{percentage}%) | "
225
+ print "Errors: #{errors} | "
226
+ print "Elapsed: #{format_time(elapsed_time)} | "
227
+ print "Est. remaining: #{format_time(estimated_remaining)}"
228
+ $stdout.flush
229
+ end
230
+
231
+ def print_summary
232
+ puts "\n\n=== Processing Complete ==="
233
+ puts "Total: #{total}"
234
+ puts "Successful: #{completed - errors}"
235
+ puts "Errors: #{errors}"
236
+ puts "Total time: #{format_time(elapsed_time)}"
237
+ puts "Average time per image: #{format_time(elapsed_time / total)}"
238
+ end
239
+
240
+ private
241
+
242
+ def format_time(seconds)
243
+ if seconds < 60
244
+ format("%.2fs", seconds)
245
+ else
246
+ minutes = (seconds / 60).to_i
247
+ secs = (seconds % 60).to_i
248
+ format("%dm %ds", minutes, secs)
249
+ end
250
+ end
251
+ end
252
+
253
+ # Main execution
254
+ if __FILE__ == $PROGRAM_NAME
255
+ # Create sample test images if they don't exist
256
+ test_images_dir = File.join(__dir__, "test_images")
257
+ FileUtils.mkdir_p(test_images_dir)
258
+
259
+ # Create dummy test image files for demonstration
260
+ sample_images = []
261
+ 10.times do |i|
262
+ img_path = File.join(test_images_dir, "sample_#{i + 1}.png")
263
+ unless File.exist?(img_path)
264
+ File.write(img_path, "FAKE_PNG_DATA_#{i}")
265
+ end
266
+ sample_images << img_path
267
+ end
268
+
269
+ puts "=== Image Batch Processor with Fractor ==="
270
+ puts "Processing #{sample_images.size} images in parallel"
271
+ puts
272
+
273
+ # Create output directory
274
+ output_dir = File.join(__dir__, "processed_images")
275
+ FileUtils.mkdir_p(output_dir)
276
+
277
+ # Define processing operations
278
+ operations = {
279
+ resize: { width: 800, height: 600 },
280
+ filter: "grayscale",
281
+ convert: "jpg"
282
+ }
283
+
284
+ # Create work items
285
+ work_items = sample_images.map do |img_path|
286
+ output_path = File.join(
287
+ output_dir,
288
+ File.basename(img_path, ".*") + "_processed.jpg"
289
+ )
290
+ ImageProcessor::ImageWork.new(img_path, output_path, operations)
291
+ end
292
+
293
+ # Initialize progress tracker
294
+ tracker = ImageProcessor::ProgressTracker.new(work_items.size)
295
+
296
+ # Process with Fractor
297
+ start_time = Time.now
298
+
299
+ supervisor = Fractor::Supervisor.new(
300
+ worker_pools: [
301
+ { worker_class: ImageProcessor::ImageProcessorWorker, num_workers: 4 }
302
+ ]
303
+ )
304
+
305
+ # Submit all work
306
+ supervisor.add_work_items(work_items)
307
+
308
+ # Start processing
309
+ supervisor.run
310
+
311
+ # Collect results and update progress
312
+ results = []
313
+ all_results = supervisor.results.results + supervisor.results.errors
314
+
315
+ all_results.each do |work_result|
316
+ result = work_result.result || {
317
+ status: "error",
318
+ error: work_result.error&.message || "Unknown error"
319
+ }
320
+ results << result
321
+
322
+ if result[:status] == "error"
323
+ tracker.increment_errors
324
+ else
325
+ tracker.increment_completed
326
+ end
327
+ end
328
+
329
+ # Print summary
330
+ tracker.print_summary
331
+
332
+ # Show sample results
333
+ puts "\n=== Sample Results ==="
334
+ results.first(3).each do |result|
335
+ puts "\nInput: #{File.basename(result[:input])}"
336
+ puts "Output: #{File.basename(result[:output])}"
337
+ puts "Operations: #{result[:operations].inspect}"
338
+ puts "Status: #{result[:status]}"
339
+ if result[:status] == "success"
340
+ puts "File size: #{result[:file_size]} bytes"
341
+ puts "Processing time: #{format("%.3f", result[:processing_time])}s"
342
+ else
343
+ puts "Error: #{result[:error]}"
344
+ end
345
+ end
346
+
347
+ puts "\nProcessed images saved to: #{output_dir}"
348
+ end
349
+ end
@@ -0,0 +1,12 @@
1
+ {
2
+ "original": "/Users/mulgogi/src/mn/fractor/examples/image_processor/test_images/sample_10.png",
3
+ "operations": {
4
+ "resize": {
5
+ "width": 800,
6
+ "height": 600
7
+ },
8
+ "filter": "grayscale",
9
+ "convert": "jpg"
10
+ },
11
+ "processed_at": "2025-10-25T22:16:20+09:00"
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "original": "/Users/mulgogi/src/mn/fractor/examples/image_processor/test_images/sample_1.png",
3
+ "operations": {
4
+ "resize": {
5
+ "width": 800,
6
+ "height": 600
7
+ },
8
+ "filter": "grayscale",
9
+ "convert": "jpg"
10
+ },
11
+ "processed_at": "2025-10-25T22:16:20+09:00"
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "original": "/Users/mulgogi/src/mn/fractor/examples/image_processor/test_images/sample_2.png",
3
+ "operations": {
4
+ "resize": {
5
+ "width": 800,
6
+ "height": 600
7
+ },
8
+ "filter": "grayscale",
9
+ "convert": "jpg"
10
+ },
11
+ "processed_at": "2025-10-25T22:16:20+09:00"
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "original": "/Users/mulgogi/src/mn/fractor/examples/image_processor/test_images/sample_3.png",
3
+ "operations": {
4
+ "resize": {
5
+ "width": 800,
6
+ "height": 600
7
+ },
8
+ "filter": "grayscale",
9
+ "convert": "jpg"
10
+ },
11
+ "processed_at": "2025-10-25T22:16:20+09:00"
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "original": "/Users/mulgogi/src/mn/fractor/examples/image_processor/test_images/sample_4.png",
3
+ "operations": {
4
+ "resize": {
5
+ "width": 800,
6
+ "height": 600
7
+ },
8
+ "filter": "grayscale",
9
+ "convert": "jpg"
10
+ },
11
+ "processed_at": "2025-10-25T22:16:20+09:00"
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "original": "/Users/mulgogi/src/mn/fractor/examples/image_processor/test_images/sample_5.png",
3
+ "operations": {
4
+ "resize": {
5
+ "width": 800,
6
+ "height": 600
7
+ },
8
+ "filter": "grayscale",
9
+ "convert": "jpg"
10
+ },
11
+ "processed_at": "2025-10-25T22:16:20+09:00"
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "original": "/Users/mulgogi/src/mn/fractor/examples/image_processor/test_images/sample_6.png",
3
+ "operations": {
4
+ "resize": {
5
+ "width": 800,
6
+ "height": 600
7
+ },
8
+ "filter": "grayscale",
9
+ "convert": "jpg"
10
+ },
11
+ "processed_at": "2025-10-25T22:16:20+09:00"
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "original": "/Users/mulgogi/src/mn/fractor/examples/image_processor/test_images/sample_7.png",
3
+ "operations": {
4
+ "resize": {
5
+ "width": 800,
6
+ "height": 600
7
+ },
8
+ "filter": "grayscale",
9
+ "convert": "jpg"
10
+ },
11
+ "processed_at": "2025-10-25T22:16:20+09:00"
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "original": "/Users/mulgogi/src/mn/fractor/examples/image_processor/test_images/sample_8.png",
3
+ "operations": {
4
+ "resize": {
5
+ "width": 800,
6
+ "height": 600
7
+ },
8
+ "filter": "grayscale",
9
+ "convert": "jpg"
10
+ },
11
+ "processed_at": "2025-10-25T22:16:20+09:00"
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "original": "/Users/mulgogi/src/mn/fractor/examples/image_processor/test_images/sample_9.png",
3
+ "operations": {
4
+ "resize": {
5
+ "width": 800,
6
+ "height": 600
7
+ },
8
+ "filter": "grayscale",
9
+ "convert": "jpg"
10
+ },
11
+ "processed_at": "2025-10-25T22:16:20+09:00"
12
+ }
@@ -0,0 +1 @@
1
+ FAKE_PNG_DATA_0
@@ -0,0 +1 @@
1
+ FAKE_PNG_DATA_9
@@ -0,0 +1 @@
1
+ FAKE_PNG_DATA_1
@@ -0,0 +1 @@
1
+ FAKE_PNG_DATA_2
@@ -0,0 +1 @@
1
+ FAKE_PNG_DATA_3
@@ -0,0 +1 @@
1
+ FAKE_PNG_DATA_4
@@ -0,0 +1 @@
1
+ FAKE_PNG_DATA_5
@@ -0,0 +1 @@
1
+ FAKE_PNG_DATA_6
@@ -0,0 +1 @@
1
+ FAKE_PNG_DATA_7
@@ -0,0 +1 @@
1
+ FAKE_PNG_DATA_8