kirei 0.0.2 → 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 +6 -5
- data/lib/boot.rb +2 -9
- data/lib/cli/commands/new_app/execute.rb +3 -2
- data/lib/cli/commands/start.rb +3 -3
- data/lib/kirei/app_base.rb +6 -1
- data/lib/kirei/base_model.rb +90 -3
- data/lib/kirei/config.rb +4 -2
- data/lib/kirei/helpers.rb +90 -0
- data/lib/kirei/logger.rb +29 -10
- data/lib/kirei/version.rb +1 -1
- data/lib/kirei.rb +12 -3
- data/sorbet/rbi/shims/base_model.rbi +7 -0
- metadata +8 -34
- data/sorbet/rbi/dsl/active_support/callbacks.rbi +0 -22
    
        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
    
    | @@ -13,10 +13,12 @@ Gem::Specification.new do |spec| | |
| 13 13 | 
             
                "oss@dbl.works",
         | 
| 14 14 | 
             
              ]
         | 
| 15 15 |  | 
| 16 | 
            -
              spec.summary = "Kirei is a strictly typed Ruby micro/REST-framework for building scaleable and performant  | 
| 16 | 
            +
              spec.summary = "Kirei is a strictly typed Ruby micro/REST-framework for building scaleable and performant microservices." # rubocop:disable Layout/LineLength
         | 
| 17 17 | 
             
              spec.description = <<~TXT
         | 
| 18 | 
            -
                Kirei | 
| 19 | 
            -
                It | 
| 18 | 
            +
                Kirei is a strictly typed Ruby micro/REST-framework for building scaleable and performant microservices.
         | 
| 19 | 
            +
                It is built from the ground up to be clean and easy to use.
         | 
| 20 | 
            +
                Kirei is based on Sequel as an ORM, Sorbet for typing, and Sinatra for routing.
         | 
| 21 | 
            +
                It strives to have zero magic and to be as explicit as possible.
         | 
| 20 22 | 
             
              TXT
         | 
| 21 23 | 
             
              spec.homepage = "https://github.com/swiknaba/kirei"
         | 
| 22 24 | 
             
              spec.license = "MIT"
         | 
| @@ -29,6 +31,7 @@ Gem::Specification.new do |spec| | |
| 29 31 | 
             
                "kirei.gemspec",
         | 
| 30 32 | 
             
                ".irbrc",
         | 
| 31 33 | 
             
                "lib/**/*",
         | 
| 34 | 
            +
                # do not include RBIs for gems, because users might use different verions
         | 
| 32 35 | 
             
                "sorbet/rbi/dsl/**/*.rbi",
         | 
| 33 36 | 
             
                "sorbet/rbi/shims/**/*.rbi",
         | 
| 34 37 | 
             
                "LICENSE",
         | 
| @@ -41,14 +44,12 @@ Gem::Specification.new do |spec| | |
| 41 44 | 
             
              spec.require_paths = ["lib"]
         | 
| 42 45 |  | 
| 43 46 | 
             
              # Utilities
         | 
| 44 | 
            -
              spec.add_dependency "activesupport", "~> 6.0"
         | 
| 45 47 | 
             
              spec.add_dependency "oj", "~> 3.0"
         | 
| 46 48 | 
             
              spec.add_dependency "rake", "~> 13.0"
         | 
| 47 49 | 
             
              spec.add_dependency "sorbet-runtime", "~> 0.5"
         | 
| 48 50 | 
             
              spec.add_dependency "tzinfo-data", "~> 1.0" # for containerized environments, e.g. on AWS ECS
         | 
| 49 51 |  | 
| 50 52 | 
             
              # Web server & routing
         | 
| 51 | 
            -
              spec.add_dependency "puma", "~> 6.0"
         | 
| 52 53 | 
             
              spec.add_dependency "sinatra", "~> 3.0"
         | 
| 53 54 | 
             
              spec.add_dependency "sinatra-contrib", "~> 3.0"
         | 
| 54 55 |  | 
    
        data/lib/boot.rb
    CHANGED
    
    | @@ -12,20 +12,13 @@ | |
| 12 12 | 
             
            require "bundler/setup"
         | 
| 13 13 |  | 
| 14 14 | 
             
            # Second: load all gems (runtime dependencies only)
         | 
| 15 | 
            +
            require "logger"
         | 
| 15 16 | 
             
            require "sorbet-runtime"
         | 
| 16 17 | 
             
            require "oj"
         | 
| 17 | 
            -
            require "active_support/all"
         | 
| 18 | 
            -
            require "puma"
         | 
| 19 18 | 
             
            require "sinatra"
         | 
| 20 19 | 
             
            require "sinatra/namespace" # from sinatra-contrib
         | 
| 21 20 | 
             
            require "pg"
         | 
| 22 | 
            -
            require "sequel"
         | 
| 23 | 
            -
            # "sequel_pg" should be auto-required by "sequel"
         | 
| 24 | 
            -
             | 
| 25 | 
            -
            Oj.default_options = {
         | 
| 26 | 
            -
              mode: :compat, # required to dump hashes with symbol-keys
         | 
| 27 | 
            -
              symbol_keys: false, # T::Struct.new works only with string-keys
         | 
| 28 | 
            -
            }
         | 
| 21 | 
            +
            require "sequel" # "sequel_pg" is auto-required by "sequel"
         | 
| 29 22 |  | 
| 30 23 | 
             
            # Third: load all application code
         | 
| 31 24 | 
             
            Dir[File.join(__dir__, "kirei/**/*.rb")].each { require(_1) }
         | 
| @@ -1,7 +1,6 @@ | |
| 1 1 | 
             
            # typed: false
         | 
| 2 2 |  | 
| 3 3 | 
             
            require "fileutils"
         | 
| 4 | 
            -
            require "active_support/all"
         | 
| 5 4 |  | 
| 6 5 | 
             
            module Cli
         | 
| 7 6 | 
             
              module Commands
         | 
| @@ -12,7 +11,9 @@ module Cli | |
| 12 11 | 
             
                      Files::App.call(app_name)
         | 
| 13 12 | 
             
                      Files::Irbrc.call
         | 
| 14 13 |  | 
| 15 | 
            -
                       | 
| 14 | 
            +
                      Kirei::Logger.logger.info(
         | 
| 15 | 
            +
                        "Kirei app '#{app_name}' scaffolded successfully!",
         | 
| 16 | 
            +
                      )
         | 
| 16 17 | 
             
                    end
         | 
| 17 18 | 
             
                  end
         | 
| 18 19 | 
             
                end
         | 
    
        data/lib/cli/commands/start.rb
    CHANGED
    
    | @@ -1,7 +1,6 @@ | |
| 1 | 
            -
            # typed:  | 
| 1 | 
            +
            # typed: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require "fileutils"
         | 
| 4 | 
            -
            require "active_support/all"
         | 
| 5 4 |  | 
| 6 5 | 
             
            module Cli
         | 
| 7 6 | 
             
              module Commands
         | 
| @@ -10,10 +9,11 @@ module Cli | |
| 10 9 | 
             
                    case args[0]
         | 
| 11 10 | 
             
                    when "new"
         | 
| 12 11 | 
             
                      app_name = args[1] || "MyApp"
         | 
| 12 | 
            +
                      # @TODO(lud, 31.12.2023): classify is from ActiveSupport -> remove this?
         | 
| 13 13 | 
             
                      app_name = app_name.gsub(/[-\s]/, "_").classify
         | 
| 14 14 | 
             
                      NewApp::Execute.call(app_name: app_name)
         | 
| 15 15 | 
             
                    else
         | 
| 16 | 
            -
                       | 
| 16 | 
            +
                      Kirei::Logger.logger.info("Unknown command")
         | 
| 17 17 | 
             
                    end
         | 
| 18 18 | 
             
                  end
         | 
| 19 19 | 
             
                end
         | 
    
        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
         | 
| @@ -62,9 +97,16 @@ module Kirei | |
| 62 97 |  | 
| 63 98 | 
             
                  include BaseClassInterface
         | 
| 64 99 |  | 
| 100 | 
            +
                  # defaults to a pluralized, underscored version of the class name
         | 
| 65 101 | 
             
                  sig { override.returns(String) }
         | 
| 66 102 | 
             
                  def table_name
         | 
| 67 | 
            -
                    T. | 
| 103 | 
            +
                    @table_name ||= T.let(
         | 
| 104 | 
            +
                      begin
         | 
| 105 | 
            +
                        table_name_ = Kirei::Helpers.underscore(T.must(name.split("::").last))
         | 
| 106 | 
            +
                        "#{table_name_}s"
         | 
| 107 | 
            +
                      end,
         | 
| 108 | 
            +
                      T.nilable(String),
         | 
| 109 | 
            +
                    )
         | 
| 68 110 | 
             
                  end
         | 
| 69 111 |  | 
| 70 112 | 
             
                  sig { override.returns(Sequel::Dataset) }
         | 
| @@ -81,6 +123,49 @@ module Kirei | |
| 81 123 | 
             
                    resolve(db.where(hash))
         | 
| 82 124 | 
             
                  end
         | 
| 83 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 | 
            +
             | 
| 84 169 | 
             
                  sig do
         | 
| 85 170 | 
             
                    override.params(
         | 
| 86 171 | 
             
                      hash: T::Hash[Symbol, T.untyped],
         | 
| @@ -106,7 +191,7 @@ module Kirei | |
| 106 191 |  | 
| 107 192 | 
             
                    query.map do |row|
         | 
| 108 193 | 
             
                      row = T.cast(row, T::Hash[Symbol, T.untyped])
         | 
| 109 | 
            -
                      row. | 
| 194 | 
            +
                      row.transform_keys!(&:to_s) # sequel returns symbolized keys
         | 
| 110 195 | 
             
                      from_hash(row, strict_loading)
         | 
| 111 196 | 
             
                    end
         | 
| 112 197 | 
             
                  end
         | 
| @@ -118,7 +203,9 @@ module Kirei | |
| 118 203 | 
             
                    ).returns(T.nilable(T.attached_class))
         | 
| 119 204 | 
             
                  end
         | 
| 120 205 | 
             
                  def resolve_first(query, strict = nil)
         | 
| 121 | 
            -
                     | 
| 206 | 
            +
                    strict_loading = strict.nil? ? AppBase.config.db_strict_type_resolving : strict
         | 
| 207 | 
            +
             | 
| 208 | 
            +
                    resolve(query.limit(1), strict_loading).first
         | 
| 122 209 | 
             
                  end
         | 
| 123 210 | 
             
                end
         | 
| 124 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 | 
             
                #
         | 
| @@ -0,0 +1,90 @@ | |
| 1 | 
            +
            # typed: strict
         | 
| 2 | 
            +
            # frozen_string_literal: true
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Kirei
         | 
| 5 | 
            +
              module Helpers
         | 
| 6 | 
            +
                class << self
         | 
| 7 | 
            +
                  extend T::Sig
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  # Simplified version from Rails' ActiveSupport::Inflector#underscore
         | 
| 10 | 
            +
                  sig { params(string: String).returns(String) }
         | 
| 11 | 
            +
                  def underscore(string)
         | 
| 12 | 
            +
                    string.gsub!(/([A-Z])(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) do
         | 
| 13 | 
            +
                      T.must((::Regexp.last_match(1) || ::Regexp.last_match(2))) << "_"
         | 
| 14 | 
            +
                    end
         | 
| 15 | 
            +
                    string.tr!("-", "_")
         | 
| 16 | 
            +
                    string.downcase!
         | 
| 17 | 
            +
                    string
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  # Simplified version from Rails' ActiveSupport
         | 
| 21 | 
            +
                  sig { params(string: T.any(String, Symbol)).returns(T::Boolean) }
         | 
| 22 | 
            +
                  def blank?(string)
         | 
| 23 | 
            +
                    string.nil? || string.to_s.empty?
         | 
| 24 | 
            +
                  end
         | 
| 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 | 
            +
             | 
| 46 | 
            +
                  # Simplified version from Rails' ActiveSupport
         | 
| 47 | 
            +
                  sig do
         | 
| 48 | 
            +
                    params(
         | 
| 49 | 
            +
                      object: T.untyped, # could be anything due to recursive calls
         | 
| 50 | 
            +
                      block: Proc,
         | 
| 51 | 
            +
                    ).returns(T.untyped) # could be anything due to recursive calls
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
                  private def deep_transform_keys(object, &block)
         | 
| 54 | 
            +
                    case object
         | 
| 55 | 
            +
                    when Hash
         | 
| 56 | 
            +
                      object.each_with_object({}) do |(key, value), result|
         | 
| 57 | 
            +
                        result[yield(key)] = deep_transform_keys(value, &block)
         | 
| 58 | 
            +
                      end
         | 
| 59 | 
            +
                    when Array
         | 
| 60 | 
            +
                      object.map { |e| deep_transform_keys(e, &block) }
         | 
| 61 | 
            +
                    else
         | 
| 62 | 
            +
                      object
         | 
| 63 | 
            +
                    end
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  sig do
         | 
| 67 | 
            +
                    params(
         | 
| 68 | 
            +
                      object: T.untyped, # could be anything due to recursive calls
         | 
| 69 | 
            +
                      block: Proc,
         | 
| 70 | 
            +
                    ).returns(T.untyped) # could be anything due to recursive calls
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
                  private def deep_transform_keys!(object, &block)
         | 
| 73 | 
            +
                    case object
         | 
| 74 | 
            +
                    when Hash
         | 
| 75 | 
            +
                      # using `each_key` results in a `RuntimeError: can't add a new key into hash during iteration`
         | 
| 76 | 
            +
                      # which is, because the receiver here does not necessarily have a `Hash` type
         | 
| 77 | 
            +
                      object.keys.each do |key| # rubocop:disable Style/HashEachMethods
         | 
| 78 | 
            +
                        value = object.delete(key)
         | 
| 79 | 
            +
                        object[yield(key)] = deep_transform_keys!(value, &block)
         | 
| 80 | 
            +
                      end
         | 
| 81 | 
            +
                      object
         | 
| 82 | 
            +
                    when Array
         | 
| 83 | 
            +
                      object.map! { |e| deep_transform_keys!(e, &block) }
         | 
| 84 | 
            +
                    else
         | 
| 85 | 
            +
                      object
         | 
| 86 | 
            +
                    end
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
              end
         | 
| 90 | 
            +
            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.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 }
         | 
| @@ -98,7 +107,7 @@ module Kirei | |
| 98 107 | 
             
                      label = log_data.fetch(:label)
         | 
| 99 108 | 
             
                      meta = T.let(log_data.fetch(:meta), T::Hash[Symbol, T.untyped])
         | 
| 100 109 | 
             
                      meta[:"service.version"] ||= Kirei::AppBase.version
         | 
| 101 | 
            -
                      meta[:timestamp] ||= Time. | 
| 110 | 
            +
                      meta[:timestamp] ||= Time.now.utc.iso8601
         | 
| 102 111 | 
             
                      meta[:level] ||= level.to_s.upcase
         | 
| 103 112 | 
             
                      meta[:label] ||= label
         | 
| 104 113 |  | 
| @@ -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 | 
            -
                   | 
| 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 | 
            -
                    new_prefix =  | 
| 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)
         | 
| @@ -158,7 +175,9 @@ module Kirei | |
| 158 175 | 
             
                      if value.respond_to?(:serialize)
         | 
| 159 176 | 
             
                        serialized_value = value.serialize
         | 
| 160 177 | 
             
                        if serialized_value.is_a?(Hash)
         | 
| 161 | 
            -
                          result.merge!( | 
| 178 | 
            +
                          result.merge!(
         | 
| 179 | 
            +
                            flatten_hash_and_mask_sensitive_values(serialized_value.transform_keys(&:to_sym), new_prefix),
         | 
| 180 | 
            +
                          )
         | 
| 162 181 | 
             
                        else
         | 
| 163 182 | 
             
                          result[new_prefix] = serialized_value&.to_s
         | 
| 164 183 | 
             
                        end
         | 
    
        data/lib/kirei/version.rb
    CHANGED
    
    
    
        data/lib/kirei.rb
    CHANGED
    
    | @@ -1,13 +1,22 @@ | |
| 1 1 | 
             
            # typed: strict
         | 
| 2 2 | 
             
            # frozen_string_literal: true
         | 
| 3 3 |  | 
| 4 | 
            -
            puts "Booting Kirei..." # rubocop:disable all
         | 
| 5 | 
            -
             | 
| 6 4 | 
             
            require "boot"
         | 
| 7 5 |  | 
| 8 6 | 
             
            module Kirei
         | 
| 9 7 | 
             
              extend T::Sig
         | 
| 10 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 | 
            +
             | 
| 11 20 | 
             
              GEM_ROOT = T.let(
         | 
| 12 21 | 
             
                Gem::Specification.find_by_name("kirei").gem_dir,
         | 
| 13 22 | 
             
                String,
         | 
| @@ -33,4 +42,4 @@ end | |
| 33 42 |  | 
| 34 43 | 
             
            Kirei.configure(&:itself)
         | 
| 35 44 |  | 
| 36 | 
            -
             | 
| 45 | 
            +
            Kirei::Logger.logger.info("Kirei (#{Kirei::VERSION}) booted successfully!")
         | 
| @@ -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,29 +1,15 @@ | |
| 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 | 
            -
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            -
              name: activesupport
         | 
| 15 | 
            -
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            -
                requirements:
         | 
| 17 | 
            -
                - - "~>"
         | 
| 18 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            -
                    version: '6.0'
         | 
| 20 | 
            -
              type: :runtime
         | 
| 21 | 
            -
              prerelease: false
         | 
| 22 | 
            -
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            -
                requirements:
         | 
| 24 | 
            -
                - - "~>"
         | 
| 25 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            -
                    version: '6.0'
         | 
| 27 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 28 14 | 
             
              name: oj
         | 
| 29 15 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -80,20 +66,6 @@ dependencies: | |
| 80 66 | 
             
                - - "~>"
         | 
| 81 67 | 
             
                  - !ruby/object:Gem::Version
         | 
| 82 68 | 
             
                    version: '1.0'
         | 
| 83 | 
            -
            - !ruby/object:Gem::Dependency
         | 
| 84 | 
            -
              name: puma
         | 
| 85 | 
            -
              requirement: !ruby/object:Gem::Requirement
         | 
| 86 | 
            -
                requirements:
         | 
| 87 | 
            -
                - - "~>"
         | 
| 88 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 89 | 
            -
                    version: '6.0'
         | 
| 90 | 
            -
              type: :runtime
         | 
| 91 | 
            -
              prerelease: false
         | 
| 92 | 
            -
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 93 | 
            -
                requirements:
         | 
| 94 | 
            -
                - - "~>"
         | 
| 95 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 96 | 
            -
                    version: '6.0'
         | 
| 97 69 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 98 70 | 
             
              name: sinatra
         | 
| 99 71 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -165,8 +137,10 @@ dependencies: | |
| 165 137 | 
             
                  - !ruby/object:Gem::Version
         | 
| 166 138 | 
             
                    version: '1.0'
         | 
| 167 139 | 
             
            description: |
         | 
| 168 | 
            -
              Kirei | 
| 169 | 
            -
              It | 
| 140 | 
            +
              Kirei is a strictly typed Ruby micro/REST-framework for building scaleable and performant microservices.
         | 
| 141 | 
            +
              It is built from the ground up to be clean and easy to use.
         | 
| 142 | 
            +
              Kirei is based on Sequel as an ORM, Sorbet for typing, and Sinatra for routing.
         | 
| 143 | 
            +
              It strives to have zero magic and to be as explicit as possible.
         | 
| 170 144 | 
             
            email:
         | 
| 171 145 | 
             
            - lud@reinmiedl.com
         | 
| 172 146 | 
             
            - oss@dbl.works
         | 
| @@ -193,9 +167,9 @@ files: | |
| 193 167 | 
             
            - lib/kirei/base_controller.rb
         | 
| 194 168 | 
             
            - lib/kirei/base_model.rb
         | 
| 195 169 | 
             
            - lib/kirei/config.rb
         | 
| 170 | 
            +
            - lib/kirei/helpers.rb
         | 
| 196 171 | 
             
            - lib/kirei/logger.rb
         | 
| 197 172 | 
             
            - lib/kirei/version.rb
         | 
| 198 | 
            -
            - sorbet/rbi/dsl/active_support/callbacks.rbi
         | 
| 199 173 | 
             
            - sorbet/rbi/shims/base_model.rbi
         | 
| 200 174 | 
             
            homepage: https://github.com/swiknaba/kirei
         | 
| 201 175 | 
             
            licenses:
         | 
| @@ -222,5 +196,5 @@ rubygems_version: 3.5.3 | |
| 222 196 | 
             
            signing_key:
         | 
| 223 197 | 
             
            specification_version: 4
         | 
| 224 198 | 
             
            summary: Kirei is a strictly typed Ruby micro/REST-framework for building scaleable
         | 
| 225 | 
            -
              and performant  | 
| 199 | 
            +
              and performant microservices.
         | 
| 226 200 | 
             
            test_files: []
         | 
| @@ -1,22 +0,0 @@ | |
| 1 | 
            -
            # typed: true
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            # DO NOT EDIT MANUALLY
         | 
| 4 | 
            -
            # This is an autogenerated file for dynamic methods in `ActiveSupport::Callbacks`.
         | 
| 5 | 
            -
            # Please instead update this file by running `bin/tapioca dsl ActiveSupport::Callbacks`.
         | 
| 6 | 
            -
             | 
| 7 | 
            -
            module ActiveSupport::Callbacks
         | 
| 8 | 
            -
              include GeneratedInstanceMethods
         | 
| 9 | 
            -
             | 
| 10 | 
            -
              mixes_in_class_methods GeneratedClassMethods
         | 
| 11 | 
            -
             | 
| 12 | 
            -
              module GeneratedClassMethods
         | 
| 13 | 
            -
                def __callbacks; end
         | 
| 14 | 
            -
                def __callbacks=(value); end
         | 
| 15 | 
            -
                def __callbacks?; end
         | 
| 16 | 
            -
              end
         | 
| 17 | 
            -
             | 
| 18 | 
            -
              module GeneratedInstanceMethods
         | 
| 19 | 
            -
                def __callbacks; end
         | 
| 20 | 
            -
                def __callbacks?; end
         | 
| 21 | 
            -
              end
         | 
| 22 | 
            -
            end
         |