regolith 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ab1bb3625f09d797596ad621a52eea0c1db33939cf262c4030807a4b37301172
4
+ data.tar.gz: 699de873f98dd3416e38feaa9a3af4377ad8810c7db1329122a55bc0898792f5
5
+ SHA512:
6
+ metadata.gz: bc39ef600c9cc6e1890ac561076ed67c36d07ae03700f715b1c1b94056b520b4a8bb076802c99bad1613f8c987c31d797c48db7943dcc7471bfde8407d5ac4b7
7
+ data.tar.gz: 4469ecbd5381b6b6019b8ecc5e1051a6da78b19526e9641872b17d95bae2bc866dfe953735359a95501ae3dc272bb02e33ffc8c9f5977fcb638f52787eb5d0fc
data/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [0.1.0] - 2025-07-30
6
+
7
+ ### Added
8
+ - Initial Regolith framework release
9
+ - CLI for creating and managing microservices
10
+ - Cross-service ActiveRecord-style associations
11
+ - Docker Compose orchestration
12
+ - Rails API service generation
13
+ - Service discovery and registry
14
+ - Working observatory example application
15
+
16
+ ### Features
17
+ - `regolith new <app>` - Create new distributed applications
18
+ - `regolith generate service <name>` - Generate Rails API services
19
+ - `regolith server` - Start all services with Docker
20
+ - `regolith console <service>` - Open Rails console for any service
21
+ - Zero-configuration service discovery
22
+ - Hot reload development environment
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Regolith Framework
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,210 @@
1
+ # Regolith
2
+
3
+ **Rails for Distributed Systems**
4
+
5
+ Regolith brings the elegance and developer experience of Rails to microservices architecture. Write cross-service associations like `user.orders.create!` and let Regolith handle the distributed complexity.
6
+
7
+ [![Gem Version](https://badge.fury.io/rb/regolith.svg)](https://badge.fury.io/rb/regolith)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+
10
+ ## ✨ Features
11
+
12
+ - **Zero-config service discovery** - Services find each other automatically
13
+ - **ActiveRecord-style cross-service associations** - `user.orders.create!` works across services
14
+ - **Docker orchestration built-in** - One command starts your entire stack
15
+ - **Rails API scaffolding** - Generate production-ready services in seconds
16
+ - **Hot reload development** - Code changes reflect immediately in running containers
17
+ - **Rails console access** - Debug any service with `regolith console users`
18
+
19
+ ## 🚀 Quick Start
20
+
21
+ ### Installation
22
+
23
+ ```bash
24
+ gem install regolith
25
+ Create your first distributed app
26
+ bash# Create new app
27
+ regolith new my-app
28
+ cd my-app
29
+
30
+ # Generate microservices
31
+ regolith generate service users
32
+ regolith generate service orders
33
+
34
+ # Start everything
35
+ regolith server
36
+ Your services are now running:
37
+
38
+ 👥 users_service at http://localhost:3001
39
+ 📦 orders_service at http://localhost:3002
40
+ 🗄️ PostgreSQL at localhost:5432
41
+
42
+ 🌟 Cross-Service Magic
43
+ Define relationships that span services:
44
+ ruby# users_service/app/models/user.rb
45
+ class User < Regolith::RegolithRecord
46
+ service :users
47
+ has_many :orders, service: :orders
48
+ end
49
+
50
+ # orders_service/app/models/order.rb
51
+ class Order < Regolith::RegolithRecord
52
+ service :orders
53
+ belongs_to :user, service: :users
54
+ end
55
+ Use them naturally:
56
+ ruby# In users_service Rails console
57
+ user = User.create!(name: "Alice", email: "alice@example.com")
58
+ user.orders.create!(product: "iPhone", amount: 999)
59
+
60
+ # In orders_service Rails console
61
+ order = Order.find(1)
62
+ order.user.name # "Alice" - fetched from users_service!
63
+ 📁 Project Structure
64
+ my-app/
65
+ ├── docker-compose.yml # Orchestration config
66
+ ├── config/
67
+ │ └── regolith.yml # Service registry
68
+ ├── services/
69
+ │ ├── users_service/ # Rails API app
70
+ │ └── orders_service/ # Rails API app
71
+ └── Makefile # Convenience commands
72
+ 🛠️ CLI Commands
73
+ CommandPurposeregolith new <app>Create new Regolith applicationregolith generate service <name>Generate new microserviceregolith serverStart all services with Docker Composeregolith console <service>Open Rails console for serviceregolith versionShow version information
74
+ 🏗️ How It Works
75
+ Service Discovery
76
+ Services are registered in config/regolith.yml:
77
+ yamlname: my-app
78
+ services:
79
+ users:
80
+ port: 3001
81
+ root: ./services/users_service
82
+ orders:
83
+ port: 3002
84
+ root: ./services/orders_service
85
+ Inter-Service Communication
86
+ The Regolith::RegolithRecord class extends ActiveModel with HTTP-based service calls:
87
+ rubyclass User < Regolith::RegolithRecord
88
+ service :users
89
+ has_many :orders, service: :orders
90
+ end
91
+ When you call user.orders.create!(), Regolith:
92
+
93
+ Identifies the target service (:orders)
94
+ Makes HTTP POST to orders_service/orders
95
+ Includes the foreign key (user_id)
96
+ Returns the created object
97
+
98
+ Docker Integration
99
+ Each service gets its own container with:
100
+
101
+ Hot reload via volume mounts
102
+ Shared PostgreSQL database
103
+ Inter-service networking
104
+ Environment-based configuration
105
+
106
+ 🧪 Development Workflow
107
+ bash# Start all services
108
+ regolith server
109
+
110
+ # Open Rails console for any service
111
+ regolith console users
112
+
113
+ # Test cross-service relationships
114
+ User.first.orders.create!(product: "Laptop", amount: 1200)
115
+
116
+ # Check logs
117
+ docker-compose logs -f users
118
+
119
+ # Add new service
120
+ regolith generate service analytics
121
+ 📦 Example: E-commerce Platform
122
+ bash# Create the platform
123
+ regolith new ecommerce
124
+ cd ecommerce
125
+
126
+ # Generate services
127
+ regolith generate service users
128
+ regolith generate service products
129
+ regolith generate service orders
130
+ regolith generate service payments
131
+
132
+ # Start everything
133
+ regolith server
134
+ ruby# Define cross-service relationships
135
+ class User < Regolith::RegolithRecord
136
+ service :users
137
+ has_many :orders, service: :orders
138
+ end
139
+
140
+ class Order < Regolith::RegolithRecord
141
+ service :orders
142
+ belongs_to :user, service: :users
143
+ belongs_to :product, service: :products
144
+ has_one :payment, service: :payments
145
+ end
146
+
147
+ # Use like Rails
148
+ user = User.create!(name: "Bob", email: "bob@shop.com")
149
+ product = Product.create!(name: "MacBook", price: 2000)
150
+ order = user.orders.create!(product: product)
151
+ order.create_payment!(amount: 2000, method: "credit_card")
152
+ 🔧 Configuration
153
+ Custom Service Configuration
154
+ ruby# config/initializers/regolith.rb
155
+ Regolith.configure do |config|
156
+ config.timeout = 30
157
+ config.retry_attempts = 3
158
+ end
159
+ Environment Variables
160
+ VariablePurposeREGOLITH_SERVICE_NAMECurrent service identifierREGOLITH_SERVICE_PORTService port mappingDATABASE_URLShared database connection
161
+ Production Deployment
162
+ For production, replace Docker Compose with:
163
+
164
+ Container orchestration: Kubernetes, ECS, or similar
165
+ Service mesh: Istio, Linkerd for advanced networking
166
+ Service discovery: Consul, etcd for dynamic registration
167
+ Load balancing: nginx, HAProxy for traffic distribution
168
+ Monitoring: Prometheus, Grafana for observability
169
+
170
+ 📚 Examples
171
+ Working Observatory App
172
+ The repository includes a complete example in observatory/:
173
+
174
+ telescope_service - User management
175
+ records_service - Observation tracking
176
+ Full cross-service associations
177
+ Docker orchestration
178
+
179
+ More Examples
180
+
181
+ E-commerce Platform - Multi-service shopping platform
182
+ Social Network - Users, posts, and comments across services
183
+ Blog Platform - Authors, articles, and analytics services
184
+
185
+ 🤝 Contributing
186
+ We love contributions! See CONTRIBUTING.md for guidelines.
187
+
188
+ Fork the repository
189
+ Create feature branch (git checkout -b feature/amazing-feature)
190
+ Commit changes (git commit -m 'Add amazing feature')
191
+ Push to branch (git push origin feature/amazing-feature)
192
+ Open Pull Request
193
+
194
+ 🐛 Issues & Support
195
+
196
+ Bug reports: GitHub Issues
197
+ Feature requests: GitHub Discussions
198
+ Documentation: GitHub Wiki
199
+
200
+ 📄 License
201
+ MIT License - see LICENSE for details.
202
+ 🙏 Acknowledgments
203
+
204
+ Inspired by the elegance of Ruby on Rails
205
+ Built for the modern microservices era
206
+ Designed to make distributed systems feel human
207
+
208
+
209
+ Regolith: Because microservices shouldn't be micro-management.
210
+ Built with ❤️ for developers who want to focus on features, not infrastructure.
data/bin/regolith ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/regolith'
4
+
5
+ Regolith::CLI.new(ARGV).run
@@ -0,0 +1,435 @@
1
+ require 'fileutils'
2
+ require 'yaml'
3
+ require 'erb'
4
+ require 'timeout'
5
+ require 'rubygems'
6
+
7
+ module Regolith
8
+ class CLI
9
+ def initialize(args)
10
+ @args = args
11
+ @command = args[0]
12
+ @subcommand = args[1]
13
+ @name = args[2]
14
+ end
15
+
16
+ def run
17
+ case @command
18
+ when 'new'
19
+ create_new_app(@subcommand)
20
+ when 'generate'
21
+ generate_resource(@subcommand, @name)
22
+ when 'server'
23
+ start_server
24
+ when 'console'
25
+ open_console(@subcommand)
26
+ when 'version'
27
+ puts "Regolith #{Regolith::VERSION}"
28
+ else
29
+ show_help
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def create_new_app(app_name)
36
+ unless app_name
37
+ puts "❌ Error: App name required"
38
+ puts "Usage: regolith new <app_name>"
39
+ exit 1
40
+ end
41
+
42
+ puts "🏗 Creating Regolith app '#{app_name}'..."
43
+
44
+ FileUtils.mkdir_p(app_name)
45
+ Dir.chdir(app_name) do
46
+ create_app_structure(app_name)
47
+ end
48
+
49
+ puts "✅ Created Regolith app '#{app_name}'"
50
+ puts "→ Next: cd #{app_name} && regolith generate service <service_name>"
51
+ end
52
+
53
+ def create_app_structure(app_name)
54
+ FileUtils.mkdir_p(['config', 'services', '.bin'])
55
+
56
+ regolith_config = {
57
+ 'name' => app_name,
58
+ 'version' => '0.1.0',
59
+ 'services' => {},
60
+ 'infrastructure' => {
61
+ 'database' => {
62
+ 'type' => 'postgresql',
63
+ 'port' => 5432
64
+ }
65
+ }
66
+ }
67
+
68
+ File.write('config/regolith.yml', YAML.dump(regolith_config))
69
+ File.write('docker-compose.yml', generate_docker_compose(app_name))
70
+ File.write('Makefile', generate_makefile)
71
+ File.write('.bin/regolith', generate_regolith_shim)
72
+ FileUtils.chmod(0755, '.bin/regolith')
73
+ end
74
+
75
+ def generate_resource(resource_type, resource_name)
76
+ unless resource_type == 'service' && resource_name
77
+ puts "❌ Error: Invalid generate command"
78
+ puts "Usage: regolith generate service <service_name>"
79
+ exit 1
80
+ end
81
+
82
+ generate_service(resource_name)
83
+ end
84
+
85
+ def generate_service(service_name)
86
+ puts "🔧 Creating service '#{service_name}'..."
87
+ config = load_regolith_config
88
+ port = 3001 + (config['services']&.size || 0)
89
+ service_dir = "services/#{service_name}_service"
90
+
91
+ puts " → Generating Rails API app..."
92
+
93
+ cmd = [
94
+ "rails", "new", service_dir,
95
+ "--api",
96
+ "--skip-active-storage",
97
+ "--skip-action-mailbox",
98
+ "--skip-action-cable",
99
+ "--skip-bundle",
100
+ "--quiet"
101
+ ]
102
+
103
+ puts "Running: #{cmd.join(' ')}"
104
+
105
+ begin
106
+ Timeout.timeout(180) do
107
+ success = system(*cmd)
108
+ unless success
109
+ puts "❌ rails new failed with exit code #{$?.exitstatus}"
110
+ exit 1
111
+ end
112
+ end
113
+ rescue Timeout::Error
114
+ puts "⏱️ rails new timed out after 3 minutes"
115
+ puts "Try running manually: #{cmd.join(' ')}"
116
+ exit 1
117
+ end
118
+
119
+ puts "🔧 Overwriting Gemfile to remove sqlite3 and other defaults..."
120
+ ruby_version = `ruby -e 'print RUBY_VERSION'`.strip
121
+ ruby_major_minor = ruby_version.split('.')[0..1].join('.')
122
+
123
+ custom_gemfile = <<~GEMFILE
124
+ source "https://rubygems.org"
125
+ ruby "~> #{ruby_major_minor}.0"
126
+ gem "rails", "~> 7.2.2.1"
127
+ gem "pg", "~> 1.5"
128
+ gem "puma", ">= 5.0"
129
+ gem "rack-cors"
130
+
131
+ group :development, :test do
132
+ gem "debug", platforms: %i[ mri mswin mswin64 mingw x64_mingw ], require: "debug/prelude"
133
+ gem "brakeman", require: false
134
+ gem "rubocop-rails-omakase", require: false
135
+ end
136
+
137
+ gem "regolith", path: "vendor/regolith"
138
+ GEMFILE
139
+
140
+ File.write("#{service_dir}/Gemfile", custom_gemfile)
141
+
142
+ vendor_dir = File.join(service_dir, "vendor")
143
+ regolith_vendor_dir = File.join(vendor_dir, "regolith")
144
+ FileUtils.mkdir_p(vendor_dir)
145
+ source_dir = File.expand_path("../..", __dir__)
146
+ FileUtils.cp_r(source_dir, regolith_vendor_dir)
147
+ Dir.glob(File.join(regolith_vendor_dir, "regolith-*")).each { |nested_dir| FileUtils.rm_rf(nested_dir) }
148
+
149
+ gemspec_path = File.join(regolith_vendor_dir, "regolith.gemspec")
150
+ File.write(gemspec_path, generate_regolith_gemspec) unless File.exist?(gemspec_path)
151
+
152
+ puts "📦 Vendored Regolith gem into #{regolith_vendor_dir}"
153
+
154
+ puts " → Running bundle install..."
155
+ Dir.chdir(service_dir) do
156
+ unless system("bundle install")
157
+ puts "❌ bundle install failed"
158
+ puts "→ You may be missing system libraries like libyaml-dev libsqlite3-dev build-essential pkg-config"
159
+ puts "→ Try: sudo apt install -y libyaml-dev libsqlite3-dev build-essential pkg-config"
160
+ exit 1
161
+ end
162
+ end
163
+
164
+ patch_rails_app(service_dir, service_name, port)
165
+
166
+ config['services'][service_name] = {
167
+ 'port' => port,
168
+ 'root' => "./#{service_dir}"
169
+ }
170
+ save_regolith_config(config)
171
+ update_docker_compose(config)
172
+
173
+ puts "✅ Created service '#{service_name}'"
174
+ puts "🚀 Service running on port #{port}"
175
+ puts "→ Next: regolith generate service <another_service> or regolith server"
176
+ end
177
+
178
+ def patch_rails_app(service_dir, service_name, port)
179
+ initializer_dir = "#{service_dir}/config/initializers"
180
+ FileUtils.mkdir_p(initializer_dir)
181
+ File.write("#{initializer_dir}/regolith.rb", generate_regolith_initializer(service_name))
182
+ File.write("#{service_dir}/Dockerfile", generate_dockerfile)
183
+
184
+ app_rb_path = "#{service_dir}/config/application.rb"
185
+ app_rb_content = File.read(app_rb_path)
186
+
187
+ # Ensure Regolith config block is inserted safely
188
+ insert_block = <<~RUBY
189
+
190
+ # Regolith configuration
191
+ config.middleware.insert_before 0, Rack::Cors do
192
+ allow do
193
+ origins '*'
194
+ resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head]
195
+ end
196
+ end
197
+
198
+ config.regolith_service_name = '#{service_name}'
199
+ config.regolith_service_port = #{port}
200
+ RUBY
201
+
202
+ app_rb_lines = app_rb_content.lines
203
+ target_index = app_rb_lines.index { |line| line =~ /^\s*class Application < Rails::Application/ }
204
+
205
+ if target_index
206
+ insertion_point = app_rb_lines.index { |line| line.strip == 'end' && app_rb_lines.index(line) > target_index }
207
+ if insertion_point
208
+ app_rb_lines.insert(insertion_point, insert_block)
209
+ File.write(app_rb_path, app_rb_lines.join)
210
+ end
211
+ end
212
+
213
+ # Clean up boot.rb again just in case
214
+ boot_path = "#{service_dir}/config/boot.rb"
215
+ if File.exist?(boot_path)
216
+ clean_boot_rb = <<~RUBY
217
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
218
+ require "bundler/setup" # Set up gems listed in the Gemfile.
219
+ RUBY
220
+ File.write(boot_path, clean_boot_rb)
221
+ end
222
+ end
223
+
224
+
225
+ def start_server
226
+ unless File.exist?('docker-compose.yml')
227
+ puts "❌ Error: Not in a Regolith app directory"
228
+ exit 1
229
+ end
230
+
231
+ puts "🚀 Starting Regolith services..."
232
+ config = load_regolith_config
233
+
234
+ config['services'].each do |name, service|
235
+ puts "🚀 #{name}_service running at http://localhost:#{service['port']}"
236
+ end
237
+
238
+ puts "🏭 Service registry loaded from config/regolith.yml"
239
+ puts ""
240
+
241
+ exec("docker-compose up --build")
242
+ end
243
+
244
+ def open_console(service_name)
245
+ unless service_name
246
+ puts "❌ Error: Service name required"
247
+ puts "Usage: regolith console <service_name>"
248
+ exit 1
249
+ end
250
+
251
+ config = load_regolith_config
252
+ unless config['services'][service_name]
253
+ puts "❌ Error: Service '#{service_name}' not found"
254
+ exit 1
255
+ end
256
+
257
+ puts "🧪 Opening Rails console for #{service_name}_service..."
258
+ exec("docker-compose exec #{service_name} rails console")
259
+ end
260
+
261
+ def load_regolith_config
262
+ return { 'services' => {} } unless File.exist?('config/regolith.yml')
263
+ config = YAML.load_file('config/regolith.yml') || {}
264
+ config['services'] ||= {}
265
+ config
266
+ end
267
+
268
+ def save_regolith_config(config)
269
+ FileUtils.mkdir_p('config')
270
+ File.write('config/regolith.yml', YAML.dump(config))
271
+ end
272
+
273
+ def update_docker_compose(config)
274
+ docker_compose = generate_docker_compose(config['name'], config['services'])
275
+ File.write('docker-compose.yml', docker_compose)
276
+ end
277
+
278
+ def generate_docker_compose(app_name, services = {})
279
+ template = <<~YAML
280
+ version: '3.8'
281
+
282
+ services:
283
+ db:
284
+ image: postgres:14
285
+ environment:
286
+ POSTGRES_DB: #{app_name}_development
287
+ POSTGRES_USER: postgres
288
+ POSTGRES_PASSWORD: password
289
+ ports:
290
+ - "5432:5432"
291
+ volumes:
292
+ - postgres_data:/var/lib/postgresql/data
293
+
294
+ <% services.each do |name, service| %>
295
+ <%= name %>:
296
+ build: <%= service['root'] %>
297
+ ports:
298
+ - "<%= service['port'] %>:3000"
299
+ depends_on:
300
+ - db
301
+ environment:
302
+ DATABASE_URL: postgres://postgres:password@db:5432/<%= app_name %>_development
303
+ REGOLITH_SERVICE_NAME: <%= name %>
304
+ REGOLITH_SERVICE_PORT: <%= service['port'] %>
305
+ volumes:
306
+ - <%= service['root'] %>:/app
307
+ command: bash -c "rm -f tmp/pids/server.pid && bundle install && rails server -b 0.0.0.0"
308
+ <% end %>
309
+
310
+ volumes:
311
+ postgres_data:
312
+ YAML
313
+
314
+ ERB.new(template).result(binding)
315
+ end
316
+
317
+ def generate_dockerfile
318
+ <<~DOCKERFILE
319
+ FROM ruby:3.1
320
+
321
+ WORKDIR /app
322
+
323
+ RUN apt-get update -qq && apt-get install -y nodejs postgresql-client libyaml-dev libsqlite3-dev build-essential pkg-config
324
+
325
+ COPY . .
326
+
327
+ RUN bundle install
328
+
329
+ EXPOSE 3000
330
+
331
+ CMD ["rails", "server", "-b", "0.0.0.0"]
332
+ DOCKERFILE
333
+ end
334
+
335
+ def generate_regolith_initializer(service_name)
336
+ <<~RUBY
337
+ # Regolith service configuration
338
+ Rails.application.configure do
339
+ config.regolith = OpenStruct.new(
340
+ service_name: '#{service_name}',
341
+ service_registry: Rails.root.join('../../config/regolith.yml')
342
+ )
343
+ end
344
+
345
+ if File.exist?(Rails.application.config.regolith.service_registry)
346
+ REGOLITH_SERVICES = YAML.load_file(Rails.application.config.regolith.service_registry)['services'] || {}
347
+ else
348
+ REGOLITH_SERVICES = {}
349
+ end
350
+ RUBY
351
+ end
352
+
353
+ def generate_makefile
354
+ <<~MAKEFILE
355
+ .PHONY: server console build clean
356
+
357
+ server:
358
+ regolith server
359
+
360
+ console:
361
+ regolith console
362
+
363
+ build:
364
+ docker-compose build
365
+
366
+ clean:
367
+ docker-compose down -v
368
+ docker system prune -f
369
+ MAKEFILE
370
+ end
371
+
372
+ def generate_regolith_shim
373
+ <<~RUBY
374
+ #!/usr/bin/env ruby
375
+ exec("regolith", *ARGV)
376
+ RUBY
377
+ end
378
+
379
+ def generate_regolith_gemspec
380
+ <<~GEMSPEC
381
+ # -*- encoding: utf-8 -*-
382
+ lib = File.expand_path('../lib', __FILE__)
383
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
384
+ require 'regolith/version'
385
+
386
+ Gem::Specification.new do |gem|
387
+ gem.name = "regolith"
388
+ gem.version = Regolith::VERSION
389
+ gem.authors = ["Regolith Team"]
390
+ gem.email = ["team@regolith.dev"]
391
+ gem.description = %q{Microservices framework for Ruby}
392
+ gem.summary = %q{Build microservices with Ruby and Rails}
393
+ gem.homepage = "https://github.com/regolith/regolith"
394
+
395
+ gem.files = Dir['lib/**/*'] + ['README.md']
396
+ gem.executables = ['regolith']
397
+ gem.test_files = []
398
+ gem.require_paths = ["lib"]
399
+
400
+ gem.add_dependency "rails", ">= 7.0"
401
+ gem.add_dependency "rack-cors"
402
+ end
403
+ GEMSPEC
404
+ end
405
+
406
+ def show_help
407
+ puts <<~HELP
408
+ Regolith #{Regolith::VERSION} - Microservices framework
409
+
410
+ USAGE:
411
+ regolith <command> [options]
412
+
413
+ COMMANDS:
414
+ new <app_name> Create a new Regolith application
415
+ generate service <name> Generate a new microservice
416
+ server Start all services with Docker Compose
417
+ console <service_name> Open Rails console for a service
418
+ version Show version information
419
+
420
+ EXAMPLES:
421
+ regolith new observatory
422
+ regolith generate service telescope
423
+ regolith generate service records
424
+ regolith server
425
+ regolith console telescope
426
+
427
+ Get started:
428
+ regolith new myapp
429
+ cd myapp
430
+ regolith generate service users
431
+ regolith server
432
+ HELP
433
+ end
434
+ end
435
+ end
@@ -0,0 +1,74 @@
1
+ # lib/regolith/regolith_association.rb
2
+ module Regolith
3
+ class RegolithAssociation
4
+ def initialize(parent_record, association_name, service, type, foreign_key_value = nil)
5
+ @parent_record = parent_record
6
+ @association_name = association_name
7
+ @service = service
8
+ @type = type
9
+ @foreign_key_value = foreign_key_value
10
+ @loaded_records = nil
11
+ end
12
+
13
+ def create!(attributes = {})
14
+ case @type
15
+ when :has_many
16
+ # For has_many, set the foreign key to parent's id
17
+ foreign_key = "#{@parent_record.class.name.underscore}_id"
18
+ attributes[foreign_key] = @parent_record.id
19
+
20
+ # Create in the target service
21
+ target_class = @association_name.to_s.singularize.camelize.constantize
22
+ target_class.create(attributes)
23
+ end
24
+ end
25
+
26
+ def first
27
+ case @type
28
+ when :belongs_to
29
+ target_class = @association_name.to_s.camelize.constantize
30
+ target_class.find(@foreign_key_value)
31
+ when :has_many
32
+ all.first
33
+ end
34
+ end
35
+
36
+ def all
37
+ load_records unless @loaded_records
38
+ @loaded_records
39
+ end
40
+
41
+ def method_missing(method_name, *args, &block)
42
+ all.send(method_name, *args, &block)
43
+ end
44
+
45
+ def respond_to_missing?(method_name, include_private = false)
46
+ all.respond_to?(method_name, include_private) || super
47
+ end
48
+
49
+ private
50
+
51
+ def load_records
52
+ case @type
53
+ when :has_many
54
+ # Query the target service for records that belong to this parent
55
+ foreign_key = "#{@parent_record.class.name.underscore}_id"
56
+ target_service = @service
57
+ resource_name = @association_name.to_s
58
+
59
+ response = ServiceClient.get(
60
+ target_service,
61
+ "#{resource_name}?#{foreign_key}=#{@parent_record.id}"
62
+ )
63
+
64
+ if response.code == '200'
65
+ data = JSON.parse(response.body)
66
+ target_class = @association_name.to_s.singularize.camelize.constantize
67
+ @loaded_records = data.map { |attrs| target_class.new(attrs) }
68
+ else
69
+ @loaded_records = []
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,140 @@
1
+ # lib/regolith/regolith_record.rb
2
+ module Regolith
3
+ class RegolithRecord
4
+ include ActiveModel::Model
5
+ include ActiveModel::Attributes
6
+ include ActiveModel::Serialization
7
+
8
+ class_attribute :service_name, :resource_name, :primary_key
9
+ self.primary_key = :id
10
+
11
+ def self.inherited(subclass)
12
+ super
13
+ # Auto-detect service from class name or allow explicit setting
14
+ subclass.resource_name = subclass.name.underscore
15
+ end
16
+
17
+ def self.service(service_name)
18
+ self.service_name = service_name
19
+ end
20
+
21
+ def self.has_many(association_name, options = {})
22
+ service = options[:service]
23
+
24
+ define_method(association_name) do
25
+ RegolithAssociation.new(
26
+ self,
27
+ association_name,
28
+ service,
29
+ :has_many
30
+ )
31
+ end
32
+ end
33
+
34
+ def self.belongs_to(association_name, options = {})
35
+ service = options[:service]
36
+ foreign_key = options[:foreign_key] || "#{association_name}_id"
37
+
38
+ # Add the foreign key attribute
39
+ attribute foreign_key, :integer
40
+
41
+ define_method(association_name) do
42
+ foreign_key_value = send(foreign_key)
43
+ return nil unless foreign_key_value
44
+
45
+ RegolithAssociation.new(
46
+ self,
47
+ association_name,
48
+ service,
49
+ :belongs_to,
50
+ foreign_key_value
51
+ ).first
52
+ end
53
+ end
54
+
55
+ def self.find(id)
56
+ response = ServiceClient.get(service_name, "#{resource_name}/#{id}")
57
+ if response.code == '200'
58
+ new(JSON.parse(response.body))
59
+ else
60
+ raise ActiveRecord::RecordNotFound, "Couldn't find #{name} with 'id'=#{id}"
61
+ end
62
+ end
63
+
64
+ def self.all
65
+ response = ServiceClient.get(service_name, resource_name)
66
+ if response.code == '200'
67
+ JSON.parse(response.body).map { |attrs| new(attrs) }
68
+ else
69
+ []
70
+ end
71
+ end
72
+
73
+ def self.create(attributes = {})
74
+ instance = new(attributes)
75
+ instance.save
76
+ instance
77
+ end
78
+
79
+ def self.create!(attributes = {})
80
+ instance = create(attributes)
81
+ raise "Record invalid" unless instance.persisted?
82
+ instance
83
+ end
84
+
85
+ def save
86
+ if persisted?
87
+ update
88
+ else
89
+ create
90
+ end
91
+ end
92
+
93
+ def create
94
+ response = ServiceClient.post(
95
+ self.class.service_name,
96
+ self.class.resource_name,
97
+ attributes.to_json
98
+ )
99
+
100
+ if response.code.to_i.between?(200, 299)
101
+ data = JSON.parse(response.body)
102
+ data.each { |key, value| send("#{key}=", value) if respond_to?("#{key}=") }
103
+ true
104
+ else
105
+ false
106
+ end
107
+ end
108
+
109
+ def update
110
+ response = ServiceClient.put(
111
+ self.class.service_name,
112
+ "#{self.class.resource_name}/#{id}",
113
+ attributes.to_json
114
+ )
115
+
116
+ response.code.to_i.between?(200, 299)
117
+ end
118
+
119
+ def destroy
120
+ response = ServiceClient.delete(
121
+ self.class.service_name,
122
+ "#{self.class.resource_name}/#{id}"
123
+ )
124
+
125
+ response.code.to_i.between?(200, 299)
126
+ end
127
+
128
+ def persisted?
129
+ id.present?
130
+ end
131
+
132
+ def id
133
+ send(self.class.primary_key) if respond_to?(self.class.primary_key)
134
+ end
135
+
136
+ def id=(value)
137
+ send("#{self.class.primary_key}=", value) if respond_to?("#{self.class.primary_key}=")
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,68 @@
1
+ # lib/regolith/service_client.rb
2
+ module Regolith
3
+ class ServiceClient
4
+ class << self
5
+ def get(service_name, path)
6
+ make_request(service_name, :get, path)
7
+ end
8
+
9
+ def post(service_name, path, body = nil)
10
+ make_request(service_name, :post, path, body)
11
+ end
12
+
13
+ def put(service_name, path, body = nil)
14
+ make_request(service_name, :put, path, body)
15
+ end
16
+
17
+ def delete(service_name, path)
18
+ make_request(service_name, :delete, path)
19
+ end
20
+
21
+ private
22
+
23
+ def make_request(service_name, method, path, body = nil)
24
+ service_config = Regolith.service_registry[service_name.to_s]
25
+
26
+ unless service_config
27
+ raise "Service '#{service_name}' not found in registry"
28
+ end
29
+
30
+ # In Docker Compose, services can be reached by service name
31
+ # In development, use localhost with the mapped port
32
+ host = in_docker? ? service_name.to_s : 'localhost'
33
+ port = in_docker? ? 3000 : service_config['port']
34
+
35
+ uri = URI("http://#{host}:#{port}/#{path}")
36
+
37
+ http = Net::HTTP.new(uri.host, uri.port)
38
+ http.read_timeout = Regolith.configuration.timeout
39
+
40
+ case method
41
+ when :get
42
+ request = Net::HTTP::Get.new(uri)
43
+ when :post
44
+ request = Net::HTTP::Post.new(uri)
45
+ request.body = body if body
46
+ request['Content-Type'] = 'application/json'
47
+ when :put
48
+ request = Net::HTTP::Put.new(uri)
49
+ request.body = body if body
50
+ request['Content-Type'] = 'application/json'
51
+ when :delete
52
+ request = Net::HTTP::Delete.new(uri)
53
+ end
54
+
55
+ begin
56
+ http.request(request)
57
+ rescue => e
58
+ # Return a mock response for connection errors
59
+ OpenStruct.new(code: '500', body: { error: e.message }.to_json)
60
+ end
61
+ end
62
+
63
+ def in_docker?
64
+ File.exist?('/.dockerenv') || ENV['REGOLITH_SERVICE_NAME'].present?
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,4 @@
1
+ # lib/regolith/version.rb
2
+ module Regolith
3
+ VERSION = "0.1.0"
4
+ end
data/lib/regolith.rb ADDED
@@ -0,0 +1,51 @@
1
+ # lib/regolith.rb
2
+ require 'active_support/all'
3
+ require 'active_model'
4
+ require 'net/http'
5
+ require 'json'
6
+ require 'yaml'
7
+
8
+ require_relative 'regolith/version'
9
+ require_relative 'regolith/service_client'
10
+ require_relative 'regolith/regolith_association'
11
+ require_relative 'regolith/regolith_record'
12
+ require_relative 'regolith/cli'
13
+
14
+ module Regolith
15
+ class << self
16
+ def configure
17
+ yield(configuration)
18
+ end
19
+
20
+ def configuration
21
+ @configuration ||= Configuration.new
22
+ end
23
+
24
+ def service_registry
25
+ @service_registry ||= load_service_registry
26
+ end
27
+
28
+ private
29
+
30
+ def load_service_registry
31
+ if defined?(Rails) && Rails.application.config.respond_to?(:regolith)
32
+ registry_path = Rails.application.config.regolith.service_registry
33
+ if File.exist?(registry_path)
34
+ YAML.load_file(registry_path)['services'] || {}
35
+ else
36
+ {}
37
+ end
38
+ else
39
+ defined?(REGOLITH_SERVICES) ? REGOLITH_SERVICES : {}
40
+ end
41
+ end
42
+ end
43
+
44
+ class Configuration
45
+ attr_accessor :service_name, :service_port, :timeout
46
+
47
+ def initialize
48
+ @timeout = 30
49
+ end
50
+ end
51
+ end
data/regolith.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ # regolith.gemspec
2
+ require_relative 'lib/regolith/version'
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "regolith"
6
+ spec.version = Regolith::VERSION
7
+ spec.authors = ["Regolith Team"]
8
+ spec.email = ["team@regolith.dev"]
9
+
10
+ spec.summary = "Rails for distributed systems"
11
+ spec.description = "Regolith provides seamless inter-service communication and orchestration for Ruby microservices"
12
+ spec.homepage = "https://github.com/regolith/regolith"
13
+ spec.license = "MIT"
14
+
15
+ spec.required_ruby_version = ">= 2.7.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/regolith/regolith"
19
+ spec.metadata["changelog_uri"] = "https://github.com/regolith/regolith/CHANGELOG.md"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
23
+ Dir['lib/**/*', 'bin/*', '*.md', '*.txt', 'LICENSE*', 'regolith.gemspec']
24
+ end
25
+
26
+ spec.bindir = "bin"
27
+ spec.executables = ["regolith"]
28
+ spec.require_paths = ["lib"]
29
+
30
+ # Dependencies
31
+ spec.add_dependency "activesupport", ">= 6.0"
32
+ spec.add_dependency "activemodel", ">= 6.0"
33
+
34
+ # Development dependencies
35
+ spec.add_development_dependency "rspec", "~> 3.0"
36
+ spec.add_development_dependency "rake", "~> 13.0"
37
+ end
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: regolith
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Regolith Team
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-07-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '6.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '6.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activemodel
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '6.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '6.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '13.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '13.0'
69
+ description: Regolith provides seamless inter-service communication and orchestration
70
+ for Ruby microservices
71
+ email:
72
+ - team@regolith.dev
73
+ executables:
74
+ - regolith
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - CHANGELOG.md
79
+ - LICENSE
80
+ - README.md
81
+ - bin/regolith
82
+ - lib/regolith.rb
83
+ - lib/regolith/cli.rb
84
+ - lib/regolith/regolith_association.rb
85
+ - lib/regolith/regolith_record.rb
86
+ - lib/regolith/service_client.rb
87
+ - lib/regolith/version.rb
88
+ - regolith.gemspec
89
+ homepage: https://github.com/regolith/regolith
90
+ licenses:
91
+ - MIT
92
+ metadata:
93
+ homepage_uri: https://github.com/regolith/regolith
94
+ source_code_uri: https://github.com/regolith/regolith
95
+ changelog_uri: https://github.com/regolith/regolith/CHANGELOG.md
96
+ post_install_message:
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: 2.7.0
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubygems_version: 3.3.15
112
+ signing_key:
113
+ specification_version: 4
114
+ summary: Rails for distributed systems
115
+ test_files: []