graphiti_graphql 0.1.1 → 0.1.2
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/Gemfile +1 -1
- data/Gemfile.lock +4 -4
- data/README.md +139 -15
- data/graphiti_graphql.gemspec +1 -0
- data/lib/graphiti_graphql.rb +47 -34
- data/lib/graphiti_graphql/engine.rb +2 -12
- data/lib/graphiti_graphql/federation.rb +12 -220
- data/lib/graphiti_graphql/federation/apollo_federation_override.rb +54 -0
- data/lib/graphiti_graphql/federation/federated_relationship.rb +26 -0
- data/lib/graphiti_graphql/federation/federated_resource.rb +26 -0
- data/lib/graphiti_graphql/federation/loaders/belongs_to.rb +22 -0
- data/lib/graphiti_graphql/federation/loaders/has_many.rb +33 -0
- data/lib/graphiti_graphql/federation/resource_dsl.rb +89 -0
- data/lib/graphiti_graphql/federation/schema_decorator.rb +150 -0
- data/lib/graphiti_graphql/graphiti_schema/resource.rb +0 -18
- data/lib/graphiti_graphql/graphiti_schema/wrapper.rb +0 -15
- data/lib/graphiti_graphql/runner.rb +15 -25
- data/lib/graphiti_graphql/schema.rb +50 -160
- data/lib/graphiti_graphql/util.rb +1 -1
- data/lib/graphiti_graphql/version.rb +1 -1
- metadata +23 -2
@@ -4,22 +4,28 @@ module GraphitiGraphQL
|
|
4
4
|
query = GraphQL::Query.new(schema, query_string, variables: variables)
|
5
5
|
definition = query.document.definitions.first
|
6
6
|
selection = definition.selections.first
|
7
|
-
|
7
|
+
is_graphiti = schemas.generated.query_field?(selection.name)
|
8
8
|
|
9
|
-
#
|
9
|
+
# Wrap *everything* in context, in case of federated request
|
10
10
|
Util.with_gql_context do
|
11
|
-
if
|
11
|
+
if is_graphiti
|
12
|
+
resource_class = schemas.generated
|
13
|
+
.resource_for_query_field(selection.name)
|
12
14
|
run_query(schema, resource_class, selection, query)
|
13
15
|
else
|
14
|
-
|
16
|
+
schemas.graphql.execute query_string,
|
15
17
|
variables: variables,
|
16
|
-
context: GraphitiGraphQL.get_context
|
18
|
+
context: GraphitiGraphQL.config.get_context
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
21
23
|
private
|
22
24
|
|
25
|
+
def schemas
|
26
|
+
GraphitiGraphQL.schemas
|
27
|
+
end
|
28
|
+
|
23
29
|
def run_query(schema, resource_class, selection, query)
|
24
30
|
if (errors = collect_errors(schema, query)).any?
|
25
31
|
{"errors" => errors.map(&:to_h)}
|
@@ -49,20 +55,8 @@ module GraphitiGraphQL
|
|
49
55
|
query.validation_errors + query.analysis_errors + query.context.errors
|
50
56
|
end
|
51
57
|
|
52
|
-
# We can't just constantize the name from the schema
|
53
|
-
# Because classes can be reopened and modified in tests (or elsewhere, in theory)
|
54
|
-
def find_entrypoint_resource_class(entrypoint)
|
55
|
-
Graphiti.resources.find(&matches_entrypoint?(entrypoint))
|
56
|
-
end
|
57
|
-
|
58
58
|
def find_entrypoint_schema_resource(entrypoint)
|
59
|
-
|
60
|
-
end
|
61
|
-
|
62
|
-
def matches_entrypoint?(entrypoint)
|
63
|
-
lambda do |resource|
|
64
|
-
resource.graphql_entrypoint.to_s.underscore == entrypoint.pluralize.underscore
|
65
|
-
end
|
59
|
+
schemas.generated.schema_resource_for_query_field(entrypoint)
|
66
60
|
end
|
67
61
|
|
68
62
|
def introspection_query?(query)
|
@@ -70,14 +64,10 @@ module GraphitiGraphQL
|
|
70
64
|
end
|
71
65
|
|
72
66
|
def find_resource_by_selection_name(name)
|
73
|
-
|
67
|
+
schemas.graphiti.resources
|
74
68
|
.find { |r| r.type == name.pluralize.underscore }
|
75
69
|
end
|
76
70
|
|
77
|
-
def graphiti_schema
|
78
|
-
Graphiti.graphql_schema.graphiti_schema
|
79
|
-
end
|
80
|
-
|
81
71
|
def schema_resource_for_selection(selection, parent_resource)
|
82
72
|
if parent_resource
|
83
73
|
parent_resource.related_resource(selection.name.underscore.to_sym)
|
@@ -146,8 +136,8 @@ module GraphitiGraphQL
|
|
146
136
|
end
|
147
137
|
|
148
138
|
fragments.each do |fragment|
|
149
|
-
resource_name =
|
150
|
-
klass =
|
139
|
+
resource_name = schemas.generated.type_registry[fragment.type.name][:resource]
|
140
|
+
klass = schemas.graphiti.resources.find { |r| r.name == resource_name }
|
151
141
|
_, _, fragment_sideload_selections = gather_fields fragment.selections,
|
152
142
|
klass,
|
153
143
|
params,
|
@@ -34,6 +34,7 @@ module GraphitiGraphQL
|
|
34
34
|
end
|
35
35
|
|
36
36
|
attr_accessor :type_registry, :schema, :graphiti_schema
|
37
|
+
attr_reader :query_fields
|
37
38
|
|
38
39
|
def self.federation?
|
39
40
|
!!@federation
|
@@ -54,26 +55,11 @@ module GraphitiGraphQL
|
|
54
55
|
def self.generate(entrypoint_resources = nil)
|
55
56
|
instance = new
|
56
57
|
schema = Class.new(::GraphitiGraphQL.schema_class || GraphQL::Schema)
|
57
|
-
|
58
|
-
if federation?
|
59
|
-
schema.send(:include, ApolloFederation::Schema)
|
60
|
-
end
|
61
|
-
|
62
58
|
graphiti_schema = GraphitiGraphQL::GraphitiSchema::Wrapper
|
63
59
|
.new(Graphiti::Schema.generate)
|
64
|
-
# TODO: if we avoid this on federation, or remove altogether
|
65
|
-
# Make sure we don't blow up
|
66
|
-
# graphiti_schema.merge_remotes!
|
67
|
-
|
68
|
-
entries = entrypoint_resources || entrypoints
|
69
|
-
instance.apply_query(graphiti_schema, schema, entries)
|
70
|
-
|
71
|
-
# NB if we add mutation support, make sure this is applied after
|
72
|
-
if federation?
|
73
|
-
schema.use GraphQL::Batch
|
74
|
-
end
|
75
|
-
instance.schema = schema
|
76
60
|
instance.graphiti_schema = graphiti_schema
|
61
|
+
instance.schema = schema
|
62
|
+
instance.apply_query(entrypoint_resources || entrypoints)
|
77
63
|
instance
|
78
64
|
end
|
79
65
|
|
@@ -83,147 +69,32 @@ module GraphitiGraphQL
|
|
83
69
|
|
84
70
|
def initialize
|
85
71
|
@type_registry = {}
|
72
|
+
@query_fields = {}
|
86
73
|
end
|
87
74
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
def apply_federation(graphiti_schema, graphql_schema)
|
92
|
-
type_registry.each_pair do |name, config|
|
93
|
-
if config[:resource]
|
94
|
-
local_type = config[:type]
|
95
|
-
local_resource = Graphiti.resources
|
96
|
-
.find { |r| r.name == config[:resource] }
|
97
|
-
# TODO: maybe turn off the graphiti debug for these?
|
98
|
-
local_type.define_singleton_method :resolve_reference do |reference, context, lookahead|
|
99
|
-
Federation::BelongsToLoader
|
100
|
-
.for(local_resource, lookahead.selections.map(&:name))
|
101
|
-
.load(reference[:id])
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
# NB: test already registered bc 2 things have same relationship
|
107
|
-
GraphitiGraphQL::Federation.external_resources.each_pair do |klass_name, config|
|
108
|
-
pre_registered = !!type_registry[klass_name]
|
109
|
-
external_klass = if pre_registered
|
110
|
-
type_registry[klass_name][:type]
|
111
|
-
else
|
112
|
-
external_klass = Class.new(self.class.base_object)
|
113
|
-
external_klass.graphql_name klass_name
|
114
|
-
external_klass
|
115
|
-
end
|
116
|
-
|
117
|
-
unless pre_registered
|
118
|
-
external_klass.key(fields: "id")
|
119
|
-
external_klass.extend_type
|
120
|
-
external_klass.field :id, String, null: false, external: true
|
121
|
-
external_klass.class_eval do
|
122
|
-
def self.resolve_reference(reference, _context, _lookup)
|
123
|
-
reference
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
unless pre_registered
|
129
|
-
# NB must be registered before processing rels
|
130
|
-
type_registry[klass_name] = { type: external_klass }
|
131
|
-
end
|
132
|
-
|
133
|
-
# TODO: only do it if field not already defined
|
134
|
-
config.relationships.each_pair do |name, relationship|
|
135
|
-
if relationship.has_many?
|
136
|
-
define_federated_has_many(graphiti_schema, external_klass, relationship)
|
137
|
-
elsif relationship.belongs_to?
|
138
|
-
define_federated_belongs_to(config, relationship)
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
# TODO: refactor to not constantly pass schemas around
|
145
|
-
def define_federated_has_many(graphiti_schema, external_klass, relationship)
|
146
|
-
local_name = GraphitiGraphQL::GraphitiSchema::Resource
|
147
|
-
.gql_name(relationship.local_resource_class.name)
|
148
|
-
local_type = type_registry[local_name][:type]
|
149
|
-
local_resource_name = type_registry[local_name][:resource]
|
150
|
-
local_resource = Graphiti.resources.find { |r| r.name == local_resource_name }
|
151
|
-
|
152
|
-
local_interface = type_registry["I#{local_name}"]
|
153
|
-
best_type = local_interface ? local_interface[:type] : local_type
|
154
|
-
|
155
|
-
field = external_klass.field relationship.name,
|
156
|
-
[best_type],
|
157
|
-
null: false,
|
158
|
-
extras: [:lookahead]
|
159
|
-
|
160
|
-
define_arguments_for_sideload_field(field, graphiti_schema.get_resource(local_resource_name))
|
161
|
-
external_klass.define_method relationship.name do |lookahead:, **arguments|
|
162
|
-
# TODO test params...do version of sort with array/symbol keys and plain string
|
163
|
-
params = arguments.as_json
|
164
|
-
.deep_transform_keys { |key| key.to_s.underscore.to_sym }
|
165
|
-
selections = lookahead.selections.map(&:name)
|
166
|
-
selections << relationship.foreign_key
|
167
|
-
selections << :_type # polymorphism
|
168
|
-
params[:fields] = { local_resource.type => selections.join(",") }
|
169
|
-
|
170
|
-
if (sort = Util.parse_sort(params[:sort]))
|
171
|
-
params[:sort] = sort
|
172
|
-
end
|
173
|
-
|
174
|
-
Federation::HasManyLoader
|
175
|
-
.for(local_resource, params, relationship.foreign_key)
|
176
|
-
.load(object[:id])
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
def define_federated_belongs_to(external_resource_config, relationship)
|
181
|
-
type_name = GraphitiSchema::Resource.gql_name(relationship.local_resource_class.name)
|
182
|
-
local_type = type_registry[type_name][:type]
|
183
|
-
|
184
|
-
# Todo maybe better way here
|
185
|
-
interface = type_registry["I#{type_name}"]
|
186
|
-
|
187
|
-
local_type = interface[:type] if interface
|
188
|
-
local_resource_name = type_registry[type_name][:resource]
|
189
|
-
local_resource_class = Graphiti.resources.find { |r| r.name == local_resource_name }
|
190
|
-
|
191
|
-
local_types = [local_type]
|
192
|
-
if interface
|
193
|
-
local_types |= interface[:implementers]
|
194
|
-
end
|
195
|
-
|
196
|
-
local_types.each do |local|
|
197
|
-
local.field relationship.name,
|
198
|
-
type_registry[external_resource_config.type_name][:type], # todo need to define the type?
|
199
|
-
null: true
|
200
|
-
end
|
201
|
-
end
|
202
|
-
|
203
|
-
def apply_query(graphiti_schema, graphql_schema, entries)
|
204
|
-
query_type = generate_schema_query(graphql_schema, graphiti_schema, entries)
|
205
|
-
if self.class.federation?
|
206
|
-
apply_federation(graphiti_schema, schema)
|
207
|
-
end
|
75
|
+
def apply_query(entries)
|
76
|
+
query_type = generate_schema_query(entries)
|
77
|
+
Federation::SchemaDecorator.decorate(self) if self.class.federation?
|
208
78
|
|
209
79
|
# NB - don't call .query here of federation will break things
|
210
|
-
if
|
211
|
-
|
212
|
-
|
80
|
+
if schema.instance_variable_get(:@query_object)
|
81
|
+
schema.instance_variable_set(:@query_object, nil)
|
82
|
+
schema.instance_variable_set(:@federation_query_object, nil)
|
213
83
|
end
|
214
|
-
|
215
|
-
|
216
|
-
|
84
|
+
schema.orphan_types(orphans(schema))
|
85
|
+
schema.query(query_type)
|
86
|
+
schema.query # Actually fires the federation code
|
217
87
|
end
|
218
88
|
|
219
|
-
def generate_schema_query(
|
220
|
-
existing_query =
|
89
|
+
def generate_schema_query(entrypoint_resources = nil)
|
90
|
+
existing_query = schema.instance_variable_get(:@query)
|
91
|
+
existing_query ||= schema.send(:find_inherited_value, :query)
|
221
92
|
# NB - don't call graphql_schema.query here of federation will break things
|
222
93
|
query_class = Class.new(existing_query || self.class.base_object)
|
223
94
|
# NB MUST be Query or federation-ruby will break things
|
224
95
|
query_class.graphql_name "Query"
|
225
96
|
|
226
|
-
|
97
|
+
get_entrypoints(entrypoint_resources).each do |resource|
|
227
98
|
next if resource.remote?
|
228
99
|
generate_type(resource)
|
229
100
|
|
@@ -244,28 +115,46 @@ module GraphitiGraphQL
|
|
244
115
|
end
|
245
116
|
end
|
246
117
|
|
118
|
+
def schema_resource_for_query_field(name)
|
119
|
+
@query_fields[name.underscore.to_sym]
|
120
|
+
end
|
121
|
+
|
122
|
+
def query_field?(name)
|
123
|
+
@query_fields.include?(name.underscore.to_sym)
|
124
|
+
end
|
125
|
+
|
126
|
+
# We can't just constantize the name from the schema
|
127
|
+
# Because classes can be reopened and modified in tests (or elsewhere, in theory)
|
128
|
+
def resource_for_query_field(name)
|
129
|
+
schema_resource = @query_fields[name.underscore.to_sym]
|
130
|
+
Graphiti.resources.find { |r| r.name == schema_resource.name }
|
131
|
+
end
|
132
|
+
|
247
133
|
private
|
248
134
|
|
249
135
|
def add_index(query_class, resource)
|
250
|
-
|
136
|
+
field_name = resource.graphql_entrypoint.to_s.underscore.to_sym
|
137
|
+
field = query_class.field field_name,
|
251
138
|
[type_registry[resource.graphql_class_name][:type]],
|
252
139
|
"List #{resource.graphql_class_name(false).pluralize}",
|
253
140
|
null: false
|
141
|
+
@query_fields[field_name] = resource
|
254
142
|
define_arguments_for_sideload_field(field, resource)
|
255
143
|
end
|
256
144
|
|
257
145
|
def add_show(query_class, resource)
|
258
|
-
|
259
|
-
field = query_class.field
|
146
|
+
field_name = resource.graphql_entrypoint.to_s.underscore.singularize.to_sym
|
147
|
+
field = query_class.field field_name,
|
260
148
|
type_registry[resource.graphql_class_name][:type],
|
261
149
|
"Single #{resource.graphql_class_name(false).singularize}",
|
262
150
|
null: true
|
151
|
+
@query_fields[field_name] = resource
|
263
152
|
define_arguments_for_sideload_field field,
|
264
153
|
resource,
|
265
154
|
top_level_single: true
|
266
155
|
end
|
267
156
|
|
268
|
-
def
|
157
|
+
def get_entrypoints(manually_specified)
|
269
158
|
resources = graphiti_schema.resources
|
270
159
|
if manually_specified
|
271
160
|
resources = resources.select { |r|
|
@@ -309,13 +198,17 @@ module GraphitiGraphQL
|
|
309
198
|
if top_level_single
|
310
199
|
field.argument(:id, String, required: true)
|
311
200
|
else
|
312
|
-
|
313
|
-
|
201
|
+
unless resource.sorts.empty?
|
202
|
+
sort_type = generate_sort_type(resource)
|
203
|
+
field.argument :sort, [sort_type], required: false
|
204
|
+
end
|
314
205
|
field.argument :page, PageType, required: false
|
315
206
|
|
316
|
-
|
317
|
-
|
318
|
-
|
207
|
+
unless resource.filters.empty?
|
208
|
+
filter_type = generate_filter_type(field, resource)
|
209
|
+
required = resource.filters.any? { |name, config| !!config[:required] }
|
210
|
+
field.argument :filter, filter_type, required: required
|
211
|
+
end
|
319
212
|
end
|
320
213
|
end
|
321
214
|
|
@@ -413,8 +306,9 @@ module GraphitiGraphQL
|
|
413
306
|
klass = Module.new
|
414
307
|
klass.send(:include, self.class.base_interface)
|
415
308
|
klass.definition_methods do
|
309
|
+
# rubocop:disable Lint/NestedMethodDefinition(Standard)
|
416
310
|
def resolve_type(object, context)
|
417
|
-
|
311
|
+
GraphitiGraphQL.schemas.graphql.types[object[:__typename]]
|
418
312
|
end
|
419
313
|
end
|
420
314
|
else
|
@@ -426,10 +320,6 @@ module GraphitiGraphQL
|
|
426
320
|
implement(klass, type_registry[implements])
|
427
321
|
end
|
428
322
|
|
429
|
-
if self.class.federation?
|
430
|
-
klass.key fields: "id"
|
431
|
-
end
|
432
|
-
|
433
323
|
klass.field(:_type, String, null: false)
|
434
324
|
resource.all_attributes.each do |name, config|
|
435
325
|
if config[:readable]
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
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.2
|
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-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: graphiti
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.2.33
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.2.33
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: activesupport
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -227,6 +241,13 @@ files:
|
|
227
241
|
- lib/graphiti_graphql/engine.rb
|
228
242
|
- lib/graphiti_graphql/errors.rb
|
229
243
|
- lib/graphiti_graphql/federation.rb
|
244
|
+
- lib/graphiti_graphql/federation/apollo_federation_override.rb
|
245
|
+
- lib/graphiti_graphql/federation/federated_relationship.rb
|
246
|
+
- lib/graphiti_graphql/federation/federated_resource.rb
|
247
|
+
- lib/graphiti_graphql/federation/loaders/belongs_to.rb
|
248
|
+
- lib/graphiti_graphql/federation/loaders/has_many.rb
|
249
|
+
- lib/graphiti_graphql/federation/resource_dsl.rb
|
250
|
+
- lib/graphiti_graphql/federation/schema_decorator.rb
|
230
251
|
- lib/graphiti_graphql/graphiti_schema/resource.rb
|
231
252
|
- lib/graphiti_graphql/graphiti_schema/sideload.rb
|
232
253
|
- lib/graphiti_graphql/graphiti_schema/wrapper.rb
|