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
@@ -2,7 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module Kirei
|
5
|
-
module
|
5
|
+
module Model
|
6
6
|
extend T::Sig
|
7
7
|
extend T::Helpers
|
8
8
|
|
@@ -115,7 +115,7 @@ module Kirei
|
|
115
115
|
|
116
116
|
sig { override.returns(Sequel::Dataset) }
|
117
117
|
def db
|
118
|
-
|
118
|
+
App.raw_db_connection[table_name.to_sym]
|
119
119
|
end
|
120
120
|
|
121
121
|
sig do
|
@@ -166,7 +166,7 @@ module Kirei
|
|
166
166
|
def wrap_jsonb_non_primivitives!(attributes)
|
167
167
|
# setting `@raw_db_connection.wrap_json_primitives = true`
|
168
168
|
# only works on JSON primitives, but not on blank hashes/arrays
|
169
|
-
return unless
|
169
|
+
return unless App.config.db_extensions.include?(:pg_json)
|
170
170
|
|
171
171
|
attributes.each_pair do |key, value|
|
172
172
|
next unless value.is_a?(Hash) || value.is_a?(Array)
|
@@ -196,7 +196,7 @@ module Kirei
|
|
196
196
|
).returns(T::Array[T.attached_class])
|
197
197
|
end
|
198
198
|
def resolve(query, strict = nil)
|
199
|
-
strict_loading = strict.nil? ?
|
199
|
+
strict_loading = strict.nil? ? App.config.db_strict_type_resolving : strict
|
200
200
|
|
201
201
|
query.map do |row|
|
202
202
|
row = T.cast(row, T::Hash[Symbol, T.untyped])
|
@@ -212,7 +212,7 @@ module Kirei
|
|
212
212
|
).returns(T.nilable(T.attached_class))
|
213
213
|
end
|
214
214
|
def resolve_first(query, strict = nil)
|
215
|
-
strict_loading = strict.nil? ?
|
215
|
+
strict_loading = strict.nil? ? App.config.db_strict_type_resolving : strict
|
216
216
|
|
217
217
|
resolve(query.limit(1), strict_loading).first
|
218
218
|
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# rubocop:disable Metrics/all
|
5
|
+
|
6
|
+
module Kirei
|
7
|
+
module Routing
|
8
|
+
class Base
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
sig { params(params: T::Hash[String, T.untyped]).void }
|
12
|
+
def initialize(params: {})
|
13
|
+
@router = T.let(Router.instance, Router)
|
14
|
+
@params = T.let(params, T::Hash[String, T.untyped])
|
15
|
+
end
|
16
|
+
|
17
|
+
sig { returns(T::Hash[String, T.untyped]) }
|
18
|
+
attr_reader :params
|
19
|
+
|
20
|
+
sig { params(env: RackEnvType).returns(RackResponseType) }
|
21
|
+
def call(env)
|
22
|
+
http_verb = Router::Verb.deserialize(env.fetch("REQUEST_METHOD"))
|
23
|
+
req_path = T.cast(env.fetch("REQUEST_PATH"), String)
|
24
|
+
#
|
25
|
+
# TODO: reject requests from unexpected hosts -> allow configuring allowed hosts in a `cors.rb` file
|
26
|
+
# ( offer a scaffold for this file )
|
27
|
+
# -> use https://github.com/cyu/rack-cors ?
|
28
|
+
#
|
29
|
+
|
30
|
+
route = Router.instance.get(http_verb, req_path)
|
31
|
+
return [404, {}, ["Not Found"]] if route.nil?
|
32
|
+
|
33
|
+
params = case route.verb
|
34
|
+
when Router::Verb::GET
|
35
|
+
query = T.cast(env.fetch("QUERY_STRING"), String)
|
36
|
+
query.split("&").to_h do |p|
|
37
|
+
k, v = p.split("=")
|
38
|
+
k = T.cast(k, String)
|
39
|
+
[k, v]
|
40
|
+
end
|
41
|
+
when Router::Verb::POST, Router::Verb::PUT, Router::Verb::PATCH
|
42
|
+
# TODO: based on content-type, parse the body differently
|
43
|
+
# build-in support for JSON & XML
|
44
|
+
body = T.cast(env.fetch("rack.input"), T.any(IO, StringIO))
|
45
|
+
res = Oj.load(body.read, Kirei::OJ_OPTIONS)
|
46
|
+
body.rewind # TODO: maybe don't rewind if we don't need to?
|
47
|
+
T.cast(res, T::Hash[String, T.untyped])
|
48
|
+
else
|
49
|
+
Logger.logger.warn("Unsupported HTTP verb: #{http_verb.serialize} send to #{req_path}")
|
50
|
+
{}
|
51
|
+
end
|
52
|
+
|
53
|
+
req_id = T.cast(env["HTTP_X_REQUEST_ID"], T.nilable(String))
|
54
|
+
req_id ||= "req_#{App.environment}_#{SecureRandom.uuid}"
|
55
|
+
Thread.current[:request_id] = req_id
|
56
|
+
|
57
|
+
controller = route.controller
|
58
|
+
before_hooks = collect_hooks(controller, :before_hooks)
|
59
|
+
run_hooks(before_hooks)
|
60
|
+
|
61
|
+
status, headers, body = T.cast(
|
62
|
+
controller.new(params: params).public_send(route.action),
|
63
|
+
RackResponseType,
|
64
|
+
)
|
65
|
+
|
66
|
+
after_hooks = collect_hooks(controller, :after_hooks)
|
67
|
+
run_hooks(after_hooks)
|
68
|
+
|
69
|
+
headers["X-Request-Id"] ||= req_id
|
70
|
+
|
71
|
+
default_headers.each do |header_name, default_value|
|
72
|
+
headers[header_name] ||= default_value
|
73
|
+
end
|
74
|
+
|
75
|
+
[
|
76
|
+
status,
|
77
|
+
headers,
|
78
|
+
body,
|
79
|
+
]
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# Kirei::App#render
|
84
|
+
# * "status": defaults to 200
|
85
|
+
# * "headers": defaults to an empty hash
|
86
|
+
#
|
87
|
+
sig do
|
88
|
+
params(
|
89
|
+
body: String,
|
90
|
+
status: Integer,
|
91
|
+
headers: T::Hash[String, String],
|
92
|
+
).returns(RackResponseType)
|
93
|
+
end
|
94
|
+
def render(body, status: 200, headers: {})
|
95
|
+
# merge default headers
|
96
|
+
# support a "type" to set content-type header? (or default to json, and users must set the header themselves for other types?)
|
97
|
+
[
|
98
|
+
status,
|
99
|
+
headers,
|
100
|
+
[body],
|
101
|
+
]
|
102
|
+
end
|
103
|
+
|
104
|
+
sig { returns(T::Hash[String, String]) }
|
105
|
+
def default_headers
|
106
|
+
# "Access-Control-Allow-Origin": the user should set that
|
107
|
+
{
|
108
|
+
# security relevant headers
|
109
|
+
"X-Frame-Options" => "DENY",
|
110
|
+
"X-Content-Type-Options" => "nosniff",
|
111
|
+
"X-XSS-Protection" => "1; mode=block", # for legacy clients/browsers
|
112
|
+
"Strict-Transport-Security" => "max-age=31536000; includeSubDomains", # for HTTPS
|
113
|
+
"Cache-Control" => "no-store", # the user should set that if caching is needed
|
114
|
+
"Referrer-Policy" => "strict-origin-when-cross-origin",
|
115
|
+
"Content-Security-Policy" => "default-src 'none'; frame-ancestors 'none'",
|
116
|
+
|
117
|
+
# other headers
|
118
|
+
"Content-Type" => "application/json; charset=utf-8",
|
119
|
+
}
|
120
|
+
end
|
121
|
+
|
122
|
+
sig { params(hooks: NilableHooksType).void }
|
123
|
+
private def run_hooks(hooks)
|
124
|
+
return if hooks.nil? || hooks.empty?
|
125
|
+
|
126
|
+
hooks.each(&:call)
|
127
|
+
end
|
128
|
+
|
129
|
+
sig do
|
130
|
+
params(
|
131
|
+
controller: T.class_of(Controller),
|
132
|
+
hooks_type: Symbol,
|
133
|
+
).returns(NilableHooksType)
|
134
|
+
end
|
135
|
+
private def collect_hooks(controller, hooks_type)
|
136
|
+
result = T.let(Set.new, T::Set[T.proc.void])
|
137
|
+
|
138
|
+
controller.ancestors.reverse.each do |ancestor|
|
139
|
+
next unless ancestor < Controller
|
140
|
+
|
141
|
+
supported_hooks = %i[before_hooks after_hooks]
|
142
|
+
unless supported_hooks.include?(hooks_type)
|
143
|
+
raise "Unexpected hook type, got #{hooks_type}, expected one of: #{supported_hooks.join(",")}"
|
144
|
+
end
|
145
|
+
|
146
|
+
hooks = T.let(ancestor.public_send(hooks_type), NilableHooksType)
|
147
|
+
result.merge(hooks) if hooks&.any?
|
148
|
+
end
|
149
|
+
|
150
|
+
result
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# rubocop:enable Metrics/all
|
@@ -2,16 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module Kirei
|
5
|
-
module
|
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
|
-
|
5
|
+
module Routing
|
15
6
|
RackEnvType = T.type_alias do
|
16
7
|
T::Hash[
|
17
8
|
String,
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Kirei
|
5
|
+
module Routing
|
6
|
+
# https://github.com/rack/rack/blob/main/UPGRADE-GUIDE.md#rack-3-upgrade-guide
|
7
|
+
RackResponseType = T.type_alias do
|
8
|
+
[
|
9
|
+
Integer, # status
|
10
|
+
T::Hash[String, String], # headers. Values may be arrays of integers for binary representations
|
11
|
+
T.any(T::Array[String], Proc), # body
|
12
|
+
]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require("singleton")
|
5
|
+
|
6
|
+
module Kirei
|
7
|
+
module Routing
|
8
|
+
#
|
9
|
+
# Usage:
|
10
|
+
#
|
11
|
+
# Router.add_routes([
|
12
|
+
# Route.new(
|
13
|
+
# verb: Kirei::Router::Verb::GET,
|
14
|
+
# path: "/livez",
|
15
|
+
# controller: Controllers::HealthController,
|
16
|
+
# action: "livez",
|
17
|
+
# ),
|
18
|
+
# ])
|
19
|
+
#
|
20
|
+
class Router
|
21
|
+
extend T::Sig
|
22
|
+
include ::Singleton
|
23
|
+
|
24
|
+
class Verb < T::Enum
|
25
|
+
enums do
|
26
|
+
# idempotent
|
27
|
+
GET = new("GET")
|
28
|
+
# non-idempotent
|
29
|
+
POST = new("POST")
|
30
|
+
# idempotent
|
31
|
+
PUT = new("PUT")
|
32
|
+
# non-idempotent
|
33
|
+
PATCH = new("PATCH")
|
34
|
+
# non-idempotent
|
35
|
+
DELETE = new("DELETE")
|
36
|
+
# idempotent
|
37
|
+
HEAD = new("HEAD")
|
38
|
+
# idempotent
|
39
|
+
OPTIONS = new("OPTIONS")
|
40
|
+
# idempotent
|
41
|
+
TRACE = new("TRACE")
|
42
|
+
# non-idempotent
|
43
|
+
CONNECT = new("CONNECT")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Route < T::Struct
|
48
|
+
const :verb, Verb
|
49
|
+
const :path, String
|
50
|
+
const :controller, T.class_of(Controller)
|
51
|
+
const :action, String
|
52
|
+
end
|
53
|
+
|
54
|
+
RoutesHash = T.type_alias do
|
55
|
+
T::Hash[String, Route]
|
56
|
+
end
|
57
|
+
|
58
|
+
sig { void }
|
59
|
+
def initialize
|
60
|
+
@routes = T.let({}, RoutesHash)
|
61
|
+
end
|
62
|
+
|
63
|
+
sig { returns(RoutesHash) }
|
64
|
+
attr_reader :routes
|
65
|
+
|
66
|
+
sig do
|
67
|
+
params(
|
68
|
+
verb: Verb,
|
69
|
+
path: String,
|
70
|
+
).returns(T.nilable(Route))
|
71
|
+
end
|
72
|
+
def get(verb, path)
|
73
|
+
key = "#{verb.serialize} #{path}"
|
74
|
+
routes[key]
|
75
|
+
end
|
76
|
+
|
77
|
+
sig { params(routes: T::Array[Route]).void }
|
78
|
+
def self.add_routes(routes)
|
79
|
+
routes.each do |route|
|
80
|
+
key = "#{route.verb.serialize} #{route.path}"
|
81
|
+
instance.routes[key] = route
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/kirei/version.rb
CHANGED
data/lib/kirei.rb
CHANGED
@@ -1,7 +1,30 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
|
4
|
+
# This is the entrypoint into the application,
|
5
|
+
# This file loads first, hence we don't have Sorbet loaded yet.
|
6
|
+
|
7
|
+
#
|
8
|
+
# Load Order is important!
|
9
|
+
#
|
10
|
+
|
11
|
+
# First: check if all gems are installed correctly
|
12
|
+
require "bundler/setup"
|
13
|
+
|
14
|
+
# Second: load all gems (runtime dependencies only)
|
15
|
+
require "logger"
|
16
|
+
require "sorbet-runtime"
|
17
|
+
require "oj"
|
18
|
+
require "rack"
|
19
|
+
require "pg"
|
20
|
+
require "sequel" # "sequel_pg" is auto-required by "sequel"
|
21
|
+
|
22
|
+
# Third: load all application code
|
23
|
+
require("zeitwerk")
|
24
|
+
loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
|
25
|
+
loader.ignore("#{__dir__}/cli")
|
26
|
+
loader.ignore("#{__dir__}/cli.rb")
|
27
|
+
loader.setup
|
5
28
|
|
6
29
|
module Kirei
|
7
30
|
extend T::Sig
|
@@ -40,6 +63,8 @@ module Kirei
|
|
40
63
|
end
|
41
64
|
end
|
42
65
|
|
66
|
+
loader.eager_load
|
67
|
+
|
43
68
|
Kirei.configure(&:itself)
|
44
69
|
|
45
|
-
Kirei::Logger.logger.info("Kirei (#{Kirei::VERSION}) booted
|
70
|
+
Kirei::Logger.logger.info("Kirei (v#{Kirei::VERSION}) booted.")
|
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.3.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-05-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: oj
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '1.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: zeitwerk
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.5'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.5'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: puma
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -123,9 +137,9 @@ dependencies:
|
|
123
137
|
- !ruby/object:Gem::Version
|
124
138
|
version: '1.0'
|
125
139
|
description: |
|
126
|
-
Kirei is a
|
140
|
+
Kirei is a Ruby micro/REST-framework for building scalable and performant microservices.
|
127
141
|
It is built from the ground up to be clean and easy to use.
|
128
|
-
|
142
|
+
It is a Rack app, and uses Sorbet for typing, Sequel as an ORM, Zeitwerk for autoloading, and Puma as a web server.
|
129
143
|
It strives to have zero magic and to be as explicit as possible.
|
130
144
|
email:
|
131
145
|
- lud@reinmiedl.com
|
@@ -140,7 +154,6 @@ files:
|
|
140
154
|
- README.md
|
141
155
|
- bin/kirei
|
142
156
|
- kirei.gemspec
|
143
|
-
- lib/boot.rb
|
144
157
|
- lib/cli.rb
|
145
158
|
- lib/cli/commands/new_app/base_directories.rb
|
146
159
|
- lib/cli/commands/new_app/execute.rb
|
@@ -154,14 +167,16 @@ files:
|
|
154
167
|
- lib/cli/commands/start.rb
|
155
168
|
- lib/kirei.rb
|
156
169
|
- lib/kirei/app.rb
|
157
|
-
- lib/kirei/app_base.rb
|
158
|
-
- lib/kirei/base_controller.rb
|
159
|
-
- lib/kirei/base_model.rb
|
160
170
|
- lib/kirei/config.rb
|
171
|
+
- lib/kirei/controller.rb
|
161
172
|
- lib/kirei/helpers.rb
|
162
173
|
- lib/kirei/logger.rb
|
163
|
-
- lib/kirei/
|
164
|
-
- lib/kirei/
|
174
|
+
- lib/kirei/model.rb
|
175
|
+
- lib/kirei/routing/base.rb
|
176
|
+
- lib/kirei/routing/nilable_hooks_type.rb
|
177
|
+
- lib/kirei/routing/rack_env_type.rb
|
178
|
+
- lib/kirei/routing/rack_response_type.rb
|
179
|
+
- lib/kirei/routing/router.rb
|
165
180
|
- lib/kirei/version.rb
|
166
181
|
- sorbet/rbi/shims/base_model.rbi
|
167
182
|
homepage: https://github.com/swiknaba/kirei
|
@@ -185,9 +200,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
185
200
|
- !ruby/object:Gem::Version
|
186
201
|
version: '0'
|
187
202
|
requirements: []
|
188
|
-
rubygems_version: 3.5.
|
203
|
+
rubygems_version: 3.5.9
|
189
204
|
signing_key:
|
190
205
|
specification_version: 4
|
191
|
-
summary: Kirei is a
|
192
|
-
|
206
|
+
summary: Kirei is a typed Ruby micro/REST-framework for building scalable and performant
|
207
|
+
microservices.
|
193
208
|
test_files: []
|
data/lib/boot.rb
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
# typed: false
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
# This is the entrypoint into the application,
|
5
|
-
# This file loads first, hence we don't have Sorbet loaded yet.
|
6
|
-
|
7
|
-
#
|
8
|
-
# Load Order is important!
|
9
|
-
#
|
10
|
-
|
11
|
-
# First: check if all gems are installed correctly
|
12
|
-
require "bundler/setup"
|
13
|
-
|
14
|
-
# Second: load all gems (runtime dependencies only)
|
15
|
-
require "logger"
|
16
|
-
require "sorbet-runtime"
|
17
|
-
require "oj"
|
18
|
-
require "rack"
|
19
|
-
require "pg"
|
20
|
-
require "sequel" # "sequel_pg" is auto-required by "sequel"
|
21
|
-
|
22
|
-
# Third: load all application code
|
23
|
-
Dir[File.join(__dir__, "kirei/**/*.rb")].each { require(_1) }
|
data/lib/kirei/app_base.rb
DELETED
@@ -1,72 +0,0 @@
|
|
1
|
-
# typed: strict
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require_relative("app")
|
5
|
-
|
6
|
-
module Kirei
|
7
|
-
class AppBase < Kirei::App
|
8
|
-
class << self
|
9
|
-
extend T::Sig
|
10
|
-
|
11
|
-
# convenience method since "Kirei.configuration" must be nilable since it is nil
|
12
|
-
# at the beginning of initilization of the app
|
13
|
-
sig { returns(Kirei::Config) }
|
14
|
-
def config
|
15
|
-
T.must(Kirei.configuration)
|
16
|
-
end
|
17
|
-
|
18
|
-
sig { returns(Pathname) }
|
19
|
-
def root
|
20
|
-
defined?(::APP_ROOT) ? Pathname.new(::APP_ROOT) : Pathname.new(Dir.pwd)
|
21
|
-
end
|
22
|
-
|
23
|
-
sig { returns(String) }
|
24
|
-
def version
|
25
|
-
@version = T.let(@version, T.nilable(String))
|
26
|
-
@version ||= ENV.fetch("APP_VERSION", nil)
|
27
|
-
@version ||= ENV.fetch("GIT_SHA", nil)
|
28
|
-
@version ||= T.must(
|
29
|
-
`command -v git && git rev-parse --short HEAD`.to_s.split("\n").last,
|
30
|
-
).freeze # localhost
|
31
|
-
end
|
32
|
-
|
33
|
-
sig { returns(String) }
|
34
|
-
def environment
|
35
|
-
ENV.fetch("RACK_ENV", "development")
|
36
|
-
end
|
37
|
-
|
38
|
-
sig { returns(String) }
|
39
|
-
def default_db_name
|
40
|
-
@default_db_name ||= T.let("#{config.app_name}_#{environment}".freeze, T.nilable(String))
|
41
|
-
end
|
42
|
-
|
43
|
-
sig { returns(String) }
|
44
|
-
def default_db_url
|
45
|
-
@default_db_url ||= T.let(
|
46
|
-
ENV.fetch("DATABASE_URL", "postgresql://localhost:5432/#{default_db_name}"),
|
47
|
-
T.nilable(String),
|
48
|
-
)
|
49
|
-
end
|
50
|
-
|
51
|
-
sig { returns(Sequel::Database) }
|
52
|
-
def raw_db_connection
|
53
|
-
@raw_db_connection = T.let(@raw_db_connection, T.nilable(Sequel::Database))
|
54
|
-
return @raw_db_connection unless @raw_db_connection.nil?
|
55
|
-
|
56
|
-
# calling "Sequel.connect" creates a new connection
|
57
|
-
@raw_db_connection = Sequel.connect(AppBase.config.db_url || default_db_url)
|
58
|
-
|
59
|
-
config.db_extensions.each do |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
|
66
|
-
end
|
67
|
-
|
68
|
-
@raw_db_connection
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
@@ -1,16 +0,0 @@
|
|
1
|
-
# typed: strict
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require_relative("app")
|
5
|
-
|
6
|
-
module Kirei
|
7
|
-
class BaseController < Kirei::App
|
8
|
-
extend T::Sig
|
9
|
-
# register(Sinatra::Namespace)
|
10
|
-
|
11
|
-
# before do
|
12
|
-
# Thread.current[:request_id] = request.env["HTTP_X_REQUEST_ID"].presence ||
|
13
|
-
# "req_#{AppBase.environment}_#{SecureRandom.uuid}"
|
14
|
-
# end
|
15
|
-
end
|
16
|
-
end
|
data/lib/kirei/router.rb
DELETED
@@ -1,61 +0,0 @@
|
|
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
|