kirei 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +40 -24
- data/bin/kirei +1 -1
- data/kirei.gemspec +4 -3
- data/lib/cli/commands/new_app/base_directories.rb +1 -1
- data/lib/cli/commands/new_app/execute.rb +2 -2
- data/lib/cli/commands/new_app/files/app.rb +9 -3
- data/lib/cli/commands/new_app/files/config_ru.rb +1 -1
- data/lib/cli/commands/new_app/files/db_rake.rb +50 -2
- data/lib/cli/commands/new_app/files/irbrc.rb +1 -1
- data/lib/cli/commands/new_app/files/rakefile.rb +1 -1
- data/lib/cli/commands/new_app/files/routes.rb +49 -12
- data/lib/cli/commands/new_app/files/sorbet_config.rb +1 -1
- data/lib/kirei/app.rb +73 -56
- data/lib/kirei/controller.rb +44 -0
- data/lib/kirei/logger.rb +8 -8
- data/lib/kirei/{base_model.rb → model.rb} +5 -5
- data/lib/kirei/routing/base.rb +156 -0
- data/lib/kirei/routing/nilable_hooks_type.rb +10 -0
- data/lib/kirei/{middleware.rb → routing/rack_env_type.rb} +1 -10
- data/lib/kirei/routing/rack_response_type.rb +15 -0
- data/lib/kirei/routing/router.rb +86 -0
- data/lib/kirei/version.rb +1 -1
- data/lib/kirei.rb +27 -2
- data/sorbet/rbi/shims/base_model.rbi +1 -1
- metadata +28 -13
- data/lib/boot.rb +0 -23
- data/lib/kirei/app_base.rb +0 -72
- data/lib/kirei/base_controller.rb +0 -16
- data/lib/kirei/router.rb +0 -61
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fb100f4fb7ab34f19caf8d428e373ac0e94230b126037147108b4826ca08b0c5
|
4
|
+
data.tar.gz: 00efe991393cecf639ffe87ac5407fcf232d068940f55da8cc1696c08bbf75e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bee31a71691f31363a7ffc4855c2ef9886f1ce3e545aafa5409a8064e7de79ec78670172b6c7e61348fb5b21f61779e080818e25077b9c300ec4f612dda83e28
|
7
|
+
data.tar.gz: 1d4511cbd01bc7f2eb5f03bd3032079232650b30226ca510dce48a07ee4fc7814e0e6e9ac25ba5df1cd14a11ee957a508d2cbededfbf29ee7806b1840d3455b9
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Kirei
|
2
2
|
|
3
|
-
Kirei is a strictly typed Ruby micro/REST-framework for building scalable and performant APIs. It is built from the ground up to be clean and easy to use. Kirei is based on [Sequel](https://github.com/jeremyevans/sequel) as an ORM, [Sorbet](https://github.com/sorbet/sorbet) for typing, and [
|
3
|
+
Kirei is a strictly typed Ruby micro/REST-framework for building scalable and performant APIs. It is built from the ground up to be clean and easy to use. Kirei is based on [Sequel](https://github.com/jeremyevans/sequel) as an ORM, [Sorbet](https://github.com/sorbet/sorbet) for typing, and [Rack](https://github.com/rack/rack) as web server interface. It strives to have zero magic and to be as explicit as possible.
|
4
4
|
|
5
5
|
Kirei's main advantages over other frameworks are its strict typing, low memory footprint, and build-in high-performance logging and metric-tracking toolkits. It is opiniated in terms of tooling, allowing you to focus on your core-business. It is a great choice for building APIs that need to scale.
|
6
6
|
|
@@ -53,12 +53,12 @@ Find a test app in the [spec/test_app](spec/test_app) directory. It is a fully f
|
|
53
53
|
|
54
54
|
#### Models
|
55
55
|
|
56
|
-
All models must inherit from `T::Struct` and include `Kirei::
|
56
|
+
All models must inherit from `T::Struct` and include `Kirei::Model`. 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)`.
|
57
57
|
|
58
58
|
```ruby
|
59
59
|
class User < T::Struct
|
60
60
|
extend T::Sig
|
61
|
-
include Kirei::
|
61
|
+
include Kirei::Model
|
62
62
|
|
63
63
|
const :id, T.any(String, Integer)
|
64
64
|
const :name, String
|
@@ -76,12 +76,21 @@ user.name # => 'John'
|
|
76
76
|
updated_user.name # => 'Johnny'
|
77
77
|
```
|
78
78
|
|
79
|
+
Delete keeps the original object intact. Returns `true` if the record was deleted. Calling delete multiple times will return `false` after the first (successful) call.
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
success = user.delete # => T::Boolean
|
83
|
+
|
84
|
+
# or delete by any query:
|
85
|
+
User.db.where('...').delete # => Integer, number of deleted records
|
86
|
+
```
|
87
|
+
|
79
88
|
To build more complex queries, Sequel can be used directly:
|
80
89
|
|
81
90
|
```ruby
|
82
91
|
query = User.db.where({ name: 'John' })
|
83
|
-
query = query.where('...')
|
84
|
-
query = query.limit(10)
|
92
|
+
query = query.where('...') # "query" is a 'Sequel::Dataset' that you can chain as you like
|
93
|
+
query = query.limit(10)
|
85
94
|
|
86
95
|
users = User.resolve(query) # T::Array[User]
|
87
96
|
first_user = User.resolve_first(query) # T.nilable(User)
|
@@ -121,11 +130,15 @@ bundle exec rake db:drop
|
|
121
130
|
# apply all pending migrations
|
122
131
|
bundle exec rake db:migrate
|
123
132
|
|
133
|
+
# annotate the models with the schema
|
134
|
+
# this runs automatically after each migration
|
135
|
+
bundle exec rake db:annotate
|
136
|
+
|
124
137
|
# roll back the last n migration
|
125
138
|
STEPS=1 bundle exec rake db:rollback
|
126
139
|
|
127
140
|
# run db/seeds.rb to seed the database
|
128
|
-
bundle exec rake db:
|
141
|
+
bundle exec rake db:seed
|
129
142
|
|
130
143
|
# scaffold a new migration file
|
131
144
|
bundle exec rake 'db:migration[CreateAirports]'
|
@@ -138,21 +151,24 @@ Define routes anywhere in your app; by convention, they are defined in `config/r
|
|
138
151
|
```ruby
|
139
152
|
# config/routes.rb
|
140
153
|
|
141
|
-
Kirei::
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
154
|
+
module Kirei::Routing
|
155
|
+
Router.add_routes(
|
156
|
+
[
|
157
|
+
Router::Route.new(
|
158
|
+
verb: Router::Verb::GET,
|
159
|
+
path: "/livez",
|
160
|
+
controller: Controllers::Health,
|
161
|
+
action: "livez",
|
162
|
+
),
|
163
|
+
Router::Route.new(
|
164
|
+
verb: Router::Verb::GET,
|
165
|
+
path: "/airports",
|
166
|
+
controller: Controllers::Airports,
|
167
|
+
action: "index",
|
168
|
+
),
|
169
|
+
],
|
170
|
+
)
|
171
|
+
end
|
156
172
|
```
|
157
173
|
|
158
174
|
#### Controllers
|
@@ -161,12 +177,12 @@ Controllers can be defined anywhere; by convention, they are defined in the `app
|
|
161
177
|
|
162
178
|
```ruby
|
163
179
|
module Controllers
|
164
|
-
class Airports < Kirei::
|
180
|
+
class Airports < Kirei::Controller
|
165
181
|
extend T::Sig
|
166
182
|
|
167
|
-
sig { returns(
|
183
|
+
sig { returns(T.anything) }
|
168
184
|
def index
|
169
|
-
airports = Airport.all
|
185
|
+
airports = Airport.all # T::Array[Airport]
|
170
186
|
|
171
187
|
# or use a serializer
|
172
188
|
data = Oj.dump(airports.map(&:serialize))
|
data/bin/kirei
CHANGED
data/kirei.gemspec
CHANGED
@@ -13,11 +13,11 @@ Gem::Specification.new do |spec|
|
|
13
13
|
"oss@dbl.works",
|
14
14
|
]
|
15
15
|
|
16
|
-
spec.summary = "Kirei is a
|
16
|
+
spec.summary = "Kirei is a typed Ruby micro/REST-framework for building scalable and performant microservices."
|
17
17
|
spec.description = <<~TXT
|
18
|
-
Kirei is a
|
18
|
+
Kirei is a Ruby micro/REST-framework for building scalable and performant microservices.
|
19
19
|
It is built from the ground up to be clean and easy to use.
|
20
|
-
|
20
|
+
It is a Rack app, and uses Sorbet for typing, Sequel as an ORM, Zeitwerk for autoloading, and Puma as a web server.
|
21
21
|
It strives to have zero magic and to be as explicit as possible.
|
22
22
|
TXT
|
23
23
|
spec.homepage = "https://github.com/swiknaba/kirei"
|
@@ -47,6 +47,7 @@ Gem::Specification.new do |spec|
|
|
47
47
|
spec.add_dependency "oj", "~> 3.0"
|
48
48
|
spec.add_dependency "sorbet-runtime", "~> 0.5"
|
49
49
|
spec.add_dependency "tzinfo-data", "~> 1.0" # for containerized environments, e.g. on AWS ECS
|
50
|
+
spec.add_dependency "zeitwerk", "~> 2.5"
|
50
51
|
|
51
52
|
# Web server & routing
|
52
53
|
spec.add_dependency "puma", "~> 6.0"
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: true
|
2
2
|
|
3
3
|
require "fileutils"
|
4
4
|
|
@@ -13,7 +13,7 @@ module Cli
|
|
13
13
|
Files::DbRake.call(app_name)
|
14
14
|
Files::Irbrc.call
|
15
15
|
Files::Rakefile.call
|
16
|
-
Files::Routes.call
|
16
|
+
Files::Routes.call(app_name)
|
17
17
|
Files::SorbetConfig.call
|
18
18
|
|
19
19
|
Kirei::Logger.logger.info(
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: true
|
2
2
|
|
3
3
|
module Cli
|
4
4
|
module Commands
|
@@ -29,15 +29,21 @@ module Cli
|
|
29
29
|
Dir[File.join(__dir__, "config/initializers", "*.rb")].each { require(_1) }
|
30
30
|
|
31
31
|
# Fourth: load all application code
|
32
|
-
|
32
|
+
loader = Zeitwerk::Loader.new
|
33
|
+
loader.tag = File.basename(__FILE__, ".rb")
|
34
|
+
loader.push_dir("#{File.dirname(__FILE__)}/app")
|
35
|
+
loader.push_dir("#{File.dirname(__FILE__)}/app/models") # make models a root namespace so we don't infer a `Models::` module
|
36
|
+
loader.setup
|
33
37
|
|
34
38
|
# Fifth: load configs
|
35
39
|
Dir[File.join(__dir__, "config", "*.rb")].each { require(_1) }
|
36
40
|
|
37
|
-
class #{app_name} < Kirei::
|
41
|
+
class #{app_name} < Kirei::App
|
38
42
|
# Kirei configuration
|
39
43
|
config.app_name = "#{snake_case_app_name}"
|
40
44
|
end
|
45
|
+
|
46
|
+
loader.eager_load
|
41
47
|
RUBY
|
42
48
|
end
|
43
49
|
end
|
@@ -21,6 +21,7 @@ module Cli
|
|
21
21
|
#
|
22
22
|
# CREATE DATABASE #{db_name}_${environment};
|
23
23
|
|
24
|
+
require 'zeitwerk/inflector'
|
24
25
|
require_relative "../../app"
|
25
26
|
|
26
27
|
namespace :db do
|
@@ -30,7 +31,7 @@ module Cli
|
|
30
31
|
envs = ENV.key?("RACK_ENV") ? [ENV.fetch("RACK_ENV")] : %w[development test]
|
31
32
|
envs.each do |env|
|
32
33
|
ENV["RACK_ENV"] = env
|
33
|
-
db_name = "#{db_name}_
|
34
|
+
db_name = "#{db_name}_\#{env}"
|
34
35
|
puts("Creating database \#{db_name}...")
|
35
36
|
|
36
37
|
reset_memoized_class_level_instance_vars(#{app_name})
|
@@ -84,6 +85,8 @@ module Cli
|
|
84
85
|
current_version = db[:schema_migrations].order(:filename).last[:filename].to_i
|
85
86
|
puts "Migrated \#{db_name} to version \#{current_version}!"
|
86
87
|
end
|
88
|
+
|
89
|
+
Rake::Task["db:annotate"].invoke
|
87
90
|
end
|
88
91
|
|
89
92
|
desc "Rollback the last migration"
|
@@ -128,7 +131,7 @@ module Cli
|
|
128
131
|
migration_number = Time.now.utc.strftime("%Y%m%d%H%M%S")
|
129
132
|
|
130
133
|
# Sanitize and format the migration name
|
131
|
-
formatted_name = args[:name].to_s.gsub(/([a-z])([A-Z])/, '
|
134
|
+
formatted_name = args[:name].to_s.gsub(/([a-z])([A-Z])/, '\\1_\\2').downcase
|
132
135
|
|
133
136
|
# Combine them to create the filename
|
134
137
|
filename = "\#{migration_number}_\#{formatted_name}.rb"
|
@@ -155,6 +158,38 @@ module Cli
|
|
155
158
|
|
156
159
|
puts "Generated migration: db/migrate/\#{filename}"
|
157
160
|
end
|
161
|
+
|
162
|
+
desc "Write the table schema to each model file, or a single file if filename (without extension) is provided"
|
163
|
+
task :annotate, [:model_file_name] do |_t, args|
|
164
|
+
require "fileutils"
|
165
|
+
|
166
|
+
db = #{app_name}.raw_db_connection
|
167
|
+
model_file_name = args[:model_file_name]&.to_s
|
168
|
+
|
169
|
+
models_dir = #{app_name}.root
|
170
|
+
|
171
|
+
Dir.glob("app/models/*.rb").each do |model_file|
|
172
|
+
next if !model_file_name.nil? && model_file == model_file_name
|
173
|
+
|
174
|
+
model_path = File.expand_path(model_file, models_dir)
|
175
|
+
model_name = Zeitwerk::Inflector.new.camelize(File.basename(model_file, ".rb"), model_path)
|
176
|
+
model_klass = Object.const_get(model_name)
|
177
|
+
table_name = model_klass.table_name
|
178
|
+
schema = db.schema(table_name)
|
179
|
+
|
180
|
+
schema_comments = format_schema_comments(table_name, schema)
|
181
|
+
|
182
|
+
file_contents = File.read(model_path)
|
183
|
+
|
184
|
+
# Remove existing schema info comments if present
|
185
|
+
updated_contents = file_contents.sub(/# == Schema Info\\n(.*?)(\\n#\\n)?\\n(?=\\s*class)/m, "")
|
186
|
+
|
187
|
+
# Insert the new schema comments before the class definition
|
188
|
+
modified_contents = updated_contents.sub(/(\A|\\n)(class \#{model_name})/m, "\\\\1\#{schema_comments}\\n\\n\\\\2")
|
189
|
+
|
190
|
+
File.write(model_path, modified_contents)
|
191
|
+
end
|
192
|
+
end
|
158
193
|
end
|
159
194
|
|
160
195
|
def reset_memoized_class_level_instance_vars(app)
|
@@ -167,6 +202,19 @@ module Cli
|
|
167
202
|
end
|
168
203
|
end
|
169
204
|
|
205
|
+
def format_schema_comments(table_name, schema)
|
206
|
+
lines = ["# == Schema Info", "#", "# Table name: \#{table_name}", "#"]
|
207
|
+
schema.each do |column|
|
208
|
+
name, info = column
|
209
|
+
type = "\#{info[:db_type]}(\#{info[:max_length]})" if info[:max_length]
|
210
|
+
type ||= info[:db_type]
|
211
|
+
null = info[:allow_null] ? 'null' : 'not null'
|
212
|
+
primary_key = info[:primary_key] ? ', primary key' : ''
|
213
|
+
lines << "# \#{name.to_s.ljust(20)}:\#{type} \#{null}\#{primary_key}"
|
214
|
+
end
|
215
|
+
lines.join("\\n") + "\\n#"
|
216
|
+
end
|
217
|
+
|
170
218
|
RUBY
|
171
219
|
end
|
172
220
|
end
|
@@ -1,27 +1,64 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: true
|
2
2
|
|
3
3
|
module Cli
|
4
4
|
module Commands
|
5
5
|
module NewApp
|
6
6
|
module Files
|
7
7
|
class Routes
|
8
|
-
def self.call
|
9
|
-
File.write("config/routes.rb",
|
8
|
+
def self.call(app_name)
|
9
|
+
File.write("config/routes.rb", router)
|
10
|
+
File.write("app/controllers/base.rb", base_controller)
|
11
|
+
File.write("app/controllers/health.rb", health_controller(app_name))
|
10
12
|
end
|
11
13
|
|
12
|
-
def self.
|
14
|
+
def self.router
|
13
15
|
<<~RUBY
|
14
16
|
# typed: strict
|
15
17
|
# frozen_string_literal: true
|
16
18
|
|
17
|
-
Kirei::
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
19
|
+
module Kirei::Routing
|
20
|
+
Router.add_routes(
|
21
|
+
[
|
22
|
+
Router::Route.new(
|
23
|
+
verb: Router::Verb::GET,
|
24
|
+
path: "/livez",
|
25
|
+
controller: Controllers::Health,
|
26
|
+
action: "livez",
|
27
|
+
),
|
28
|
+
],
|
29
|
+
)
|
30
|
+
end
|
31
|
+
RUBY
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.base_controller
|
35
|
+
<<~RUBY
|
36
|
+
# typed: strict
|
37
|
+
# frozen_string_literal: true
|
38
|
+
|
39
|
+
module Controllers
|
40
|
+
class Base < Kirei::Controller
|
41
|
+
extend T::Sig
|
42
|
+
end
|
43
|
+
end
|
44
|
+
RUBY
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.health_controller(app_name)
|
48
|
+
<<~RUBY
|
49
|
+
# typed: strict
|
50
|
+
# frozen_string_literal: true
|
51
|
+
|
52
|
+
module Controllers
|
53
|
+
class Health < Base
|
54
|
+
sig { returns(T.anything) }
|
55
|
+
def livez
|
56
|
+
#{app_name}.config.logger.info("Health check")
|
57
|
+
#{app_name}.config.logger.info(params.inspect)
|
58
|
+
render(#{app_name}.version, status: 200)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
25
62
|
RUBY
|
26
63
|
end
|
27
64
|
end
|
data/lib/kirei/app.rb
CHANGED
@@ -1,72 +1,89 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
|
4
|
+
module Kirei
|
5
|
+
class App < Routing::Base
|
6
|
+
class << self
|
7
|
+
extend T::Sig
|
5
8
|
|
6
|
-
#
|
9
|
+
#
|
10
|
+
# convenience method since "Kirei.configuration" must be nilable since it is nil
|
11
|
+
# at the beginning of initilization of the app
|
12
|
+
#
|
13
|
+
sig { returns(Kirei::Config) }
|
14
|
+
def config
|
15
|
+
T.must(Kirei.configuration)
|
16
|
+
end
|
7
17
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
18
|
+
sig { returns(Pathname) }
|
19
|
+
def root
|
20
|
+
defined?(::APP_ROOT) ? Pathname.new(::APP_ROOT) : Pathname.new(Dir.pwd)
|
21
|
+
end
|
12
22
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
23
|
+
#
|
24
|
+
# Returns the version of the app. It checks in the following order:
|
25
|
+
# * ENV["APP_VERSION"]
|
26
|
+
# * ENV["GIT_SHA"]
|
27
|
+
# * `git rev-parse --short HEAD`
|
28
|
+
#
|
29
|
+
sig { returns(String) }
|
30
|
+
def version
|
31
|
+
@version = T.let(@version, T.nilable(String))
|
32
|
+
@version ||= ENV.fetch("APP_VERSION", nil)
|
33
|
+
@version ||= ENV.fetch("GIT_SHA", nil)
|
34
|
+
@version ||= T.must(
|
35
|
+
`command -v git && git rev-parse --short HEAD`.to_s.split("\n").last,
|
36
|
+
).freeze # localhost
|
37
|
+
end
|
18
38
|
|
19
|
-
|
20
|
-
|
39
|
+
#
|
40
|
+
# Returns ENV["RACK_ENV"] or "development" if it is not set
|
41
|
+
#
|
42
|
+
sig { returns(String) }
|
43
|
+
def environment
|
44
|
+
ENV.fetch("RACK_ENV", "development")
|
45
|
+
end
|
21
46
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
47
|
+
#
|
48
|
+
# Returns the name of the database based on the app name and the environment,
|
49
|
+
# e.g. "myapp_development"
|
50
|
+
#
|
51
|
+
sig { returns(String) }
|
52
|
+
def default_db_name
|
53
|
+
@default_db_name ||= T.let("#{config.app_name}_#{environment}".freeze, T.nilable(String))
|
54
|
+
end
|
29
55
|
|
30
|
-
|
31
|
-
|
56
|
+
#
|
57
|
+
# Returns the database URL based on the DATABASE_URL environment variable or
|
58
|
+
# a default value based on the default_db_name
|
59
|
+
#
|
60
|
+
sig { returns(String) }
|
61
|
+
def default_db_url
|
62
|
+
@default_db_url ||= T.let(
|
63
|
+
ENV.fetch("DATABASE_URL", "postgresql://localhost:5432/#{default_db_name}"),
|
64
|
+
T.nilable(String),
|
65
|
+
)
|
66
|
+
end
|
32
67
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
68
|
+
sig { returns(Sequel::Database) }
|
69
|
+
def raw_db_connection
|
70
|
+
@raw_db_connection = T.let(@raw_db_connection, T.nilable(Sequel::Database))
|
71
|
+
return @raw_db_connection unless @raw_db_connection.nil?
|
72
|
+
|
73
|
+
# calling "Sequel.connect" creates a new connection
|
74
|
+
@raw_db_connection = Sequel.connect(App.config.db_url || default_db_url)
|
75
|
+
|
76
|
+
config.db_extensions.each do |ext|
|
77
|
+
T.cast(@raw_db_connection, Sequel::Database).extension(ext)
|
39
78
|
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
79
|
|
49
|
-
|
50
|
-
|
51
|
-
|
80
|
+
if config.db_extensions.include?(:pg_json)
|
81
|
+
# https://github.com/jeremyevans/sequel/blob/5.75.0/lib/sequel/extensions/pg_json.rb#L8
|
82
|
+
@raw_db_connection.wrap_json_primitives = true
|
83
|
+
end
|
52
84
|
|
53
|
-
|
54
|
-
|
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
|
-
]
|
85
|
+
@raw_db_connection
|
86
|
+
end
|
68
87
|
end
|
69
88
|
end
|
70
89
|
end
|
71
|
-
|
72
|
-
# rubocop:enable Metrics/AbcSize, Layout/LineLength
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Kirei
|
5
|
+
class Controller < Routing::Base
|
6
|
+
class << self
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig { returns(Routing::NilableHooksType) }
|
10
|
+
attr_reader :before_hooks, :after_hooks
|
11
|
+
end
|
12
|
+
|
13
|
+
extend T::Sig
|
14
|
+
|
15
|
+
#
|
16
|
+
# Statements to be executed before every action.
|
17
|
+
#
|
18
|
+
# In development mode, Rack Reloader might reload this file causing
|
19
|
+
# the before hooks to be executed multiple times.
|
20
|
+
#
|
21
|
+
sig do
|
22
|
+
params(
|
23
|
+
block: T.nilable(T.proc.void),
|
24
|
+
).void
|
25
|
+
end
|
26
|
+
def self.before(&block)
|
27
|
+
@before_hooks ||= T.let(Set.new, Routing::NilableHooksType)
|
28
|
+
@before_hooks.add(block) if block
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# Statements to be executed after every action.
|
33
|
+
#
|
34
|
+
sig do
|
35
|
+
params(
|
36
|
+
block: T.nilable(T.proc.void),
|
37
|
+
).void
|
38
|
+
end
|
39
|
+
def self.after(&block)
|
40
|
+
@after_hooks ||= T.let(Set.new, Routing::NilableHooksType)
|
41
|
+
@after_hooks.add(block) if block
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
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::
|
20
|
+
# Kirei::App.config.log_transformer = Proc.new { _1 }
|
21
21
|
#
|
22
|
-
# By default, "meta" is flattened, and sensitive values are masked using see `Kirei::
|
22
|
+
# By default, "meta" is flattened, and sensitive values are masked using see `Kirei::App.config.sensitive_keys`.
|
23
23
|
# You can also build on top of the provided log transformer:
|
24
24
|
#
|
25
|
-
# Kirei::
|
25
|
+
# Kirei::App.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 }
|
@@ -85,7 +85,7 @@ module Kirei
|
|
85
85
|
).void
|
86
86
|
end
|
87
87
|
def call(level:, label:, meta: {})
|
88
|
-
Kirei::
|
88
|
+
Kirei::App.config.log_default_metadata.each_pair do |key, value|
|
89
89
|
meta[key] ||= value
|
90
90
|
end
|
91
91
|
|
@@ -94,7 +94,7 @@ module Kirei
|
|
94
94
|
# Source: https://opentelemetry.io/docs/concepts/semantic-conventions/
|
95
95
|
#
|
96
96
|
meta[:"service.instance.id"] ||= Thread.current[:request_id]
|
97
|
-
meta[:"service.name"] ||= Kirei::
|
97
|
+
meta[:"service.name"] ||= Kirei::App.config.app_name
|
98
98
|
|
99
99
|
# The Ruby logger only accepts one string as the only argument
|
100
100
|
@queue << { level: level, label: label, meta: meta }
|
@@ -108,12 +108,12 @@ module Kirei
|
|
108
108
|
level = log_data.fetch(:level)
|
109
109
|
label = log_data.fetch(:label)
|
110
110
|
meta = T.let(log_data.fetch(:meta), T::Hash[Symbol, T.untyped])
|
111
|
-
meta[:"service.version"] ||= Kirei::
|
111
|
+
meta[:"service.version"] ||= Kirei::App.version
|
112
112
|
meta[:timestamp] ||= Time.now.utc.iso8601
|
113
113
|
meta[:level] ||= level.to_s.upcase
|
114
114
|
meta[:label] ||= label
|
115
115
|
|
116
|
-
log_transformer =
|
116
|
+
log_transformer = App.config.log_transformer
|
117
117
|
|
118
118
|
loglines = if log_transformer
|
119
119
|
log_transformer.call(meta)
|
@@ -137,7 +137,7 @@ module Kirei
|
|
137
137
|
).returns(String)
|
138
138
|
end
|
139
139
|
def self.mask(k, v)
|
140
|
-
return Kirei::Logger::FILTERED if
|
140
|
+
return Kirei::Logger::FILTERED if App.config.sensitive_keys.any? { k.match?(_1) }
|
141
141
|
|
142
142
|
v
|
143
143
|
end
|