oriole 0.1.0.alpha.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 +7 -0
- data/.gitignore +56 -0
- data/.rubocop.yml +32 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +1 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +121 -0
- data/LICENSE +21 -0
- data/README.md +84 -0
- data/Rakefile +12 -0
- data/bench/bench.rb +35 -0
- data/bench/execute.rb +66 -0
- data/bench/schemas/bluejay.rb +149 -0
- data/bench/schemas/models.rb +216 -0
- data/bench/schemas/oriole.rb +144 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/bin/style +2 -0
- data/bin/tapioca +27 -0
- data/bin/typecheck +2 -0
- data/lib/oriole/builtin_scalar_definition.rb +53 -0
- data/lib/oriole/definition/abstract/definition.rb +21 -0
- data/lib/oriole/definition/abstract/field_definition.rb +22 -0
- data/lib/oriole/definition/abstract/object_type_definition.rb +21 -0
- data/lib/oriole/definition/abstract/schema_definition.rb +18 -0
- data/lib/oriole/definition/abstract/visibility.rb +18 -0
- data/lib/oriole/definition/base_output_type.rb +13 -0
- data/lib/oriole/definition/field_definition.rb +27 -0
- data/lib/oriole/definition/object_type.rb +40 -0
- data/lib/oriole/definition/output_type.rb +79 -0
- data/lib/oriole/definition/schema.rb +45 -0
- data/lib/oriole/definition/visibility_scoped/object_type_definition.rb +62 -0
- data/lib/oriole/definition/visibility_scoped/schema_definition.rb +26 -0
- data/lib/oriole/execution/coerce_result.rb +16 -0
- data/lib/oriole/execution/engine.rb +207 -0
- data/lib/oriole/version.rb +6 -0
- data/lib/oriole.rb +14 -0
- data/oriole.gemspec +38 -0
- data/rakelib/bench.rake +69 -0
- data/sorbet/config +5 -0
- data/sorbet/rbi/annotations/rainbow.rbi +269 -0
- data/sorbet/rbi/gems/ast@2.4.2.rbi +584 -0
- data/sorbet/rbi/gems/base64@0.1.1.rbi +172 -0
- data/sorbet/rbi/gems/bluejay@0.1.0.alpha.2-a45c1b3b47aa4524f94bbe03db5ff1ae792afd09.rbi +1206 -0
- data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +1083 -0
- data/sorbet/rbi/gems/erubi@1.12.0.rbi +145 -0
- data/sorbet/rbi/gems/json@2.6.3.rbi +1533 -0
- data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +14237 -0
- data/sorbet/rbi/gems/minitest@5.19.0.rbi +1491 -0
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
- data/sorbet/rbi/gems/parallel@1.23.0.rbi +273 -0
- data/sorbet/rbi/gems/parser@3.2.2.3.rbi +7253 -0
- data/sorbet/rbi/gems/prettier_print@1.2.1.rbi +951 -0
- data/sorbet/rbi/gems/racc@1.7.1.rbi +161 -0
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +402 -0
- data/sorbet/rbi/gems/rake@13.0.6.rbi +3024 -0
- data/sorbet/rbi/gems/rbi@0.0.17.rbi +2972 -0
- data/sorbet/rbi/gems/regexp_parser@2.8.1.rbi +3749 -0
- data/sorbet/rbi/gems/rexml@3.2.6.rbi +4781 -0
- data/sorbet/rbi/gems/rubocop-ast@1.29.0.rbi +7006 -0
- data/sorbet/rbi/gems/rubocop-minitest@0.27.0.rbi +2371 -0
- data/sorbet/rbi/gems/rubocop-rake@0.6.0.rbi +328 -0
- data/sorbet/rbi/gems/rubocop-shopify@2.14.0.rbi +8 -0
- data/sorbet/rbi/gems/rubocop-sorbet@0.6.11.rbi +975 -0
- data/sorbet/rbi/gems/rubocop@1.56.1.rbi +56525 -0
- data/sorbet/rbi/gems/ruby-lsp@0.4.5.rbi +11 -0
- data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1317 -0
- data/sorbet/rbi/gems/spoom@1.2.3.rbi +3203 -0
- data/sorbet/rbi/gems/syntax_tree@6.1.1.rbi +22855 -0
- data/sorbet/rbi/gems/tapioca@0.11.8.rbi +3349 -0
- data/sorbet/rbi/gems/thor@1.2.2.rbi +3965 -0
- data/sorbet/rbi/gems/tinygql@0.1.4.rbi +2363 -0
- data/sorbet/rbi/gems/unicode-display_width@2.4.2.rbi +65 -0
- data/sorbet/rbi/gems/unparser@0.6.8.rbi +4525 -0
- data/sorbet/rbi/gems/yard-sorbet@0.8.1.rbi +428 -0
- data/sorbet/rbi/gems/yard@0.9.34.rbi +18219 -0
- data/sorbet/rbi/gems/zeitwerk@2.6.11.rbi +999 -0
- data/sorbet/tapioca/config.yml +13 -0
- data/sorbet/tapioca/require.rb +5 -0
- metadata +208 -0
@@ -0,0 +1,79 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Oriole
|
5
|
+
module Definition
|
6
|
+
module OutputType
|
7
|
+
extend(T::Sig)
|
8
|
+
extend(T::Helpers)
|
9
|
+
|
10
|
+
class << self
|
11
|
+
extend(T::Sig)
|
12
|
+
|
13
|
+
sig { params(inner: BaseOutputType).returns(OutputType) }
|
14
|
+
def named(inner) = Named.new(required: false, inner:)
|
15
|
+
|
16
|
+
sig { params(inner: BaseOutputType).returns(OutputType) }
|
17
|
+
def named!(inner) = Named.new(required: true, inner:)
|
18
|
+
|
19
|
+
sig { params(inner: OutputType).returns(OutputType) }
|
20
|
+
def list(inner) = List.new(required: false, inner:)
|
21
|
+
|
22
|
+
sig { params(inner: OutputType).returns(OutputType) }
|
23
|
+
def list!(inner) = List.new(required: true, inner:)
|
24
|
+
end
|
25
|
+
|
26
|
+
sealed!
|
27
|
+
abstract!
|
28
|
+
|
29
|
+
sig { abstract.returns(T::Boolean) }
|
30
|
+
def required?; end
|
31
|
+
|
32
|
+
sig { abstract.returns(T::Boolean) }
|
33
|
+
def list?; end
|
34
|
+
|
35
|
+
sig { abstract.returns(BaseOutputType) }
|
36
|
+
def base; end
|
37
|
+
|
38
|
+
sig { abstract.params(base: BaseOutputType).returns(T.self_type) }
|
39
|
+
def with_base(base); end
|
40
|
+
|
41
|
+
class List < T::Struct
|
42
|
+
extend(T::Sig)
|
43
|
+
include(OutputType)
|
44
|
+
|
45
|
+
const(:required, T::Boolean)
|
46
|
+
const(:inner, OutputType)
|
47
|
+
|
48
|
+
alias_method(:required?, :required)
|
49
|
+
|
50
|
+
sig { override.returns(T::Boolean) }
|
51
|
+
def list? = true
|
52
|
+
|
53
|
+
sig { override.returns(BaseOutputType) }
|
54
|
+
def base = inner.base
|
55
|
+
|
56
|
+
sig { override.params(base: BaseOutputType).returns(T.self_type) }
|
57
|
+
def with_base(base) = List.new(required:, inner: inner.with_base(base))
|
58
|
+
end
|
59
|
+
|
60
|
+
class Named < T::Struct
|
61
|
+
extend(T::Sig)
|
62
|
+
include(OutputType)
|
63
|
+
|
64
|
+
const(:required, T::Boolean)
|
65
|
+
const(:inner, BaseOutputType)
|
66
|
+
|
67
|
+
alias_method(:required?, :required)
|
68
|
+
|
69
|
+
sig { override.returns(T::Boolean) }
|
70
|
+
def list? = false
|
71
|
+
|
72
|
+
alias_method(:base, :inner)
|
73
|
+
|
74
|
+
sig { override.params(base: BaseOutputType).returns(T.self_type) }
|
75
|
+
def with_base(base) = Named.new(required:, inner: base)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Oriole
|
5
|
+
module Definition
|
6
|
+
class Schema
|
7
|
+
class << self
|
8
|
+
extend(T::Sig)
|
9
|
+
extend(T::Helpers)
|
10
|
+
include(Abstract::SchemaDefinition)
|
11
|
+
|
12
|
+
abstract!
|
13
|
+
|
14
|
+
sig { abstract.returns(T.class_of(ObjectType)) }
|
15
|
+
def query; end
|
16
|
+
|
17
|
+
sig do
|
18
|
+
params(
|
19
|
+
query: String,
|
20
|
+
context: Object,
|
21
|
+
root_value: Object,
|
22
|
+
operation_name: T.nilable(String),
|
23
|
+
).returns(Object)
|
24
|
+
end
|
25
|
+
def execute(
|
26
|
+
query:,
|
27
|
+
context:,
|
28
|
+
root_value:,
|
29
|
+
operation_name: nil
|
30
|
+
)
|
31
|
+
Execution::Engine.execute(
|
32
|
+
schema_definition: VisibilityScoped::SchemaDefinition.new(
|
33
|
+
definition: self,
|
34
|
+
context:,
|
35
|
+
),
|
36
|
+
query:,
|
37
|
+
context:,
|
38
|
+
root_value:,
|
39
|
+
operation_name:,
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Oriole
|
5
|
+
module Definition
|
6
|
+
module VisibilityScoped
|
7
|
+
class ObjectTypeDefinition
|
8
|
+
extend(T::Sig)
|
9
|
+
include(Abstract::ObjectTypeDefinition)
|
10
|
+
|
11
|
+
sig { params(definition: T.class_of(ObjectType), context: Object).void }
|
12
|
+
def initialize(definition:, context:)
|
13
|
+
@definition = definition
|
14
|
+
@context = context
|
15
|
+
@field_definitions = T.let(nil, T.nilable(T::Array[FieldDefinition]))
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
extend(T::Sig)
|
20
|
+
|
21
|
+
sig { params(definition: T.class_of(ObjectType), context: Object).returns(T.nilable(T.attached_class)) }
|
22
|
+
def build(definition:, context:)
|
23
|
+
if definition.visible?(context:)
|
24
|
+
new(definition: definition, context: context)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
sig { override.returns(String) }
|
30
|
+
def graphql_name = @definition.graphql_name
|
31
|
+
|
32
|
+
sig { override.returns(T.nilable(String)) }
|
33
|
+
def description = @definition.description
|
34
|
+
|
35
|
+
sig { override.returns(T::Array[FieldDefinition]) }
|
36
|
+
def field_definitions
|
37
|
+
@field_definitions ||= @definition.field_definitions.filter_map do |field_definition|
|
38
|
+
next nil unless field_definition.visible?(context: @context)
|
39
|
+
|
40
|
+
base_type = field_definition.type.base
|
41
|
+
|
42
|
+
scoped_base_type = if base_type.is_a?(Class) && base_type < ObjectType
|
43
|
+
ObjectTypeDefinition.build(definition: base_type, context: @context)
|
44
|
+
else
|
45
|
+
base_type
|
46
|
+
end
|
47
|
+
|
48
|
+
if scoped_base_type
|
49
|
+
FieldDefinition.new(
|
50
|
+
graphql_name: field_definition.graphql_name,
|
51
|
+
description: field_definition.description,
|
52
|
+
type: field_definition.type.with_base(scoped_base_type),
|
53
|
+
visibility: field_definition.visibility,
|
54
|
+
method_name: field_definition.method_name,
|
55
|
+
)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Oriole
|
5
|
+
module Definition
|
6
|
+
module VisibilityScoped
|
7
|
+
class SchemaDefinition
|
8
|
+
extend(T::Sig)
|
9
|
+
include(Abstract::SchemaDefinition)
|
10
|
+
|
11
|
+
sig { params(definition: T.class_of(Schema), context: Object).void }
|
12
|
+
def initialize(definition:, context:)
|
13
|
+
@definition = definition
|
14
|
+
@context = context
|
15
|
+
@query = T.let(
|
16
|
+
ObjectTypeDefinition.new(definition: @definition.query, context: @context),
|
17
|
+
ObjectTypeDefinition,
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { override.returns(ObjectTypeDefinition) }
|
22
|
+
attr_reader(:query)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Oriole
|
5
|
+
module Execution
|
6
|
+
module CoerceResult
|
7
|
+
extend(T::Sig)
|
8
|
+
extend(T::Helpers)
|
9
|
+
|
10
|
+
abstract!
|
11
|
+
|
12
|
+
sig { abstract.params(result: Object).returns(Object) }
|
13
|
+
def coerce_result(result:); end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "tinygql"
|
5
|
+
require "set"
|
6
|
+
|
7
|
+
module Oriole
|
8
|
+
module Execution
|
9
|
+
class Engine
|
10
|
+
extend(T::Sig)
|
11
|
+
|
12
|
+
sig do
|
13
|
+
params(
|
14
|
+
schema_definition: Definition::Abstract::SchemaDefinition,
|
15
|
+
document: TinyGQL::Nodes::Document,
|
16
|
+
context: Object,
|
17
|
+
).void
|
18
|
+
end
|
19
|
+
def initialize(schema_definition:, document:, context:)
|
20
|
+
@schema_definition = schema_definition
|
21
|
+
@document = document
|
22
|
+
@context = context
|
23
|
+
@collect_fields_cache = T.let(
|
24
|
+
{},
|
25
|
+
T::Hash[Integer, T::Hash[String, T::Array[TinyGQL::Nodes::Field]]],
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
private_class_method(:new)
|
30
|
+
|
31
|
+
class << self
|
32
|
+
extend(T::Sig)
|
33
|
+
|
34
|
+
sig do
|
35
|
+
params(
|
36
|
+
schema_definition: Definition::Abstract::SchemaDefinition,
|
37
|
+
query: String,
|
38
|
+
context: Object,
|
39
|
+
operation_name: T.nilable(String),
|
40
|
+
root_value: Object,
|
41
|
+
).returns(Object)
|
42
|
+
end
|
43
|
+
def execute(schema_definition:, query:, context:, operation_name:, root_value:)
|
44
|
+
document = TinyGQL.parse(query)
|
45
|
+
|
46
|
+
new(schema_definition:, document:, context:).execute(operation_name:, root_value:)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
sig { params(operation_name: T.nilable(String), root_value: Object).returns(Object) }
|
51
|
+
def execute(operation_name:, root_value:)
|
52
|
+
operation_definition = if operation_name
|
53
|
+
@document.definitions.find do |definition|
|
54
|
+
definition.is_a?(TinyGQL::Nodes::OperationDefinition) && definition.name == operation_name
|
55
|
+
end || raise("Cannot find operation definition with name #{operation_name}")
|
56
|
+
else
|
57
|
+
operation_definitions = @document.definitions.select do |definition|
|
58
|
+
definition.is_a?(TinyGQL::Nodes::OperationDefinition)
|
59
|
+
end
|
60
|
+
if operation_definitions.one?
|
61
|
+
operation_definitions.first
|
62
|
+
else
|
63
|
+
raise "Cannot use anonymous operation with multiple operation definitions"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
execute_operation(operation_definition: operation_definition, initial_value: root_value)
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
sig { params(operation_definition: TinyGQL::Nodes::OperationDefinition, initial_value: Object).returns(Object) }
|
73
|
+
def execute_operation(operation_definition:, initial_value:)
|
74
|
+
case operation_definition.type
|
75
|
+
when "query", nil
|
76
|
+
execute_selection_set(
|
77
|
+
selection_set: operation_definition.selection_set,
|
78
|
+
object_type: @schema_definition.query,
|
79
|
+
object_value: initial_value,
|
80
|
+
)
|
81
|
+
else
|
82
|
+
raise(NotImplementedError)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
sig do
|
87
|
+
params(
|
88
|
+
selection_set: T::Array[TinyGQL::Nodes::Node],
|
89
|
+
object_type: Definition::Abstract::ObjectTypeDefinition,
|
90
|
+
object_value: Object,
|
91
|
+
).returns(Object)
|
92
|
+
end
|
93
|
+
def execute_selection_set(selection_set:, object_type:, object_value:)
|
94
|
+
grouped_fields = collect_fields(object_type:, selection_set:, visited_fragments: Set.new)
|
95
|
+
|
96
|
+
result_map = {}
|
97
|
+
|
98
|
+
has_null_for_required = T.let(false, T::Boolean)
|
99
|
+
|
100
|
+
grouped_fields.each do |_response_key, fields|
|
101
|
+
field_name = T.must(fields.first).name
|
102
|
+
field_definition = object_type.field_definitions.find do |definition|
|
103
|
+
definition.graphql_name == field_name
|
104
|
+
end || raise("Cannot find field definition for #{field_name}")
|
105
|
+
|
106
|
+
field_value = execute_field(
|
107
|
+
object_type:,
|
108
|
+
field_definition:,
|
109
|
+
object_value:,
|
110
|
+
fields:,
|
111
|
+
)
|
112
|
+
|
113
|
+
result_map[field_name] = field_value
|
114
|
+
|
115
|
+
if field_definition.type.required? && field_value.nil?
|
116
|
+
has_null_for_required = true
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
result_map
|
121
|
+
end
|
122
|
+
|
123
|
+
sig do
|
124
|
+
params(
|
125
|
+
object_type: Definition::Abstract::ObjectTypeDefinition,
|
126
|
+
selection_set: T::Array[TinyGQL::Nodes::Node],
|
127
|
+
visited_fragments: T::Set[String],
|
128
|
+
).returns(T::Hash[String, T::Array[TinyGQL::Nodes::Field]])
|
129
|
+
end
|
130
|
+
def collect_fields(object_type:, selection_set:, visited_fragments:)
|
131
|
+
if (cached_value = @collect_fields_cache[selection_set.object_id])
|
132
|
+
return cached_value
|
133
|
+
end
|
134
|
+
|
135
|
+
grouped_fields = {}
|
136
|
+
|
137
|
+
selection_set.each do |selection|
|
138
|
+
# TODO: handle include/skip directives
|
139
|
+
|
140
|
+
case selection
|
141
|
+
when TinyGQL::Nodes::Field
|
142
|
+
response_key = selection.aliaz || selection.name
|
143
|
+
grouped_fields[response_key] ||= []
|
144
|
+
grouped_fields[response_key] << selection
|
145
|
+
else
|
146
|
+
raise NotImplementedError
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
@collect_fields_cache[selection_set.object_id] = grouped_fields
|
151
|
+
|
152
|
+
grouped_fields
|
153
|
+
end
|
154
|
+
|
155
|
+
sig do
|
156
|
+
params(
|
157
|
+
object_type: Definition::Abstract::ObjectTypeDefinition,
|
158
|
+
object_value: Object,
|
159
|
+
field_definition: Definition::Abstract::FieldDefinition,
|
160
|
+
fields: T::Array[TinyGQL::Nodes::Field],
|
161
|
+
).returns(Object)
|
162
|
+
end
|
163
|
+
def execute_field(object_type:, object_value:, field_definition:, fields:)
|
164
|
+
resolved_value = object_value.send(field_definition.resolver_method_name)
|
165
|
+
|
166
|
+
complete_value(field_type: field_definition.type, fields:, resolved_value:)
|
167
|
+
end
|
168
|
+
|
169
|
+
sig do
|
170
|
+
params(
|
171
|
+
field_type: Definition::OutputType,
|
172
|
+
fields: T::Array[TinyGQL::Nodes::Field],
|
173
|
+
resolved_value: Object,
|
174
|
+
).returns(Object)
|
175
|
+
end
|
176
|
+
def complete_value(field_type:, fields:, resolved_value:)
|
177
|
+
if resolved_value.nil? && field_type.required?
|
178
|
+
raise "Cannot return null for non-null list type"
|
179
|
+
elsif resolved_value.nil?
|
180
|
+
return
|
181
|
+
end
|
182
|
+
|
183
|
+
case field_type
|
184
|
+
when Definition::OutputType::Named
|
185
|
+
case (inner_type = field_type.inner)
|
186
|
+
when BuiltinScalarDefinition
|
187
|
+
inner_type.coerce_result(result: resolved_value)
|
188
|
+
when Definition::Abstract::ObjectTypeDefinition
|
189
|
+
execute_selection_set(
|
190
|
+
selection_set: T.must(fields.first).selection_set,
|
191
|
+
object_type: inner_type,
|
192
|
+
object_value: resolved_value,
|
193
|
+
)
|
194
|
+
end
|
195
|
+
when Definition::OutputType::List
|
196
|
+
if resolved_value.is_a?(Array)
|
197
|
+
resolved_value.map do |value|
|
198
|
+
complete_value(field_type: field_type.inner, fields: fields, resolved_value: value)
|
199
|
+
end
|
200
|
+
else
|
201
|
+
raise "Cannot return non-list value for list type"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
data/lib/oriole.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "sorbet-runtime"
|
5
|
+
require "zeitwerk"
|
6
|
+
loader = Zeitwerk::Loader.for_gem
|
7
|
+
loader.setup
|
8
|
+
|
9
|
+
module Oriole
|
10
|
+
class Error < StandardError; end
|
11
|
+
# Your code goes here...
|
12
|
+
end
|
13
|
+
|
14
|
+
loader.eager_load
|
data/oriole.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path("lib", __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require "oriole/version"
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "oriole"
|
9
|
+
spec.version = Oriole::VERSION
|
10
|
+
spec.authors = ["Adam Petro"]
|
11
|
+
spec.email = ["adamapetro@gmail.com"]
|
12
|
+
|
13
|
+
spec.summary = "A fast GraphQL engine written in pure Ruby."
|
14
|
+
spec.description = "A fast GraphQL engine written in pure Ruby."
|
15
|
+
spec.homepage = "https://github.com/adampetro/oriole"
|
16
|
+
spec.license = "MIT"
|
17
|
+
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = "https://github.com/adampetro/oriole"
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/adampetro/oriole/blob/main/CHANGELOG"
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
25
|
+
%x(git ls-files -z).split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
26
|
+
end
|
27
|
+
spec.bindir = "exe"
|
28
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
29
|
+
spec.require_paths = ["lib"]
|
30
|
+
|
31
|
+
spec.add_development_dependency("bundler", "~> 2.4.10")
|
32
|
+
spec.add_development_dependency("minitest", "~> 5.0")
|
33
|
+
spec.add_development_dependency("rake", "~> 13.0")
|
34
|
+
|
35
|
+
spec.add_dependency("sorbet-runtime", "~> 0.5")
|
36
|
+
spec.add_dependency("tinygql", "~> 0.1.4")
|
37
|
+
spec.add_dependency("zeitwerk", "~> 2.6")
|
38
|
+
end
|
data/rakelib/bench.rake
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# typed: ignore
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "open3"
|
5
|
+
require "sorbet-runtime"
|
6
|
+
|
7
|
+
class BenchmarkToDocument < T::Struct
|
8
|
+
const(:path, String)
|
9
|
+
const(:description, String)
|
10
|
+
end
|
11
|
+
|
12
|
+
BENCHMARKS_TO_DOCUMENT = [
|
13
|
+
BenchmarkToDocument.new(path: "execute", description: "Parse + Execute"),
|
14
|
+
]
|
15
|
+
|
16
|
+
namespace :bench do
|
17
|
+
all_tasks = [:compile]
|
18
|
+
|
19
|
+
Dir.glob("bench/*.rb").each do |path|
|
20
|
+
task_name = File.basename(path, ".rb")
|
21
|
+
next if task_name == "bench" # Bench helper
|
22
|
+
|
23
|
+
desc "Run #{path} benchmark"
|
24
|
+
task task_name do
|
25
|
+
sh "ruby -Ilib #{path}"
|
26
|
+
puts
|
27
|
+
end
|
28
|
+
|
29
|
+
all_tasks << task_name
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "Run all benchmarks"
|
33
|
+
task all: all_tasks
|
34
|
+
|
35
|
+
desc "Document benchmark results"
|
36
|
+
task :doc do
|
37
|
+
output = ""
|
38
|
+
|
39
|
+
BENCHMARKS_TO_DOCUMENT.each do |benchmark|
|
40
|
+
puts "Benchmarking #{benchmark.path}"
|
41
|
+
[true, false].each do |yjit|
|
42
|
+
env = yjit ? { "RUBY_YJIT_ENABLE" => "1" } : {}
|
43
|
+
stdout, status = Open3.capture2e(env, "ruby", "-Ilib", "bench/#{benchmark.path}.rb")
|
44
|
+
unless status.success?
|
45
|
+
abort("Encountered an error: #{stdout}")
|
46
|
+
end
|
47
|
+
padded_stdout = stdout.lines.map { |line| " #{line.chomp}".tap(&:rstrip!) }.join("\n")
|
48
|
+
output += <<~END
|
49
|
+
<details>
|
50
|
+
<summary>#{benchmark.description} (Ruby 3.2, YJIT #{yjit ? "enabled" : "disabled"})</summary>
|
51
|
+
|
52
|
+
```
|
53
|
+
#{padded_stdout}
|
54
|
+
```
|
55
|
+
</details>
|
56
|
+
END
|
57
|
+
output += "\n"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
readme = "README.md"
|
62
|
+
contents = File.read(readme)
|
63
|
+
pattern = /(?<=<!---benchmark result start-->\n).*?(?=<!---benchmark result end-->)/m
|
64
|
+
File.write(readme, contents.gsub!(pattern, output.chomp))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
desc "Run all benchmarks"
|
69
|
+
task bench: "bench:all"
|