ruby_reactor 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +10 -2
- data/README.md +72 -3
- data/Rakefile +27 -2
- data/documentation/images/failed_order_processing.png +0 -0
- data/documentation/images/payment_workflow.png +0 -0
- data/documentation/interrupts.md +161 -0
- data/gui/.gitignore +24 -0
- data/gui/README.md +73 -0
- data/gui/eslint.config.js +23 -0
- data/gui/index.html +13 -0
- data/gui/package-lock.json +5925 -0
- data/gui/package.json +46 -0
- data/gui/postcss.config.js +6 -0
- data/gui/public/vite.svg +1 -0
- data/gui/src/App.css +42 -0
- data/gui/src/App.tsx +51 -0
- data/gui/src/assets/react.svg +1 -0
- data/gui/src/components/DagVisualizer.tsx +424 -0
- data/gui/src/components/Dashboard.tsx +163 -0
- data/gui/src/components/ErrorBoundary.tsx +47 -0
- data/gui/src/components/ReactorDetail.tsx +135 -0
- data/gui/src/components/StepInspector.tsx +492 -0
- data/gui/src/components/__tests__/DagVisualizer.test.tsx +140 -0
- data/gui/src/components/__tests__/ReactorDetail.test.tsx +111 -0
- data/gui/src/components/__tests__/StepInspector.test.tsx +408 -0
- data/gui/src/globals.d.ts +7 -0
- data/gui/src/index.css +14 -0
- data/gui/src/lib/utils.ts +13 -0
- data/gui/src/main.tsx +14 -0
- data/gui/src/test/setup.ts +11 -0
- data/gui/tailwind.config.js +11 -0
- data/gui/tsconfig.app.json +28 -0
- data/gui/tsconfig.json +7 -0
- data/gui/tsconfig.node.json +26 -0
- data/gui/vite.config.ts +8 -0
- data/gui/vitest.config.ts +13 -0
- data/lib/ruby_reactor/async_router.rb +6 -2
- data/lib/ruby_reactor/context.rb +35 -9
- data/lib/ruby_reactor/dependency_graph.rb +2 -0
- data/lib/ruby_reactor/dsl/compose_builder.rb +8 -0
- data/lib/ruby_reactor/dsl/interrupt_builder.rb +48 -0
- data/lib/ruby_reactor/dsl/interrupt_step_config.rb +21 -0
- data/lib/ruby_reactor/dsl/map_builder.rb +8 -0
- data/lib/ruby_reactor/dsl/reactor.rb +12 -0
- data/lib/ruby_reactor/dsl/step_builder.rb +4 -0
- data/lib/ruby_reactor/executor/compensation_manager.rb +60 -27
- data/lib/ruby_reactor/executor/graph_manager.rb +2 -0
- data/lib/ruby_reactor/executor/result_handler.rb +117 -39
- data/lib/ruby_reactor/executor/retry_manager.rb +1 -0
- data/lib/ruby_reactor/executor/step_executor.rb +38 -4
- data/lib/ruby_reactor/executor.rb +86 -13
- data/lib/ruby_reactor/interrupt_result.rb +20 -0
- data/lib/ruby_reactor/map/collector.rb +0 -2
- data/lib/ruby_reactor/map/element_executor.rb +3 -0
- data/lib/ruby_reactor/map/execution.rb +28 -1
- data/lib/ruby_reactor/map/helpers.rb +44 -6
- data/lib/ruby_reactor/reactor.rb +187 -1
- data/lib/ruby_reactor/registry.rb +25 -0
- data/lib/ruby_reactor/sidekiq_workers/worker.rb +1 -1
- data/lib/ruby_reactor/step/compose_step.rb +22 -6
- data/lib/ruby_reactor/step/map_step.rb +30 -3
- data/lib/ruby_reactor/storage/adapter.rb +32 -0
- data/lib/ruby_reactor/storage/redis_adapter.rb +154 -11
- data/lib/ruby_reactor/utils/code_extractor.rb +31 -0
- data/lib/ruby_reactor/version.rb +1 -1
- data/lib/ruby_reactor/web/api.rb +206 -0
- data/lib/ruby_reactor/web/application.rb +53 -0
- data/lib/ruby_reactor/web/config.ru +5 -0
- data/lib/ruby_reactor/web/public/assets/index-VdeLgH9k.js +19 -0
- data/lib/ruby_reactor/web/public/assets/index-_z-6BvuM.css +1 -0
- data/lib/ruby_reactor/web/public/index.html +14 -0
- data/lib/ruby_reactor/web/public/vite.svg +1 -0
- data/lib/ruby_reactor.rb +94 -28
- data/llms-full.txt +66 -0
- data/llms.txt +7 -0
- metadata +63 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 598c9e49f00183da45e7b370c394445e9efddfe5952e3aabd701bbf954aa9712
|
|
4
|
+
data.tar.gz: 949ed81bd787d7bee47284e7f7b5905af3de4ae4057a90a4d0afa707959f5cbb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ae8f8f8a2916254068757d79d352df0a0148d4693f92238b456cecc1cb90d9f6cc77087f2b486e3359f5f0042779aecc9112df1da9898fa8efba6c9ed425ef39
|
|
7
|
+
data.tar.gz: 14fcbf6f880396176503008239e6517415977b19193f3f0d8600226acb7cf53d54a18bb79a42012646a1746d3202a62b73479715b6d088f69c208ce1c143aa92
|
data/.rubocop.yml
CHANGED
|
@@ -13,6 +13,7 @@ AllCops:
|
|
|
13
13
|
- bin/*
|
|
14
14
|
- db/**/*
|
|
15
15
|
- config/**/*
|
|
16
|
+
- demo_app/**/*
|
|
16
17
|
|
|
17
18
|
Style/StringLiterals:
|
|
18
19
|
EnforcedStyle: double_quotes
|
|
@@ -27,13 +28,13 @@ Metrics/BlockLength:
|
|
|
27
28
|
Metrics/MethodLength:
|
|
28
29
|
Exclude:
|
|
29
30
|
- spec/**/*
|
|
30
|
-
Max:
|
|
31
|
+
Max: 36
|
|
31
32
|
CountComments: false
|
|
32
33
|
|
|
33
34
|
Metrics/ClassLength:
|
|
34
35
|
Exclude:
|
|
35
36
|
- spec/**/*
|
|
36
|
-
Max:
|
|
37
|
+
Max: 250
|
|
37
38
|
|
|
38
39
|
Style/Documentation:
|
|
39
40
|
Exclude:
|
|
@@ -96,3 +97,10 @@ RSpec/DescribeClass:
|
|
|
96
97
|
|
|
97
98
|
RSpec/AnyInstance:
|
|
98
99
|
Enabled: false
|
|
100
|
+
|
|
101
|
+
Naming/VariableNumber:
|
|
102
|
+
Enabled: false
|
|
103
|
+
|
|
104
|
+
Metrics/ModuleLength:
|
|
105
|
+
Max: 200
|
|
106
|
+
|
data/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
A dynamic, dependency-resolving saga orchestrator for Ruby. Ruby Reactor implements the Saga pattern with compensation-based error handling and DAG-based execution planning. It leverages **Sidekiq** for asynchronous execution and **Redis** for state persistence.
|
|
4
4
|
|
|
5
|
+

|
|
6
|
+
|
|
5
7
|
## Features
|
|
6
8
|
|
|
7
9
|
- **DAG-based Execution**: Steps are executed based on their dependencies, allowing for parallel execution of independent steps.
|
|
@@ -9,6 +11,7 @@ A dynamic, dependency-resolving saga orchestrator for Ruby. Ruby Reactor impleme
|
|
|
9
11
|
- **Map & Parallel Execution**: Iterate over collections in parallel with the `map` step, distributing work across multiple workers.
|
|
10
12
|
- **Retries**: Configurable retry logic for failed steps, with exponential backoff.
|
|
11
13
|
- **Compensation**: Automatic rollback of completed steps when a failure occurs.
|
|
14
|
+
- **Interrupts**: Pause and resume workflows to wait for external events (webhooks, user approvals).
|
|
12
15
|
- **Input Validation**: Integrated with `dry-validation` for robust input checking.
|
|
13
16
|
|
|
14
17
|
## Installation
|
|
@@ -47,6 +50,25 @@ RubyReactor.configure do |config|
|
|
|
47
50
|
end
|
|
48
51
|
```
|
|
49
52
|
|
|
53
|
+
## Web Dashboard
|
|
54
|
+
|
|
55
|
+
RubyReactor comes with a built-in web dashboard to inspect reactor executions, view logs, and retry failed steps.
|
|
56
|
+
|
|
57
|
+
### Rails Installation
|
|
58
|
+
|
|
59
|
+
Mount the dashboard engine in your `config/routes.rb`:
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
Rails.application.routes.draw do
|
|
63
|
+
# ... other routes
|
|
64
|
+
mount RubyReactor::Web::Application => '/ruby_reactor'
|
|
65
|
+
end
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+

|
|
69
|
+
|
|
70
|
+
You can secure the dashboard using standard Rails authentication methods (e.g., `authenticate` block with Devise).
|
|
71
|
+
|
|
50
72
|
## Usage
|
|
51
73
|
|
|
52
74
|
RubyReactor allows you to define complex workflows as "reactors" with steps that can depend on each other, handle failures with compensations, and validate inputs.
|
|
@@ -198,6 +220,42 @@ def create(params)
|
|
|
198
220
|
end
|
|
199
221
|
```
|
|
200
222
|
|
|
223
|
+
### Interrupts (Pause & Resume)
|
|
224
|
+
|
|
225
|
+
Pause execution to wait for external events like webhooks or user approvals.
|
|
226
|
+
|
|
227
|
+
```ruby
|
|
228
|
+
class ApprovalReactor < RubyReactor::Reactor
|
|
229
|
+
step :submit_request do
|
|
230
|
+
run { |args| RequestService.submit(args) }
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
interrupt :wait_for_manager do
|
|
234
|
+
wait_for :submit_request
|
|
235
|
+
# Resume using this ID
|
|
236
|
+
correlation_id { |ctx| "req-#{ctx.result(:submit_request)[:id]}" }
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
step :process_decision do
|
|
240
|
+
argument :decision, result(:wait_for_manager)
|
|
241
|
+
run do |args|
|
|
242
|
+
args[:decision] == 'approved' ? Success() : Failure("Rejected")
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Usage:
|
|
248
|
+
# 1. Start execution
|
|
249
|
+
execution = ApprovalReactor.run(params) # => Returns Paused status
|
|
250
|
+
|
|
251
|
+
# 2. Later, resume it via correlation ID
|
|
252
|
+
ApprovalReactor.continue_by_correlation_id(
|
|
253
|
+
correlation_id: "req-123",
|
|
254
|
+
payload: "approved",
|
|
255
|
+
step_name: :wait_for_manager
|
|
256
|
+
)
|
|
257
|
+
```
|
|
258
|
+
|
|
201
259
|
### Map & Parallel Execution
|
|
202
260
|
|
|
203
261
|
Process collections in parallel using the `map` step:
|
|
@@ -540,6 +598,9 @@ Master the `map` feature for processing collections. Learn about parallel execut
|
|
|
540
598
|
### [Retry Configuration](documentation/retry_configuration.md)
|
|
541
599
|
Configure robust retry policies for your steps. This guide details the available backoff strategies (exponential, linear, fixed), how to configure retries at the reactor or step level, and how async retries work without blocking workers.
|
|
542
600
|
|
|
601
|
+
### [Interrupts](documentation/interrupts.md)
|
|
602
|
+
Learn how to pause and resume reactors to handle long-running processes, manual approvals, and asynchronous callbacks. Patterns for correlation IDs, timeouts, and payload validation.
|
|
603
|
+
|
|
543
604
|
### Examples
|
|
544
605
|
- [Order Processing](documentation/examples/order_processing.md) - Complete order processing workflow example
|
|
545
606
|
- [Payment Processing](documentation/examples/payment_processing.md) - Payment handling with compensation
|
|
@@ -548,12 +609,20 @@ Configure robust retry policies for your steps. This guide details the available
|
|
|
548
609
|
## Future improvements
|
|
549
610
|
|
|
550
611
|
- [X] Global id to serialize ActiveRecord classes
|
|
551
|
-
- [
|
|
552
|
-
- [ ] Descriptive errors
|
|
612
|
+
- [X] Descriptive errors
|
|
553
613
|
- [X] `map` step to iterate over arrays in parallel
|
|
554
614
|
- [X] `compose` special step to execute reactors as step
|
|
615
|
+
- [X] `interrupt` to pause and resume reactors
|
|
616
|
+
- [ ] Middlewares
|
|
555
617
|
- [ ] Async ruby to parallelize same level steps
|
|
556
|
-
- [
|
|
618
|
+
- [x] Web dashboard to inspect reactor results and errors
|
|
619
|
+
- [ ] Multiple storage adapters
|
|
620
|
+
- [X] Redis
|
|
621
|
+
- [ ] ActiveRecord
|
|
622
|
+
- [ ] Multiple Async adapters
|
|
623
|
+
- [X] Sidekiq
|
|
624
|
+
- [ ] ActiveJob
|
|
625
|
+
- [ ] OpenTelemetry support
|
|
557
626
|
|
|
558
627
|
## Development
|
|
559
628
|
|
data/Rakefile
CHANGED
|
@@ -5,8 +5,33 @@ require "rspec/core/rake_task"
|
|
|
5
5
|
|
|
6
6
|
RSpec::Core::RakeTask.new(:spec)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
namespace :build do
|
|
9
|
+
desc "Build the UI assets"
|
|
10
|
+
task :ui do
|
|
11
|
+
puts "Building UI..."
|
|
12
|
+
system("cd gui && npm install && npm run build") || abort("UI build failed")
|
|
9
13
|
|
|
10
|
-
|
|
14
|
+
# Copy assets to public
|
|
15
|
+
# Vite builds to dist by default. We want it in lib/ruby_reactor/web/public
|
|
16
|
+
# Actually, we should configure Vite to build to the right place or copy it.
|
|
17
|
+
# Let's copy.
|
|
18
|
+
FileUtils.rm_rf("lib/ruby_reactor/web/public")
|
|
19
|
+
FileUtils.mkdir_p("lib/ruby_reactor/web/public")
|
|
20
|
+
FileUtils.cp_r("gui/dist/.", "lib/ruby_reactor/web/public/")
|
|
21
|
+
puts "UI built and assets copied to lib/ruby_reactor/web/public"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
namespace :server do
|
|
26
|
+
desc "Start the server"
|
|
27
|
+
task :start do
|
|
28
|
+
puts "Starting server..."
|
|
29
|
+
system("rackup -Ilib lib/ruby_reactor/web/config.ru -p 9292") || abort("Server failed to start")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# require "rubocop/rake_task"
|
|
34
|
+
|
|
35
|
+
# RuboCop::RakeTask.new
|
|
11
36
|
|
|
12
37
|
task default: %i[spec rubocop]
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Interrupts (Pause & Resume)
|
|
2
|
+
|
|
3
|
+
RubyReactor introduces the `interrupt` mechanism to support long-running processes that require external input, such as user approvals, webhooks, or asynchronous job completions. Unlike standard steps that execute immediately, an `interrupt` pauses the reactor execution and persists its state, waiting for a signal to resume.
|
|
4
|
+
|
|
5
|
+
## DSL Usage
|
|
6
|
+
|
|
7
|
+
Use the `interrupt` keyword to define a pause point in your reactor.
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
class ReportReactor < RubyReactor::Reactor
|
|
11
|
+
step :request_report do
|
|
12
|
+
run do
|
|
13
|
+
response = HTTP.post("https://api.example.com/reports")
|
|
14
|
+
Success(response.fetch(:id))
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
interrupt :wait_for_report do
|
|
19
|
+
# Declare dependency: execution must trigger this interrupt only after :request_report succeeds
|
|
20
|
+
wait_for :request_report
|
|
21
|
+
|
|
22
|
+
# Optional: deterministic correlation ID for looking up this execution later
|
|
23
|
+
correlation_id do |context|
|
|
24
|
+
"report-#{context.result(:request_report)}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Optional: timeout in seconds
|
|
28
|
+
# Strategies:
|
|
29
|
+
# - :lazy (default) - checked only when resume is attempted
|
|
30
|
+
# - :active - schedules a background job to wake up the reactor and fail it
|
|
31
|
+
timeout 1800, strategy: :active
|
|
32
|
+
|
|
33
|
+
# Optional: validate incoming payload immediately using dry-validation
|
|
34
|
+
validate do
|
|
35
|
+
required(:status).filled(:string)
|
|
36
|
+
required(:url).filled(:string)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Optional: limit validation attempts (default: 1)
|
|
40
|
+
# If exhausted, the reactor is cancelled and compensated.
|
|
41
|
+
# Use :infinity for unlimited attempts.
|
|
42
|
+
max_attempts 3
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
step :process_report do
|
|
46
|
+
# The result of the interrupt step is the payload provided when resuming
|
|
47
|
+
argument :webhook_payload, result(:wait_for_report)
|
|
48
|
+
|
|
49
|
+
run do |args|
|
|
50
|
+
Success(ReportProcessor.call(args[:webhook_payload]))
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Options
|
|
57
|
+
|
|
58
|
+
* **`wait_for`**: declare dependencies similar to `step`.
|
|
59
|
+
* **`correlation_id`**: A block that returns a unique string to identify this execution. This allows you to resume the reactor using a business key (e.g., order ID) instead of the internal execution UUID.
|
|
60
|
+
* **`timeout`**: Set a time limit for the interrupt.
|
|
61
|
+
* **`validate`**: A `dry-validation` schema block to validate the payload provided when resuming.
|
|
62
|
+
* **`max_attempts`**: Limit the number of times `continue` can be called with an invalid payload before the reactor is automatically cancelled and compensated. Defaults to 1. Set to `:infinity` for unlimited retries.
|
|
63
|
+
|
|
64
|
+
## Runtime Behavior
|
|
65
|
+
|
|
66
|
+
When a reactor encounters an `interrupt`:
|
|
67
|
+
|
|
68
|
+
1. It executes any dependencies.
|
|
69
|
+
2. It persists the full `Context` (results of previous steps) to the configured storage (e.g., Redis).
|
|
70
|
+
3. It returns an `InterruptResult` and halts execution.
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
execution = ReportReactor.run(company_id: 1)
|
|
74
|
+
|
|
75
|
+
if execution.paused?
|
|
76
|
+
execution.id # => "uuid-123"
|
|
77
|
+
execution.status # => :paused
|
|
78
|
+
end
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Resuming Execution
|
|
82
|
+
|
|
83
|
+
You can resume a paused reactor using its UUID or the defined `correlation_id`.
|
|
84
|
+
|
|
85
|
+
### By UUID
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
ReportReactor.continue(
|
|
89
|
+
id: "uuid-123",
|
|
90
|
+
payload: { status: "completed", url: "..." },
|
|
91
|
+
step_name: :wait_for_report
|
|
92
|
+
)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### By Correlation ID
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
ReportReactor.continue_by_correlation_id(
|
|
99
|
+
correlation_id: "report-999",
|
|
100
|
+
payload: { status: "completed", url: "..." },
|
|
101
|
+
step_name: :wait_for_report
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Resuming Method Styles
|
|
106
|
+
|
|
107
|
+
There are two ways to invoke continuation:
|
|
108
|
+
|
|
109
|
+
1. **Strict / Fire-and-Forget (Class Method)**:
|
|
110
|
+
* `Reactor.continue(...)`
|
|
111
|
+
* If payload is invalid, it **automatically compensates (undo)** and cancels the reactor.
|
|
112
|
+
* Best for webhooks where you can't ask the sender to fix the payload.
|
|
113
|
+
|
|
114
|
+
2. **Flexible (Instance Method)**:
|
|
115
|
+
* First find the reactor: `reactor = ReportReactor.find("uuid-123")`
|
|
116
|
+
* Then call: `result = reactor.continue(payload: ..., step_name: :wait_for_report)`
|
|
117
|
+
* If payload is invalid, it returns a failure result but **does not** cancel execution.
|
|
118
|
+
* Allows you to handle the error (e.g., show a form error to a user) and try again.
|
|
119
|
+
|
|
120
|
+
## Cancellation & Undo
|
|
121
|
+
|
|
122
|
+
You can cancel a paused reactor if the operation is no longer needed.
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
# Undo: Runs defined compensations for completed steps in reverse order, then deletes execution.
|
|
126
|
+
ReportReactor.undo("uuid-123")
|
|
127
|
+
|
|
128
|
+
# Cancel: Stops execution immediately and marks the reactor as cancelled with the provided reason.
|
|
129
|
+
# The context is preserved for inspection, but resumption is disabled.
|
|
130
|
+
ReportReactor.cancel("uuid-123", reason: "User cancelled")
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Common Use Cases
|
|
134
|
+
|
|
135
|
+
### Human Approvals
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
interrupt :wait_for_approval do
|
|
139
|
+
wait_for :submit_request
|
|
140
|
+
correlation_id { |ctx| "approval-#{ctx.input(:request_id)}" }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
step :process_decision do
|
|
144
|
+
argument :decision, result(:wait_for_approval)
|
|
145
|
+
run do |args|
|
|
146
|
+
if args[:decision][:approved]
|
|
147
|
+
Success("Approved")
|
|
148
|
+
else
|
|
149
|
+
Failure("Rejected")
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Webhooks
|
|
156
|
+
|
|
157
|
+
Use `correlation_id` to map an external resource ID (like a Payment Intent ID) back to the reactor waiting for confirmation.
|
|
158
|
+
|
|
159
|
+
### Scheduled Follow-ups
|
|
160
|
+
|
|
161
|
+
Using `timeout` with `strategy: :active` to wake up a reactor after a delay if no external event occurs (e.g., expiring a reservation).
|
data/gui/.gitignore
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Logs
|
|
2
|
+
logs
|
|
3
|
+
*.log
|
|
4
|
+
npm-debug.log*
|
|
5
|
+
yarn-debug.log*
|
|
6
|
+
yarn-error.log*
|
|
7
|
+
pnpm-debug.log*
|
|
8
|
+
lerna-debug.log*
|
|
9
|
+
|
|
10
|
+
node_modules
|
|
11
|
+
dist
|
|
12
|
+
dist-ssr
|
|
13
|
+
*.local
|
|
14
|
+
|
|
15
|
+
# Editor directories and files
|
|
16
|
+
.vscode/*
|
|
17
|
+
!.vscode/extensions.json
|
|
18
|
+
.idea
|
|
19
|
+
.DS_Store
|
|
20
|
+
*.suo
|
|
21
|
+
*.ntvs*
|
|
22
|
+
*.njsproj
|
|
23
|
+
*.sln
|
|
24
|
+
*.sw?
|
data/gui/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# React + TypeScript + Vite
|
|
2
|
+
|
|
3
|
+
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
|
4
|
+
|
|
5
|
+
Currently, two official plugins are available:
|
|
6
|
+
|
|
7
|
+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
|
8
|
+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
|
9
|
+
|
|
10
|
+
## React Compiler
|
|
11
|
+
|
|
12
|
+
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
|
13
|
+
|
|
14
|
+
## Expanding the ESLint configuration
|
|
15
|
+
|
|
16
|
+
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
export default defineConfig([
|
|
20
|
+
globalIgnores(['dist']),
|
|
21
|
+
{
|
|
22
|
+
files: ['**/*.{ts,tsx}'],
|
|
23
|
+
extends: [
|
|
24
|
+
// Other configs...
|
|
25
|
+
|
|
26
|
+
// Remove tseslint.configs.recommended and replace with this
|
|
27
|
+
tseslint.configs.recommendedTypeChecked,
|
|
28
|
+
// Alternatively, use this for stricter rules
|
|
29
|
+
tseslint.configs.strictTypeChecked,
|
|
30
|
+
// Optionally, add this for stylistic rules
|
|
31
|
+
tseslint.configs.stylisticTypeChecked,
|
|
32
|
+
|
|
33
|
+
// Other configs...
|
|
34
|
+
],
|
|
35
|
+
languageOptions: {
|
|
36
|
+
parserOptions: {
|
|
37
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
38
|
+
tsconfigRootDir: import.meta.dirname,
|
|
39
|
+
},
|
|
40
|
+
// other options...
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
])
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
// eslint.config.js
|
|
50
|
+
import reactX from 'eslint-plugin-react-x'
|
|
51
|
+
import reactDom from 'eslint-plugin-react-dom'
|
|
52
|
+
|
|
53
|
+
export default defineConfig([
|
|
54
|
+
globalIgnores(['dist']),
|
|
55
|
+
{
|
|
56
|
+
files: ['**/*.{ts,tsx}'],
|
|
57
|
+
extends: [
|
|
58
|
+
// Other configs...
|
|
59
|
+
// Enable lint rules for React
|
|
60
|
+
reactX.configs['recommended-typescript'],
|
|
61
|
+
// Enable lint rules for React DOM
|
|
62
|
+
reactDom.configs.recommended,
|
|
63
|
+
],
|
|
64
|
+
languageOptions: {
|
|
65
|
+
parserOptions: {
|
|
66
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
67
|
+
tsconfigRootDir: import.meta.dirname,
|
|
68
|
+
},
|
|
69
|
+
// other options...
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
])
|
|
73
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import globals from 'globals'
|
|
3
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
4
|
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
5
|
+
import tseslint from 'typescript-eslint'
|
|
6
|
+
import { defineConfig, globalIgnores } from 'eslint/config'
|
|
7
|
+
|
|
8
|
+
export default defineConfig([
|
|
9
|
+
globalIgnores(['dist']),
|
|
10
|
+
{
|
|
11
|
+
files: ['**/*.{ts,tsx}'],
|
|
12
|
+
extends: [
|
|
13
|
+
js.configs.recommended,
|
|
14
|
+
tseslint.configs.recommended,
|
|
15
|
+
reactHooks.configs.flat.recommended,
|
|
16
|
+
reactRefresh.configs.vite,
|
|
17
|
+
],
|
|
18
|
+
languageOptions: {
|
|
19
|
+
ecmaVersion: 2020,
|
|
20
|
+
globals: globals.browser,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
])
|
data/gui/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>ui</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|