kirei 0.2.1 → 0.4.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 +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
data/lib/kirei/model.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Kirei
|
5
|
+
module Model
|
6
|
+
extend T::Sig
|
7
|
+
extend T::Helpers
|
8
|
+
|
9
|
+
sig { returns(Kirei::Model::BaseClassInterface) }
|
10
|
+
def class; super; end # rubocop:disable all
|
11
|
+
|
12
|
+
# An update keeps the original object intact, and returns a new object with the updated values.
|
13
|
+
sig do
|
14
|
+
params(
|
15
|
+
hash: T::Hash[Symbol, T.untyped],
|
16
|
+
).returns(T.self_type)
|
17
|
+
end
|
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)
|
21
|
+
self.class.query.where({ id: id }).update(hash)
|
22
|
+
self.class.find_by({ id: id })
|
23
|
+
end
|
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.query.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
|
+
|
50
|
+
mixes_in_class_methods(Kirei::Model::ClassMethods)
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,187 @@
|
|
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 { returns(Router) }
|
21
|
+
attr_reader :router; private :router
|
22
|
+
|
23
|
+
sig { params(env: RackEnvType).returns(RackResponseType) }
|
24
|
+
def call(env)
|
25
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
26
|
+
|
27
|
+
http_verb = Verb.deserialize(env.fetch("REQUEST_METHOD"))
|
28
|
+
req_path = T.cast(env.fetch("REQUEST_PATH"), String)
|
29
|
+
#
|
30
|
+
# TODO: reject requests from unexpected hosts -> allow configuring allowed hosts in a `cors.rb` file
|
31
|
+
# ( offer a scaffold for this file )
|
32
|
+
# -> use https://github.com/cyu/rack-cors ?
|
33
|
+
#
|
34
|
+
|
35
|
+
route = router.get(http_verb, req_path)
|
36
|
+
return [404, {}, ["Not Found"]] if route.nil?
|
37
|
+
|
38
|
+
params = case route.verb
|
39
|
+
when Verb::GET
|
40
|
+
query = T.cast(env.fetch("QUERY_STRING"), String)
|
41
|
+
query.split("&").to_h do |p|
|
42
|
+
k, v = p.split("=")
|
43
|
+
k = T.cast(k, String)
|
44
|
+
[k, v]
|
45
|
+
end
|
46
|
+
when Verb::POST, Verb::PUT, Verb::PATCH
|
47
|
+
# TODO: based on content-type, parse the body differently
|
48
|
+
# build-in support for JSON & XML
|
49
|
+
body = T.cast(env.fetch("rack.input"), T.any(IO, StringIO))
|
50
|
+
res = Oj.load(body.read, Kirei::OJ_OPTIONS)
|
51
|
+
body.rewind # TODO: maybe don't rewind if we don't need to?
|
52
|
+
T.cast(res, T::Hash[String, T.untyped])
|
53
|
+
else
|
54
|
+
Logging::Logger.logger.warn("Unsupported HTTP verb: #{http_verb.serialize} send to #{req_path}")
|
55
|
+
{}
|
56
|
+
end
|
57
|
+
|
58
|
+
req_id = T.cast(env["HTTP_X_REQUEST_ID"], T.nilable(String))
|
59
|
+
req_id ||= "req_#{App.environment}_#{SecureRandom.uuid}"
|
60
|
+
Thread.current[:request_id] = req_id
|
61
|
+
|
62
|
+
controller = route.controller
|
63
|
+
before_hooks = collect_hooks(controller, :before_hooks)
|
64
|
+
run_hooks(before_hooks)
|
65
|
+
|
66
|
+
Kirei::Logging::Logger.call(
|
67
|
+
level: Kirei::Logging::Level::INFO,
|
68
|
+
label: "Request Started",
|
69
|
+
meta: params,
|
70
|
+
)
|
71
|
+
|
72
|
+
statsd_timing_tags = {
|
73
|
+
"controller" => controller.name,
|
74
|
+
"route" => route.action,
|
75
|
+
}
|
76
|
+
Logging::Metric.inject_defaults(statsd_timing_tags)
|
77
|
+
|
78
|
+
status, headers, response_body = T.cast(
|
79
|
+
controller.new(params: params).public_send(route.action),
|
80
|
+
RackResponseType,
|
81
|
+
)
|
82
|
+
|
83
|
+
after_hooks = collect_hooks(controller, :after_hooks)
|
84
|
+
run_hooks(after_hooks)
|
85
|
+
|
86
|
+
headers["X-Request-Id"] ||= req_id
|
87
|
+
|
88
|
+
default_headers.each do |header_name, default_value|
|
89
|
+
headers[header_name] ||= default_value
|
90
|
+
end
|
91
|
+
|
92
|
+
[
|
93
|
+
status,
|
94
|
+
headers,
|
95
|
+
response_body,
|
96
|
+
]
|
97
|
+
ensure
|
98
|
+
stop = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
99
|
+
if start # early return for 404
|
100
|
+
latency_in_ms = stop - start
|
101
|
+
::StatsD.measure("request", latency_in_ms, tags: statsd_timing_tags)
|
102
|
+
|
103
|
+
Kirei::Logging::Logger.call(
|
104
|
+
level: Kirei::Logging::Level::INFO,
|
105
|
+
label: "Request Finished",
|
106
|
+
meta: { "response.body" => response_body, "response.latency_in_ms" => latency_in_ms },
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
# reset global variables after the request has been served
|
111
|
+
# and after all "after" hooks have run to avoid leaking
|
112
|
+
Thread.current[:enduser_id] = nil
|
113
|
+
Thread.current[:request_id] = nil
|
114
|
+
end
|
115
|
+
|
116
|
+
#
|
117
|
+
# * "status": defaults to 200
|
118
|
+
# * "headers": Kirei adds some default headers for security, but the user can override them
|
119
|
+
#
|
120
|
+
sig do
|
121
|
+
params(
|
122
|
+
body: String,
|
123
|
+
status: Integer,
|
124
|
+
headers: T::Hash[String, String],
|
125
|
+
).returns(RackResponseType)
|
126
|
+
end
|
127
|
+
def render(body, status: 200, headers: {})
|
128
|
+
[
|
129
|
+
status,
|
130
|
+
headers,
|
131
|
+
[body],
|
132
|
+
]
|
133
|
+
end
|
134
|
+
|
135
|
+
sig { returns(T::Hash[String, String]) }
|
136
|
+
def default_headers
|
137
|
+
# "Access-Control-Allow-Origin": the user should set that, see comment about "cors" above
|
138
|
+
{
|
139
|
+
# security relevant headers
|
140
|
+
"X-Frame-Options" => "DENY",
|
141
|
+
"X-Content-Type-Options" => "nosniff",
|
142
|
+
"X-XSS-Protection" => "1; mode=block", # for legacy clients/browsers
|
143
|
+
"Strict-Transport-Security" => "max-age=31536000; includeSubDomains", # for HTTPS
|
144
|
+
"Cache-Control" => "no-store", # the user should set that if caching is needed
|
145
|
+
"Referrer-Policy" => "strict-origin-when-cross-origin",
|
146
|
+
"Content-Security-Policy" => "default-src 'none'; frame-ancestors 'none'",
|
147
|
+
|
148
|
+
# other headers
|
149
|
+
"Content-Type" => "application/json; charset=utf-8",
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
sig { params(hooks: NilableHooksType).void }
|
154
|
+
private def run_hooks(hooks)
|
155
|
+
return if hooks.nil? || hooks.empty?
|
156
|
+
|
157
|
+
hooks.each(&:call)
|
158
|
+
end
|
159
|
+
|
160
|
+
sig do
|
161
|
+
params(
|
162
|
+
controller: T.class_of(Controller),
|
163
|
+
hooks_type: Symbol,
|
164
|
+
).returns(NilableHooksType)
|
165
|
+
end
|
166
|
+
private def collect_hooks(controller, hooks_type)
|
167
|
+
result = T.let(Set.new, T::Set[T.proc.void])
|
168
|
+
|
169
|
+
controller.ancestors.reverse.each do |ancestor|
|
170
|
+
next unless ancestor < Controller
|
171
|
+
|
172
|
+
supported_hooks = %i[before_hooks after_hooks]
|
173
|
+
unless supported_hooks.include?(hooks_type)
|
174
|
+
raise "Unexpected hook type, got #{hooks_type}, expected one of: #{supported_hooks.join(",")}"
|
175
|
+
end
|
176
|
+
|
177
|
+
hooks = T.let(ancestor.public_send(hooks_type), NilableHooksType)
|
178
|
+
result.merge(hooks) if hooks&.any?
|
179
|
+
end
|
180
|
+
|
181
|
+
result
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# 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,56 @@
|
|
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: 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
|
+
RoutesHash = T.type_alias do
|
25
|
+
T::Hash[String, Route]
|
26
|
+
end
|
27
|
+
|
28
|
+
sig { void }
|
29
|
+
def initialize
|
30
|
+
@routes = T.let({}, RoutesHash)
|
31
|
+
end
|
32
|
+
|
33
|
+
sig { returns(RoutesHash) }
|
34
|
+
attr_reader :routes
|
35
|
+
|
36
|
+
sig do
|
37
|
+
params(
|
38
|
+
verb: Verb,
|
39
|
+
path: String,
|
40
|
+
).returns(T.nilable(Route))
|
41
|
+
end
|
42
|
+
def get(verb, path)
|
43
|
+
key = "#{verb.serialize} #{path}"
|
44
|
+
routes[key]
|
45
|
+
end
|
46
|
+
|
47
|
+
sig { params(routes: T::Array[Route]).void }
|
48
|
+
def self.add_routes(routes)
|
49
|
+
routes.each do |route|
|
50
|
+
key = "#{route.verb.serialize} #{route.path}"
|
51
|
+
instance.routes[key] = route
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Kirei
|
5
|
+
module Routing
|
6
|
+
class Verb < T::Enum
|
7
|
+
enums do
|
8
|
+
# idempotent
|
9
|
+
GET = new("GET")
|
10
|
+
|
11
|
+
# non-idempotent
|
12
|
+
POST = new("POST")
|
13
|
+
|
14
|
+
# idempotent
|
15
|
+
PUT = new("PUT")
|
16
|
+
|
17
|
+
# non-idempotent
|
18
|
+
PATCH = new("PATCH")
|
19
|
+
|
20
|
+
# non-idempotent
|
21
|
+
DELETE = new("DELETE")
|
22
|
+
|
23
|
+
# idempotent
|
24
|
+
HEAD = new("HEAD")
|
25
|
+
|
26
|
+
# idempotent
|
27
|
+
OPTIONS = new("OPTIONS")
|
28
|
+
|
29
|
+
# idempotent
|
30
|
+
TRACE = new("TRACE")
|
31
|
+
|
32
|
+
# non-idempotent
|
33
|
+
CONNECT = new("CONNECT")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Kirei
|
5
|
+
module Services
|
6
|
+
class Result
|
7
|
+
extend T::Sig
|
8
|
+
extend T::Generic
|
9
|
+
|
10
|
+
ErrorType = type_member { { fixed: T::Array[Errors::JsonApiError] } }
|
11
|
+
ResultType = type_member { { upper: Object } }
|
12
|
+
|
13
|
+
sig do
|
14
|
+
params(
|
15
|
+
result: T.nilable(ResultType),
|
16
|
+
errors: ErrorType,
|
17
|
+
).void
|
18
|
+
end
|
19
|
+
def initialize(result: nil, errors: [])
|
20
|
+
if (result && !errors.empty?) || (!result && errors.empty?)
|
21
|
+
raise ArgumentError, "Must provide either result or errors, got both or neither"
|
22
|
+
end
|
23
|
+
|
24
|
+
@result = result
|
25
|
+
@errors = errors
|
26
|
+
end
|
27
|
+
|
28
|
+
sig { returns(T::Boolean) }
|
29
|
+
def success?
|
30
|
+
@errors.empty?
|
31
|
+
end
|
32
|
+
|
33
|
+
sig { returns(T::Boolean) }
|
34
|
+
def failed?
|
35
|
+
!success?
|
36
|
+
end
|
37
|
+
|
38
|
+
sig { returns(ResultType) }
|
39
|
+
def result
|
40
|
+
raise "Cannot call 'result' when there are errors" if failed?
|
41
|
+
|
42
|
+
T.must(@result)
|
43
|
+
end
|
44
|
+
|
45
|
+
sig { returns(ErrorType) }
|
46
|
+
def errors
|
47
|
+
raise "Cannot call 'errors' when there is a result" if success?
|
48
|
+
|
49
|
+
@errors
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Kirei
|
5
|
+
module Services
|
6
|
+
class Runner
|
7
|
+
extend T::Sig
|
8
|
+
extend T::Generic
|
9
|
+
|
10
|
+
sig do
|
11
|
+
type_parameters(:T)
|
12
|
+
.params(
|
13
|
+
class_name: String,
|
14
|
+
log_tags: T::Hash[String, T.untyped],
|
15
|
+
_: T.proc.returns(T.type_parameter(:T)),
|
16
|
+
).returns(T.type_parameter(:T))
|
17
|
+
end
|
18
|
+
def self.call(class_name, log_tags: {}, &_)
|
19
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
20
|
+
service = yield
|
21
|
+
|
22
|
+
service
|
23
|
+
ensure
|
24
|
+
stop = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
25
|
+
latency_in_ms = stop - T.must(start)
|
26
|
+
|
27
|
+
result = case service
|
28
|
+
when Services::Result
|
29
|
+
service.success? ? "success" : "failure"
|
30
|
+
else
|
31
|
+
"unknown"
|
32
|
+
end
|
33
|
+
|
34
|
+
metric_tags = Logging::Metric.inject_defaults({ "service.result" => result })
|
35
|
+
::StatsD.measure(class_name, latency_in_ms, tags: metric_tags)
|
36
|
+
|
37
|
+
logtags = {
|
38
|
+
"service.name" => class_name,
|
39
|
+
"service.latency_in_ms" => latency_in_ms,
|
40
|
+
"service.result" => result,
|
41
|
+
}
|
42
|
+
logtags.merge!(log_tags)
|
43
|
+
Logging::Logger.call(level: Logging::Level::INFO, label: "Service Finished", meta: logtags)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/kirei/version.rb
CHANGED
data/lib/kirei.rb
CHANGED
@@ -1,7 +1,31 @@
|
|
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 "statsd-instrument"
|
17
|
+
require "sorbet-runtime"
|
18
|
+
require "oj"
|
19
|
+
require "rack"
|
20
|
+
require "pg"
|
21
|
+
require "sequel" # "sequel_pg" is auto-required by "sequel"
|
22
|
+
|
23
|
+
# Third: load all application code
|
24
|
+
require("zeitwerk")
|
25
|
+
loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
|
26
|
+
loader.ignore("#{__dir__}/cli")
|
27
|
+
loader.ignore("#{__dir__}/cli.rb")
|
28
|
+
loader.setup
|
5
29
|
|
6
30
|
module Kirei
|
7
31
|
extend T::Sig
|
@@ -10,7 +34,7 @@ module Kirei
|
|
10
34
|
# rubocop:disable Style/MutableConstant
|
11
35
|
OJ_OPTIONS = T.let(
|
12
36
|
{
|
13
|
-
mode: :compat, # required to dump hashes with symbol-keys
|
37
|
+
mode: :compat, # required to dump hashes with symbol-keys. @TODO(lud, 14.05.2024): drop this, and enforce String Keys?
|
14
38
|
symbol_keys: false, # T::Struct.new works only with string-keys
|
15
39
|
},
|
16
40
|
T::Hash[Symbol, T.untyped],
|
@@ -40,6 +64,10 @@ module Kirei
|
|
40
64
|
end
|
41
65
|
end
|
42
66
|
|
67
|
+
loader.eager_load
|
68
|
+
|
43
69
|
Kirei.configure(&:itself)
|
44
70
|
|
45
|
-
|
71
|
+
yjit_enabled = defined?(RubyVM::YJIT) ? RubyVM::YJIT.enabled? : false
|
72
|
+
|
73
|
+
Kirei::Logging::Logger.logger.info("Kirei v#{Kirei::VERSION} booted; YJIT enabled: #{yjit_enabled}")
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# The RubyVM module only exists on MRI. RubyVM is not defined in other Ruby implementations such as JRuby and TruffleRuby.
|
5
|
+
#
|
6
|
+
# The RubyVM module provides some access to MRI internals. This module is for very limited purposes, such as debugging, prototyping, and research. Normal users must not use it. This module is not portable between Ruby implementations.
|
7
|
+
#
|
8
|
+
class RubyVM
|
9
|
+
module YJIT
|
10
|
+
extend T::Sig
|
11
|
+
|
12
|
+
sig { returns(T::Boolean) }
|
13
|
+
def self.enabled?; end
|
14
|
+
end
|
15
|
+
end
|