kirei 0.2.1 → 0.4.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/README.md +74 -25
- data/bin/kirei +1 -1
- data/kirei.gemspec +5 -3
- data/lib/cli/commands/new_app/base_directories.rb +1 -1
- data/lib/cli/commands/new_app/execute.rb +3 -3
- data/lib/cli/commands/new_app/files/app.rb +16 -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/cli/commands/start.rb +1 -1
- data/lib/kirei/app.rb +76 -56
- data/lib/kirei/config.rb +4 -1
- data/lib/kirei/controller.rb +44 -0
- data/lib/kirei/errors/json_api_error.rb +25 -0
- data/lib/kirei/errors/json_api_error_source.rb +12 -0
- data/lib/kirei/logging/level.rb +33 -0
- data/lib/kirei/logging/logger.rb +198 -0
- data/lib/kirei/logging/metric.rb +40 -0
- data/lib/kirei/model/base_class_interface.rb +55 -0
- data/lib/kirei/{base_model.rb → model/class_methods.rb} +42 -108
- data/lib/kirei/model/human_id_generator.rb +40 -0
- data/lib/kirei/model.rb +52 -0
- data/lib/kirei/routing/base.rb +187 -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/route.rb +13 -0
- data/lib/kirei/routing/router.rb +56 -0
- data/lib/kirei/routing/verb.rb +37 -0
- data/lib/kirei/services/result.rb +53 -0
- data/lib/kirei/services/runner.rb +47 -0
- data/lib/kirei/version.rb +1 -1
- data/lib/kirei.rb +31 -3
- data/sorbet/rbi/shims/base_model.rbi +1 -1
- data/sorbet/rbi/shims/ruby.rbi +15 -0
- metadata +55 -14
- 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/logger.rb +0 -196
- 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: 788221745d889864fe2ec7075cce289f6d44361dece7711fbe91ac24c16f693b
|
4
|
+
data.tar.gz: e03e3cf0c4c6db69bc0106107ccb002b8f472ba077ef457d88a84b92b188371f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 30bef2a1458e1aeeebd774d577df1dc231b0cfa46fb7483b076b69ac05de1529527442599dca18bb278fec42e1a17888eee849feb1b3f81dd502fd92284dd879
|
7
|
+
data.tar.gz: 181df5735f94c222fc29e9bb3c18aa33340995925a324a18a0bb28e1001eaeaee24f8c7442cc5852adf31ae7e5e7a707743441e1c64a585e27039e7b50ba96da
|
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.query.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
|
-
query = User.
|
83
|
-
query = query.where('...')
|
84
|
-
query = query.limit(10)
|
91
|
+
query = User.query.where({ name: 'John' })
|
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
|
+
Route.new(
|
158
|
+
verb: Verb::GET,
|
159
|
+
path: "/livez",
|
160
|
+
controller: Controllers::Health,
|
161
|
+
action: "livez",
|
162
|
+
),
|
163
|
+
Route.new(
|
164
|
+
verb: 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,16 @@ 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
|
-
|
185
|
+
search = T.let(params.fetch("q", nil), T.nilable(String))
|
186
|
+
|
187
|
+
airports = Kirei::Services::Runner.call("Airports::Filter") do
|
188
|
+
Airports::Filter.call(search) # T::Array[Airport]
|
189
|
+
end
|
170
190
|
|
171
191
|
# or use a serializer
|
172
192
|
data = Oj.dump(airports.map(&:serialize))
|
@@ -177,6 +197,35 @@ module Controllers
|
|
177
197
|
end
|
178
198
|
```
|
179
199
|
|
200
|
+
Services can be PORO. You can wrap an execution in `Kirei::Services::Runner` which will emit a standardized logline and track its execution time.
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
module Airports
|
204
|
+
class Filter
|
205
|
+
extend T::Sig
|
206
|
+
|
207
|
+
sig do
|
208
|
+
params(
|
209
|
+
search: T.nilable(String),
|
210
|
+
).returns(T::Array[Airport])
|
211
|
+
end
|
212
|
+
def self.call(search)
|
213
|
+
return Airport.all if search.nil?
|
214
|
+
|
215
|
+
#
|
216
|
+
# SELECT *
|
217
|
+
# FROM "airports"
|
218
|
+
# WHERE (("name" ILIKE 'xx%') OR ("id" ILIKE 'xx%'))
|
219
|
+
#
|
220
|
+
query = Airport.query.where(Sequel.ilike(:name, "#{search}%"))
|
221
|
+
query = query.or(Sequel.ilike(:id, "#{search}%"))
|
222
|
+
|
223
|
+
Airport.resolve(query)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
```
|
228
|
+
|
180
229
|
## Contributions
|
181
230
|
|
182
231
|
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/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"
|
@@ -46,7 +46,9 @@ Gem::Specification.new do |spec|
|
|
46
46
|
# Utilities
|
47
47
|
spec.add_dependency "oj", "~> 3.0"
|
48
48
|
spec.add_dependency "sorbet-runtime", "~> 0.5"
|
49
|
+
spec.add_dependency "statsd-instrument", "~> 3.0"
|
49
50
|
spec.add_dependency "tzinfo-data", "~> 1.0" # for containerized environments, e.g. on AWS ECS
|
51
|
+
spec.add_dependency "zeitwerk", "~> 2.5"
|
50
52
|
|
51
53
|
# Web server & routing
|
52
54
|
spec.add_dependency "puma", "~> 6.0"
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: true
|
2
2
|
|
3
3
|
require "fileutils"
|
4
4
|
|
@@ -13,10 +13,10 @@ 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
|
-
Kirei::Logger.logger.info(
|
19
|
+
Kirei::Logging::Logger.logger.info(
|
20
20
|
"Kirei app '#{app_name}' scaffolded successfully!",
|
21
21
|
)
|
22
22
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: true
|
2
2
|
|
3
3
|
module Cli
|
4
4
|
module Commands
|
@@ -29,15 +29,28 @@ 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
|
+
[
|
35
|
+
"/app",
|
36
|
+
"/app/models",
|
37
|
+
"/app/services",
|
38
|
+
].each do |root_namespace|
|
39
|
+
# a root namespace skips the auto-infered module for this folder
|
40
|
+
# so we don't have to write e.g. `Models::` or `Services::`
|
41
|
+
loader.push_dir("\#{File.dirname(__FILE__)}\#{root_namespace}")
|
42
|
+
end
|
43
|
+
loader.setup
|
33
44
|
|
34
45
|
# Fifth: load configs
|
35
46
|
Dir[File.join(__dir__, "config", "*.rb")].each { require(_1) }
|
36
47
|
|
37
|
-
class #{app_name} < Kirei::
|
48
|
+
class #{app_name} < Kirei::App
|
38
49
|
# Kirei configuration
|
39
50
|
config.app_name = "#{snake_case_app_name}"
|
40
51
|
end
|
52
|
+
|
53
|
+
loader.eager_load
|
41
54
|
RUBY
|
42
55
|
end
|
43
56
|
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
|
+
Route.new(
|
23
|
+
verb: 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/cli/commands/start.rb
CHANGED
@@ -13,7 +13,7 @@ module Cli
|
|
13
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
|
-
Kirei::Logger.logger.info("Unknown command")
|
16
|
+
Kirei::Logging::Logger.logger.info("Unknown command")
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
data/lib/kirei/app.rb
CHANGED
@@ -1,72 +1,92 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
|
4
|
+
module Kirei
|
5
|
+
#
|
6
|
+
# This is the entrypoint into the application; it implements the Rack interface.
|
7
|
+
#
|
8
|
+
class App < Routing::Base
|
9
|
+
class << self
|
10
|
+
extend T::Sig
|
5
11
|
|
6
|
-
#
|
12
|
+
#
|
13
|
+
# convenience method since "Kirei.configuration" must be nilable since it is nil
|
14
|
+
# at the beginning of initilization of the app
|
15
|
+
#
|
16
|
+
sig { returns(Kirei::Config) }
|
17
|
+
def config
|
18
|
+
T.must(Kirei.configuration)
|
19
|
+
end
|
7
20
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
21
|
+
sig { returns(Pathname) }
|
22
|
+
def root
|
23
|
+
defined?(::APP_ROOT) ? Pathname.new(::APP_ROOT) : Pathname.new(Dir.pwd)
|
24
|
+
end
|
12
25
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
26
|
+
#
|
27
|
+
# Returns the version of the app. It checks in the following order:
|
28
|
+
# * ENV["APP_VERSION"]
|
29
|
+
# * ENV["GIT_SHA"]
|
30
|
+
# * `git rev-parse --short HEAD`
|
31
|
+
#
|
32
|
+
sig { returns(String) }
|
33
|
+
def version
|
34
|
+
@version = T.let(@version, T.nilable(String))
|
35
|
+
@version ||= ENV.fetch("APP_VERSION", nil)
|
36
|
+
@version ||= ENV.fetch("GIT_SHA", nil)
|
37
|
+
@version ||= T.must(
|
38
|
+
`command -v git && git rev-parse --short HEAD`.to_s.split("\n").last,
|
39
|
+
).freeze # localhost
|
40
|
+
end
|
18
41
|
|
19
|
-
|
20
|
-
|
42
|
+
#
|
43
|
+
# Returns ENV["RACK_ENV"] or "development" if it is not set
|
44
|
+
#
|
45
|
+
sig { returns(String) }
|
46
|
+
def environment
|
47
|
+
ENV.fetch("RACK_ENV", "development")
|
48
|
+
end
|
21
49
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
50
|
+
#
|
51
|
+
# Returns the name of the database based on the app name and the environment,
|
52
|
+
# e.g. "myapp_development"
|
53
|
+
#
|
54
|
+
sig { returns(String) }
|
55
|
+
def default_db_name
|
56
|
+
@default_db_name ||= T.let("#{config.app_name}_#{environment}".freeze, T.nilable(String))
|
57
|
+
end
|
29
58
|
|
30
|
-
|
31
|
-
|
59
|
+
#
|
60
|
+
# Returns the database URL based on the DATABASE_URL environment variable or
|
61
|
+
# a default value based on the default_db_name
|
62
|
+
#
|
63
|
+
sig { returns(String) }
|
64
|
+
def default_db_url
|
65
|
+
@default_db_url ||= T.let(
|
66
|
+
ENV.fetch("DATABASE_URL", "postgresql://localhost:5432/#{default_db_name}"),
|
67
|
+
T.nilable(String),
|
68
|
+
)
|
69
|
+
end
|
32
70
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
71
|
+
sig { returns(Sequel::Database) }
|
72
|
+
def raw_db_connection
|
73
|
+
@raw_db_connection = T.let(@raw_db_connection, T.nilable(Sequel::Database))
|
74
|
+
return @raw_db_connection unless @raw_db_connection.nil?
|
75
|
+
|
76
|
+
# calling "Sequel.connect" creates a new connection
|
77
|
+
@raw_db_connection = Sequel.connect(App.config.db_url || default_db_url)
|
78
|
+
|
79
|
+
config.db_extensions.each do |ext|
|
80
|
+
T.cast(@raw_db_connection, Sequel::Database).extension(ext)
|
39
81
|
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
82
|
|
49
|
-
|
50
|
-
|
51
|
-
|
83
|
+
if config.db_extensions.include?(:pg_json)
|
84
|
+
# https://github.com/jeremyevans/sequel/blob/5.75.0/lib/sequel/extensions/pg_json.rb#L8
|
85
|
+
@raw_db_connection.wrap_json_primitives = true
|
86
|
+
end
|
52
87
|
|
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
|
-
]
|
88
|
+
@raw_db_connection
|
89
|
+
end
|
68
90
|
end
|
69
91
|
end
|
70
92
|
end
|
71
|
-
|
72
|
-
# rubocop:enable Metrics/AbcSize, Layout/LineLength
|
data/lib/kirei/config.rb
CHANGED
@@ -17,7 +17,10 @@ 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[
|
20
|
+
prop :log_default_metadata, T::Hash[String, T.untyped], default: {}
|
21
|
+
prop :log_level, Kirei::Logging::Level, default: Kirei::Logging::Level::INFO
|
22
|
+
|
23
|
+
prop :metric_default_tags, T::Hash[String, T.untyped], default: {}
|
21
24
|
|
22
25
|
# dup to allow the user to extend the existing list of sensitive keys
|
23
26
|
prop :sensitive_keys, T::Array[Regexp], factory: -> { SENSITIVE_KEYS.dup }
|