ruby_reactor 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-config.json +3 -0
  3. data/.release-please-manifest.json +1 -1
  4. data/.rubocop.yml +1 -0
  5. data/CHANGELOG.md +15 -0
  6. data/README.md +8 -2
  7. data/lib/ruby_reactor/configuration.rb +6 -1
  8. data/lib/ruby_reactor/context.rb +6 -2
  9. data/lib/ruby_reactor/context_serializer.rb +44 -2
  10. data/lib/ruby_reactor/dsl/reactor.rb +16 -3
  11. data/lib/ruby_reactor/executor/compensation_manager.rb +75 -47
  12. data/lib/ruby_reactor/executor/result_handler.rb +1 -12
  13. data/lib/ruby_reactor/executor/retry_manager.rb +15 -5
  14. data/lib/ruby_reactor/executor/step_executor.rb +36 -18
  15. data/lib/ruby_reactor/executor.rb +119 -37
  16. data/lib/ruby_reactor/map/collector.rb +4 -4
  17. data/lib/ruby_reactor/map/element_executor.rb +15 -1
  18. data/lib/ruby_reactor/map/helpers.rb +17 -4
  19. data/lib/ruby_reactor/middleware.rb +13 -0
  20. data/lib/ruby_reactor/middleware_runner.rb +29 -0
  21. data/lib/ruby_reactor/open_telemetry.rb +647 -0
  22. data/lib/ruby_reactor/reactor.rb +12 -4
  23. data/lib/ruby_reactor/rspec/test_subject.rb +0 -1
  24. data/lib/ruby_reactor/sidekiq_adapter.rb +7 -21
  25. data/lib/ruby_reactor/step/map_step.rb +25 -33
  26. data/lib/ruby_reactor/storage/redis_adapter.rb +10 -3
  27. data/lib/ruby_reactor/storage/redis_locking.rb +17 -0
  28. data/lib/ruby_reactor/utils/backtrace_location.rb +37 -0
  29. data/lib/ruby_reactor/version.rb +1 -1
  30. data/lib/ruby_reactor/web/api.rb +68 -8
  31. data/lib/ruby_reactor/web/coordination_serializer.rb +174 -0
  32. data/lib/ruby_reactor/web/public/assets/index-CCnNVQy5.css +1 -0
  33. data/lib/ruby_reactor/web/public/assets/index-D7IBZvos.js +21 -0
  34. data/lib/ruby_reactor/web/public/index.html +2 -2
  35. data/lib/ruby_reactor.rb +7 -2
  36. data/teley/Dockerfile +60 -0
  37. metadata +10 -6
  38. data/lib/ruby_reactor/map/execution.rb +0 -101
  39. data/lib/ruby_reactor/sidekiq_workers/map_execution_worker.rb +0 -15
  40. data/lib/ruby_reactor/web/public/assets/index-VdeLgH9k.js +0 -19
  41. data/lib/ruby_reactor/web/public/assets/index-_z-6BvuM.css +0 -1
@@ -5,8 +5,8 @@
5
5
  <link rel="icon" type="image/svg+xml" href="./vite.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>ui</title>
8
- <script type="module" crossorigin src="./assets/index-VdeLgH9k.js"></script>
9
- <link rel="stylesheet" crossorigin href="./assets/index-_z-6BvuM.css">
8
+ <script type="module" crossorigin src="./assets/index-D7IBZvos.js"></script>
9
+ <link rel="stylesheet" crossorigin href="./assets/index-CCnNVQy5.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
data/lib/ruby_reactor.rb CHANGED
@@ -238,13 +238,14 @@ module RubyReactor
238
238
  return backtrace if ENV["RUBY_REACTOR_DEBUG"] == "true"
239
239
  return backtrace if backtrace.nil? || backtrace.empty?
240
240
 
241
- root_path = RubyReactor.root.to_s
241
+ internal_prefix = RubyReactor.internal_lib_path
242
242
  filtered = []
243
243
  filtered << backtrace.first
244
244
 
245
245
  internal_block = false
246
246
  backtrace[1..]&.each do |line|
247
- if line.start_with?(root_path)
247
+ file_path, = RubyReactor::Utils::BacktraceLocation.parse(line)
248
+ if file_path&.start_with?(internal_prefix)
248
249
  unless internal_block
249
250
  filtered << "... [ruby-reactor-internals-redacted-trace]"
250
251
  internal_block = true
@@ -331,4 +332,8 @@ module RubyReactor
331
332
  def self.root
332
333
  Pathname.new(File.expand_path("..", __dir__))
333
334
  end
335
+
336
+ def self.internal_lib_path
337
+ File.join(root.to_s, "lib")
338
+ end
334
339
  end
data/teley/Dockerfile ADDED
@@ -0,0 +1,60 @@
1
+ # Dockerfile for teley
2
+ FROM node:24-slim
3
+
4
+ WORKDIR /app
5
+
6
+ # Install git and other build deps
7
+ RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
8
+
9
+ # Clone teley repo
10
+ RUN git clone https://github.com/logaretm/teley.git .
11
+
12
+ # Allow native builds for dependencies (like esbuild) via workspace config
13
+ RUN echo "dangerouslyAllowAllBuilds: true" > pnpm-workspace.yaml
14
+
15
+ # Patch workers/src/durable-object.ts to support default_token
16
+ RUN node -e ' \
17
+ const fs = require("fs"); \
18
+ const file = "workers/src/durable-object.ts"; \
19
+ let content = fs.readFileSync(file, "utf8"); \
20
+ content = content.replace("const token = url.searchParams.get(\x27token\x27);", "const token = url.searchParams.get(\x27token\x27) || \x27default_token\x27;"); \
21
+ fs.writeFileSync(file, content, "utf8"); \
22
+ '
23
+
24
+ # Patch app/pages/live/[roomId].vue to support default_token fallback
25
+ RUN node -e ' \
26
+ const fs = require("fs"); \
27
+ const file = "app/pages/live/[roomId].vue"; \
28
+ let content = fs.readFileSync(file, "utf8"); \
29
+ content = content.replace("const token = (route.query.token as string) || \x27\x27;", "const token = (route.query.token as string) || \x27default_token\x27;"); \
30
+ fs.writeFileSync(file, content, "utf8"); \
31
+ '
32
+
33
+ # Patch app/composables/useSession.ts to default to ruby_reactor_session and default_token
34
+ RUN node -e ' \
35
+ const fs = require("fs"); \
36
+ const file = "app/composables/useSession.ts"; \
37
+ let content = fs.readFileSync(file, "utf8"); \
38
+ content = content.replace(/const initialize = async \(\): Promise<void> => {[\s\S]*?};/, "const initialize = async (): Promise<void> => {\n if (initialized.value) return;\n roomId.value = \x27ruby_reactor_session\x27;\n receiveToken.value = \x27default_token\x27;\n isNewSession.value = false;\n initialized.value = true;\n };"); \
39
+ fs.writeFileSync(file, content, "utf8"); \
40
+ '
41
+
42
+
43
+ # Enable corepack and install pnpm
44
+ RUN corepack enable && corepack prepare pnpm@latest --activate
45
+
46
+ # Install dependencies
47
+ RUN pnpm install --frozen-lockfile
48
+
49
+ # Build the Nuxt static files
50
+ RUN pnpm build
51
+
52
+ # Expose port
53
+ EXPOSE 3000
54
+
55
+ ENV HOST=0.0.0.0
56
+ ENV PORT=3000
57
+
58
+ # Start Cloudflare Pages local dev server via wrangler
59
+ WORKDIR /app/workers
60
+ CMD ["npx", "wrangler", "dev", "--ip", "0.0.0.0", "--port", "3000"]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_reactor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Artur
@@ -133,10 +133,12 @@ files:
133
133
  - lib/ruby_reactor/map/collector.rb
134
134
  - lib/ruby_reactor/map/dispatcher.rb
135
135
  - lib/ruby_reactor/map/element_executor.rb
136
- - lib/ruby_reactor/map/execution.rb
137
136
  - lib/ruby_reactor/map/helpers.rb
138
137
  - lib/ruby_reactor/map/result_enumerator.rb
139
138
  - lib/ruby_reactor/max_retries_exhausted_failure.rb
139
+ - lib/ruby_reactor/middleware.rb
140
+ - lib/ruby_reactor/middleware_runner.rb
141
+ - lib/ruby_reactor/open_telemetry.rb
140
142
  - lib/ruby_reactor/period.rb
141
143
  - lib/ruby_reactor/rate_limit.rb
142
144
  - lib/ruby_reactor/reactor.rb
@@ -152,7 +154,6 @@ files:
152
154
  - lib/ruby_reactor/sidekiq_adapter.rb
153
155
  - lib/ruby_reactor/sidekiq_workers/map_collector_worker.rb
154
156
  - lib/ruby_reactor/sidekiq_workers/map_element_worker.rb
155
- - lib/ruby_reactor/sidekiq_workers/map_execution_worker.rb
156
157
  - lib/ruby_reactor/sidekiq_workers/worker.rb
157
158
  - lib/ruby_reactor/step.rb
158
159
  - lib/ruby_reactor/step/compose_step.rb
@@ -167,6 +168,7 @@ files:
167
168
  - lib/ruby_reactor/template/input.rb
168
169
  - lib/ruby_reactor/template/result.rb
169
170
  - lib/ruby_reactor/template/value.rb
171
+ - lib/ruby_reactor/utils/backtrace_location.rb
170
172
  - lib/ruby_reactor/utils/code_extractor.rb
171
173
  - lib/ruby_reactor/validation/base.rb
172
174
  - lib/ruby_reactor/validation/input_validator.rb
@@ -175,13 +177,15 @@ files:
175
177
  - lib/ruby_reactor/web/api.rb
176
178
  - lib/ruby_reactor/web/application.rb
177
179
  - lib/ruby_reactor/web/config.ru
178
- - lib/ruby_reactor/web/public/assets/index-VdeLgH9k.js
179
- - lib/ruby_reactor/web/public/assets/index-_z-6BvuM.css
180
+ - lib/ruby_reactor/web/coordination_serializer.rb
181
+ - lib/ruby_reactor/web/public/assets/index-CCnNVQy5.css
182
+ - lib/ruby_reactor/web/public/assets/index-D7IBZvos.js
180
183
  - lib/ruby_reactor/web/public/index.html
181
184
  - lib/ruby_reactor/web/public/vite.svg
182
185
  - llms-full.txt
183
186
  - llms.txt
184
187
  - sig/ruby_reactor.rbs
188
+ - teley/Dockerfile
185
189
  homepage: https://github.com/arturictus/ruby_reactor
186
190
  licenses: []
187
191
  metadata:
@@ -204,7 +208,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
204
208
  - !ruby/object:Gem::Version
205
209
  version: '0'
206
210
  requirements: []
207
- rubygems_version: 4.0.10
211
+ rubygems_version: 3.6.9
208
212
  specification_version: 4
209
213
  summary: A dynamic, concurrent, dependency-resolving saga orchestrator for Ruby.
210
214
  test_files: []
@@ -1,101 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyReactor
4
- module Map
5
- class Execution
6
- extend Helpers
7
-
8
- def self.perform(arguments)
9
- arguments = arguments.transform_keys(&:to_sym)
10
- storage = RubyReactor.configuration.storage_adapter
11
-
12
- parent_context = load_parent_context_from_storage(
13
- arguments[:parent_context_id], arguments[:parent_reactor_class_name], storage
14
- )
15
- reactor_class = resolve_reactor_class(arguments[:reactor_class_info])
16
- inputs = ContextSerializer.deserialize_value(arguments[:serialized_inputs])
17
-
18
- results = execute_all_elements(
19
- source: inputs[:source], mappings: inputs[:mappings],
20
- reactor_class: reactor_class, parent_context: parent_context,
21
- storage_options: {
22
- map_id: arguments[:map_id], storage: storage,
23
- parent_reactor_class_name: arguments[:parent_reactor_class_name],
24
- strict_ordering: arguments[:strict_ordering],
25
- fail_fast: arguments[:fail_fast]
26
- }
27
- )
28
-
29
- finalize_execution(results, parent_context, arguments[:step_name], arguments[:parent_reactor_class_name],
30
- storage)
31
- end
32
-
33
- def self.execute_all_elements(source:, mappings:, reactor_class:, parent_context:, storage_options:)
34
- # rubocop:disable Metrics/BlockLength
35
- source.map.with_index do |element, index|
36
- if storage_options[:fail_fast]
37
- failed_context_id = storage_options[:storage].retrieve_map_failed_context_id(
38
- storage_options[:map_id], storage_options[:parent_reactor_class_name]
39
- )
40
- next if failed_context_id
41
- end
42
- element_inputs = build_element_inputs(mappings, parent_context, element)
43
-
44
- # Manually create and link context to ensure parent_context_id is set
45
- child_context = RubyReactor::Context.new(element_inputs, reactor_class)
46
- link_contexts(child_context, parent_context)
47
-
48
- # Ensure we store the element context linkage
49
- storage_options[:storage].store_map_element_context_id(
50
- storage_options[:map_id], child_context.context_id, storage_options[:parent_reactor_class_name]
51
- )
52
-
53
- # Set map metadata for failure handling
54
- metadata = {
55
- map_id: storage_options[:map_id],
56
- parent_reactor_class_name: storage_options[:parent_reactor_class_name],
57
- index: index
58
- }
59
- child_context.map_metadata = metadata
60
-
61
- executor = RubyReactor::Executor.new(reactor_class, {}, child_context)
62
- executor.execute
63
- result = executor.result
64
-
65
- store_result(result, index, storage_options)
66
-
67
- if result.failure? && storage_options[:fail_fast]
68
- storage_options[:storage].store_map_failed_context_id(
69
- storage_options[:map_id], child_context.context_id, storage_options[:parent_reactor_class_name]
70
- )
71
- end
72
-
73
- result
74
- end.compact
75
- # rubocop:enable Metrics/BlockLength
76
- end
77
-
78
- def self.link_contexts(child_context, parent_context)
79
- child_context.parent_context = parent_context
80
- child_context.root_context = parent_context.root_context || parent_context
81
- child_context.inline_async_execution = parent_context.inline_async_execution
82
- end
83
-
84
- def self.store_result(result, index, options)
85
- value = result.success? ? result.value : { _error: result.error }
86
- options[:storage].store_map_result(
87
- options[:map_id], index, ContextSerializer.serialize_value(value), options[:parent_reactor_class_name],
88
- strict_ordering: options[:strict_ordering]
89
- )
90
- end
91
-
92
- def self.finalize_execution(results, parent_context, step_name, parent_reactor_class_name, storage)
93
- step_config = Object.const_get(parent_reactor_class_name).steps[step_name.to_sym]
94
- final_result = apply_collect_block(results, step_config)
95
- resume_parent_execution(parent_context, step_name, final_result, storage)
96
- end
97
-
98
- private_class_method :execute_all_elements, :store_result, :finalize_execution
99
- end
100
- end
101
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "sidekiq"
4
-
5
- module RubyReactor
6
- module SidekiqWorkers
7
- class MapExecutionWorker
8
- include ::Sidekiq::Worker
9
-
10
- def perform(arguments)
11
- RubyReactor::Map::Execution.perform(arguments)
12
- end
13
- end
14
- end
15
- end