kirei 0.0.3 → 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 +1 -3
- data/lib/boot.rb +1 -8
- 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 +3 -2
- data/lib/kirei/app.rb +72 -0
- data/lib/kirei/app_base.rb +9 -2
- data/lib/kirei/base_controller.rb +8 -6
- data/lib/kirei/base_model.rb +91 -2
- data/lib/kirei/config.rb +4 -2
- data/lib/kirei/helpers.rb +22 -2
- data/lib/kirei/logger.rb +27 -8
- data/lib/kirei/middleware.rb +32 -0
- data/lib/kirei/router.rb +61 -0
- data/lib/kirei/version.rb +1 -1
- data/lib/kirei.rb +11 -0
- data/sorbet/rbi/shims/base_model.rbi +7 -0
- metadata +11 -33
- 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,14 +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
52
|
spec.add_dependency "puma", "~> 6.0"
|
54
|
-
spec.add_dependency "
|
55
|
-
spec.add_dependency "sinatra-contrib", "~> 3.0"
|
53
|
+
spec.add_dependency "rack", "~> 3.0"
|
56
54
|
|
57
55
|
# Database (Postgres)
|
58
56
|
spec.add_dependency "pg", "~> 1.0"
|
data/lib/boot.rb
CHANGED
@@ -15,16 +15,9 @@ require "bundler/setup"
|
|
15
15
|
require "logger"
|
16
16
|
require "sorbet-runtime"
|
17
17
|
require "oj"
|
18
|
-
require "
|
19
|
-
require "sinatra"
|
20
|
-
require "sinatra/namespace" # from sinatra-contrib
|
18
|
+
require "rack"
|
21
19
|
require "pg"
|
22
20
|
require "sequel" # "sequel_pg" is auto-required by "sequel"
|
23
21
|
|
24
|
-
Oj.default_options = {
|
25
|
-
mode: :compat, # required to dump hashes with symbol-keys
|
26
|
-
symbol_keys: false, # T::Struct.new works only with string-keys
|
27
|
-
}
|
28
|
-
|
29
22
|
# Third: load all application code
|
30
23
|
Dir[File.join(__dir__, "kirei/**/*.rb")].each { require(_1) }
|
@@ -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
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: true
|
2
2
|
|
3
3
|
require "fileutils"
|
4
4
|
|
@@ -9,7 +9,8 @@ module Cli
|
|
9
9
|
case args[0]
|
10
10
|
when "new"
|
11
11
|
app_name = args[1] || "MyApp"
|
12
|
-
app_name = app_name.gsub(/[-\s]/, "_")
|
12
|
+
app_name = app_name.gsub(/[-\s]/, "_")
|
13
|
+
app_name = app_name.split("_").map(&:capitalize).join if app_name.include?("_")
|
13
14
|
NewApp::Execute.call(app_name: app_name)
|
14
15
|
else
|
15
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,8 +1,10 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require_relative("app")
|
5
|
+
|
4
6
|
module Kirei
|
5
|
-
class AppBase < ::
|
7
|
+
class AppBase < Kirei::App
|
6
8
|
class << self
|
7
9
|
extend T::Sig
|
8
10
|
|
@@ -55,7 +57,12 @@ module Kirei
|
|
55
57
|
@raw_db_connection = Sequel.connect(AppBase.config.db_url || default_db_url)
|
56
58
|
|
57
59
|
config.db_extensions.each do |ext|
|
58
|
-
@raw_db_connection.extension(ext)
|
60
|
+
T.cast(@raw_db_connection, Sequel::Database).extension(ext)
|
61
|
+
end
|
62
|
+
|
63
|
+
if config.db_extensions.include?(:pg_json)
|
64
|
+
# https://github.com/jeremyevans/sequel/blob/5.75.0/lib/sequel/extensions/pg_json.rb#L8
|
65
|
+
@raw_db_connection.wrap_json_primitives = true
|
59
66
|
end
|
60
67
|
|
61
68
|
@raw_db_connection
|
@@ -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
@@ -16,10 +16,37 @@ module Kirei
|
|
16
16
|
).returns(T.self_type)
|
17
17
|
end
|
18
18
|
def update(hash)
|
19
|
+
hash[:updated_at] = Time.now.utc if respond_to?(:updated_at) && hash[:updated_at].nil?
|
20
|
+
self.class.wrap_jsonb_non_primivitives!(hash)
|
19
21
|
self.class.db.where({ id: id }).update(hash)
|
20
22
|
self.class.find_by({ id: id })
|
21
23
|
end
|
22
24
|
|
25
|
+
# Delete keeps the original object intact. Returns true if the record was deleted.
|
26
|
+
# Calling delete multiple times will return false after the first (successful) call.
|
27
|
+
sig { returns(T::Boolean) }
|
28
|
+
def delete
|
29
|
+
count = self.class.db.where({ id: id }).delete
|
30
|
+
count == 1
|
31
|
+
end
|
32
|
+
|
33
|
+
# warning: this is not concurrency-safe
|
34
|
+
# save keeps the original object intact, and returns a new object with the updated values.
|
35
|
+
sig { returns(T.self_type) }
|
36
|
+
def save
|
37
|
+
previous_record = self.class.find_by({ id: id })
|
38
|
+
|
39
|
+
hash = serialize
|
40
|
+
Helpers.deep_symbolize_keys!(hash)
|
41
|
+
hash = T.cast(hash, T::Hash[Symbol, T.untyped])
|
42
|
+
|
43
|
+
if previous_record.nil?
|
44
|
+
self.class.create(hash)
|
45
|
+
else
|
46
|
+
update(hash)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
23
50
|
module BaseClassInterface
|
24
51
|
extend T::Sig
|
25
52
|
extend T::Helpers
|
@@ -33,6 +60,18 @@ module Kirei
|
|
33
60
|
def where(hash)
|
34
61
|
end
|
35
62
|
|
63
|
+
sig { abstract.returns(T.untyped) }
|
64
|
+
def all
|
65
|
+
end
|
66
|
+
|
67
|
+
sig { abstract.params(hash: T.untyped).returns(T.untyped) }
|
68
|
+
def create(hash)
|
69
|
+
end
|
70
|
+
|
71
|
+
sig { abstract.params(attributes: T.untyped).void }
|
72
|
+
def wrap_jsonb_non_primivitives!(attributes)
|
73
|
+
end
|
74
|
+
|
36
75
|
sig { abstract.params(hash: T.untyped).returns(T.untyped) }
|
37
76
|
def resolve(hash)
|
38
77
|
end
|
@@ -88,6 +127,54 @@ module Kirei
|
|
88
127
|
resolve(db.where(hash))
|
89
128
|
end
|
90
129
|
|
130
|
+
sig { override.returns(T::Array[T.attached_class]) }
|
131
|
+
def all
|
132
|
+
resolve(db.all)
|
133
|
+
end
|
134
|
+
|
135
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
136
|
+
# default values defined in the model are used, if omitted in the hash
|
137
|
+
sig do
|
138
|
+
override.params(
|
139
|
+
hash: T::Hash[Symbol, T.untyped],
|
140
|
+
).returns(T.attached_class)
|
141
|
+
end
|
142
|
+
def create(hash)
|
143
|
+
# instantiate a new object to ensure we use default values defined in the model
|
144
|
+
without_id = !hash.key?(:id)
|
145
|
+
hash[:id] = "kirei-fake-id" if without_id
|
146
|
+
new_record = from_hash(Helpers.deep_stringify_keys(hash))
|
147
|
+
all_attributes = T.let(new_record.serialize, T::Hash[String, T.untyped])
|
148
|
+
all_attributes.delete("id") if without_id && all_attributes["id"] == "kirei-fake-id"
|
149
|
+
|
150
|
+
wrap_jsonb_non_primivitives!(all_attributes)
|
151
|
+
|
152
|
+
if new_record.respond_to?(:created_at) && all_attributes["created_at"].nil?
|
153
|
+
all_attributes["created_at"] = Time.now.utc
|
154
|
+
end
|
155
|
+
if new_record.respond_to?(:updated_at) && all_attributes["updated_at"].nil?
|
156
|
+
all_attributes["updated_at"] = Time.now.utc
|
157
|
+
end
|
158
|
+
|
159
|
+
pkey = T.let(db.insert(all_attributes), String)
|
160
|
+
|
161
|
+
T.must(find_by({ id: pkey }))
|
162
|
+
end
|
163
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
164
|
+
|
165
|
+
sig { override.params(attributes: T::Hash[T.any(Symbol, String), T.untyped]).void }
|
166
|
+
def wrap_jsonb_non_primivitives!(attributes)
|
167
|
+
# setting `@raw_db_connection.wrap_json_primitives = true`
|
168
|
+
# only works on JSON primitives, but not on blank hashes/arrays
|
169
|
+
return unless AppBase.config.db_extensions.include?(:pg_json)
|
170
|
+
|
171
|
+
attributes.each_pair do |key, value|
|
172
|
+
next unless value.is_a?(Hash) || value.is_a?(Array)
|
173
|
+
|
174
|
+
attributes[key] = T.unsafe(Sequel).pg_jsonb_wrap(value)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
91
178
|
sig do
|
92
179
|
override.params(
|
93
180
|
hash: T::Hash[Symbol, T.untyped],
|
@@ -104,7 +191,7 @@ module Kirei
|
|
104
191
|
# "strict" defaults to "false".
|
105
192
|
sig do
|
106
193
|
override.params(
|
107
|
-
query: Sequel::Dataset,
|
194
|
+
query: T.any(Sequel::Dataset, T::Array[T::Hash[Symbol, T.untyped]]),
|
108
195
|
strict: T.nilable(T::Boolean),
|
109
196
|
).returns(T::Array[T.attached_class])
|
110
197
|
end
|
@@ -125,7 +212,9 @@ module Kirei
|
|
125
212
|
).returns(T.nilable(T.attached_class))
|
126
213
|
end
|
127
214
|
def resolve_first(query, strict = nil)
|
128
|
-
|
215
|
+
strict_loading = strict.nil? ? AppBase.config.db_strict_type_resolving : strict
|
216
|
+
|
217
|
+
resolve(query.limit(1), strict_loading).first
|
129
218
|
end
|
130
219
|
end
|
131
220
|
|
data/lib/kirei/config.rb
CHANGED
@@ -17,16 +17,18 @@ module Kirei
|
|
17
17
|
|
18
18
|
prop :logger, ::Logger, factory: -> { ::Logger.new($stdout) }
|
19
19
|
prop :log_transformer, T.nilable(T.proc.params(msg: T::Hash[Symbol, T.untyped]).returns(T::Array[String]))
|
20
|
+
prop :log_default_metadata, T::Hash[Symbol, String], default: {}
|
21
|
+
|
20
22
|
# dup to allow the user to extend the existing list of sensitive keys
|
21
23
|
prop :sensitive_keys, T::Array[Regexp], factory: -> { SENSITIVE_KEYS.dup }
|
24
|
+
|
22
25
|
prop :app_name, String, default: "kirei"
|
23
|
-
prop :db_url, T.nilable(String)
|
24
26
|
|
25
27
|
# must use "pg_json" to parse jsonb columns to hashes
|
26
28
|
#
|
27
29
|
# Source: https://github.com/jeremyevans/sequel/blob/5.75.0/lib/sequel/extensions/pg_json.rb
|
28
30
|
prop :db_extensions, T::Array[Symbol], default: %i[pg_json pg_array]
|
29
|
-
|
31
|
+
prop :db_url, T.nilable(String)
|
30
32
|
# Extra or unknown properties present in the Hash do not raise exceptions at runtime
|
31
33
|
# unless the optional strict argument to from_hash is passed
|
32
34
|
#
|
data/lib/kirei/helpers.rb
CHANGED
@@ -23,6 +23,26 @@ module Kirei
|
|
23
23
|
string.nil? || string.to_s.empty?
|
24
24
|
end
|
25
25
|
|
26
|
+
sig { params(object: T.untyped).returns(T.untyped) }
|
27
|
+
def deep_stringify_keys(object)
|
28
|
+
deep_transform_keys(object) { _1.to_s rescue _1 } # rubocop:disable Style/RescueModifier
|
29
|
+
end
|
30
|
+
|
31
|
+
sig { params(object: T.untyped).returns(T.untyped) }
|
32
|
+
def deep_stringify_keys!(object)
|
33
|
+
deep_transform_keys!(object) { _1.to_s rescue _1 } # rubocop:disable Style/RescueModifier
|
34
|
+
end
|
35
|
+
|
36
|
+
sig { params(object: T.untyped).returns(T.untyped) }
|
37
|
+
def deep_symbolize_keys(object)
|
38
|
+
deep_transform_keys(object) { _1.to_sym rescue _1 } # rubocop:disable Style/RescueModifier
|
39
|
+
end
|
40
|
+
|
41
|
+
sig { params(object: T.untyped).returns(T.untyped) }
|
42
|
+
def deep_symbolize_keys!(object)
|
43
|
+
deep_transform_keys!(object) { _1.to_sym rescue _1 } # rubocop:disable Style/RescueModifier
|
44
|
+
end
|
45
|
+
|
26
46
|
# Simplified version from Rails' ActiveSupport
|
27
47
|
sig do
|
28
48
|
params(
|
@@ -30,7 +50,7 @@ module Kirei
|
|
30
50
|
block: Proc,
|
31
51
|
).returns(T.untyped) # could be anything due to recursive calls
|
32
52
|
end
|
33
|
-
def deep_transform_keys(object, &block)
|
53
|
+
private def deep_transform_keys(object, &block)
|
34
54
|
case object
|
35
55
|
when Hash
|
36
56
|
object.each_with_object({}) do |(key, value), result|
|
@@ -49,7 +69,7 @@ module Kirei
|
|
49
69
|
block: Proc,
|
50
70
|
).returns(T.untyped) # could be anything due to recursive calls
|
51
71
|
end
|
52
|
-
def deep_transform_keys!(object, &block)
|
72
|
+
private def deep_transform_keys!(object, &block)
|
53
73
|
case object
|
54
74
|
when Hash
|
55
75
|
# using `each_key` results in a `RuntimeError: can't add a new key into hash during iteration`
|
data/lib/kirei/logger.rb
CHANGED
@@ -17,12 +17,12 @@ module Kirei
|
|
17
17
|
#
|
18
18
|
# You can define a custom log transformer to transform the logline:
|
19
19
|
#
|
20
|
-
# Kirei.config.log_transformer = Proc.new { _1 }
|
20
|
+
# Kirei::AppBase.config.log_transformer = Proc.new { _1 }
|
21
21
|
#
|
22
|
-
# By default, "meta" is flattened, and sensitive values are masked using see `Kirei.config.sensitive_keys`.
|
22
|
+
# By default, "meta" is flattened, and sensitive values are masked using see `Kirei::AppBase.config.sensitive_keys`.
|
23
23
|
# You can also build on top of the provided log transformer:
|
24
24
|
#
|
25
|
-
# Kirei.config.log_transformer = Proc.new do |meta|
|
25
|
+
# Kirei::AppBase.config.log_transformer = Proc.new do |meta|
|
26
26
|
# flattened_meta = Kirei::Logger.flatten_hash_and_mask_sensitive_values(meta)
|
27
27
|
# # Do something with the flattened meta
|
28
28
|
# flattened_meta.map { _1.to_json }
|
@@ -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))
|
@@ -83,7 +85,16 @@ module Kirei
|
|
83
85
|
).void
|
84
86
|
end
|
85
87
|
def call(level:, label:, meta: {})
|
88
|
+
Kirei::AppBase.config.log_default_metadata.each_pair do |key, value|
|
89
|
+
meta[key] ||= value
|
90
|
+
end
|
91
|
+
|
92
|
+
#
|
93
|
+
# key names follow OpenTelemetry Semantic Conventions
|
94
|
+
# Source: https://opentelemetry.io/docs/concepts/semantic-conventions/
|
95
|
+
#
|
86
96
|
meta[:"service.instance.id"] ||= Thread.current[:request_id]
|
97
|
+
meta[:"service.name"] ||= Kirei::AppBase.config.app_name
|
87
98
|
|
88
99
|
# The Ruby logger only accepts one string as the only argument
|
89
100
|
@queue << { level: level, label: label, meta: meta }
|
@@ -107,7 +118,10 @@ module Kirei
|
|
107
118
|
loglines = if log_transformer
|
108
119
|
log_transformer.call(meta)
|
109
120
|
else
|
110
|
-
[Oj.dump(
|
121
|
+
[Oj.dump(
|
122
|
+
Kirei::Logger.flatten_hash_and_mask_sensitive_values(meta),
|
123
|
+
Kirei::OJ_OPTIONS,
|
124
|
+
)]
|
111
125
|
end
|
112
126
|
|
113
127
|
loglines.each { Kirei::Logger.logger.error(_1) }
|
@@ -131,19 +145,24 @@ module Kirei
|
|
131
145
|
|
132
146
|
sig do
|
133
147
|
params(
|
134
|
-
hash: T::Hash[Symbol, T.untyped],
|
148
|
+
hash: T::Hash[T.any(Symbol, String), T.untyped],
|
135
149
|
prefix: Symbol,
|
136
150
|
).returns(T::Hash[Symbol, T.untyped])
|
137
151
|
end
|
138
152
|
def self.flatten_hash_and_mask_sensitive_values(hash, prefix = :'')
|
139
153
|
result = T.let({}, T::Hash[Symbol, T.untyped])
|
140
|
-
Kirei::Helpers.
|
154
|
+
Kirei::Helpers.deep_symbolize_keys!(hash)
|
155
|
+
hash = T.cast(hash, T::Hash[Symbol, T.untyped])
|
141
156
|
|
142
157
|
hash.each do |key, value|
|
143
158
|
new_prefix = Kirei::Helpers.blank?(prefix) ? key : :"#{prefix}.#{key}"
|
144
159
|
|
145
160
|
case value
|
146
|
-
when Hash
|
161
|
+
when Hash
|
162
|
+
# Some libraries have a custom Hash class that inhert from Hash, but act differently, e.g. OmniAuth::AuthHash.
|
163
|
+
# This results in `transform_keys` being available but without any effect.
|
164
|
+
value = value.to_h if value.class != Hash
|
165
|
+
result.merge!(flatten_hash_and_mask_sensitive_values(value.transform_keys(&:to_sym), new_prefix))
|
147
166
|
when Array
|
148
167
|
value.each_with_index do |element, index|
|
149
168
|
if element.is_a?(Hash) || element.is_a?(Array)
|
@@ -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
data/lib/kirei.rb
CHANGED
@@ -6,6 +6,17 @@ require "boot"
|
|
6
6
|
module Kirei
|
7
7
|
extend T::Sig
|
8
8
|
|
9
|
+
# we don't know what Oj does under the hood with the options hash, so don't freeze it
|
10
|
+
# rubocop:disable Style/MutableConstant
|
11
|
+
OJ_OPTIONS = T.let(
|
12
|
+
{
|
13
|
+
mode: :compat, # required to dump hashes with symbol-keys
|
14
|
+
symbol_keys: false, # T::Struct.new works only with string-keys
|
15
|
+
},
|
16
|
+
T::Hash[Symbol, T.untyped],
|
17
|
+
)
|
18
|
+
# rubocop:enable Style/MutableConstant
|
19
|
+
|
9
20
|
GEM_ROOT = T.let(
|
10
21
|
Gem::Specification.find_by_name("kirei").gem_dir,
|
11
22
|
String,
|
@@ -3,6 +3,9 @@
|
|
3
3
|
# rubocop:disable Style/EmptyMethod
|
4
4
|
module Kirei
|
5
5
|
module BaseModel
|
6
|
+
include Kernel # "self" is a class since we include the module in a class
|
7
|
+
include T::Props::Serializable
|
8
|
+
|
6
9
|
sig { returns(T.any(String, Integer)) }
|
7
10
|
def id; end
|
8
11
|
|
@@ -12,6 +15,10 @@ module Kirei
|
|
12
15
|
sig { returns(String) }
|
13
16
|
def name; end
|
14
17
|
end
|
18
|
+
|
19
|
+
module BaseClassInterface
|
20
|
+
# include T::Props::Serializable::ClassMethods
|
21
|
+
end
|
15
22
|
end
|
16
23
|
end
|
17
24
|
# rubocop:enable Style/EmptyMethod
|
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.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:
|
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
|
@@ -81,21 +67,7 @@ dependencies:
|
|
81
67
|
- !ruby/object:Gem::Version
|
82
68
|
version: '6.0'
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - "~>"
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '3.0'
|
90
|
-
type: :runtime
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - "~>"
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: '3.0'
|
97
|
-
- !ruby/object:Gem::Dependency
|
98
|
-
name: sinatra-contrib
|
70
|
+
name: rack
|
99
71
|
requirement: !ruby/object:Gem::Requirement
|
100
72
|
requirements:
|
101
73
|
- - "~>"
|
@@ -173,16 +145,22 @@ files:
|
|
173
145
|
- lib/cli/commands/new_app/base_directories.rb
|
174
146
|
- lib/cli/commands/new_app/execute.rb
|
175
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
|
176
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
|
177
153
|
- lib/cli/commands/start.rb
|
178
154
|
- lib/kirei.rb
|
155
|
+
- lib/kirei/app.rb
|
179
156
|
- lib/kirei/app_base.rb
|
180
|
-
- lib/kirei/base.rb
|
181
157
|
- lib/kirei/base_controller.rb
|
182
158
|
- lib/kirei/base_model.rb
|
183
159
|
- lib/kirei/config.rb
|
184
160
|
- lib/kirei/helpers.rb
|
185
161
|
- lib/kirei/logger.rb
|
162
|
+
- lib/kirei/middleware.rb
|
163
|
+
- lib/kirei/router.rb
|
186
164
|
- lib/kirei/version.rb
|
187
165
|
- sorbet/rbi/shims/base_model.rbi
|
188
166
|
homepage: https://github.com/swiknaba/kirei
|
@@ -206,7 +184,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
206
184
|
- !ruby/object:Gem::Version
|
207
185
|
version: '0'
|
208
186
|
requirements: []
|
209
|
-
rubygems_version: 3.5.
|
187
|
+
rubygems_version: 3.5.6
|
210
188
|
signing_key:
|
211
189
|
specification_version: 4
|
212
190
|
summary: Kirei is a strictly typed Ruby micro/REST-framework for building scaleable
|