kirei 0.6.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +84 -0
- data/kirei.gemspec +2 -1
- data/lib/cli/commands/new_app/files/app.rb +3 -1
- data/lib/cli/commands/new_app/files/db_rake.rb +34 -16
- data/lib/cli/commands/start.rb +4 -0
- data/lib/kirei/config.rb +1 -1
- data/lib/kirei/controller.rb +33 -0
- data/lib/kirei/domain/entity.rb +22 -0
- data/lib/kirei/domain/value_object.rb +41 -0
- data/lib/kirei/routing/base.rb +2 -0
- data/lib/kirei/routing/router.rb +3 -0
- data/lib/kirei/services/array_comparison.rb +35 -0
- data/lib/kirei/version.rb +1 -1
- data/sorbet/rbi/shims/domain.rbi +16 -0
- metadata +21 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df4eacd42ab9ef8cc4b3474e4c58b5ccdc0dc7c7b3631fdf4640a5ea38febc3b
|
4
|
+
data.tar.gz: 3203e23caa1ffa4cc1352b3d87bc30df0639c4434cfa26ae8b5cb3133a8586d7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9bffc31e1489abec7e249e6f9328f0cd1337ee2a007bab192583de9b2d98c8bf2816ad743991f8c1ef073235da8fb072804a0a9d1af0248b28b3cd520bbfb8fe
|
7
|
+
data.tar.gz: e7ecfe6be3594b7ea70d12646d38a1b4e11e824453c63adfda37d96877791ca30fe2748098336cab13f25b774be92700065bbafeee84e648679242c2c1d47ad7
|
data/README.md
CHANGED
@@ -55,6 +55,10 @@ Find a test app in the [spec/test_app](spec/test_app) directory. It is a fully f
|
|
55
55
|
|
56
56
|
All models must inherit from `T::Struct` and include `Kirei::Model`. They must implement `id` which must hold the primary key of the table. The primary key must be named `id` and be of type `T.any(String, Integer)`.
|
57
57
|
|
58
|
+
Kirei models are immutable by convention - all properties are defined using `const` and updating a record returns a new instance rather than mutating the original. This immutability, combined with strict typing, makes them naturally suitable for both traditional data-centric applications and domain-driven design approaches.
|
59
|
+
|
60
|
+
In a domain-driven design, `Kirei::Model` serves as the persistence layer, while domain concepts are expressed through `Entity` and `ValueObject`. The domain layer might combine or transform data from multiple models to match the domain's understanding, keeping the internal data structure separate from the public interface.
|
61
|
+
|
58
62
|
```ruby
|
59
63
|
class User < T::Struct
|
60
64
|
extend T::Sig
|
@@ -99,6 +103,61 @@ first_user = User.resolve_first(query) # T.nilable(User)
|
|
99
103
|
first_user = User.from_hash(query.first.stringify_keys)
|
100
104
|
```
|
101
105
|
|
106
|
+
#### Domain Objects
|
107
|
+
|
108
|
+
Kirei provides support for Domain-Driven Design patterns through `Kirei::Domain::Entity` and `Kirei::Domain::ValueObject`. Here's how to use them:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
# An Entity is identified by its ID
|
112
|
+
class Flight < T::Struct
|
113
|
+
include Kirei::Domain::Entity
|
114
|
+
|
115
|
+
const :id, Integer
|
116
|
+
|
117
|
+
const :flight_number, String
|
118
|
+
const :departure_airport_id, Integer
|
119
|
+
const :arrival_airport_id, Integer
|
120
|
+
const :scheduled_departure_at, Time
|
121
|
+
const :status, String
|
122
|
+
|
123
|
+
sig { returns(T::Boolean) }
|
124
|
+
def can_board?
|
125
|
+
Time.now.utc <= scheduled_departure_at && status == 'on_time'
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# A Value Object is identified by its attributes
|
130
|
+
# I.e. two Coordinates with the same lat/long will be equal
|
131
|
+
# regardless of object identity
|
132
|
+
class Coordinates < T::Struct
|
133
|
+
include Kirei::Domain::ValueObject
|
134
|
+
|
135
|
+
const :latitude, Float
|
136
|
+
const :longitude, Float
|
137
|
+
|
138
|
+
sig { returns(String) }
|
139
|
+
def to_s
|
140
|
+
"#{latitude},#{longitude}"
|
141
|
+
end
|
142
|
+
|
143
|
+
sig { params(other_coords: Coordinates).returns(Float) }
|
144
|
+
def distance_to(other_coords)
|
145
|
+
# Implement e.g. Haversine here.
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Usage example
|
150
|
+
coords = Coordinates.new(latitude: 37.6188, longitude: -122.3750)
|
151
|
+
coords2 = Coordinates.new(latitude: 37.6188, longitude: -122.3750)
|
152
|
+
|
153
|
+
coords == coords2 # true
|
154
|
+
|
155
|
+
flight = Flight.new(id: 123, flight_number: 'UA123', status: 'on_time', scheduled_departure_at: Time.now.utc)
|
156
|
+
flight2 = Flight.new(id: 123, flight_number: 'UA123', status: 'delayed', scheduled_departure_at: Time.now.utc)
|
157
|
+
|
158
|
+
flight == flight2 # true - same entity even with different status
|
159
|
+
```
|
160
|
+
|
102
161
|
#### Database Migrations
|
103
162
|
|
104
163
|
Read the [Sequel Migrations](https://github.com/jeremyevans/sequel/blob/5.78.0/doc/schema_modification.rdoc) documentation for detailed information.
|
@@ -226,6 +285,31 @@ module Airports
|
|
226
285
|
end
|
227
286
|
```
|
228
287
|
|
288
|
+
### Goes well with these gems
|
289
|
+
|
290
|
+
* [pagy](https://github.com/ddnexus/pagy) for pagination
|
291
|
+
* [argon2](https://github.com/technion/ruby-argon2) for password hashing
|
292
|
+
* [rack-session](https://github.com/rack/rack-session) for session management
|
293
|
+
|
294
|
+
### Middlewares
|
295
|
+
|
296
|
+
If you place custom middlewares under `config/middleware/*.rb`, they will be automatically loaded, see [app.rb](spec/test_app/app.rb) "load configs".
|
297
|
+
|
298
|
+
However, you still need to `use` them in the `config.ru` file, see the generated [config.ru](spec/test_app/config.ru) file, this gives you full control over the order in which middlewares are executed:
|
299
|
+
|
300
|
+
```ruby
|
301
|
+
# Load middlewares here
|
302
|
+
use(Rack::Reloader, 0) if TestApp.environment == "development"
|
303
|
+
|
304
|
+
# add more custom middlewares here, e.g.
|
305
|
+
use(Middleware::Example)
|
306
|
+
|
307
|
+
# Launch the app
|
308
|
+
run(TestApp.new)
|
309
|
+
```
|
310
|
+
|
311
|
+
Middleware provided by a gem like [rack-session](https://github.com/rack/rack-session) must be added to the `config.ru` file as well if you want to use it.
|
312
|
+
|
229
313
|
## Contributions
|
230
314
|
|
231
315
|
We welcome contributions from the community. Before starting work on a major feature, please get in touch with us either via email or by opening an issue on GitHub. "Major feature" means anything that changes user-facing features or significant changes to the codebase itself.
|
data/kirei.gemspec
CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.description = <<~TXT
|
18
18
|
Kirei is a Ruby micro/REST-framework for building scalable and performant microservices.
|
19
19
|
It is built from the ground up to be clean and easy to use.
|
20
|
-
It is a Rack app, and uses Sorbet for typing, Sequel as an ORM, Zeitwerk for autoloading
|
20
|
+
It is a Rack app, and uses Sorbet for typing, Sequel as an ORM, and Zeitwerk for autoloading.
|
21
21
|
It strives to have zero magic and to be as explicit as possible.
|
22
22
|
TXT
|
23
23
|
spec.homepage = "https://github.com/swiknaba/kirei"
|
@@ -44,6 +44,7 @@ Gem::Specification.new do |spec|
|
|
44
44
|
spec.require_paths = ["lib"]
|
45
45
|
|
46
46
|
# Utilities
|
47
|
+
spec.add_dependency "logger", "~> 1.5" # for Ruby 3.5+
|
47
48
|
spec.add_dependency "oj", "~> 3.0"
|
48
49
|
spec.add_dependency "sorbet-runtime", "~> 0.5"
|
49
50
|
spec.add_dependency "statsd-instrument", "~> 3.0"
|
@@ -43,7 +43,9 @@ module Cli
|
|
43
43
|
loader.setup
|
44
44
|
|
45
45
|
# Fifth: load configs
|
46
|
-
Dir[File.join(__dir__, "config", "*.rb")].each
|
46
|
+
Dir[File.join(__dir__, "config", "**", "*.rb")].each do |cnf|
|
47
|
+
require(cnf) unless cnf.split("/").include?("initializers")
|
48
|
+
end
|
47
49
|
|
48
50
|
class #{app_name} < Kirei::App
|
49
51
|
# Kirei configuration
|
@@ -166,33 +166,51 @@ module Cli
|
|
166
166
|
db = #{app_name}.raw_db_connection
|
167
167
|
model_file_name = args[:model_file_name]&.to_s
|
168
168
|
|
169
|
-
|
169
|
+
app_root_dir = TestApp.root
|
170
|
+
app_dir = File.join(TestApp.root, "app")
|
170
171
|
|
171
|
-
Dir.glob("app
|
172
|
+
Dir.glob("app/**/*.rb").each do |model_file|
|
172
173
|
next if !model_file_name.nil? && model_file == model_file_name
|
173
174
|
|
174
|
-
model_path = File.expand_path(model_file,
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
175
|
+
model_path = File.expand_path(model_file, app_root_dir)
|
176
|
+
loader = Zeitwerk::Registry.loaders.find { |l| l.tag == "app" }
|
177
|
+
|
178
|
+
full_path = File.expand_path(model_file, app_root_dir)
|
179
|
+
klass_constant_name = loader.inflector.camelize(File.basename(model_file, ".rb"), full_path)
|
180
|
+
|
181
|
+
#
|
182
|
+
# root namespaces in Zeitwerk are flattend, e.g. if "app/models" is a root namespace
|
183
|
+
# then a file "app/models/airport.rb" is loaded as "::Airport".
|
184
|
+
# if it weren't a root namespace, it would be "::Models::Airport".
|
185
|
+
#
|
186
|
+
root_dir_namespaces = loader.dirs.filter_map { |dir| dir == app_dir ? nil : Pathname.new(dir).relative_path_from(Pathname.new(app_dir)).to_s }
|
187
|
+
relative_path = Pathname.new(full_path).relative_path_from(Pathname.new(app_dir)).to_s
|
188
|
+
root_dir_of_model = root_dir_namespaces.find { |root_dir| relative_path.start_with?(root_dir) }
|
189
|
+
relative_path.sub!("\#{root_dir_of_model}/", "") unless root_dir_of_model.nil? || root_dir_of_model.empty?
|
190
|
+
|
191
|
+
namespace_parts = relative_path.split("/")
|
192
|
+
namespace_parts.pop
|
193
|
+
namespace_parts.map! { |part| loader.inflector.camelize(part, full_path) }
|
194
|
+
|
195
|
+
constant_name = "\#{namespace_parts.join('::')}::\#{klass_constant_name}"
|
196
|
+
|
197
|
+
model_klass = Object.const_get(constant_name)
|
198
|
+
next unless model_klass.respond_to?(:table_name)
|
179
199
|
|
180
200
|
table_name = model_klass.table_name
|
181
201
|
schema = db.schema(table_name)
|
182
202
|
|
183
203
|
schema_comments = format_schema_comments(table_name, schema)
|
204
|
+
file_content = File.read(model_path)
|
184
205
|
|
185
|
-
|
186
|
-
|
187
|
-
# Remove existing schema info comments if present
|
188
|
-
updated_contents = file_contents.sub(/# == Schema Info\\n(.*?)(\\n#\\n)?\\n(?=\\s*(?:class|module))/m, "")
|
206
|
+
file_content_without_schema_info = file_content.sub(/# == Schema Info\\n(.*?)(\\n#\\n)?\\n(?=\\s*(?:class|module))/m, "")
|
189
207
|
|
190
208
|
# Insert the new schema comments before the module/class definition
|
191
|
-
|
192
|
-
first_module_or_class =
|
193
|
-
|
209
|
+
first_module = namespace_parts.first
|
210
|
+
first_module_or_class = first_module.nil? ? "class \#{klass_constant_name}" : "module \#{first_module}"
|
211
|
+
modified_content = file_content_without_schema_info.sub(/(A|\\n)(\#{first_module_or_class})/m, "\\\\1\#{schema_comments}\\n\\n\\\\2")
|
194
212
|
|
195
|
-
File.write(model_path,
|
213
|
+
File.write(model_path, modified_content)
|
196
214
|
end
|
197
215
|
end
|
198
216
|
end
|
@@ -215,7 +233,7 @@ module Cli
|
|
215
233
|
type ||= info[:db_type]
|
216
234
|
null = info[:allow_null] ? 'null' : 'not null'
|
217
235
|
primary_key = info[:primary_key] ? ', primary key' : ''
|
218
|
-
lines << "# \#{name.to_s.ljust(20)}:\#{type}
|
236
|
+
lines << "# \#{name.to_s.ljust(20)}:\#{type.to_s.ljust(20)}\#{null}\#{primary_key}"
|
219
237
|
end
|
220
238
|
lines.join("\\n") + "\\n#"
|
221
239
|
end
|
data/lib/cli/commands/start.rb
CHANGED
@@ -12,6 +12,10 @@ module Cli
|
|
12
12
|
app_name = app_name.gsub(/[-\s]/, "_")
|
13
13
|
app_name = app_name.split("_").map(&:capitalize).join if app_name.include?("_")
|
14
14
|
NewApp::Execute.call(app_name: app_name)
|
15
|
+
when "test"
|
16
|
+
# for internal testing
|
17
|
+
app_name = args[1] || "TestApp"
|
18
|
+
# test single services here
|
15
19
|
else
|
16
20
|
Kirei::Logging::Logger.logger.info("Unknown command")
|
17
21
|
end
|
data/lib/kirei/config.rb
CHANGED
@@ -30,7 +30,7 @@ module Kirei
|
|
30
30
|
# must use "pg_json" to parse jsonb columns to hashes
|
31
31
|
#
|
32
32
|
# Source: https://github.com/jeremyevans/sequel/blob/5.75.0/lib/sequel/extensions/pg_json.rb
|
33
|
-
prop :db_extensions, T::Array[Symbol], default: %i[pg_json pg_array]
|
33
|
+
prop :db_extensions, T::Array[Symbol], default: %i[pg_json pg_array] # add "fiber_concurrency" by default, too?
|
34
34
|
prop :db_url, T.nilable(String)
|
35
35
|
# Extra or unknown properties present in the Hash do not raise exceptions at runtime
|
36
36
|
# unless the optional strict argument to from_hash is passed
|
data/lib/kirei/controller.rb
CHANGED
@@ -40,5 +40,38 @@ module Kirei
|
|
40
40
|
@after_hooks ||= T.let(Set.new, Routing::NilableHooksType)
|
41
41
|
@after_hooks.add(block) if block
|
42
42
|
end
|
43
|
+
|
44
|
+
sig { returns(String) }
|
45
|
+
def req_host
|
46
|
+
env.fetch("HTTP_HOST")
|
47
|
+
end
|
48
|
+
|
49
|
+
sig { returns(String) }
|
50
|
+
def req_domain
|
51
|
+
T.must(req_host.split(":").first).split(".").last(2).join(".")
|
52
|
+
end
|
53
|
+
|
54
|
+
sig { returns(T.nilable(String)) }
|
55
|
+
def req_subdomain
|
56
|
+
parts = T.must(req_host.split(":").first).split(".")
|
57
|
+
return if parts.size <= 2
|
58
|
+
|
59
|
+
T.must(parts[0..-3]).join(".")
|
60
|
+
end
|
61
|
+
|
62
|
+
sig { returns(Integer) }
|
63
|
+
def req_port
|
64
|
+
env.fetch("SERVER_PORT")&.to_i
|
65
|
+
end
|
66
|
+
|
67
|
+
sig { returns(T::Boolean) }
|
68
|
+
def req_ssl?
|
69
|
+
env.fetch("HTTPS", env.fetch("rack.url_scheme", "http")) == "https"
|
70
|
+
end
|
71
|
+
|
72
|
+
sig { returns(T::Hash[String, T.untyped]) }
|
73
|
+
private def env
|
74
|
+
T.cast(@router.current_env, T::Hash[String, T.untyped])
|
75
|
+
end
|
43
76
|
end
|
44
77
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Kirei
|
5
|
+
module Domain
|
6
|
+
module Entity
|
7
|
+
extend T::Sig
|
8
|
+
extend T::Helpers
|
9
|
+
|
10
|
+
sig { returns(T.class_of(T::Struct)) }
|
11
|
+
def class; super; end # rubocop:disable all
|
12
|
+
|
13
|
+
sig { params(other: T.nilable(Kirei::Domain::Entity)).returns(T::Boolean) }
|
14
|
+
def ==(other)
|
15
|
+
return false unless other.is_a?(Kirei::Domain::Entity)
|
16
|
+
return false unless instance_of?(other.class)
|
17
|
+
|
18
|
+
id == other.id
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Kirei
|
5
|
+
module Domain
|
6
|
+
module ValueObject
|
7
|
+
extend T::Sig
|
8
|
+
extend T::Helpers
|
9
|
+
|
10
|
+
sig { returns(T.class_of(T::Struct)) }
|
11
|
+
def class; super; end # rubocop:disable all
|
12
|
+
|
13
|
+
sig { params(other: T.untyped).returns(T::Boolean) }
|
14
|
+
def ==(other)
|
15
|
+
return false unless instance_of?(other.class)
|
16
|
+
|
17
|
+
instance_variables.all? do |var|
|
18
|
+
instance_variable_get(var) == other.instance_variable_get(var)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
sig do
|
23
|
+
params(
|
24
|
+
other: T.untyped,
|
25
|
+
array_mode: Kirei::Services::ArrayComparison::Mode,
|
26
|
+
).returns(T::Boolean)
|
27
|
+
end
|
28
|
+
def equal_with_array_mode?(other, array_mode: Kirei::Services::ArrayComparison::Mode::STRICT)
|
29
|
+
return false unless instance_of?(other.class)
|
30
|
+
|
31
|
+
instance_variables.all? do |var|
|
32
|
+
one = instance_variable_get(var)
|
33
|
+
two = other.instance_variable_get(var)
|
34
|
+
next one == two unless one.is_a?(Array)
|
35
|
+
|
36
|
+
Kirei::Services::ArrayComparison.call(one, two, mode: array_mode)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/kirei/routing/base.rb
CHANGED
data/lib/kirei/routing/router.rb
CHANGED
@@ -0,0 +1,35 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Kirei
|
5
|
+
module Services
|
6
|
+
class ArrayComparison
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
class Mode < T::Enum
|
10
|
+
enums do
|
11
|
+
STRICT = new("strict")
|
12
|
+
IGNORE_ORDER = new("ignore_order")
|
13
|
+
IGNORE_ORDER_AND_DUPLICATES = new("ignore_order_and_duplicates")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
sig do
|
18
|
+
params(
|
19
|
+
array_one: T::Array[T.untyped],
|
20
|
+
array_two: T::Array[T.untyped],
|
21
|
+
mode: Mode,
|
22
|
+
).returns(T::Boolean)
|
23
|
+
end
|
24
|
+
def self.call(array_one, array_two, mode: Mode::STRICT)
|
25
|
+
case mode
|
26
|
+
when Mode::STRICT then array_one == array_two
|
27
|
+
when Mode::IGNORE_ORDER then array_one.sort == array_two.sort
|
28
|
+
when Mode::IGNORE_ORDER_AND_DUPLICATES then array_one.to_set == array_two.to_set
|
29
|
+
else
|
30
|
+
T.absurd(mode)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/kirei/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kirei
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.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: 2025-
|
11
|
+
date: 2025-03-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: logger
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: oj
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -139,7 +153,7 @@ dependencies:
|
|
139
153
|
description: |
|
140
154
|
Kirei is a Ruby micro/REST-framework for building scalable and performant microservices.
|
141
155
|
It is built from the ground up to be clean and easy to use.
|
142
|
-
It is a Rack app, and uses Sorbet for typing, Sequel as an ORM, Zeitwerk for autoloading
|
156
|
+
It is a Rack app, and uses Sorbet for typing, Sequel as an ORM, and Zeitwerk for autoloading.
|
143
157
|
It strives to have zero magic and to be as explicit as possible.
|
144
158
|
email:
|
145
159
|
- lud@reinmiedl.com
|
@@ -169,6 +183,8 @@ files:
|
|
169
183
|
- lib/kirei/app.rb
|
170
184
|
- lib/kirei/config.rb
|
171
185
|
- lib/kirei/controller.rb
|
186
|
+
- lib/kirei/domain/entity.rb
|
187
|
+
- lib/kirei/domain/value_object.rb
|
172
188
|
- lib/kirei/errors/json_api_error.rb
|
173
189
|
- lib/kirei/errors/json_api_error_source.rb
|
174
190
|
- lib/kirei/helpers.rb
|
@@ -186,11 +202,13 @@ files:
|
|
186
202
|
- lib/kirei/routing/route.rb
|
187
203
|
- lib/kirei/routing/router.rb
|
188
204
|
- lib/kirei/routing/verb.rb
|
205
|
+
- lib/kirei/services/array_comparison.rb
|
189
206
|
- lib/kirei/services/result.rb
|
190
207
|
- lib/kirei/services/runner.rb
|
191
208
|
- lib/kirei/version.rb
|
192
209
|
- lib/tasks/routes.rake
|
193
210
|
- sorbet/rbi/shims/base_model.rbi
|
211
|
+
- sorbet/rbi/shims/domain.rbi
|
194
212
|
- sorbet/rbi/shims/ruby.rbi
|
195
213
|
homepage: https://github.com/swiknaba/kirei
|
196
214
|
licenses:
|