kirei 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/CHANGELOG.md +11 -0
- data/README.md +91 -0
- data/kirei.gemspec +2 -3
- data/lib/boot.rb +1 -2
- data/lib/cli/commands/new_app/base_directories.rb +3 -0
- data/lib/cli/commands/new_app/execute.rb +4 -0
- data/lib/cli/commands/new_app/files/app.rb +23 -1
- data/lib/cli/commands/new_app/files/config_ru.rb +34 -0
- data/lib/cli/commands/new_app/files/db_rake.rb +178 -0
- data/lib/cli/commands/new_app/files/irbrc.rb +4 -4
- data/lib/cli/commands/new_app/files/rakefile.rb +27 -0
- data/lib/cli/commands/new_app/files/routes.rb +31 -0
- data/lib/cli/commands/start.rb +2 -2
- data/lib/kirei/app.rb +72 -0
- data/lib/kirei/app_base.rb +3 -1
- data/lib/kirei/base_controller.rb +8 -6
- data/lib/kirei/base_model.rb +10 -1
- data/lib/kirei/logger.rb +3 -1
- data/lib/kirei/middleware.rb +32 -0
- data/lib/kirei/router.rb +61 -0
- data/lib/kirei/version.rb +1 -1
- metadata +14 -22
- data/lib/kirei/base.rb +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a37402ff7c216a16e3cc2a279d477e91d7ed8c40fd58169a8c598e47efd90d7
|
4
|
+
data.tar.gz: 1fd249f44b20dc1dfba0ace6ea52f6de6f67ee47e4c2a7f511438ceafba4985f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d6d29c323e8b3438a0c9775f2b92312182a709443aa04843145bfed76226d71e2d78081361b178dc6293dfefa75571d525981b5ed84fee2664c93a880eb5f6fa
|
7
|
+
data.tar.gz: 6b33ff256a0a199333d789c81ff70f4dca49225069f0c854fe0f4cfec4c6a68c918a6966b92c93ef76aebd14117029c5f3e7ad36d65d7d74033c5d7a0f905ab4
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
1
3
|
## [Unreleased]
|
2
4
|
|
3
5
|
## [0.1.0] - 2023-09-02
|
4
6
|
|
5
7
|
- Initial release
|
8
|
+
- added base model
|
9
|
+
- added database connection
|
10
|
+
- WIP: basic cli to scaffold a new project
|
11
|
+
|
12
|
+
## [0.2.0] - 2023-09-02
|
13
|
+
|
14
|
+
- added routing
|
15
|
+
- added base controller
|
16
|
+
- added database tasks (create, drop, migrate, rollback, generate migration)
|
data/README.md
CHANGED
@@ -49,6 +49,10 @@ bundle exec kirei new "MyApp"
|
|
49
49
|
|
50
50
|
### Quick Start
|
51
51
|
|
52
|
+
Find a test app in the [spec/test_app](spec/test_app) directory. It is a fully functional example of a Kirei app.
|
53
|
+
|
54
|
+
#### Models
|
55
|
+
|
52
56
|
All models must inherit from `T::Struct` and include `Kirei::BaseModel`. They must implement `id` which must hold the primary key of the table. The primary key must be named `id` and be of type `T.any(String, Integer)`.
|
53
57
|
|
54
58
|
```ruby
|
@@ -86,6 +90,93 @@ first_user = User.resolve_first(query) # T.nilable(User)
|
|
86
90
|
first_user = User.from_hash(query.first.stringify_keys)
|
87
91
|
```
|
88
92
|
|
93
|
+
#### Database Migrations
|
94
|
+
|
95
|
+
Read the [Sequel Migrations](https://github.com/jeremyevans/sequel/blob/5.78.0/doc/schema_modification.rdoc) documentation for detailed information.
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
Sequel.migration do
|
99
|
+
up do
|
100
|
+
create_table(:airports) do
|
101
|
+
primary_key :id
|
102
|
+
String :name, null: false
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
down do
|
107
|
+
drop_table(:airports)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
Applying migrations:
|
113
|
+
|
114
|
+
```shell
|
115
|
+
# create the database
|
116
|
+
bundle exec rake db:create
|
117
|
+
|
118
|
+
# drop the database
|
119
|
+
bundle exec rake db:drop
|
120
|
+
|
121
|
+
# apply all pending migrations
|
122
|
+
bundle exec rake db:migrate
|
123
|
+
|
124
|
+
# roll back the last n migration
|
125
|
+
STEPS=1 bundle exec rake db:rollback
|
126
|
+
|
127
|
+
# run db/seeds.rb to seed the database
|
128
|
+
bundle exec rake db:migrate
|
129
|
+
|
130
|
+
# scaffold a new migration file
|
131
|
+
bundle exec rake 'db:migration[CreateAirports]'
|
132
|
+
```
|
133
|
+
|
134
|
+
#### Routing
|
135
|
+
|
136
|
+
Define routes anywhere in your app; by convention, they are defined in `config/routes.rb`:
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
# config/routes.rb
|
140
|
+
|
141
|
+
Kirei::Router.add_routes([
|
142
|
+
Kirei::Router::Route.new(
|
143
|
+
verb: "GET",
|
144
|
+
path: "/livez",
|
145
|
+
controller: Controllers::Health,
|
146
|
+
action: "livez",
|
147
|
+
),
|
148
|
+
|
149
|
+
Kirei::Router::Route.new(
|
150
|
+
verb: "GET",
|
151
|
+
path: "/airports",
|
152
|
+
controller: Controllers::Airports,
|
153
|
+
action: "index",
|
154
|
+
),
|
155
|
+
])
|
156
|
+
```
|
157
|
+
|
158
|
+
#### Controllers
|
159
|
+
|
160
|
+
Controllers can be defined anywhere; by convention, they are defined in the `app/controllers` directory:
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
module Controllers
|
164
|
+
class Airports < Kirei::BaseController
|
165
|
+
extend T::Sig
|
166
|
+
|
167
|
+
sig { returns(Kirei::Middleware::RackResponseType) }
|
168
|
+
def index
|
169
|
+
airports = Airport.all
|
170
|
+
|
171
|
+
# or use a serializer
|
172
|
+
data = Oj.dump(airports.map(&:serialize))
|
173
|
+
|
174
|
+
render(status: 200, body: data)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
```
|
179
|
+
|
89
180
|
## Contributions
|
90
181
|
|
91
182
|
We welcome contributions from the community. Before starting work on a major feature, please get in touch with us either via email or by opening an issue on GitHub. "Major feature" means anything that changes user-facing features or significant changes to the codebase itself.
|
data/kirei.gemspec
CHANGED
@@ -45,13 +45,12 @@ Gem::Specification.new do |spec|
|
|
45
45
|
|
46
46
|
# Utilities
|
47
47
|
spec.add_dependency "oj", "~> 3.0"
|
48
|
-
spec.add_dependency "rake", "~> 13.0"
|
49
48
|
spec.add_dependency "sorbet-runtime", "~> 0.5"
|
50
49
|
spec.add_dependency "tzinfo-data", "~> 1.0" # for containerized environments, e.g. on AWS ECS
|
51
50
|
|
52
51
|
# Web server & routing
|
53
|
-
spec.add_dependency "
|
54
|
-
spec.add_dependency "
|
52
|
+
spec.add_dependency "puma", "~> 6.0"
|
53
|
+
spec.add_dependency "rack", "~> 3.0"
|
55
54
|
|
56
55
|
# Database (Postgres)
|
57
56
|
spec.add_dependency "pg", "~> 1.0"
|
data/lib/boot.rb
CHANGED
@@ -15,8 +15,7 @@ require "bundler/setup"
|
|
15
15
|
require "logger"
|
16
16
|
require "sorbet-runtime"
|
17
17
|
require "oj"
|
18
|
-
require "
|
19
|
-
require "sinatra/namespace" # from sinatra-contrib
|
18
|
+
require "rack"
|
20
19
|
require "pg"
|
21
20
|
require "sequel" # "sequel_pg" is auto-required by "sequel"
|
22
21
|
|
@@ -9,7 +9,11 @@ module Cli
|
|
9
9
|
def self.call(app_name:)
|
10
10
|
BaseDirectories.call
|
11
11
|
Files::App.call(app_name)
|
12
|
+
Files::ConfigRu.call(app_name)
|
13
|
+
Files::DbRake.call(app_name)
|
12
14
|
Files::Irbrc.call
|
15
|
+
Files::Rakefile.call
|
16
|
+
Files::Routes.call
|
13
17
|
|
14
18
|
Kirei::Logger.logger.info(
|
15
19
|
"Kirei app '#{app_name}' scaffolded successfully!",
|
@@ -10,11 +10,33 @@ module Cli
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.content(app_name)
|
13
|
+
snake_case_app_name = app_name.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
14
|
+
|
13
15
|
<<~RUBY
|
14
16
|
# typed: true
|
15
17
|
# frozen_string_literal: true
|
16
18
|
|
17
|
-
|
19
|
+
# First: check if all gems are installed correctly
|
20
|
+
require "bundler/setup"
|
21
|
+
|
22
|
+
# Second: load all gems
|
23
|
+
# we have runtime/production ("default") and development gems ("development")
|
24
|
+
Bundler.require(:default)
|
25
|
+
Bundler.require(:development) if ENV["RACK_ENV"] == "development"
|
26
|
+
Bundler.require(:test) if ENV["RACK_ENV"] == "test"
|
27
|
+
|
28
|
+
# Third: load all initializers
|
29
|
+
Dir[File.join(__dir__, "config/initializers", "*.rb")].each { require(_1) }
|
30
|
+
|
31
|
+
# Fourth: load all application code
|
32
|
+
Dir[File.join(__dir__, "app/**/*", "*.rb")].each { require(_1) }
|
33
|
+
|
34
|
+
# Fifth: load configs
|
35
|
+
Dir[File.join(__dir__, "config", "*.rb")].each { require(_1) }
|
36
|
+
|
37
|
+
class #{app_name} < Kirei::AppBase
|
38
|
+
# Kirei configuration
|
39
|
+
config.app_name = "#{snake_case_app_name}"
|
18
40
|
end
|
19
41
|
RUBY
|
20
42
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# typed: false
|
2
|
+
|
3
|
+
module Cli
|
4
|
+
module Commands
|
5
|
+
module NewApp
|
6
|
+
module Files
|
7
|
+
class ConfigRu
|
8
|
+
def self.call(app_name)
|
9
|
+
File.write("config.ru", content(app_name))
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.content(app_name)
|
13
|
+
<<~RUBY
|
14
|
+
# typed: false
|
15
|
+
# frozen_string_literal: true
|
16
|
+
|
17
|
+
require_relative("app")
|
18
|
+
|
19
|
+
# Load middlewares here
|
20
|
+
use(Rack::Reloader, 0) if #{app_name}.environment == "development"
|
21
|
+
|
22
|
+
# Launch the app
|
23
|
+
run(#{app_name}.new)
|
24
|
+
|
25
|
+
# "use" all controllers
|
26
|
+
# store all routes in a global variable to render (localhost only)
|
27
|
+
# put "booted" statement
|
28
|
+
RUBY
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# typed: false
|
2
|
+
|
3
|
+
# rubocop:disable Metrics/ClassLength
|
4
|
+
|
5
|
+
module Cli
|
6
|
+
module Commands
|
7
|
+
module NewApp
|
8
|
+
module Files
|
9
|
+
class DbRake
|
10
|
+
def self.call(app_name)
|
11
|
+
# set db_name to snake_case version of app_name
|
12
|
+
db_name = app_name.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
13
|
+
File.write("lib/tasks/db.rake", content(app_name, db_name))
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.content(app_name, db_name)
|
17
|
+
<<~RUBY
|
18
|
+
# typed: false
|
19
|
+
|
20
|
+
# run on the database server once:
|
21
|
+
#
|
22
|
+
# CREATE DATABASE #{db_name}_${environment};
|
23
|
+
|
24
|
+
require_relative "../../app"
|
25
|
+
|
26
|
+
namespace :db do
|
27
|
+
# RACK_ENV=development bundle exec rake db:create
|
28
|
+
desc "Create the database"
|
29
|
+
task :create do
|
30
|
+
envs = ENV.key?("RACK_ENV") ? [ENV.fetch("RACK_ENV")] : %w[development test]
|
31
|
+
envs.each do |env|
|
32
|
+
ENV["RACK_ENV"] = env
|
33
|
+
db_name = "#{db_name}_#{env}"
|
34
|
+
puts("Creating database \#{db_name}...")
|
35
|
+
|
36
|
+
reset_memoized_class_level_instance_vars(#{app_name})
|
37
|
+
url = #{app_name}.default_db_url.dup # frozen string
|
38
|
+
url.gsub!(db_name, "postgres")
|
39
|
+
puts("Connecting to \#{url.gsub(%r{://.*@}, "_REDACTED_")}")
|
40
|
+
db = Sequel.connect(url)
|
41
|
+
|
42
|
+
begin
|
43
|
+
db.execute("CREATE DATABASE \#{db_name}")
|
44
|
+
puts("Created database \#{db_name}.")
|
45
|
+
rescue Sequel::DatabaseError, PG::DuplicateDatabase
|
46
|
+
puts("Database \#{db_name} already exists, skipping.")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
desc "Drop the database"
|
52
|
+
task :drop do
|
53
|
+
envs = ENV.key?("RACK_ENV") ? [ENV.fetch("RACK_ENV")] : %w[development test]
|
54
|
+
envs.each do |env|
|
55
|
+
ENV["RACK_ENV"] = env
|
56
|
+
db_name = "#{db_name}_\#{env}"
|
57
|
+
puts("Dropping database \#{db_name}...")
|
58
|
+
|
59
|
+
reset_memoized_class_level_instance_vars(#{app_name})
|
60
|
+
url = #{app_name}.default_db_url.dup # frozen string
|
61
|
+
url.gsub!(db_name, "postgres")
|
62
|
+
puts("Connecting to \#{url.gsub(%r{://.*@}, "_REDACTED_")}")
|
63
|
+
db = Sequel.connect(url)
|
64
|
+
|
65
|
+
begin
|
66
|
+
db.execute("DROP DATABASE \#{db_name} (FORCE)")
|
67
|
+
puts("Dropped database \#{db_name}.")
|
68
|
+
rescue Sequel::DatabaseError, PG::DuplicateDatabase
|
69
|
+
puts("Database \#{db_name} does not exists, nothing to drop.")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
desc "Run migrations"
|
75
|
+
task :migrate do
|
76
|
+
Sequel.extension(:migration)
|
77
|
+
envs = ENV.key?("RACK_ENV") ? [ENV.fetch("RACK_ENV")] : %w[development test]
|
78
|
+
envs.each do |env|
|
79
|
+
ENV["RACK_ENV"] = env
|
80
|
+
db_name = "#{db_name}_\#{env}"
|
81
|
+
reset_memoized_class_level_instance_vars(#{app_name})
|
82
|
+
db = Sequel.connect(#{app_name}.default_db_url)
|
83
|
+
Sequel::Migrator.run(db, File.join(#{app_name}.root, "db/migrate"))
|
84
|
+
current_version = db[:schema_migrations].order(:filename).last[:filename].to_i
|
85
|
+
puts "Migrated \#{db_name} to version \#{current_version}!"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
desc "Rollback the last migration"
|
90
|
+
task :rollback do
|
91
|
+
envs = ENV.key?("RACK_ENV") ? [ENV.fetch("RACK_ENV")] : %w[development test]
|
92
|
+
Sequel.extension(:migration)
|
93
|
+
envs.each do |env|
|
94
|
+
ENV["RACK_ENV"] = env
|
95
|
+
db_name = "#{db_name}_\#{env}"
|
96
|
+
reset_memoized_class_level_instance_vars(#{app_name})
|
97
|
+
db = Sequel.connect(#{app_name}.default_db_url)
|
98
|
+
|
99
|
+
steps = (ENV["STEPS"] || 1).to_i + 1
|
100
|
+
versions = db[:schema_migrations].order(:filename).all
|
101
|
+
|
102
|
+
if versions[-steps].nil?
|
103
|
+
puts "No more migrations to rollback"
|
104
|
+
else
|
105
|
+
target_version = versions[-steps][:filename].to_i
|
106
|
+
|
107
|
+
Sequel::Migrator.run(db, File.join(#{app_name}.root, "db/migrate"), target: target_version)
|
108
|
+
puts "Rolled back \#{db_name} \#{steps} steps to version \#{target_version}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
desc "Seed the database"
|
114
|
+
task :seed do
|
115
|
+
load File.join(#{app_name}.root, "db/seeds.rb")
|
116
|
+
end
|
117
|
+
|
118
|
+
desc "Generate a new migration file"
|
119
|
+
task :migration, [:name] do |_t, args|
|
120
|
+
require "fileutils"
|
121
|
+
require "time"
|
122
|
+
|
123
|
+
# Ensure the migrations directory exists
|
124
|
+
migrations_dir = File.join(#{app_name}.root, "db/migrate")
|
125
|
+
FileUtils.mkdir_p(migrations_dir)
|
126
|
+
|
127
|
+
# Generate the migration number
|
128
|
+
migration_number = Time.now.utc.strftime("%Y%m%d%H%M%S")
|
129
|
+
|
130
|
+
# Sanitize and format the migration name
|
131
|
+
formatted_name = args[:name].to_s.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
132
|
+
|
133
|
+
# Combine them to create the filename
|
134
|
+
filename = "\#{migration_number}_\#{formatted_name}.rb"
|
135
|
+
file_path = File.join(migrations_dir, filename)
|
136
|
+
|
137
|
+
# Define the content of the migration file
|
138
|
+
content = <<~MIGRATION
|
139
|
+
# typed: strict
|
140
|
+
# frozen_string_literal: true
|
141
|
+
|
142
|
+
Sequel.migration do
|
143
|
+
up do
|
144
|
+
# your code here
|
145
|
+
end
|
146
|
+
|
147
|
+
down do
|
148
|
+
# your code here
|
149
|
+
end
|
150
|
+
end
|
151
|
+
MIGRATION
|
152
|
+
|
153
|
+
# Write the migration file
|
154
|
+
File.write(file_path, content)
|
155
|
+
|
156
|
+
puts "Generated migration: db/migrate/\#{filename}"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def reset_memoized_class_level_instance_vars(app)
|
161
|
+
%i[
|
162
|
+
@default_db_name
|
163
|
+
@default_db_url
|
164
|
+
@raw_db_connection
|
165
|
+
].each do |ivar|
|
166
|
+
app.remove_instance_variable(ivar) if app.instance_variable_defined?(ivar)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
RUBY
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# rubocop:enable Metrics/ClassLength
|
@@ -16,10 +16,10 @@ module Cli
|
|
16
16
|
# Kirei needs to know where the root of the project is
|
17
17
|
APP_ROOT = File.expand_path(__dir__)
|
18
18
|
|
19
|
-
ENV[
|
20
|
-
ENV[
|
21
|
-
require(
|
22
|
-
require_relative(
|
19
|
+
ENV["RACK_ENV"] ||= "development"
|
20
|
+
ENV["APP_VERSION"] ||= (ENV["GIT_SHA"] ||= `git rev-parse --short HEAD`.to_s.chomp.freeze)
|
21
|
+
require("dotenv/load") if %w[test development].include?(ENV["RACK_ENV"])
|
22
|
+
require_relative("app")
|
23
23
|
RUBY
|
24
24
|
end
|
25
25
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# typed: false
|
2
|
+
|
3
|
+
module Cli
|
4
|
+
module Commands
|
5
|
+
module NewApp
|
6
|
+
module Files
|
7
|
+
class Rakefile
|
8
|
+
def self.call
|
9
|
+
File.write("Rakefile", content)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.content
|
13
|
+
<<~RUBY
|
14
|
+
# typed: false
|
15
|
+
# frozen_string_literal: true
|
16
|
+
|
17
|
+
require "rake"
|
18
|
+
|
19
|
+
Dir.glob("lib/tasks/**/*.rake").each { import(_1) }
|
20
|
+
|
21
|
+
RUBY
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# typed: false
|
2
|
+
|
3
|
+
module Cli
|
4
|
+
module Commands
|
5
|
+
module NewApp
|
6
|
+
module Files
|
7
|
+
class Routes
|
8
|
+
def self.call
|
9
|
+
File.write("config/routes.rb", content)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.content
|
13
|
+
<<~RUBY
|
14
|
+
# typed: strict
|
15
|
+
# frozen_string_literal: true
|
16
|
+
|
17
|
+
Kirei::Router.add_routes([
|
18
|
+
# Kirei::Router::Route.new(
|
19
|
+
# verb: "GET",
|
20
|
+
# path: "/livez",
|
21
|
+
# controller: Controllers::HealthController,
|
22
|
+
# action: "livez",
|
23
|
+
# )
|
24
|
+
])
|
25
|
+
RUBY
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/cli/commands/start.rb
CHANGED
@@ -9,8 +9,8 @@ module Cli
|
|
9
9
|
case args[0]
|
10
10
|
when "new"
|
11
11
|
app_name = args[1] || "MyApp"
|
12
|
-
|
13
|
-
app_name = app_name.
|
12
|
+
app_name = app_name.gsub(/[-\s]/, "_")
|
13
|
+
app_name = app_name.split("_").map(&:capitalize).join if app_name.include?("_")
|
14
14
|
NewApp::Execute.call(app_name: app_name)
|
15
15
|
else
|
16
16
|
Kirei::Logger.logger.info("Unknown command")
|
data/lib/kirei/app.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative("middleware")
|
5
|
+
|
6
|
+
# rubocop:disable Metrics/AbcSize, Layout/LineLength
|
7
|
+
|
8
|
+
module Kirei
|
9
|
+
class App
|
10
|
+
include Middleware
|
11
|
+
extend T::Sig
|
12
|
+
|
13
|
+
sig { params(params: T::Hash[String, T.untyped]).void }
|
14
|
+
def initialize(params: {})
|
15
|
+
@router = T.let(Router.instance, Router)
|
16
|
+
@params = T.let(params, T::Hash[String, T.untyped])
|
17
|
+
end
|
18
|
+
|
19
|
+
sig { returns(T::Hash[String, T.untyped]) }
|
20
|
+
attr_reader :params
|
21
|
+
|
22
|
+
sig { params(env: RackEnvType).returns(RackResponseType) }
|
23
|
+
def call(env)
|
24
|
+
http_verb = T.cast(env.fetch("REQUEST_METHOD"), String)
|
25
|
+
req_path = T.cast(env.fetch("REQUEST_PATH"), String)
|
26
|
+
# reject requests from unexpected hosts -> allow configuring allowed hosts in a `cors.rb` file
|
27
|
+
# ( offer a scaffold for this file )
|
28
|
+
# -> use https://github.com/cyu/rack-cors
|
29
|
+
|
30
|
+
route = Router.instance.get(http_verb, req_path)
|
31
|
+
return [404, {}, ["Not Found"]] if route.nil?
|
32
|
+
|
33
|
+
params = if route.verb == "GET"
|
34
|
+
query = T.cast(env.fetch("QUERY_STRING"), String)
|
35
|
+
query.split("&").to_h do |p|
|
36
|
+
k, v = p.split("=")
|
37
|
+
k = T.cast(k, String)
|
38
|
+
[k, v]
|
39
|
+
end
|
40
|
+
else
|
41
|
+
# TODO: based on content-type, parse the body differently
|
42
|
+
# build-in support for JSON & XML
|
43
|
+
body = T.cast(env.fetch("rack.input"), T.any(IO, StringIO))
|
44
|
+
res = Oj.load(body.read, Kirei::OJ_OPTIONS)
|
45
|
+
body.rewind # TODO: maybe don't rewind if we don't need to?
|
46
|
+
T.cast(res, T::Hash[String, T.untyped])
|
47
|
+
end
|
48
|
+
|
49
|
+
instance = route.controller.new(params: params)
|
50
|
+
instance.public_send(route.action)
|
51
|
+
end
|
52
|
+
|
53
|
+
sig do
|
54
|
+
params(
|
55
|
+
status: Integer,
|
56
|
+
body: String,
|
57
|
+
headers: T::Hash[String, String],
|
58
|
+
).returns(RackResponseType)
|
59
|
+
end
|
60
|
+
def render(status:, body:, headers: {})
|
61
|
+
# merge default headers
|
62
|
+
# support a "type" to set content-type header? (or default to json, and users must set the header themselves for other types?)
|
63
|
+
[
|
64
|
+
status,
|
65
|
+
headers,
|
66
|
+
[body],
|
67
|
+
]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# rubocop:enable Metrics/AbcSize, Layout/LineLength
|
data/lib/kirei/app_base.rb
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require_relative("app")
|
5
|
+
|
4
6
|
module Kirei
|
5
|
-
class BaseController <
|
7
|
+
class BaseController < Kirei::App
|
6
8
|
extend T::Sig
|
7
|
-
register(Sinatra::Namespace)
|
9
|
+
# register(Sinatra::Namespace)
|
8
10
|
|
9
|
-
before do
|
10
|
-
|
11
|
-
|
12
|
-
end
|
11
|
+
# before do
|
12
|
+
# Thread.current[:request_id] = request.env["HTTP_X_REQUEST_ID"].presence ||
|
13
|
+
# "req_#{AppBase.environment}_#{SecureRandom.uuid}"
|
14
|
+
# end
|
13
15
|
end
|
14
16
|
end
|
data/lib/kirei/base_model.rb
CHANGED
@@ -60,6 +60,10 @@ module Kirei
|
|
60
60
|
def where(hash)
|
61
61
|
end
|
62
62
|
|
63
|
+
sig { abstract.returns(T.untyped) }
|
64
|
+
def all
|
65
|
+
end
|
66
|
+
|
63
67
|
sig { abstract.params(hash: T.untyped).returns(T.untyped) }
|
64
68
|
def create(hash)
|
65
69
|
end
|
@@ -123,6 +127,11 @@ module Kirei
|
|
123
127
|
resolve(db.where(hash))
|
124
128
|
end
|
125
129
|
|
130
|
+
sig { override.returns(T::Array[T.attached_class]) }
|
131
|
+
def all
|
132
|
+
resolve(db.all)
|
133
|
+
end
|
134
|
+
|
126
135
|
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
127
136
|
# default values defined in the model are used, if omitted in the hash
|
128
137
|
sig do
|
@@ -182,7 +191,7 @@ module Kirei
|
|
182
191
|
# "strict" defaults to "false".
|
183
192
|
sig do
|
184
193
|
override.params(
|
185
|
-
query: Sequel::Dataset,
|
194
|
+
query: T.any(Sequel::Dataset, T::Array[T::Hash[Symbol, T.untyped]]),
|
186
195
|
strict: T.nilable(T::Boolean),
|
187
196
|
).returns(T::Array[T.attached_class])
|
188
197
|
end
|
data/lib/kirei/logger.rb
CHANGED
@@ -30,7 +30,9 @@ module Kirei
|
|
30
30
|
#
|
31
31
|
# NOTE: The log transformer must return an array of strings to allow emitting multiple lines per log event.
|
32
32
|
#
|
33
|
-
class Logger
|
33
|
+
class Logger
|
34
|
+
extend T::Sig
|
35
|
+
|
34
36
|
FILTERED = "[FILTERED]"
|
35
37
|
|
36
38
|
@instance = T.let(nil, T.nilable(Kirei::Logger))
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Kirei
|
5
|
+
module Middleware
|
6
|
+
# https://github.com/rack/rack/blob/main/UPGRADE-GUIDE.md#rack-3-upgrade-guide
|
7
|
+
RackResponseType = T.type_alias do
|
8
|
+
[
|
9
|
+
Integer,
|
10
|
+
T::Hash[String, String], # in theory, the values are allowed to be arrays of integers for binary representations
|
11
|
+
T.any(T::Array[String], Proc),
|
12
|
+
]
|
13
|
+
end
|
14
|
+
|
15
|
+
RackEnvType = T.type_alias do
|
16
|
+
T::Hash[
|
17
|
+
String,
|
18
|
+
T.any(
|
19
|
+
T::Array[T.untyped],
|
20
|
+
IO,
|
21
|
+
T::Boolean,
|
22
|
+
String,
|
23
|
+
Numeric,
|
24
|
+
TCPSocket,
|
25
|
+
Puma::Client,
|
26
|
+
StringIO,
|
27
|
+
Puma::Configuration,
|
28
|
+
)
|
29
|
+
]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/kirei/router.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require("singleton")
|
5
|
+
|
6
|
+
module Kirei
|
7
|
+
#
|
8
|
+
# Usage:
|
9
|
+
#
|
10
|
+
# Router.add_routes([
|
11
|
+
# Route.new(
|
12
|
+
# verb: "GET",
|
13
|
+
# path: "/livez",
|
14
|
+
# controller: Controllers::HealthController,
|
15
|
+
# action: "livez",
|
16
|
+
# ),
|
17
|
+
# ])
|
18
|
+
#
|
19
|
+
class Router
|
20
|
+
extend T::Sig
|
21
|
+
include ::Singleton
|
22
|
+
|
23
|
+
class Route < T::Struct
|
24
|
+
const :verb, String
|
25
|
+
const :path, String
|
26
|
+
const :controller, T.class_of(BaseController)
|
27
|
+
const :action, String
|
28
|
+
end
|
29
|
+
|
30
|
+
RoutesHash = T.type_alias do
|
31
|
+
T::Hash[String, Route]
|
32
|
+
end
|
33
|
+
|
34
|
+
sig { void }
|
35
|
+
def initialize
|
36
|
+
@routes = T.let({}, RoutesHash)
|
37
|
+
end
|
38
|
+
|
39
|
+
sig { returns(RoutesHash) }
|
40
|
+
attr_reader :routes
|
41
|
+
|
42
|
+
sig do
|
43
|
+
params(
|
44
|
+
verb: String,
|
45
|
+
path: String,
|
46
|
+
).returns(T.nilable(Route))
|
47
|
+
end
|
48
|
+
def get(verb, path)
|
49
|
+
key = "#{verb} #{path}"
|
50
|
+
routes[key]
|
51
|
+
end
|
52
|
+
|
53
|
+
sig { params(routes: T::Array[Route]).void }
|
54
|
+
def self.add_routes(routes)
|
55
|
+
routes.each do |route|
|
56
|
+
key = "#{route.verb} #{route.path}"
|
57
|
+
instance.routes[key] = route
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/kirei/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kirei
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ludwig Reinmiedl
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-03-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: oj
|
@@ -24,20 +24,6 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '3.0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: rake
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '13.0'
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '13.0'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: sorbet-runtime
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -67,21 +53,21 @@ dependencies:
|
|
67
53
|
- !ruby/object:Gem::Version
|
68
54
|
version: '1.0'
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
56
|
+
name: puma
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
72
58
|
requirements:
|
73
59
|
- - "~>"
|
74
60
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
61
|
+
version: '6.0'
|
76
62
|
type: :runtime
|
77
63
|
prerelease: false
|
78
64
|
version_requirements: !ruby/object:Gem::Requirement
|
79
65
|
requirements:
|
80
66
|
- - "~>"
|
81
67
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
68
|
+
version: '6.0'
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
70
|
+
name: rack
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
86
72
|
requirements:
|
87
73
|
- - "~>"
|
@@ -159,16 +145,22 @@ files:
|
|
159
145
|
- lib/cli/commands/new_app/base_directories.rb
|
160
146
|
- lib/cli/commands/new_app/execute.rb
|
161
147
|
- lib/cli/commands/new_app/files/app.rb
|
148
|
+
- lib/cli/commands/new_app/files/config_ru.rb
|
149
|
+
- lib/cli/commands/new_app/files/db_rake.rb
|
162
150
|
- lib/cli/commands/new_app/files/irbrc.rb
|
151
|
+
- lib/cli/commands/new_app/files/rakefile.rb
|
152
|
+
- lib/cli/commands/new_app/files/routes.rb
|
163
153
|
- lib/cli/commands/start.rb
|
164
154
|
- lib/kirei.rb
|
155
|
+
- lib/kirei/app.rb
|
165
156
|
- lib/kirei/app_base.rb
|
166
|
-
- lib/kirei/base.rb
|
167
157
|
- lib/kirei/base_controller.rb
|
168
158
|
- lib/kirei/base_model.rb
|
169
159
|
- lib/kirei/config.rb
|
170
160
|
- lib/kirei/helpers.rb
|
171
161
|
- lib/kirei/logger.rb
|
162
|
+
- lib/kirei/middleware.rb
|
163
|
+
- lib/kirei/router.rb
|
172
164
|
- lib/kirei/version.rb
|
173
165
|
- sorbet/rbi/shims/base_model.rbi
|
174
166
|
homepage: https://github.com/swiknaba/kirei
|
@@ -192,7 +184,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
192
184
|
- !ruby/object:Gem::Version
|
193
185
|
version: '0'
|
194
186
|
requirements: []
|
195
|
-
rubygems_version: 3.5.
|
187
|
+
rubygems_version: 3.5.6
|
196
188
|
signing_key:
|
197
189
|
specification_version: 4
|
198
190
|
summary: Kirei is a strictly typed Ruby micro/REST-framework for building scaleable
|