graphiti_graphql 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -3
- data/Gemfile.lock +11 -15
- data/README.md +4 -2
- data/graphiti_graphql.gemspec +1 -1
- data/lib/graphiti_graphql/federation/schema_decorator.rb +27 -7
- data/lib/graphiti_graphql/graphiti_schema/resource.rb +4 -0
- data/lib/graphiti_graphql/runner.rb +27 -4
- data/lib/graphiti_graphql/schema.rb +68 -6
- data/lib/graphiti_graphql/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 75a9873f8fda91fa24f41d06e146826f3de27165452b61202dc45d597e869ef5
|
4
|
+
data.tar.gz: 69b67b64c1af770a2df57a74e6a30175e7c80f4d26d4a73a2db958f153b1c5b3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fe62cbb5d5dd96ae3008add2bd13c465e0f2fd944869a478f544690bf6c459db2daf1504957adb243412a88cd318759fc6cd46f03a436b7b428d912d2530d2be
|
7
|
+
data.tar.gz: 2bd729f975995cf7433a3fc7c6864586dd5515955c68941c91e6036d2ef54cd024a3508a0869ed67eda7d6e61f612c37ae1b9b9b13ef19bb636948629edb4222
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,19 +1,9 @@
|
|
1
|
-
PATH
|
2
|
-
remote: ../graphiti
|
3
|
-
specs:
|
4
|
-
graphiti (1.2.32)
|
5
|
-
activesupport (>= 4.1)
|
6
|
-
concurrent-ruby (~> 1.0)
|
7
|
-
dry-types (>= 0.15.0, < 2.0)
|
8
|
-
graphiti_errors (~> 1.1.0)
|
9
|
-
jsonapi-renderer (~> 0.2, >= 0.2.2)
|
10
|
-
jsonapi-serializable (~> 0.3.0)
|
11
|
-
|
12
1
|
PATH
|
13
2
|
remote: .
|
14
3
|
specs:
|
15
|
-
graphiti_graphql (0.1.
|
4
|
+
graphiti_graphql (0.1.2)
|
16
5
|
activesupport (>= 4.1)
|
6
|
+
graphiti (>= 1.2.36)
|
17
7
|
graphql (~> 1.12)
|
18
8
|
|
19
9
|
GEM
|
@@ -54,6 +44,13 @@ GEM
|
|
54
44
|
dry-inflector (~> 0.1, >= 0.1.2)
|
55
45
|
dry-logic (~> 1.0, >= 1.0.2)
|
56
46
|
google-protobuf (3.15.2)
|
47
|
+
graphiti (1.2.36)
|
48
|
+
activesupport (>= 4.1)
|
49
|
+
concurrent-ruby (~> 1.0)
|
50
|
+
dry-types (>= 0.15.0, < 2.0)
|
51
|
+
graphiti_errors (~> 1.1.0)
|
52
|
+
jsonapi-renderer (~> 0.2, >= 0.2.2)
|
53
|
+
jsonapi-serializable (~> 0.3.0)
|
57
54
|
graphiti_errors (1.1.2)
|
58
55
|
jsonapi-serializable (~> 0.1)
|
59
56
|
graphiti_spec_helpers (1.0.5)
|
@@ -63,13 +60,13 @@ GEM
|
|
63
60
|
graphql-batch (0.4.3)
|
64
61
|
graphql (>= 1.3, < 2)
|
65
62
|
promise.rb (~> 0.7.2)
|
66
|
-
i18n (1.8.
|
63
|
+
i18n (1.8.9)
|
67
64
|
concurrent-ruby (~> 1.0)
|
68
65
|
jsonapi-renderer (0.2.2)
|
69
66
|
jsonapi-serializable (0.3.1)
|
70
67
|
jsonapi-renderer (~> 0.2.0)
|
71
68
|
method_source (1.0.0)
|
72
|
-
minitest (5.14.
|
69
|
+
minitest (5.14.4)
|
73
70
|
parallel (1.20.1)
|
74
71
|
parser (3.0.0.0)
|
75
72
|
ast (~> 2.4.1)
|
@@ -129,7 +126,6 @@ DEPENDENCIES
|
|
129
126
|
activemodel (>= 4.1)
|
130
127
|
apollo-federation (~> 1.1)
|
131
128
|
bundler (~> 2.0)
|
132
|
-
graphiti!
|
133
129
|
graphiti_graphql!
|
134
130
|
graphiti_spec_helpers
|
135
131
|
graphql (~> 1.12)
|
data/README.md
CHANGED
@@ -4,12 +4,14 @@ GraphQL (and Apollo Federation) support for Graphiti. Serve traditional Rails JS
|
|
4
4
|
|
5
5
|
Currently read-only, but you can add your own Mutations [manually](#blending-with-graphql-ruby).
|
6
6
|
|
7
|
+
Pre-alpha. Stay tuned.
|
8
|
+
|
7
9
|
## Setup
|
8
10
|
|
9
11
|
Add to your `Gemfile`:
|
10
12
|
|
11
13
|
```rb
|
12
|
-
gem 'graphiti', ">= 1.2.
|
14
|
+
gem 'graphiti', ">= 1.2.33"
|
13
15
|
gem "graphiti_graphql"
|
14
16
|
```
|
15
17
|
|
@@ -59,7 +61,7 @@ Define your Schema and Type classes as normal. Then in an initializer:
|
|
59
61
|
GraphitiGraphQL.schema_class = MySchema
|
60
62
|
```
|
61
63
|
|
62
|
-
Any pre-existing GraphQL endpoint will continue working as normal. But the GQL endpoint you mounted in `config/routes.rb` will now serve BOTH your low-level `graphql-ruby` schema AND your Graphiti-specific schema. Note these cannot (currently) be served side-by-side
|
64
|
+
Any pre-existing GraphQL endpoint will continue working as normal. But the GQL endpoint you mounted in `config/routes.rb` will now serve BOTH your low-level `graphql-ruby` schema AND your Graphiti-specific schema. Note these cannot (currently) be served side-by-side under `query` within the *same request*.
|
63
65
|
|
64
66
|
By default the GraphQL context will be `Graphiti.context[:object]`, which is the controller being called. You might want to customize this so your existing graphql-ruby code continues to expect the same context:
|
65
67
|
|
data/graphiti_graphql.gemspec
CHANGED
@@ -27,7 +27,7 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
28
|
spec.require_paths = ["lib"]
|
29
29
|
|
30
|
-
spec.add_dependency "graphiti", ">= 1.2.
|
30
|
+
spec.add_dependency "graphiti", ">= 1.2.36"
|
31
31
|
spec.add_dependency "activesupport", ">= 4.1"
|
32
32
|
spec.add_dependency "graphql", "~> 1.12"
|
33
33
|
|
@@ -90,6 +90,22 @@ module GraphitiGraphQL
|
|
90
90
|
federated_type
|
91
91
|
end
|
92
92
|
|
93
|
+
def define_connection_type(name, type_class)
|
94
|
+
name = "#{name}FederatedConnection"
|
95
|
+
if (registered = type_registry[name])
|
96
|
+
return registered[:type]
|
97
|
+
end
|
98
|
+
|
99
|
+
klass = Class.new(@schema.class.base_object)
|
100
|
+
klass.graphql_name(name)
|
101
|
+
klass.field :nodes,
|
102
|
+
[type_class],
|
103
|
+
null: false,
|
104
|
+
extras: [:lookahead]
|
105
|
+
@schema.send :register, name, klass
|
106
|
+
klass
|
107
|
+
end
|
108
|
+
|
93
109
|
def define_federated_has_many(type_class, relationship)
|
94
110
|
local_name = GraphitiGraphQL::GraphitiSchema::Resource
|
95
111
|
.gql_name(relationship.local_resource_class.name)
|
@@ -100,16 +116,20 @@ module GraphitiGraphQL
|
|
100
116
|
local_interface = type_registry["I#{local_name}"]
|
101
117
|
best_type = local_interface ? local_interface[:type] : local_type
|
102
118
|
|
119
|
+
connection_type = define_connection_type(local_name, best_type)
|
120
|
+
|
103
121
|
field = type_class.field relationship.name,
|
104
|
-
|
122
|
+
connection_type,
|
105
123
|
null: false,
|
106
|
-
|
107
|
-
|
124
|
+
connection: false
|
108
125
|
@schema.send :define_arguments_for_sideload_field,
|
109
126
|
field, @schema.graphiti_schema.get_resource(local_resource_name)
|
110
|
-
|
111
|
-
|
112
|
-
|
127
|
+
|
128
|
+
type_class.define_method relationship.name do |**arguments|
|
129
|
+
{data: object, arguments: arguments}
|
130
|
+
end
|
131
|
+
connection_type.define_method :nodes do |lookahead:, **arguments|
|
132
|
+
params = object[:arguments].as_json
|
113
133
|
.deep_transform_keys { |key| key.to_s.underscore.to_sym }
|
114
134
|
selections = lookahead.selections.map(&:name)
|
115
135
|
selections << relationship.foreign_key
|
@@ -122,7 +142,7 @@ module GraphitiGraphQL
|
|
122
142
|
|
123
143
|
Federation::Loaders::HasMany
|
124
144
|
.for(relationship, params)
|
125
|
-
.load(object[:id])
|
145
|
+
.load(object[:data][:id])
|
126
146
|
end
|
127
147
|
end
|
128
148
|
|
@@ -38,7 +38,7 @@ module GraphitiGraphQL
|
|
38
38
|
|
39
39
|
def render(json, selection_name)
|
40
40
|
payload = if find_one?(selection_name)
|
41
|
-
{selection_name.to_sym => json.values[0][0]}
|
41
|
+
{selection_name.to_sym => json.values[0][:nodes][0]}
|
42
42
|
else
|
43
43
|
json
|
44
44
|
end
|
@@ -108,15 +108,19 @@ module GraphitiGraphQL
|
|
108
108
|
gather_filters(params, selection, variables_hash, chained_name)
|
109
109
|
gather_sorts(params, selection, variables_hash, chained_name)
|
110
110
|
gather_pages(params, selection, variables_hash, chained_name)
|
111
|
+
gather_stats(params, selection, variables_hash, chained_name)
|
111
112
|
end
|
112
113
|
|
113
114
|
params[:include] ||= []
|
114
115
|
params[:include] << chained_name if chained_name
|
115
116
|
|
116
|
-
|
117
|
+
nodes = selection.selections.find { |s| s.respond_to?(:name) && s.name == "nodes" }
|
118
|
+
children = nodes ? nodes.children : selection.selections
|
119
|
+
|
120
|
+
fragments = children.select { |s|
|
117
121
|
s.is_a?(GraphQL::Language::Nodes::InlineFragment)
|
118
122
|
}
|
119
|
-
non_fragments =
|
123
|
+
non_fragments = children - fragments
|
120
124
|
|
121
125
|
if pbt
|
122
126
|
# Only id/_type possible here
|
@@ -223,7 +227,9 @@ module GraphitiGraphQL
|
|
223
227
|
|
224
228
|
attr_arg.value.arguments.each do |operator_arg|
|
225
229
|
value = operator_arg.value
|
226
|
-
if value.
|
230
|
+
if value.is_a?(GraphQL::Language::Nodes::Enum)
|
231
|
+
value = value.name
|
232
|
+
elsif value.respond_to?(:name) # is a variable
|
227
233
|
value = variable_hash[operator_arg.value.name]
|
228
234
|
end
|
229
235
|
f[filter_param_name] = {operator_arg.name.underscore => value}
|
@@ -299,5 +305,22 @@ module GraphitiGraphQL
|
|
299
305
|
params[:page].merge!(pages)
|
300
306
|
end
|
301
307
|
end
|
308
|
+
|
309
|
+
def gather_stats(params, selection, variable_hash, chained_name = nil)
|
310
|
+
stats = selection.children.find { |c| c.name == "stats" }
|
311
|
+
nodes = selection.children.find { |c| c.name == "nodes" }
|
312
|
+
|
313
|
+
if stats
|
314
|
+
stat_param = {}
|
315
|
+
stats.children.each do |stat_node|
|
316
|
+
stat_name = stat_node.name
|
317
|
+
calculations = stat_node.children.map(&:name)
|
318
|
+
stat_param[stat_name.to_sym] = calculations.join(",")
|
319
|
+
end
|
320
|
+
stat_param = {chained_name => stat_param} if chained_name
|
321
|
+
params[:stats] = stat_param
|
322
|
+
params[:page] = {size: 0} unless nodes
|
323
|
+
end
|
324
|
+
end
|
302
325
|
end
|
303
326
|
end
|
@@ -132,11 +132,30 @@ module GraphitiGraphQL
|
|
132
132
|
|
133
133
|
private
|
134
134
|
|
135
|
+
def generate_connection_type(resource, top_level: true)
|
136
|
+
name = "#{resource.graphql_class_name}#{top_level ? "TopLevel" : ""}Connection"
|
137
|
+
if registered = type_registry[name]
|
138
|
+
return registered[:type]
|
139
|
+
end
|
140
|
+
|
141
|
+
type = type_registry[resource.graphql_class_name][:type]
|
142
|
+
klass = Class.new(self.class.base_object)
|
143
|
+
klass.graphql_name(name)
|
144
|
+
klass.field :nodes,
|
145
|
+
[type],
|
146
|
+
"List #{resource.graphql_class_name(false).pluralize}",
|
147
|
+
null: false
|
148
|
+
if top_level
|
149
|
+
klass.field :stats, generate_stat_class(resource), null: false
|
150
|
+
end
|
151
|
+
register(name, klass)
|
152
|
+
klass
|
153
|
+
end
|
154
|
+
|
135
155
|
def add_index(query_class, resource)
|
136
156
|
field_name = resource.graphql_entrypoint.to_s.underscore.to_sym
|
137
157
|
field = query_class.field field_name,
|
138
|
-
|
139
|
-
"List #{resource.graphql_class_name(false).pluralize}",
|
158
|
+
generate_connection_type(resource, top_level: true),
|
140
159
|
null: false
|
141
160
|
@query_fields[field_name] = resource
|
142
161
|
define_arguments_for_sideload_field(field, resource)
|
@@ -147,7 +166,7 @@ module GraphitiGraphQL
|
|
147
166
|
field = query_class.field field_name,
|
148
167
|
type_registry[resource.graphql_class_name][:type],
|
149
168
|
"Single #{resource.graphql_class_name(false).singularize}",
|
150
|
-
null:
|
169
|
+
null: false
|
151
170
|
@query_fields[field_name] = resource
|
152
171
|
define_arguments_for_sideload_field field,
|
153
172
|
resource,
|
@@ -232,17 +251,38 @@ module GraphitiGraphQL
|
|
232
251
|
# TODO guarded operators or otherwise whatever eq => nil is
|
233
252
|
def generate_filter_attribute_type(type_name, filter_name, filter_config)
|
234
253
|
klass = Class.new(GraphQL::Schema::InputObject)
|
235
|
-
|
254
|
+
filter_graphql_name = "#{type_name}Filter#{filter_name.to_s.camelize(:lower)}"
|
255
|
+
klass.graphql_name(filter_graphql_name)
|
236
256
|
filter_config[:operators].each do |operator|
|
237
257
|
canonical_graphiti_type = Graphiti::Types
|
238
258
|
.name_for(filter_config[:type])
|
239
259
|
type = GQL_TYPE_MAP[canonical_graphiti_type]
|
240
260
|
required = !!filter_config[:required] && operator == "eq"
|
261
|
+
|
262
|
+
if (allowlist = filter_config[:allow])
|
263
|
+
type = define_allowlist_type(filter_graphql_name, allowlist)
|
264
|
+
end
|
265
|
+
|
266
|
+
type = [type] unless !!filter_config[:single]
|
241
267
|
klass.argument operator, type, required: required
|
242
268
|
end
|
243
269
|
klass
|
244
270
|
end
|
245
271
|
|
272
|
+
def define_allowlist_type(filter_graphql_name, allowlist)
|
273
|
+
name = "#{filter_graphql_name}Allow"
|
274
|
+
if (registered = type_registry[name])
|
275
|
+
return registered[:type]
|
276
|
+
end
|
277
|
+
klass = Class.new(GraphQL::Schema::Enum)
|
278
|
+
klass.graphql_name(name)
|
279
|
+
allowlist.each do |allowed|
|
280
|
+
klass.value(allowed)
|
281
|
+
end
|
282
|
+
register(name, klass)
|
283
|
+
klass
|
284
|
+
end
|
285
|
+
|
246
286
|
def generate_resource_for_sideload(sideload)
|
247
287
|
if sideload.type == :polymorphic_belongs_to
|
248
288
|
unless registered?(sideload.parent_resource)
|
@@ -269,7 +309,11 @@ module GraphitiGraphQL
|
|
269
309
|
type_registry[sideload.graphql_class_name][:type]
|
270
310
|
end
|
271
311
|
|
272
|
-
gql_field_type = sideload.to_many?
|
312
|
+
gql_field_type = if sideload.to_many?
|
313
|
+
generate_connection_type(sideload.resource, top_level: false)
|
314
|
+
else
|
315
|
+
gql_type
|
316
|
+
end
|
273
317
|
field_name = name.to_s.camelize(:lower)
|
274
318
|
unless type_class.fields[field_name]
|
275
319
|
field = type_class.field field_name.to_sym,
|
@@ -277,7 +321,6 @@ module GraphitiGraphQL
|
|
277
321
|
null: !sideload.to_many?
|
278
322
|
|
279
323
|
# No sort/filter/paginate on belongs_to
|
280
|
-
# unless sideload.type.to_s.include?('belongs_to')
|
281
324
|
unless sideload.type == :polymorphic_belongs_to
|
282
325
|
define_arguments_for_sideload_field(field, sideload.resource)
|
283
326
|
end
|
@@ -373,6 +416,25 @@ module GraphitiGraphQL
|
|
373
416
|
klass
|
374
417
|
end
|
375
418
|
|
419
|
+
def generate_stat_class(resource)
|
420
|
+
klass = Class.new(self.class.base_object)
|
421
|
+
klass.graphql_name "#{resource.graphql_class_name(false)}Stats"
|
422
|
+
resource.stats.each_pair do |name, calculations|
|
423
|
+
calc_class = generate_calc_class(resource, name, calculations)
|
424
|
+
klass.field name, calc_class, null: false
|
425
|
+
end
|
426
|
+
klass
|
427
|
+
end
|
428
|
+
|
429
|
+
def generate_calc_class(resource, stat_name, calculations)
|
430
|
+
klass = Class.new(self.class.base_object)
|
431
|
+
klass.graphql_name "#{resource.graphql_class_name(false)}#{stat_name}Calculations"
|
432
|
+
calculations.each do |calc|
|
433
|
+
klass.field calc, Float, null: false
|
434
|
+
end
|
435
|
+
klass
|
436
|
+
end
|
437
|
+
|
376
438
|
def registered?(resource)
|
377
439
|
name = resource.graphql_class_name(false)
|
378
440
|
!!type_registry[name]
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphiti_graphql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lee Richmond
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-03-
|
11
|
+
date: 2021-03-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphiti
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.2.
|
19
|
+
version: 1.2.36
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 1.2.
|
26
|
+
version: 1.2.36
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activesupport
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|