kirei 0.0.3 → 0.1.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/kirei.gemspec +0 -1
- data/lib/boot.rb +0 -6
- data/lib/cli/commands/start.rb +2 -1
- data/lib/kirei/app_base.rb +6 -1
- data/lib/kirei/base_model.rb +81 -1
- data/lib/kirei/config.rb +4 -2
- data/lib/kirei/helpers.rb +22 -2
- data/lib/kirei/logger.rb +24 -7
- data/lib/kirei/version.rb +1 -1
- data/lib/kirei.rb +11 -0
- data/sorbet/rbi/shims/base_model.rbi +7 -0
- metadata +2 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cea2982351607eeafadd7e8ee3bb3ce13ba2c7e1e37eed22fa2f5a124b625022
|
4
|
+
data.tar.gz: 84a47be77945fa7a2faf5950441290fde74293388467729f2eb52c1c6041d29b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b420ea6cbfce60bf741bfd85cbfeb310dfd71031519a6d5fcc330bff788d24a0df1ef7268abbdbe49a2f373f4ff5df413dc6cf1b83589f96a6f8867164ffcef
|
7
|
+
data.tar.gz: 061a15d94de3e73dcd8d41db48601bfa5390d59aa1438c322d590ec3c33db9da4dd38d1f9258bf37a13c248ded433883a6da9ff52d41ee5873ca13b0cda414ca
|
data/kirei.gemspec
CHANGED
@@ -50,7 +50,6 @@ Gem::Specification.new do |spec|
|
|
50
50
|
spec.add_dependency "tzinfo-data", "~> 1.0" # for containerized environments, e.g. on AWS ECS
|
51
51
|
|
52
52
|
# Web server & routing
|
53
|
-
spec.add_dependency "puma", "~> 6.0"
|
54
53
|
spec.add_dependency "sinatra", "~> 3.0"
|
55
54
|
spec.add_dependency "sinatra-contrib", "~> 3.0"
|
56
55
|
|
data/lib/boot.rb
CHANGED
@@ -15,16 +15,10 @@ require "bundler/setup"
|
|
15
15
|
require "logger"
|
16
16
|
require "sorbet-runtime"
|
17
17
|
require "oj"
|
18
|
-
require "puma"
|
19
18
|
require "sinatra"
|
20
19
|
require "sinatra/namespace" # from sinatra-contrib
|
21
20
|
require "pg"
|
22
21
|
require "sequel" # "sequel_pg" is auto-required by "sequel"
|
23
22
|
|
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
23
|
# Third: load all application code
|
30
24
|
Dir[File.join(__dir__, "kirei/**/*.rb")].each { require(_1) }
|
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,6 +9,7 @@ module Cli
|
|
9
9
|
case args[0]
|
10
10
|
when "new"
|
11
11
|
app_name = args[1] || "MyApp"
|
12
|
+
# @TODO(lud, 31.12.2023): classify is from ActiveSupport -> remove this?
|
12
13
|
app_name = app_name.gsub(/[-\s]/, "_").classify
|
13
14
|
NewApp::Execute.call(app_name: app_name)
|
14
15
|
else
|
data/lib/kirei/app_base.rb
CHANGED
@@ -55,7 +55,12 @@ module Kirei
|
|
55
55
|
@raw_db_connection = Sequel.connect(AppBase.config.db_url || default_db_url)
|
56
56
|
|
57
57
|
config.db_extensions.each do |ext|
|
58
|
-
@raw_db_connection.extension(ext)
|
58
|
+
T.cast(@raw_db_connection, Sequel::Database).extension(ext)
|
59
|
+
end
|
60
|
+
|
61
|
+
if config.db_extensions.include?(:pg_json)
|
62
|
+
# https://github.com/jeremyevans/sequel/blob/5.75.0/lib/sequel/extensions/pg_json.rb#L8
|
63
|
+
@raw_db_connection.wrap_json_primitives = true
|
59
64
|
end
|
60
65
|
|
61
66
|
@raw_db_connection
|
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,14 @@ module Kirei
|
|
33
60
|
def where(hash)
|
34
61
|
end
|
35
62
|
|
63
|
+
sig { abstract.params(hash: T.untyped).returns(T.untyped) }
|
64
|
+
def create(hash)
|
65
|
+
end
|
66
|
+
|
67
|
+
sig { abstract.params(attributes: T.untyped).void }
|
68
|
+
def wrap_jsonb_non_primivitives!(attributes)
|
69
|
+
end
|
70
|
+
|
36
71
|
sig { abstract.params(hash: T.untyped).returns(T.untyped) }
|
37
72
|
def resolve(hash)
|
38
73
|
end
|
@@ -88,6 +123,49 @@ module Kirei
|
|
88
123
|
resolve(db.where(hash))
|
89
124
|
end
|
90
125
|
|
126
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
127
|
+
# default values defined in the model are used, if omitted in the hash
|
128
|
+
sig do
|
129
|
+
override.params(
|
130
|
+
hash: T::Hash[Symbol, T.untyped],
|
131
|
+
).returns(T.attached_class)
|
132
|
+
end
|
133
|
+
def create(hash)
|
134
|
+
# instantiate a new object to ensure we use default values defined in the model
|
135
|
+
without_id = !hash.key?(:id)
|
136
|
+
hash[:id] = "kirei-fake-id" if without_id
|
137
|
+
new_record = from_hash(Helpers.deep_stringify_keys(hash))
|
138
|
+
all_attributes = T.let(new_record.serialize, T::Hash[String, T.untyped])
|
139
|
+
all_attributes.delete("id") if without_id && all_attributes["id"] == "kirei-fake-id"
|
140
|
+
|
141
|
+
wrap_jsonb_non_primivitives!(all_attributes)
|
142
|
+
|
143
|
+
if new_record.respond_to?(:created_at) && all_attributes["created_at"].nil?
|
144
|
+
all_attributes["created_at"] = Time.now.utc
|
145
|
+
end
|
146
|
+
if new_record.respond_to?(:updated_at) && all_attributes["updated_at"].nil?
|
147
|
+
all_attributes["updated_at"] = Time.now.utc
|
148
|
+
end
|
149
|
+
|
150
|
+
pkey = T.let(db.insert(all_attributes), String)
|
151
|
+
|
152
|
+
T.must(find_by({ id: pkey }))
|
153
|
+
end
|
154
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
155
|
+
|
156
|
+
sig { override.params(attributes: T::Hash[T.any(Symbol, String), T.untyped]).void }
|
157
|
+
def wrap_jsonb_non_primivitives!(attributes)
|
158
|
+
# setting `@raw_db_connection.wrap_json_primitives = true`
|
159
|
+
# only works on JSON primitives, but not on blank hashes/arrays
|
160
|
+
return unless AppBase.config.db_extensions.include?(:pg_json)
|
161
|
+
|
162
|
+
attributes.each_pair do |key, value|
|
163
|
+
next unless value.is_a?(Hash) || value.is_a?(Array)
|
164
|
+
|
165
|
+
attributes[key] = T.unsafe(Sequel).pg_jsonb_wrap(value)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
91
169
|
sig do
|
92
170
|
override.params(
|
93
171
|
hash: T::Hash[Symbol, T.untyped],
|
@@ -125,7 +203,9 @@ module Kirei
|
|
125
203
|
).returns(T.nilable(T.attached_class))
|
126
204
|
end
|
127
205
|
def resolve_first(query, strict = nil)
|
128
|
-
|
206
|
+
strict_loading = strict.nil? ? AppBase.config.db_strict_type_resolving : strict
|
207
|
+
|
208
|
+
resolve(query.limit(1), strict_loading).first
|
129
209
|
end
|
130
210
|
end
|
131
211
|
|
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 }
|
@@ -83,7 +83,16 @@ module Kirei
|
|
83
83
|
).void
|
84
84
|
end
|
85
85
|
def call(level:, label:, meta: {})
|
86
|
+
Kirei::AppBase.config.log_default_metadata.each_pair do |key, value|
|
87
|
+
meta[key] ||= value
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# key names follow OpenTelemetry Semantic Conventions
|
92
|
+
# Source: https://opentelemetry.io/docs/concepts/semantic-conventions/
|
93
|
+
#
|
86
94
|
meta[:"service.instance.id"] ||= Thread.current[:request_id]
|
95
|
+
meta[:"service.name"] ||= Kirei::AppBase.config.app_name
|
87
96
|
|
88
97
|
# The Ruby logger only accepts one string as the only argument
|
89
98
|
@queue << { level: level, label: label, meta: meta }
|
@@ -107,7 +116,10 @@ module Kirei
|
|
107
116
|
loglines = if log_transformer
|
108
117
|
log_transformer.call(meta)
|
109
118
|
else
|
110
|
-
[Oj.dump(
|
119
|
+
[Oj.dump(
|
120
|
+
Kirei::Logger.flatten_hash_and_mask_sensitive_values(meta),
|
121
|
+
Kirei::OJ_OPTIONS,
|
122
|
+
)]
|
111
123
|
end
|
112
124
|
|
113
125
|
loglines.each { Kirei::Logger.logger.error(_1) }
|
@@ -131,19 +143,24 @@ module Kirei
|
|
131
143
|
|
132
144
|
sig do
|
133
145
|
params(
|
134
|
-
hash: T::Hash[Symbol, T.untyped],
|
146
|
+
hash: T::Hash[T.any(Symbol, String), T.untyped],
|
135
147
|
prefix: Symbol,
|
136
148
|
).returns(T::Hash[Symbol, T.untyped])
|
137
149
|
end
|
138
150
|
def self.flatten_hash_and_mask_sensitive_values(hash, prefix = :'')
|
139
151
|
result = T.let({}, T::Hash[Symbol, T.untyped])
|
140
|
-
Kirei::Helpers.
|
152
|
+
Kirei::Helpers.deep_symbolize_keys!(hash)
|
153
|
+
hash = T.cast(hash, T::Hash[Symbol, T.untyped])
|
141
154
|
|
142
155
|
hash.each do |key, value|
|
143
156
|
new_prefix = Kirei::Helpers.blank?(prefix) ? key : :"#{prefix}.#{key}"
|
144
157
|
|
145
158
|
case value
|
146
|
-
when Hash
|
159
|
+
when Hash
|
160
|
+
# Some libraries have a custom Hash class that inhert from Hash, but act differently, e.g. OmniAuth::AuthHash.
|
161
|
+
# This results in `transform_keys` being available but without any effect.
|
162
|
+
value = value.to_h if value.class != Hash
|
163
|
+
result.merge!(flatten_hash_and_mask_sensitive_values(value.transform_keys(&:to_sym), new_prefix))
|
147
164
|
when Array
|
148
165
|
value.each_with_index do |element, index|
|
149
166
|
if element.is_a?(Hash) || element.is_a?(Array)
|
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.1.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-01-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: oj
|
@@ -66,20 +66,6 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '1.0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: puma
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '6.0'
|
76
|
-
type: :runtime
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '6.0'
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
70
|
name: sinatra
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|