graph_weaver 0.2.0 → 0.2.1
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/CHANGELOG.md +43 -0
- data/Gemfile.lock +2 -2
- data/PLAN.md +7 -0
- data/docs/generated_modules.md +43 -4
- data/docs/getting_started.md +8 -6
- data/docs/testing.md +21 -14
- data/lib/graph_weaver/client.rb +3 -2
- data/lib/graph_weaver/codegen/emit.rb +246 -56
- data/lib/graph_weaver/codegen/nodes.rb +35 -80
- data/lib/graph_weaver/codegen.rb +41 -104
- data/lib/graph_weaver/input_struct.rb +59 -0
- data/lib/graph_weaver/railtie.rb +2 -1
- data/lib/graph_weaver/rspec.rb +4 -6
- data/lib/graph_weaver/testing.rb +4 -4
- data/lib/graph_weaver/version.rb +1 -1
- data/lib/graph_weaver.rb +103 -22
- metadata +2 -1
data/lib/graph_weaver.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "sorbet-runtime"
|
|
|
4
4
|
require_relative "graph_weaver/logging"
|
|
5
5
|
require_relative "graph_weaver/errors"
|
|
6
6
|
require_relative "graph_weaver/hints"
|
|
7
|
+
require_relative "graph_weaver/input_struct"
|
|
7
8
|
require_relative "graph_weaver/response"
|
|
8
9
|
require_relative "graph_weaver/inflect"
|
|
9
10
|
require_relative "graph_weaver/codegen"
|
|
@@ -56,15 +57,58 @@ module GraphWeaver
|
|
|
56
57
|
target.is_a?(Client) ? target.transport! : target
|
|
57
58
|
end
|
|
58
59
|
|
|
59
|
-
# Conventional locations, factory_bot-style
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
|
|
60
|
+
# Conventional locations, factory_bot-style — LISTS, so extra
|
|
61
|
+
# locations (a test-only dir, an engine's) can be appended and every
|
|
62
|
+
# loader walks them all:
|
|
63
|
+
#
|
|
64
|
+
# # e.g. in spec/support/graph_weaver.rb
|
|
65
|
+
# GraphWeaver.generated_paths << "spec/support/graphql/generated"
|
|
66
|
+
# GraphWeaver.queries_paths << "spec/support/graphql/queries"
|
|
67
|
+
#
|
|
68
|
+
# The singular accessors read the first entry (the default target
|
|
69
|
+
# for generate! and the rake tasks); assigning one replaces the list.
|
|
70
|
+
attr_writer :queries_paths, :generated_paths, :schema_path
|
|
71
|
+
|
|
72
|
+
# Entries may be glob patterns — the generated default also matches
|
|
73
|
+
# per-schema layouts (app/graphql/github/generated). Queries stay
|
|
74
|
+
# single-schema: load_queries! parses everything against one client.
|
|
75
|
+
def queries_paths = @queries_paths ||= ["app/graphql/queries"]
|
|
76
|
+
def generated_paths = @generated_paths ||= ["app/graphql/generated", "app/graphql/*/generated"]
|
|
77
|
+
|
|
78
|
+
def queries_path = queries_paths.first
|
|
79
|
+
def generated_path = generated_paths.first
|
|
80
|
+
|
|
81
|
+
def queries_path=(path)
|
|
82
|
+
@queries_paths = path.nil? ? nil : [path]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def generated_path=(path)
|
|
86
|
+
@generated_paths = path.nil? ? nil : [path]
|
|
87
|
+
end
|
|
63
88
|
|
|
64
|
-
def queries_path = @queries_path || "app/graphql/queries"
|
|
65
|
-
def generated_path = @generated_path || "app/graphql/generated"
|
|
66
89
|
def schema_path = @schema_path || "app/graphql/schema.json"
|
|
67
90
|
|
|
91
|
+
# The shared-inputs module name: set it globally, pass inputs_module:
|
|
92
|
+
# per generate!, or let it derive from the output path — the
|
|
93
|
+
# directory above generated/ names the schema in multi-schema
|
|
94
|
+
# layouts (app/graphql/github/generated => GithubInputs); the
|
|
95
|
+
# conventional layout (and anything unrecognizable) stays
|
|
96
|
+
# GraphQLInputs.
|
|
97
|
+
attr_writer :inputs_module
|
|
98
|
+
|
|
99
|
+
def inputs_module(output = generated_path)
|
|
100
|
+
return @inputs_module if @inputs_module
|
|
101
|
+
|
|
102
|
+
segments = File.expand_path(output.to_s).split(File::SEPARATOR)
|
|
103
|
+
segments.pop if segments.last == "generated"
|
|
104
|
+
parent = segments.last.to_s
|
|
105
|
+
if parent.match?(/\A[a-zA-Z]\w*\z/) && !%w[graphql app lib spec support test].include?(parent)
|
|
106
|
+
"#{Inflect.camelize(parent)}Inputs"
|
|
107
|
+
else
|
|
108
|
+
"GraphQLInputs"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
68
112
|
# Generate every .graphql query in a directory into checked-in Ruby
|
|
69
113
|
# files. Paths default to the conventions above; schema: defaults to
|
|
70
114
|
# the dump at schema_path (any supported extension):
|
|
@@ -73,16 +117,28 @@ module GraphWeaver
|
|
|
73
117
|
#
|
|
74
118
|
# person.graphql => person_query.rb defining PersonQuery. Returns the
|
|
75
119
|
# written paths. Pair with a freshness spec (docs/generated_modules.md).
|
|
76
|
-
def generate!(schema: nil, queries: queries_path, output: generated_path, client: nil
|
|
120
|
+
def generate!(schema: nil, queries: queries_path, output: generated_path, client: nil,
|
|
121
|
+
shared_inputs: true, inputs_module: nil)
|
|
77
122
|
schema ||= locate_schema!
|
|
78
|
-
|
|
123
|
+
inputs_module ||= self.inputs_module(output)
|
|
79
124
|
|
|
80
|
-
|
|
81
|
-
|
|
125
|
+
plan = generation_plan(queries:, schema:, client:, shared_inputs:, inputs_module:)
|
|
126
|
+
written = plan.map do |filename, source|
|
|
127
|
+
target = File.join(output, filename)
|
|
128
|
+
FileUtils.mkdir_p(File.dirname(target))
|
|
82
129
|
File.write(target, source)
|
|
83
130
|
log(:info) { "generated #{target}" }
|
|
84
131
|
target
|
|
85
132
|
end
|
|
133
|
+
|
|
134
|
+
# a type dropped from the schema must not linger as a stale file —
|
|
135
|
+
# inputs/ is wholly generated, so pruning is safe
|
|
136
|
+
(Dir[File.join(output, "inputs", "*.rb")] - written).each do |orphan|
|
|
137
|
+
File.delete(orphan)
|
|
138
|
+
log(:info) { "pruned #{orphan}" }
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
written
|
|
86
142
|
end
|
|
87
143
|
|
|
88
144
|
# The freshness guard: raise unless every generated file matches what
|
|
@@ -92,12 +148,17 @@ module GraphWeaver
|
|
|
92
148
|
# it "generated queries are current" do
|
|
93
149
|
# GraphWeaver.verify_generated!
|
|
94
150
|
# end
|
|
95
|
-
def verify_generated!(schema: nil, queries: queries_path, output: generated_path, client: nil
|
|
151
|
+
def verify_generated!(schema: nil, queries: queries_path, output: generated_path, client: nil,
|
|
152
|
+
shared_inputs: true, inputs_module: nil)
|
|
96
153
|
schema ||= locate_schema!
|
|
97
|
-
|
|
98
|
-
|
|
154
|
+
inputs_module ||= self.inputs_module(output)
|
|
155
|
+
plan = generation_plan(queries:, schema:, client:, shared_inputs:, inputs_module:)
|
|
156
|
+
stale = plan.filter_map do |filename, source|
|
|
157
|
+
target = File.join(output, filename)
|
|
99
158
|
target unless File.exist?(target) && File.read(target) == source
|
|
100
159
|
end
|
|
160
|
+
# strays: a type file the current schema no longer produces
|
|
161
|
+
stale += Dir[File.join(output, "inputs", "*.rb")] - plan.map { |f, _| File.join(output, f) }
|
|
101
162
|
|
|
102
163
|
unless stale.empty?
|
|
103
164
|
raise Error, "stale generated queries — regenerate (rake graph_weaver:generate): #{stale.join(", ")}"
|
|
@@ -116,10 +177,11 @@ module GraphWeaver
|
|
|
116
177
|
# Generated::PersonQuery from generated/person_query.rb, and
|
|
117
178
|
# generated code only changes on regeneration anyway (restart, like
|
|
118
179
|
# a schema migration).
|
|
119
|
-
def load_generated!(path =
|
|
120
|
-
|
|
180
|
+
def load_generated!(path = nil)
|
|
181
|
+
paths = path ? [path] : generated_paths
|
|
182
|
+
files = paths.flat_map { |dir| Dir[File.join(dir, "**/*.rb")].sort }.uniq
|
|
121
183
|
files.each { |file| require File.expand_path(file) }
|
|
122
|
-
log(:info) { "loaded #{files.size} generated module(s) from #{
|
|
184
|
+
log(:info) { "loaded #{files.size} generated module(s) from #{paths.join(", ")}" }
|
|
123
185
|
files
|
|
124
186
|
end
|
|
125
187
|
|
|
@@ -130,20 +192,39 @@ module GraphWeaver
|
|
|
130
192
|
end
|
|
131
193
|
private :locate_schema!
|
|
132
194
|
|
|
133
|
-
# (
|
|
134
|
-
|
|
135
|
-
|
|
195
|
+
# (filename, source) per artifact: shared_inputs (the default) emits
|
|
196
|
+
# every variable type once into inputs.rb, with query modules
|
|
197
|
+
# aliasing what they use — the difference between hundreds of
|
|
198
|
+
# duplicated bool_exp structs and one copy per schema.
|
|
199
|
+
def generation_plan(queries:, schema:, client:, shared_inputs:, inputs_module: self.inputs_module)
|
|
200
|
+
namespace = shared_inputs ? inputs_module : nil
|
|
201
|
+
used = { inputs: [], enums: [], mapped: [] }
|
|
202
|
+
|
|
203
|
+
plan = Dir[File.join(queries, "*.graphql")].sort.map do |path|
|
|
136
204
|
base = File.basename(path, ".graphql")
|
|
137
|
-
|
|
205
|
+
codegen = Codegen.new(
|
|
138
206
|
schema:,
|
|
139
207
|
query: File.read(path),
|
|
140
208
|
module_name: "#{Inflect.camelize(base)}Query",
|
|
141
209
|
client:,
|
|
210
|
+
inputs_namespace: namespace,
|
|
142
211
|
)
|
|
143
|
-
|
|
212
|
+
source = codegen.generate
|
|
213
|
+
codegen.variable_type_names.each { |kind, names| used[kind] |= names }
|
|
214
|
+
["#{base}_query.rb", source]
|
|
144
215
|
end
|
|
216
|
+
|
|
217
|
+
if namespace && used.values.any?(&:any?)
|
|
218
|
+
shared = Codegen.generate_inputs(
|
|
219
|
+
schema:, module_name: namespace,
|
|
220
|
+
input_types: used[:inputs], enum_types: used[:enums] + used[:mapped],
|
|
221
|
+
)
|
|
222
|
+
plan = shared.to_a + plan
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
plan
|
|
145
226
|
end
|
|
146
|
-
private :
|
|
227
|
+
private :generation_plan
|
|
147
228
|
|
|
148
229
|
# Default input coercion for scalars that don't say coerce: themselves,
|
|
149
230
|
# resolved lazily at generation time (so set it any time before you
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: graph_weaver
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel Pepper
|
|
@@ -255,6 +255,7 @@ files:
|
|
|
255
255
|
- lib/graph_weaver/errors.rb
|
|
256
256
|
- lib/graph_weaver/hints.rb
|
|
257
257
|
- lib/graph_weaver/inflect.rb
|
|
258
|
+
- lib/graph_weaver/input_struct.rb
|
|
258
259
|
- lib/graph_weaver/logging.rb
|
|
259
260
|
- lib/graph_weaver/railtie.rb
|
|
260
261
|
- lib/graph_weaver/response.rb
|