rsodx 0.0.1 → 0.0.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9530a9b38e69fdae2f3eee74634ab298aabf3fa11df94d5e28dffd46e7fbffed
4
- data.tar.gz: 45db9f15c1a2fbee0d2047373c2958ac913fa262e1833ba9c07b925d76f2577e
3
+ metadata.gz: 74be3af36e2f6319995f69ec786e1067923306f658d57147902cbaca1e285387
4
+ data.tar.gz: 8434da27ab813e8b757f4d5a3c6acc71cf9b9522987aea52b119c97aa8dcfda6
5
5
  SHA512:
6
- metadata.gz: 2074732db6099bd5681845ed895d5679e9b33b5aea3fe5a8a83d767ac47dbc670d86511d330599b79e940ae51797a6c9d77861dff8472082ec8806d4c9f07c9b
7
- data.tar.gz: eb6590b94d33281d0d179a9c6cb62b1c70fef8115c8b8486e230f21154672cdf618ed37738b1fe1d6e918fa60282d5e98c27cec1b5a41dfe56226002661b4d2b
6
+ metadata.gz: 40244cd860d2e0e3f9010a2abca202c3c4f87720b2f99122071c240330e9d54ad82398c0f78fef94388ed6336dbdd162c0956bb24ba847c5270966488539b21a
7
+ data.tar.gz: 72f2e95d4a9ed42bbe82f3c013471a9b3aa4789568013dcb038edf9e514249e2b06591cfa99be7c14408c1a6f7b5bded9fa99118cfbaf430157bc37568d990d3
data/README.md CHANGED
@@ -1,21 +1,37 @@
1
1
  # Rsodx
2
2
 
3
- > Add Rsodx and just code ✨
3
+ <p align="left">
4
+ <img src="docs/banner.jpg" width="300px" alt="Rsodx banner">
5
+ </p>
6
+
7
+ `rsodx` is a minimal, modular framework for building fast and maintainable Ruby microservices.
8
+ Inspired by the best of Rails, Sinatra, and Sequel — it gives you just enough structure to scale, without the overhead.
9
+
10
+ No magic. Just clean code and powerful tools.
11
+
12
+
13
+ ```bash
14
+ $ rsodx new my_rail_company
15
+ 🛤️ Initializing Transport Empire...
16
+ 📦 Creating folders...
17
+ ✅ Done! Start building your microservice railway!
18
+ ```
4
19
 
5
- Rsodx is a lightweight, modular microservice framework for Ruby — designed to be fast, clean, and scalable. It provides a minimal architecture inspired by Rails, Sinatra, and Sequel, allowing you to focus on writing business logic without boilerplate.
6
20
 
7
21
  ---
8
22
 
9
23
  ## 🧠 Philosophy
10
24
 
11
- - No monoliths build small services
12
- - No magicjust plain Ruby
13
- - No opinionated ORM or router just simple tools
14
- - Easily extendable and production-ready
25
+ - **Micro-first**focus on small, single-purpose services
26
+ - **Explicit over implicit** no hidden behavior, no global state
27
+ - **Modular by design** include only what you need
28
+ - **Ruby-native** use familiar patterns, no learning curve
29
+ - **Production-oriented** — simple to develop, easy to deploy
15
30
 
16
31
  ---
17
32
 
18
- Вот аккуратно оформленный раздел `Installation` для твоего README:
33
+ > `rsodx` includes routing, interactors, validation, and service structure —
34
+ all wrapped into a fast and lightweight toolkit made for modern Ruby apps.
19
35
 
20
36
  ---
21
37
 
@@ -39,84 +55,195 @@ bundle add rsodx
39
55
  gem install rsodx
40
56
  ```
41
57
 
42
- > ✅ `rsodx` is designed for microservice architecture and includes routing, interactors, validation, and more — all in one lightweight package.
58
+ ---
59
+
60
+ ## 🚀 CLI Commands
61
+
62
+ `rsodx` ships with a powerful and lightweight CLI tool for generating services and scaffolding applications.
63
+
64
+ You can run CLI commands via:
65
+
66
+ ```bash
67
+ bin/rsodx [command] [args]
68
+ ```
43
69
 
44
70
  ---
45
71
 
72
+ ## 🔧 Generators
46
73
 
47
- ## 📦 Project Structure
74
+ Generate various application components using simple commands:
48
75
 
49
- ```text
50
- my_service/
76
+ ```bash
77
+ bin/rsodx generate controller v1/users/index
78
+ bin/rsodx generate presenter v1/users/index
79
+ bin/rsodx generate serializer v1/users/index
80
+ ```
81
+
82
+ Or using aliases:
83
+
84
+ ```bash
85
+ bin/rsodx g controller v1/users/index
86
+ ```
87
+
88
+ To generate **all three** at once (controller, presenter, serializer):
89
+
90
+ ```bash
91
+ bin/rsodx g action v1/users/index
92
+ ```
93
+
94
+ This creates:
95
+
96
+ - `app/controllers/v1/users/index_controller.rb`
97
+ - `app/presenters/v1/users/index_presenter.rb`
98
+ - `app/serializers/v1/users/index_serializer.rb`
99
+
100
+ ---
101
+
102
+ ## 🛠 Scaffold New App
103
+
104
+ Create a full Rsodx project with:
105
+
106
+ ```bash
107
+ bin/rsodx new my_app
108
+ ```
109
+
110
+ Or using alias:
111
+
112
+ ```bash
113
+ bin/rsodx n my_app
114
+ ```
115
+
116
+ This will create a new directory `my_app` with:
117
+
118
+ - `Gemfile`, `.env`, `.ruby-version`
119
+ - `config.ru`, `Rakefile`, environment loader
120
+ - `app/` structure (`controllers`, `services`, etc.)
121
+ - `bin/console` and `bin/rsodx` CLI entrypoints
122
+
123
+ It will be immediately runnable:
124
+
125
+ ```bash
126
+ cd my_app
127
+ bundle install
128
+ bin/rsodx server
129
+ ```
130
+
131
+ ---
132
+
133
+ ## 🌐 Server Command
134
+
135
+ Run a local Rack-based Puma server:
136
+
137
+ ```bash
138
+ bin/rsodx server
139
+ ```
140
+
141
+ Available options:
142
+
143
+ - `--port=PORT` – default: `9292`
144
+ - `--env=ENV` – default: `development`
145
+
146
+ Example:
147
+
148
+ ```bash
149
+ bin/rsodx server --port=3000 --env=production
150
+ ```
151
+
152
+ ### 🔍 How it works
153
+
154
+ This command launches Puma via `rackup`:
155
+
156
+ ```ruby
157
+ pid = spawn("bundle exec rackup --port=#{port} --host=0.0.0.0")
158
+ Process.wait(pid)
159
+ ```
160
+
161
+ It ensures that your `config.ru` is used correctly and delegates the full startup to Rack.
162
+
163
+ ---
164
+
165
+ ## ✅ Command Summary
166
+
167
+ | Command | Description |
168
+ |--------|-------------|
169
+ | `rsodx new NAME` | Scaffold a new Rsodx project |
170
+ | `rsodx server` | Run the Rack/Puma development server |
171
+ | `rsodx generate controller PATH` | Generate a controller |
172
+ | `rsodx generate presenter PATH` | Generate a presenter |
173
+ | `rsodx generate serializer PATH` | Generate a serializer |
174
+ | `rsodx generate action PATH` | Generate controller + presenter + serializer |
175
+ | Aliases: `g`, `n`, `s` | All commands support short versions |
176
+
177
+ ---
178
+
179
+ ## 📁 Folder Structure (Generated App)
180
+
181
+ ```
182
+ my_app/
51
183
  ├── app/
52
- │ ├── api/
53
- │ ├── interactors/
54
- │ ├── models/
184
+ │ ├── controllers/
55
185
  │ ├── presenters/
56
186
  │ ├── serializers/
57
- │ ├── app.rb
58
- │ └── router.rb
187
+ │ ├── services/
188
+ ├── bin/
189
+ │ ├── console
190
+ │ └── rsodx
59
191
  ├── config/
60
192
  │ ├── environment.rb
61
- ├── environments/
62
- │ └── initializers/
193
+ └── environments/
63
194
  ├── db/
64
195
  │ └── migrations/
65
- ├── bin/
66
- │ └── console
67
- ├── .env
196
+ ├── spec/
68
197
  ├── config.ru
198
+ ├── .env
69
199
  ├── Gemfile
70
200
  └── Rakefile
71
201
  ```
72
202
 
73
203
  ---
74
204
 
75
- ## 🧰 CLI Commands
205
+ ## 🗄️ PostgreSQL Setup
76
206
 
77
- ### Create new service
207
+ Your application connects to PostgreSQL using the `DATABASE_URL` defined in `.env`:
78
208
 
79
- ```bash
80
- rsodx new my_service
209
+ ```
210
+ DATABASE_URL=postgres://rsodx:paSs4321@localhost:5432/rsodx_development
81
211
  ```
82
212
 
83
- ---
213
+ ### 📌 To create this database manually:
84
214
 
85
- ### Generate interactor
215
+ 1. Open your terminal and run the PostgreSQL client:
86
216
 
87
217
  ```bash
88
- bin/rsodx generate interactor CreateUser
218
+ psql -U postgres
89
219
  ```
90
- Creates `app/interactors/create_user.rb`:
91
220
 
92
- ```ruby
93
- class CreateUser < Rsodx::Interactor
94
- def call
95
- # business logic here
96
- end
97
- end
221
+ 2. Then, execute the following SQL commands:
222
+
223
+ ```sql
224
+ -- Create the user
225
+ CREATE USER rsodx WITH PASSWORD 'paSs4321';
226
+
227
+ -- Create the database
228
+ CREATE DATABASE rsodx_development;
229
+
230
+ -- Grant privileges
231
+ GRANT ALL PRIVILEGES ON DATABASE rsodx_development TO rsodx;
98
232
  ```
99
233
 
234
+ > 📍 If your system uses a different PostgreSQL superuser, adjust `-U postgres` accordingly.
235
+
100
236
  ---
101
237
 
102
- ### Generate migration
238
+ ## Quick Check
239
+
240
+ You can test the connection:
103
241
 
104
242
  ```bash
105
- bin/rsodx generate migration CreateUsers
243
+ psql postgres://rsodx:paSs4321@localhost:5432/rsodx_development
106
244
  ```
107
- Creates `db/migrations/20240413_create_users.rb`:
108
245
 
109
- ```ruby
110
- Sequel.migration do
111
- change do
112
- # create_table :users do
113
- # primary_key :id
114
- # String :email
115
- # DateTime :created_at
116
- # end
117
- end
118
- end
119
- ```
246
+ If it connects successfully, your database is ready for development!
120
247
 
121
248
  ---
122
249
 
@@ -125,7 +252,7 @@ end
125
252
  ```ruby
126
253
  class Router < Rsodx::Router
127
254
  namespace "/v1" do
128
- post "/users", CreateUsers
255
+ post "/users", V1::Users::Create
129
256
  end
130
257
  end
131
258
  ```
@@ -168,11 +295,48 @@ CreateUser.call(params: {...})
168
295
 
169
296
  ---
170
297
 
171
- ## 🔜 Roadmap
172
- - Generator for models, serializers, presenters
173
- - HTTP + JSON helpers
174
- - Authentication middleware
175
- - Better documentation site
298
+ ## 🛣️ Roadmap
299
+
300
+ Planned features and improvements for upcoming versions of `rsodx`.
301
+
302
+ ### 🔄 Inter-service Communication
303
+
304
+ - ✅ **Add RabbitMQ support** for event-driven microservices
305
+ - CLI: `rsodx g subscriber user/created`
306
+ - DSL: `on_event "user.created", with: HandleUserCreated`
307
+
308
+ ### 🐳 Docker Support
309
+
310
+ - ✅ Dockerfile and docker-compose.yml templates
311
+ - PostgreSQL, RabbitMQ, App
312
+
313
+ ### 📦 Generators & Tooling
314
+
315
+ - [ ] `rsodx g worker fetch_data`
316
+ - [ ] `rsodx g subscriber event_name`
317
+ - [ ] CLI flags: `--dry-run`, `--force`, `--skip`
318
+ - [ ] Generate test stubs with each component
319
+
320
+ ### 🌐 Web Server Improvements
321
+
322
+ - [ ] Auto-discovery of config.ru or App class
323
+ - [ ] `rsodx server --daemon`, `--log`
324
+
325
+ ### 📚 Documentation & API
326
+
327
+ - [ ] Auto-generate OpenAPI / Swagger docs from declared routes and schemas
328
+ - [ ] DRY validation + schema → Swagger with types and examples
329
+ - [ ] `rsodx g docs` or `rsodx docs generate`
330
+
331
+ ### 🛠 Developer Experience
332
+
333
+ - [ ] `rsodx console` (IRB + app preload)
334
+ - [ ] `rsodx doctor` for environment diagnostics
335
+ - [ ] Starter project templates (`--template=api`, `--template=evented`)
336
+
337
+ ---
338
+
339
+ If you’d like to contribute or suggest new features — feel free to open an issue or PR. Let’s make `rsodx` fast, lean and production-ready together! ❤️
176
340
 
177
341
  ---
178
342
 
data/bin/console CHANGED
@@ -3,10 +3,12 @@
3
3
 
4
4
  require "bundler/setup"
5
5
  require "rsodx"
6
- require_relative '../lib/config/environment'
6
+ puts Rsodx::LOGO
7
7
 
8
- # You can add fixtures and/or initialization code here to make experimenting
9
- # with your gem easier. You can also use a different console, if you like.
8
+ def reload!
9
+ puts "♻️ Reloading lib/rsodx..."
10
+ load File.expand_path("../lib/rsodx.rb", __dir__)
11
+ end
10
12
 
11
13
  require "irb"
12
- IRB.start(__FILE__)
14
+ IRB.start
data/bin/rsodx CHANGED
@@ -1,9 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "fileutils"
4
- require "optparse"
5
- require "rsodx"
3
+ require_relative "../lib/rsodx"
6
4
 
7
- if __FILE__ == $0
8
- Rsodx::Cli::Handler.run
9
- end
5
+ Rsodx::Cli.setup!
6
+ Rsodx::CLI.call
@@ -0,0 +1,63 @@
1
+ module Rsodx
2
+ class Action < Rsodx::Service
3
+ delegate :request, :options, :handler_class, to: :context
4
+
5
+ before do
6
+ context.options ||= {}
7
+ raise "Missing handler_class in context" unless context.handler_class
8
+ end
9
+
10
+ def call
11
+ run_handler
12
+ rescue StandardError => e
13
+ handle_exception(e)
14
+ end
15
+
16
+ private
17
+
18
+ def run_handler
19
+ result = context.handler_class.call(
20
+ options.merge(request: request, params: request.params)
21
+ )
22
+
23
+ return error_result(result) if result.failure?
24
+
25
+ serializer = resolve_serializer
26
+ serialized = serializer.call(object: result.object)
27
+ return error_result(serialized) if serialized.failure?
28
+
29
+ if presenter_class
30
+ context.json_response = presenter_class.new(serialized.dto).call
31
+ elsif request.request_method == 'POST'
32
+ context.code = 201
33
+ else
34
+ context.code = 204
35
+ end
36
+ end
37
+
38
+ def resolve_serializer
39
+ from_handler_with_suffix("Serializer") || AppSerializer
40
+ end
41
+
42
+ def presenter_class
43
+ from_handler_with_suffix("Presenter")
44
+ end
45
+
46
+ def from_handler_with_suffix(suffix)
47
+ klass_name = handler_class.name.sub(/Controller$/, suffix)
48
+ Object.const_get(klass_name)
49
+ rescue NameError
50
+ nil
51
+ end
52
+
53
+ def error_result(result)
54
+ code = result.error_code || 500
55
+ error = result.error || 'Unknown error'
56
+ halt(code, error)
57
+ end
58
+
59
+ def handle_exception(e)
60
+ halt(500, e.message)
61
+ end
62
+ end
63
+ end
data/lib/rsodx/boot.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  module Rsodx
2
2
  module Boot
3
3
  def self.load_app_structure(app_root)
4
- %w[models interactors presenters serializers api].each do |folder|
4
+ %w[models controllers services serializers presenters].each do |folder|
5
5
  path = File.join(app_root, "app", folder)
6
6
  $LOAD_PATH.unshift(path) if Dir.exist?(path)
7
+ Dir["#{path}/*.rb"].sort.each { |file| require file }
7
8
  Dir["#{path}/**/*.rb"].sort.each { |file| require file }
8
9
  end
9
10
  end
@@ -0,0 +1,56 @@
1
+ require "dry/cli"
2
+
3
+ module Rsodx::Cli
4
+ module Commands
5
+ end
6
+ module Commands::Generators
7
+ end
8
+
9
+ extend Dry::CLI::Registry
10
+
11
+ def self.setup!
12
+ register_commands_with_alias(
13
+ group: "",
14
+ alias_prefix: "",
15
+ commands: {
16
+ "new" => ::Rsodx::Cli::Commands::Scaffold,
17
+ "n" => ::Rsodx::Cli::Commands::Scaffold
18
+ }
19
+ )
20
+
21
+ register_commands_with_alias(
22
+ group: "generate",
23
+ alias_prefix: "g",
24
+ commands: {
25
+ "migration" => ::Rsodx::Cli::Commands::Generators::Migration,
26
+ "controller" => ::Rsodx::Cli::Commands::Generators::Controller,
27
+ "presenter" => ::Rsodx::Cli::Commands::Generators::Presenter,
28
+ "serializer" => ::Rsodx::Cli::Commands::Generators::Serializer,
29
+ "action" => ::Rsodx::Cli::Commands::Generators::Action
30
+ }
31
+ )
32
+
33
+ register_commands_with_alias(
34
+ group: "",
35
+ alias_prefix: "",
36
+ commands: {
37
+ "server" => ::Rsodx::Cli::Commands::Server,
38
+ "s" => ::Rsodx::Cli::Commands::Server
39
+ }
40
+ )
41
+ end
42
+
43
+ def self.register_commands_with_alias(group:, alias_prefix:, commands:)
44
+ commands.each do |name, klass|
45
+ full_name = group && !group.empty? ? "#{group} #{name}" : name
46
+ alias_name = alias_prefix && !alias_prefix.empty? ? "#{alias_prefix} #{name}" : name
47
+
48
+ register full_name.strip, klass
49
+ register alias_name.strip, klass
50
+ end
51
+ end
52
+ end
53
+
54
+ module Rsodx
55
+ CLI = Dry::CLI.new(::Rsodx::Cli)
56
+ end
@@ -0,0 +1,60 @@
1
+ require "fileutils"
2
+
3
+ module Rsodx::Cli
4
+ class Generator
5
+ ROOT_MAP = {
6
+ controller: "app/controllers",
7
+ presenter: "app/presenters",
8
+ serializer: "app/serializers"
9
+ }
10
+
11
+ SUFFIX_MAP = {
12
+ controller: "Controller",
13
+ presenter: "Presenter",
14
+ serializer: "Serializer"
15
+ }
16
+
17
+ def self.generate(type, args)
18
+ path = args[:path]
19
+ type = type.to_sym
20
+ raise "Unknown type: #{type}" unless ROOT_MAP.key?(type)
21
+
22
+ segments = path.split("/")
23
+ file_name = segments.pop
24
+ file_path = "#{ROOT_MAP[type]}/#{segments.join("/")}/#{file_name}_#{type}.rb"
25
+ namespace = segments.map { |seg| camelize(seg) }.join("::")
26
+ class_name = "#{camelize(file_name)}#{SUFFIX_MAP[type]}"
27
+ full_class = [namespace, class_name].reject(&:empty?).join("::")
28
+
29
+ FileUtils.mkdir_p(File.dirname(file_path))
30
+ File.write(file_path, template(full_class, type))
31
+
32
+ puts "✅ Created #{file_path}"
33
+ end
34
+
35
+ def self.camelize(str)
36
+ str.split(/[_-]/).map { |part| part.capitalize }.join
37
+ end
38
+
39
+ def self.template(full_class, type)
40
+ base_class = {
41
+ controller: "AppController",
42
+ presenter: "AppPresenter",
43
+ serializer: "AppSerializer"
44
+ }[type]
45
+
46
+ namespace = full_class.split("::")[0..-2].join("::")
47
+ class_name = full_class.split("::").last
48
+
49
+ <<~RUBY
50
+ module #{namespace}
51
+ class #{class_name} < #{base_class}
52
+ def call
53
+ # TODO: implement
54
+ end
55
+ end
56
+ end
57
+ RUBY
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,13 @@
1
+ module Rsodx::Cli::Commands::Generators
2
+ class Action < Dry::CLI::Command
3
+ desc "Generate full action (controller + serializer + presenter)"
4
+
5
+ argument :path, desc: "Path to endpoint (e.g. v1/users/index)"
6
+
7
+ def call(args)
8
+ %i[controller serializer presenter].each do |type|
9
+ Rsodx::Cli::Generator.generate(type, args)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ module Rsodx::Cli::Commands::Generators
2
+ class Controller < Dry::CLI::Command
3
+ desc "Generate a controller"
4
+
5
+ argument :path, desc: "Path to controller, e.g. v1/users/index"
6
+
7
+ def call(args)
8
+ Rsodx::Cli::Generator.generate(:controller, args)
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,52 @@
1
+ require "fileutils"
2
+ require "time"
3
+
4
+ module Rsodx
5
+ module Cli
6
+ module Commands
7
+ module Generators
8
+ class Migration < Dry::CLI::Command
9
+ desc "Generate a Sequel migration"
10
+
11
+ argument :name, required: true, desc: "Name of the migration"
12
+
13
+ def call(name:)
14
+ timestamp = Time.now.utc.strftime("%Y%m%d%H%M%S")
15
+ file_name = File.join("db/migrations", "#{timestamp}_#{snake_case(name)}.rb")
16
+
17
+ puts "📦 Creating migration: #{file_name}"
18
+ FileUtils.mkdir_p("db/migrations")
19
+ File.write(file_name, MIGRATION_TEMPLATE)
20
+
21
+ puts "✅ Done"
22
+ rescue => e
23
+ abort "❌ Failed to generate migration: #{e.message}"
24
+ end
25
+
26
+ private
27
+
28
+ MIGRATION_TEMPLATE = <<~MIGRATION_TEMPLATE.freeze
29
+ Sequel.migration do
30
+ change do
31
+ # create_table(:example) do
32
+ # primary_key :id
33
+ # String :name
34
+ # DateTime :created_at
35
+ # DateTime :updated_at
36
+ # end
37
+ end
38
+ end
39
+ MIGRATION_TEMPLATE
40
+
41
+ def snake_case(str)
42
+ str.gsub(/::/, '/')
43
+ .gsub(/([A-Z]+)([A-Z][a-z])/,'\\1_\\2')
44
+ .gsub(/([a-z\\d])([A-Z])/,'\\1_\\2')
45
+ .tr("-", "_")
46
+ .downcase
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,11 @@
1
+ module Rsodx::Cli::Commands::Generators
2
+ class Presenter < Dry::CLI::Command
3
+ desc "Generate presenter"
4
+
5
+ argument :path, desc: "Path to presenter (e.g. v1/users/index)"
6
+
7
+ def call(args)
8
+ Rsodx::Cli::Generator.generate(:presenter, args)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Rsodx::Cli::Commands::Generators
2
+ class Serializer < Dry::CLI::Command
3
+ desc "Generate serializer"
4
+
5
+ argument :path, desc: "Path to serializer (e.g. v1/users/index)"
6
+
7
+ def call(args)
8
+ Rsodx::Cli::Generator.generate(:serializer, args)
9
+ end
10
+ end
11
+ end