graph_weaver 0.7.2 → 0.7.3
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.lock +19 -19
- data/docs/generated_modules.md +3 -2
- data/docs/getting_started.md +3 -2
- data/examples/github/generated/star_mutation.rb +1 -1
- data/examples/github/generated/stargazers_query.rb +1 -1
- data/examples/github/generated/starred_query.rb +1 -1
- data/lib/graph_weaver/railtie.rb +202 -147
- data/lib/graph_weaver/version.rb +1 -1
- data/lib/graph_weaver.rb +16 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 653ecbcc2215bf5358e34bc83d701ded874a6ee1910ceeca18f0ed205a44798f
|
|
4
|
+
data.tar.gz: 2d1e49f247e84347c7bd86123b54b1b9d31d8978bc2a6a8f8d9538342c3ab67d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 220c5d9ab87c671a602b226b2789fee65307e3b8a85407fa5c979eb272cde0e27d551e0a6d5ef32ad2714a6eec211fcf0657d1a2b7eb7a53c33d063756d8e82c
|
|
7
|
+
data.tar.gz: 650c62d1ca107aaaccfa03ddd4686475c15ad68c0cf421be67379a7b88212af8366681da61208dee39ee2a68a468ca792f8cc9fee54c8982087924f927c2a2d1
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
graph_weaver (0.7.
|
|
4
|
+
graph_weaver (0.7.3)
|
|
5
5
|
graphql (>= 2.6.7)
|
|
6
6
|
sorbet-runtime
|
|
7
7
|
|
|
@@ -195,16 +195,16 @@ GEM
|
|
|
195
195
|
rubydex (0.2.7-x86_64-darwin)
|
|
196
196
|
rubydex (0.2.7-x86_64-linux)
|
|
197
197
|
securerandom (0.4.1)
|
|
198
|
-
simplecov (1.
|
|
199
|
-
sorbet (0.6.
|
|
200
|
-
sorbet-static (= 0.6.
|
|
201
|
-
sorbet-runtime (0.6.
|
|
202
|
-
sorbet-static (0.6.
|
|
203
|
-
sorbet-static (0.6.
|
|
204
|
-
sorbet-static (0.6.
|
|
205
|
-
sorbet-static-and-runtime (0.6.
|
|
206
|
-
sorbet (= 0.6.
|
|
207
|
-
sorbet-runtime (= 0.6.
|
|
198
|
+
simplecov (1.3.0)
|
|
199
|
+
sorbet (0.6.13498)
|
|
200
|
+
sorbet-static (= 0.6.13498)
|
|
201
|
+
sorbet-runtime (0.6.13498)
|
|
202
|
+
sorbet-static (0.6.13498-aarch64-linux)
|
|
203
|
+
sorbet-static (0.6.13498-universal-darwin)
|
|
204
|
+
sorbet-static (0.6.13498-x86_64-linux)
|
|
205
|
+
sorbet-static-and-runtime (0.6.13498)
|
|
206
|
+
sorbet (= 0.6.13498)
|
|
207
|
+
sorbet-runtime (= 0.6.13498)
|
|
208
208
|
spoom (1.8.3)
|
|
209
209
|
erubi (>= 1.10.0)
|
|
210
210
|
prism (>= 0.28.0)
|
|
@@ -299,7 +299,7 @@ CHECKSUMS
|
|
|
299
299
|
google-protobuf (4.35.1-arm64-darwin) sha256=d9c957df04fa89c749fa9a72a7b383eb4296efc9b2303dc6fd6fbe39c698ad6b
|
|
300
300
|
google-protobuf (4.35.1-x86_64-darwin) sha256=66b62b4df00931018a692806df66393efa960d6d2b7da69735187249f950d3ee
|
|
301
301
|
google-protobuf (4.35.1-x86_64-linux-gnu) sha256=c786439087512a3fbd199e9897d265b855f951d4027e218ea55e858d45969edd
|
|
302
|
-
graph_weaver (0.7.
|
|
302
|
+
graph_weaver (0.7.3)
|
|
303
303
|
graphql (2.6.10) sha256=9b7c8633767f516ff9d48a8d6305b2a00a2101c82aa871b92e69086944f9f83e
|
|
304
304
|
hashdiff (1.2.1) sha256=9c079dbc513dfc8833ab59c0c2d8f230fa28499cc5efb4b8dd276cf931457cd1
|
|
305
305
|
i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5
|
|
@@ -350,13 +350,13 @@ CHECKSUMS
|
|
|
350
350
|
rubydex (0.2.7-x86_64-darwin) sha256=b002b259d118ac69de44470eff1597143318402c45630c47371f9542631447dc
|
|
351
351
|
rubydex (0.2.7-x86_64-linux) sha256=dacfade9fa42ce4469618da6dac07e69d5f3ac6a313b4caced5234c8f052419a
|
|
352
352
|
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
|
|
353
|
-
simplecov (1.
|
|
354
|
-
sorbet (0.6.
|
|
355
|
-
sorbet-runtime (0.6.
|
|
356
|
-
sorbet-static (0.6.
|
|
357
|
-
sorbet-static (0.6.
|
|
358
|
-
sorbet-static (0.6.
|
|
359
|
-
sorbet-static-and-runtime (0.6.
|
|
353
|
+
simplecov (1.3.0) sha256=d9886307863c1ead47657dcbed869bfffd82318808b09014d8f0ff77dcfe7492
|
|
354
|
+
sorbet (0.6.13498) sha256=cb5279f6a9dbfc8d526bc9fb71d05020afb8043c40708e4aff2244ba329ce224
|
|
355
|
+
sorbet-runtime (0.6.13498) sha256=2da4627f9341a1fc6e4148cc891989b7b108106c206c46ca69940e10a21e9133
|
|
356
|
+
sorbet-static (0.6.13498-aarch64-linux) sha256=cbee732c49379d38029f295b45edaeb5757ab180d308d8383dc79f03fd4991be
|
|
357
|
+
sorbet-static (0.6.13498-universal-darwin) sha256=4cc97aa0da3dd175b8278e1617954d6dd9ba1f5aeb62981f21616b9ec791fb75
|
|
358
|
+
sorbet-static (0.6.13498-x86_64-linux) sha256=75d02e8af7d6c1081d4693e2ebefe679760327f1304377519970784faf9f913d
|
|
359
|
+
sorbet-static-and-runtime (0.6.13498) sha256=5f5686869dceae893b4171b05e3db159fe12f1aadf04aa8c2bf9dfa3edc0d902
|
|
360
360
|
spoom (1.8.3) sha256=32871fa189bbfa49cf557a50f819f23cc9a6ceefd0346caa7a6adc193becd5dd
|
|
361
361
|
tapioca (0.19.2) sha256=938731b07811aee8d23871b1aee8861d464fbaf2cfffbf79a62b0c869a5120ec
|
|
362
362
|
thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73
|
data/docs/generated_modules.md
CHANGED
|
@@ -678,8 +678,9 @@ spot a *schema* change for you — `schema:diff`, `schema:refresh`, `queries:che
|
|
|
678
678
|
### Loading what it wrote
|
|
679
679
|
|
|
680
680
|
In Rails, loading is automatic — the Railtie requires every generated file at
|
|
681
|
-
|
|
682
|
-
|
|
681
|
+
the end of boot, after your initializers and after any registrations of your own
|
|
682
|
+
in a `to_prepare` block, and again after each development reload. Elsewhere it's
|
|
683
|
+
explicit, factory_bot-style:
|
|
683
684
|
`GraphWeaver.load_generated!` requires every file under `generated_paths`.
|
|
684
685
|
|
|
685
686
|
**Outside Rails, four things have to agree**, and nothing wires them together for
|
data/docs/getting_started.md
CHANGED
|
@@ -99,8 +99,9 @@ What it wrote:
|
|
|
99
99
|
|
|
100
100
|
Rake needs no wiring: in Rails the `graph_weaver:*` tasks register themselves and
|
|
101
101
|
depend on `:environment`, so your initializer runs first. The generated modules
|
|
102
|
-
load at
|
|
103
|
-
already in place when the
|
|
102
|
+
load at the end of boot, after your initializers and after any `to_prepare`
|
|
103
|
+
block of yours, so a helper or enum you registered is already in place when the
|
|
104
|
+
file that names it loads.
|
|
104
105
|
|
|
105
106
|
All of that describes **one** schema, which is the usual case; an app with a
|
|
106
107
|
second one declares each as a graph ([more than one
|
data/lib/graph_weaver/railtie.rb
CHANGED
|
@@ -7,16 +7,25 @@
|
|
|
7
7
|
# rake_tasks block, so graph_weaver:* tasks appear with no Rakefile
|
|
8
8
|
# edit. (Outside Rails there is no task-discovery hook — add
|
|
9
9
|
# `require "graph_weaver/tasks"` to your Rakefile.)
|
|
10
|
-
# - generated modules: required
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
10
|
+
# - generated modules: required once every registration has run — both the
|
|
11
|
+
# initializer kind and the to_prepare kind, since a generated file
|
|
12
|
+
# `include`s the type helper it was generated with and that constant must
|
|
13
|
+
# resolve. load_generated! stays idempotent, so calling it yourself too is
|
|
14
|
+
# harmless.
|
|
15
15
|
# - Zeitwerk: the generated directory is hidden from it, since the
|
|
16
16
|
# default one lives under app/ and its files define top-level
|
|
17
17
|
# constants.
|
|
18
18
|
# - watch mode: in development, editing a .graphql regenerates before the
|
|
19
19
|
# next request, the way editing a route or a locale takes effect.
|
|
20
|
+
#
|
|
21
|
+
# All of that is hung off Rails' lifecycle HOOKS — before_initialize,
|
|
22
|
+
# after_initialize, the reloader — rather than off initializers with
|
|
23
|
+
# `after:`/`before:` edges naming other railties' initializers. Rails
|
|
24
|
+
# topologically sorts every railtie's initializers together, so such an edge
|
|
25
|
+
# constrains the whole app's boot order and tsort is free to satisfy it by
|
|
26
|
+
# moving initializers that aren't ours: a real app failed to boot with
|
|
27
|
+
# graph_weaver in the Gemfile and every one of these bodies neutered. A hook
|
|
28
|
+
# has a fixed place in boot and adds no edge. See DECISIONS.md.
|
|
20
29
|
class GraphWeaver::Railtie < Rails::Railtie
|
|
21
30
|
# config.graph_weaver.watch — false to never regenerate during a request.
|
|
22
31
|
# Default: development only. Every other setting is a top-level one, and an
|
|
@@ -62,94 +71,87 @@ class GraphWeaver::Railtie < Rails::Railtie
|
|
|
62
71
|
config.graph_weaver = Options.new
|
|
63
72
|
|
|
64
73
|
class << self
|
|
65
|
-
# The file watcher, so the
|
|
66
|
-
#
|
|
74
|
+
# The file watcher, so the prepare hook below can ask it whether a query
|
|
75
|
+
# changed. nil when not watching.
|
|
67
76
|
attr_accessor :watcher
|
|
68
77
|
|
|
69
|
-
# What
|
|
78
|
+
# What has been hidden from Zeitwerk, resolved. Zeitwerk only reads its
|
|
70
79
|
# ignore list at setup, so anything that arrives later isn't hidden by
|
|
71
80
|
# calling ignore again — check_generated_ignored! refuses instead.
|
|
72
81
|
attr_accessor :ignored_dirs
|
|
82
|
+
|
|
83
|
+
# Whether hiding an output right now would actually hide it: false until
|
|
84
|
+
# the initializer below has swept (it picks up anything declared so far),
|
|
85
|
+
# and false again from the moment Zeitwerk sets the main autoloader up.
|
|
86
|
+
attr_accessor :hiding_outputs
|
|
73
87
|
end
|
|
74
88
|
|
|
75
89
|
rake_tasks do
|
|
76
90
|
require "graph_weaver/tasks"
|
|
77
91
|
end
|
|
78
92
|
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
93
|
+
# ONE initializer, and it declares no `after:` — see the note on the class.
|
|
94
|
+
# The `before:` that is left is inert: it only says "not after Zeitwerk's
|
|
95
|
+
# setup", and this is emitted at its own place in the railties block anyway,
|
|
96
|
+
# so nothing moves. It records the deadline.
|
|
82
97
|
#
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
# silently, while queries and variables kept reaching a debug Rails.logger.
|
|
90
|
-
# Adding the `before:` without moving them is a TSort::Cyclic at boot, since
|
|
91
|
-
# the implicit `after:` would then point through ignore_generated.
|
|
92
|
-
#
|
|
93
|
-
# Rails.logger, unless the app already chose one (set
|
|
94
|
-
# GraphWeaver.logger = nil in an initializer to silence)
|
|
95
|
-
initializer "graph_weaver.logger", before: :load_config_initializers do
|
|
96
|
-
GraphWeaver.logger = Rails.logger if GraphWeaver.logger.nil?
|
|
98
|
+
# What it sweeps is what config/application.rb configured. A graph declared
|
|
99
|
+
# in config/initializers is later than this and hides its own output as it is
|
|
100
|
+
# declared (ignore_output!) — which is where that knowledge arrives, and so
|
|
101
|
+
# needs no edge to wait for.
|
|
102
|
+
initializer "graph_weaver.ignore_generated", before: :setup_main_autoloader do
|
|
103
|
+
GraphWeaver::Railtie.hide_generated!
|
|
97
104
|
end
|
|
98
105
|
|
|
99
|
-
#
|
|
100
|
-
# the
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
106
|
+
# generated/person_query.rb defines ::PersonQuery, but Zeitwerk infers
|
|
107
|
+
# Generated::PersonQuery from the path — and app/graphql/generated is inside
|
|
108
|
+
# an autoload root by default, so eager loading raised "uninitialized
|
|
109
|
+
# constant Generated::PersonQuery" in production while development (lazy) was
|
|
110
|
+
# fine. prepare_generated! requires them instead.
|
|
111
|
+
def self.hide_generated!
|
|
112
|
+
self.ignored_dirs = []
|
|
113
|
+
self.hiding_outputs = true
|
|
114
|
+
GraphWeaver::Internal::Util.generated_dirs.each { |path| ignore_output!(path) }
|
|
115
|
+
|
|
116
|
+
# Zeitwerk reads its ignore list once, at setup, and nothing in Rails
|
|
117
|
+
# announces that moment — Zeitwerk itself does. With no such signal, shut
|
|
118
|
+
# the window here: an ignore registered after setup hides nothing, and
|
|
119
|
+
# check_generated_ignored! has to be the one to say so.
|
|
120
|
+
main = Rails.autoloaders.main if Rails.autoloaders.respond_to?(:main)
|
|
121
|
+
if main.respond_to?(:on_setup)
|
|
122
|
+
main.on_setup { GraphWeaver::Railtie.hiding_outputs = false }
|
|
123
|
+
else
|
|
124
|
+
self.hiding_outputs = false
|
|
117
125
|
end
|
|
118
|
-
|
|
119
|
-
# ActiveSupport::LogSubscriber is one of ActiveSupport's own eager
|
|
120
|
-
# autoloads, so naming it is enough — no require of theirs needed
|
|
121
|
-
require "graph_weaver/log_subscriber"
|
|
122
|
-
# idempotent — Subscriber.add_event_subscriber skips a pattern it already has
|
|
123
|
-
GraphWeaver::LogSubscriber.attach_to :graph_weaver
|
|
124
126
|
end
|
|
125
127
|
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
#
|
|
130
|
-
# development (lazy) was fine. load_generated! below requires them.
|
|
128
|
+
# Hide one output from Zeitwerk, the moment it is named — a graph declared in
|
|
129
|
+
# config/initializers says where it writes long after the sweep above ran.
|
|
130
|
+
# Waiting for it instead is what `after: :load_config_initializers` used to
|
|
131
|
+
# buy, at the cost of the whole app's boot order.
|
|
131
132
|
#
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
#
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
133
|
+
# Outside the window this is a no-op on purpose: before it, the sweep will
|
|
134
|
+
# pick the path up; after it, calling Zeitwerk's `ignore` would only teach
|
|
135
|
+
# check_generated_ignored! a lie.
|
|
136
|
+
def self.ignore_output!(path)
|
|
137
|
+
return unless hiding_outputs
|
|
138
|
+
|
|
139
|
+
# patterns, not paths — generated_paths may be globs, and Zeitwerk expands
|
|
140
|
+
# its own at setup, which is what the window stays open until
|
|
141
|
+
dir = autoload_path(path)
|
|
142
|
+
return if ignored_dirs.include?(dir)
|
|
143
|
+
|
|
144
|
+
check_autoload_once!([dir])
|
|
145
|
+
ignored_dirs << dir
|
|
146
|
+
Rails.autoloaders.each { |loader| loader.ignore(dir) }
|
|
145
147
|
end
|
|
146
148
|
|
|
147
|
-
# The `once` autoloader is set up in bootstrap,
|
|
148
|
-
# list only at setup
|
|
149
|
-
#
|
|
150
|
-
#
|
|
151
|
-
#
|
|
152
|
-
#
|
|
149
|
+
# The `once` autoloader is set up in bootstrap, before any of this — and
|
|
150
|
+
# Zeitwerk reads its ignore list only at setup, so `loader.ignore` hides
|
|
151
|
+
# nothing from it however the output is spelled. The generic advice ("name it
|
|
152
|
+
# in GraphWeaver.generated_paths from config/initializers") produced
|
|
153
|
+
# byte-identical output for this one, so it gets its own refusal, naming the
|
|
154
|
+
# place that is still early enough.
|
|
153
155
|
def self.check_autoload_once!(dirs)
|
|
154
156
|
return unless Rails.respond_to?(:autoloaders) && Rails.autoloaders.respond_to?(:once)
|
|
155
157
|
|
|
@@ -232,13 +234,64 @@ class GraphWeaver::Railtie < Rails::Railtie
|
|
|
232
234
|
end
|
|
233
235
|
end
|
|
234
236
|
|
|
237
|
+
# The two auto-wires an app gets for free, and the only two it can turn off
|
|
238
|
+
# by assigning nil.
|
|
239
|
+
#
|
|
240
|
+
# before_initialize runs in Rails' own :bootstrap_hook — after
|
|
241
|
+
# :initialize_logger and before the first railtie initializer, so the default
|
|
242
|
+
# is in place for anything that boots, and config/initializers still runs
|
|
243
|
+
# later and still wins. That timing used to be a `before:
|
|
244
|
+
# :load_config_initializers` edge, and getting it wrong shipped: declared
|
|
245
|
+
# without one, these ran AFTER config/initializers, so the `if nil?` fallback
|
|
246
|
+
# overwrote an app that had just said nil — the documented PII opt-out did
|
|
247
|
+
# nothing, silently, while queries and variables kept reaching a debug
|
|
248
|
+
# Rails.logger.
|
|
249
|
+
config.before_initialize { GraphWeaver::Railtie.default_logger! }
|
|
250
|
+
|
|
251
|
+
# Rails.logger, unless the app already chose one (set GraphWeaver.logger =
|
|
252
|
+
# nil in an initializer to silence)
|
|
253
|
+
def self.default_logger!
|
|
254
|
+
GraphWeaver.logger = Rails.logger if GraphWeaver.logger.nil?
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# An APM sees every GraphQL call without the app configuring anything:
|
|
258
|
+
# the ActiveSupport::Notifications adapter from docs/logging.md, plus the
|
|
259
|
+
# LogSubscriber that turns its event into one line. Measured at ~4.5µs per
|
|
260
|
+
# execution all told (0.13µs of that ActiveSupport::Notifications itself
|
|
261
|
+
# with nothing subscribed; the rest is its Event machinery) — 0.05% of a
|
|
262
|
+
# 10ms round trip, so there is nothing to weigh.
|
|
263
|
+
#
|
|
264
|
+
# An instrumenter the app set is never replaced: assigned before this (in
|
|
265
|
+
# config/application.rb) the nil check leaves it, and config/initializers runs
|
|
266
|
+
# later, so one assigned there wins on its own — including
|
|
267
|
+
# `GraphWeaver.instrumenter = nil` to opt out.
|
|
268
|
+
config.before_initialize { GraphWeaver::Railtie.default_instrumenter! }
|
|
269
|
+
|
|
270
|
+
def self.default_instrumenter!
|
|
271
|
+
return unless defined?(ActiveSupport::Notifications)
|
|
272
|
+
|
|
273
|
+
if GraphWeaver.instrumenter.nil?
|
|
274
|
+
GraphWeaver.instrumenter = lambda do |event, payload, &block|
|
|
275
|
+
ActiveSupport::Notifications.instrument(event, payload, &block)
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# ActiveSupport::LogSubscriber is one of ActiveSupport's own eager
|
|
280
|
+
# autoloads, so naming it is enough — no require of theirs needed
|
|
281
|
+
require "graph_weaver/log_subscriber"
|
|
282
|
+
# idempotent — Subscriber.add_event_subscriber skips a pattern it already has
|
|
283
|
+
GraphWeaver::LogSubscriber.attach_to :graph_weaver
|
|
284
|
+
end
|
|
285
|
+
|
|
235
286
|
# The app already declared what is sensitive, so variables logged at debug
|
|
236
287
|
# honour the same list as its request logs — including the Procs and dotted
|
|
237
|
-
# paths only ParameterFilter understands.
|
|
238
|
-
#
|
|
239
|
-
|
|
288
|
+
# paths only ParameterFilter understands. after_initialize, since
|
|
289
|
+
# filter_parameter_logging.rb is where an app adds to it.
|
|
290
|
+
config.after_initialize { |app| GraphWeaver::Railtie.adopt_filter_parameters!(app) }
|
|
291
|
+
|
|
292
|
+
def self.adopt_filter_parameters!(app)
|
|
240
293
|
filters = app.config.filter_parameters
|
|
241
|
-
|
|
294
|
+
return if filters.empty? || GraphWeaver.filter_parameters != GraphWeaver::DEFAULT_FILTER_PARAMETERS
|
|
242
295
|
|
|
243
296
|
GraphWeaver.filter_parameters = ActiveSupport::ParameterFilter.new(filters)
|
|
244
297
|
end
|
|
@@ -246,41 +299,31 @@ class GraphWeaver::Railtie < Rails::Railtie
|
|
|
246
299
|
# Watch mode. A .graphql edit should reach the next request the way a route
|
|
247
300
|
# or a locale change does, so the query directories and the schema dump
|
|
248
301
|
# become one of Rails' own reloaders: a change there alone triggers a reload
|
|
249
|
-
# cycle, and the
|
|
302
|
+
# cycle, and the prepare hook below regenerates before it loads. Off with
|
|
250
303
|
#
|
|
251
304
|
# config.graph_weaver.watch = false
|
|
252
305
|
#
|
|
253
|
-
#
|
|
254
|
-
#
|
|
255
|
-
#
|
|
256
|
-
#
|
|
257
|
-
#
|
|
258
|
-
#
|
|
259
|
-
#
|
|
260
|
-
|
|
261
|
-
# an app registers its own during :load_config_initializers, which this is
|
|
262
|
-
# `after:`, so every graph is in by the time this runs; app.reloaders is read
|
|
263
|
-
# per request, so joining it this late still counts. Once, though: a dev
|
|
264
|
-
# reload re-runs to_prepare, and a second watcher is a second reloader over
|
|
265
|
-
# the same files.
|
|
266
|
-
initializer "graph_weaver.watch", after: :load_config_initializers do |app|
|
|
267
|
-
watched = false
|
|
268
|
-
app.config.to_prepare do
|
|
269
|
-
next if watched
|
|
270
|
-
|
|
271
|
-
watched = true
|
|
272
|
-
GraphWeaver::Railtie.watch!(app)
|
|
273
|
-
end
|
|
274
|
-
end
|
|
306
|
+
# after_initialize, because every graph has to be declared before the watcher
|
|
307
|
+
# is built: a graph declared from a to_prepare block (what the docs say to do
|
|
308
|
+
# when its block names an autoloaded constant) isn't declared until the
|
|
309
|
+
# prepare callbacks have run, and a watcher built before it watched the
|
|
310
|
+
# default queries_paths — an edit to that graph's .graphql silently never
|
|
311
|
+
# regenerated. app.reloaders is read per request, so joining it this late
|
|
312
|
+
# still counts.
|
|
313
|
+
config.after_initialize { |app| GraphWeaver::Railtie.watch!(app) }
|
|
275
314
|
|
|
276
315
|
# Registers the watcher, and says so: this is the one thing GraphWeaver does
|
|
277
316
|
# that writes a checked-in file outside a rake task. Returns it, or nil when
|
|
278
317
|
# nothing is being watched.
|
|
279
318
|
def self.watch!(app)
|
|
319
|
+
# exactly one watcher per app, however many times this is called — a second
|
|
320
|
+
# one is a second reloader polling the same files
|
|
321
|
+
app.reloaders.delete(watcher) if watcher
|
|
322
|
+
|
|
280
323
|
watch = app.config.graph_weaver.watch
|
|
281
324
|
watch = Rails.env.development? if watch.nil?
|
|
282
|
-
# with reloading off nothing re-runs
|
|
283
|
-
# promise something it can't do
|
|
325
|
+
# with reloading off nothing re-runs the prepare hook, so a watcher could
|
|
326
|
+
# only promise something it can't do
|
|
284
327
|
return self.watcher = nil unless watch && app.config.reloading_enabled?
|
|
285
328
|
|
|
286
329
|
# a directory that doesn't exist yet is still watched — FileUpdateChecker
|
|
@@ -321,51 +364,63 @@ class GraphWeaver::Railtie < Rails::Railtie
|
|
|
321
364
|
end
|
|
322
365
|
|
|
323
366
|
# A generated file `include`s the type helper it was generated with, so it
|
|
324
|
-
# can't load until that constant resolves — and both Zeitwerk's setup and
|
|
325
|
-
#
|
|
326
|
-
#
|
|
327
|
-
#
|
|
328
|
-
#
|
|
329
|
-
#
|
|
330
|
-
# generated
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
367
|
+
# can't load until that constant resolves — and both Zeitwerk's setup and the
|
|
368
|
+
# app's own to_prepare blocks (where extend_type/register_enum are told to
|
|
369
|
+
# register, Codegen::AUTOLOAD_HINT) happen after config/initializers.
|
|
370
|
+
# after_initialize is past all of them, and past the watcher above.
|
|
371
|
+
#
|
|
372
|
+
# Except where the app eager loads: an eager-loaded class may name a
|
|
373
|
+
# generated constant in its class body, and Zeitwerk can't resolve one (the
|
|
374
|
+
# directory is ignored, by design), so the modules have to be in place before
|
|
375
|
+
# Rails' :eager_load! rather than after it. before_eager_load is that point,
|
|
376
|
+
# and Rails only runs it when config.eager_load is on — which is exactly when
|
|
377
|
+
# after_initialize must not do the work a second time.
|
|
378
|
+
config.before_eager_load { GraphWeaver::Railtie.prepare_generated! }
|
|
379
|
+
|
|
380
|
+
config.after_initialize do |app|
|
|
381
|
+
GraphWeaver::Railtie.prepare_generated! unless app.config.eager_load
|
|
382
|
+
# and again on every dev reload, which picks up a module generated since
|
|
383
|
+
# boot and re-requires one whose namespace Zeitwerk just unloaded. On the
|
|
384
|
+
# reloader itself, not config.to_prepare: the :add_to_prepare_blocks
|
|
385
|
+
# finisher has already drained that list by now.
|
|
386
|
+
ActiveSupport::Reloader.to_prepare { GraphWeaver::Railtie.prepare_generated! }
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def self.prepare_generated!
|
|
390
|
+
# The graph_weaver tasks write these files and need none of them loaded.
|
|
391
|
+
# Loading them would let a stale one block its own repair: a dropped
|
|
392
|
+
# extend_type leaves a dangling include, and generate depends on
|
|
393
|
+
# :environment, so boot failed before the task that would regenerate it.
|
|
394
|
+
return if GraphWeaver.skip_generated_load
|
|
395
|
+
|
|
396
|
+
# every graph is declared by now, and this is the last point before one of
|
|
397
|
+
# them loads
|
|
398
|
+
check_generated_ignored!
|
|
399
|
+
|
|
400
|
+
# Regenerate first, then load — and here rather than in the watcher's own
|
|
401
|
+
# to_run, so an extend_type or register_enum the app registers in its own
|
|
402
|
+
# to_prepare is already in place. A run that regenerated has already
|
|
403
|
+
# reloaded what it wrote.
|
|
404
|
+
return if watcher&.execute_if_updated
|
|
405
|
+
|
|
406
|
+
# entries may be globs, so Dir[] rather than Dir.exist?
|
|
407
|
+
generated = GraphWeaver::Internal::Util.generated_dirs.any? do |dir|
|
|
408
|
+
Dir[GraphWeaver::Internal::Util.resolve(dir)].any?
|
|
409
|
+
end
|
|
410
|
+
return unless generated
|
|
411
|
+
|
|
412
|
+
# `require` no-ops on a file it has already read — which is what we want,
|
|
413
|
+
# except when the constant that file defined is gone. A graph's
|
|
414
|
+
# `namespace:` is normally a module Zeitwerk owns (app/graphql/accounts/
|
|
415
|
+
# implies Accounts), and unloading it on a dev reload takes the generated
|
|
416
|
+
# module nested inside it with it; require then restores nothing and every
|
|
417
|
+
# request 500s on "uninitialized constant Accounts::PersonQuery" until a
|
|
418
|
+
# .graphql edit happens to trigger the watcher. An un-namespaced module
|
|
419
|
+
# defines a top-level constant Zeitwerk never manages, so it survives.
|
|
420
|
+
if GraphWeaver.graphs.any?(&:namespace)
|
|
421
|
+
GraphWeaver.reload_generated!
|
|
422
|
+
else
|
|
423
|
+
GraphWeaver.load_generated!
|
|
369
424
|
end
|
|
370
425
|
end
|
|
371
426
|
end
|
data/lib/graph_weaver/version.rb
CHANGED
data/lib/graph_weaver.rb
CHANGED
|
@@ -195,6 +195,7 @@ module GraphWeaver
|
|
|
195
195
|
|
|
196
196
|
def generated_paths=(paths)
|
|
197
197
|
@generated_paths = paths && Array(paths)
|
|
198
|
+
hide_from_autoloading(generated_paths)
|
|
198
199
|
end
|
|
199
200
|
|
|
200
201
|
def fragments_paths=(paths)
|
|
@@ -287,9 +288,24 @@ module GraphWeaver
|
|
|
287
288
|
# generate! reports in however many times the initializer has re-run
|
|
288
289
|
existing = @graphs.index { |candidate| candidate.name == name }
|
|
289
290
|
existing ? @graphs[existing] = graph : @graphs << graph
|
|
291
|
+
hide_from_autoloading([graph.output])
|
|
290
292
|
graph
|
|
291
293
|
end
|
|
292
294
|
|
|
295
|
+
# Rails only: where a graph writes is hidden from Zeitwerk the moment the
|
|
296
|
+
# graph says it. Knowing it any later needed an initializer edge waiting on
|
|
297
|
+
# config/initializers, and such an edge reorders the whole app's boot (see
|
|
298
|
+
# the Railtie). Registered first, so a refusal can name the graph.
|
|
299
|
+
def hide_from_autoloading(paths)
|
|
300
|
+
# by name, not spelled: the railtie is Rails-only and `typed: ignore`, so
|
|
301
|
+
# sorbet has no constant to resolve
|
|
302
|
+
return unless const_defined?(:Railtie, false)
|
|
303
|
+
|
|
304
|
+
railtie = const_get(:Railtie, false)
|
|
305
|
+
paths.each { |path| railtie.ignore_output!(path) }
|
|
306
|
+
end
|
|
307
|
+
private :hide_from_autoloading
|
|
308
|
+
|
|
293
309
|
# Every graph an entry point walks: the declared ones, or the single graph
|
|
294
310
|
# the top-level settings describe. Never empty.
|
|
295
311
|
def graphs = @graphs&.dup || [default_graph]
|
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.7.
|
|
4
|
+
version: 0.7.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel Pepper
|
|
@@ -363,7 +363,7 @@ licenses:
|
|
|
363
363
|
- MIT
|
|
364
364
|
metadata:
|
|
365
365
|
bug_tracker_uri: https://github.com/dpep/graph_weaver/issues
|
|
366
|
-
changelog_uri: https://github.com/dpep/graph_weaver/blob/v0.7.
|
|
366
|
+
changelog_uri: https://github.com/dpep/graph_weaver/blob/v0.7.3/CHANGELOG.md
|
|
367
367
|
documentation_uri: https://github.com/dpep/graph_weaver/tree/main/docs
|
|
368
368
|
rubygems_mfa_required: 'true'
|
|
369
369
|
source_code_uri: https://github.com/dpep/graph_weaver
|