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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2cb0372c9f8505bec490fbb13344cbd586da68cb4932cbce0f86de2467d0eaf4
4
- data.tar.gz: 6bc17449eea717c8cb145c159a2395f61f484a2f585c016ee31521f979f1a6d6
3
+ metadata.gz: 653ecbcc2215bf5358e34bc83d701ded874a6ee1910ceeca18f0ed205a44798f
4
+ data.tar.gz: 2d1e49f247e84347c7bd86123b54b1b9d31d8978bc2a6a8f8d9538342c3ab67d
5
5
  SHA512:
6
- metadata.gz: 63670eee3838644725166822028b4816164450c853e24d74dd6ce47212e4e18c2bda7309d5549f8830e709d547e73678425ae9984d960a5367b97f5102613f5c
7
- data.tar.gz: d7b30350095090f9c69e7b5316137ac5324b60965a242ffe5c9be4a893812a48351a172bde26f36000797d830eb01c3e15ea0352eea8ecda9ac775f55b12ac82
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.2)
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.2.0)
199
- sorbet (0.6.13485)
200
- sorbet-static (= 0.6.13485)
201
- sorbet-runtime (0.6.13485)
202
- sorbet-static (0.6.13485-aarch64-linux)
203
- sorbet-static (0.6.13485-universal-darwin)
204
- sorbet-static (0.6.13485-x86_64-linux)
205
- sorbet-static-and-runtime (0.6.13485)
206
- sorbet (= 0.6.13485)
207
- sorbet-runtime (= 0.6.13485)
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.2)
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.2.0) sha256=ea6acd05eece5a41990e2a5171c57d15700d329326c7666c85ee8c6a0dd0977e
354
- sorbet (0.6.13485) sha256=b885d6a2fcde57bc46c3cdd597b9014b82619dee0f811a6fdf36f57c8f82c380
355
- sorbet-runtime (0.6.13485) sha256=2982504e662eb424e515b3aa1c8139070451a014b6c1af2302250ed82495b4cc
356
- sorbet-static (0.6.13485-aarch64-linux) sha256=5d802793633363442f189615fdb6c6d1be726b402d78a3d63eab96ca74b98d40
357
- sorbet-static (0.6.13485-universal-darwin) sha256=6c7c4551872d556b37bf1f1a3495509b9fd9479b4a080bd57f9c5db0e8116658
358
- sorbet-static (0.6.13485-x86_64-linux) sha256=e22d88df0bba5a97e664a329a2212ea59e6a2020626597a162c60c5ddfb4f9b4
359
- sorbet-static-and-runtime (0.6.13485) sha256=b893346f9e1a244133071ad3c3d06b8ddf70c1b8246bb594c9aaa5cfc778df5f
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
@@ -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
- boot from a `to_prepare` block, after your initializers and after any
682
- registrations of your own in one. Elsewhere it's explicit, factory_bot-style:
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
@@ -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 boot from a `to_prepare` block, so a helper or enum you registered is
103
- already in place when the file that names it loads.
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
@@ -1,7 +1,7 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- # Generated by GraphWeaver 0.7.2 — do not edit.
4
+ # Generated by GraphWeaver 0.7.3 — do not edit.
5
5
 
6
6
  module StarMutation
7
7
  extend T::Sig
@@ -1,7 +1,7 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- # Generated by GraphWeaver 0.7.2 — do not edit.
4
+ # Generated by GraphWeaver 0.7.3 — do not edit.
5
5
 
6
6
  require "time"
7
7
 
@@ -1,7 +1,7 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- # Generated by GraphWeaver 0.7.2 — do not edit.
4
+ # Generated by GraphWeaver 0.7.3 — do not edit.
5
5
 
6
6
  module StarredQuery
7
7
  extend T::Sig
@@ -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 at boot once every registration has run —
11
- # both the initializer kind and the to_prepare kind, since a generated
12
- # file `include`s the type helper it was generated with and that
13
- # constant must resolve. load_generated! stays idempotent, so calling
14
- # it yourself too is harmless.
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 to_prepare block below can ask it whether a
66
- # query changed. nil when not watching.
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 ignore_generated actually hid, resolved. Zeitwerk only reads its
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
- # The two auto-wires an app gets for free, and the only two it can turn off
80
- # by assigning nil. They are declared FIRST, and `before:
81
- # :load_config_initializers`, and both of those are load-bearing:
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
- # Rails gives an initializer an implicit `after:` of the previous one in the
84
- # same railtie (Initializable#initializer: `opts[:after] ||=
85
- # initializers.last&.name`). Declared after ignore_generated, which is
86
- # `after: :load_config_initializers`, these two inherited that position —
87
- # they ran AFTER config/initializers, so the `if nil?` fallback overwrote an
88
- # app that had just said nil and the documented PII opt-out did nothing,
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
- # An APM sees every GraphQL call without the app configuring anything:
100
- # the ActiveSupport::Notifications adapter from docs/logging.md, plus the
101
- # LogSubscriber that turns its event into one line. Measured at ~4.5µs per
102
- # execution all told (0.13µs of that ActiveSupport::Notifications itself
103
- # with nothing subscribed; the rest is its Event machinery) — 0.05% of a
104
- # 10ms round trip, so there is nothing to weigh.
105
- #
106
- # An instrumenter the app set is never replaced: assigned before this (in
107
- # config/application.rb) the nil check leaves it, and config/initializers now
108
- # genuinely runs later, so one assigned there wins on its own — including
109
- # `GraphWeaver.instrumenter = nil` to opt out.
110
- initializer "graph_weaver.instrumentation", before: :load_config_initializers do
111
- next unless defined?(ActiveSupport::Notifications)
112
-
113
- if GraphWeaver.instrumenter.nil?
114
- GraphWeaver.instrumenter = lambda do |event, payload, &block|
115
- ActiveSupport::Notifications.instrument(event, payload, &block)
116
- end
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
- # generated/person_query.rb defines ::PersonQuery, but Zeitwerk infers
127
- # Generated::PersonQuery from the path and app/graphql/generated is
128
- # inside an autoload root by default, so eager loading raised
129
- # "uninitialized constant Generated::PersonQuery" in production while
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
- # after: :load_config_initializers as well as before Zeitwerk's setup a
133
- # graph's output: is only known once the app has declared its graphs, and
134
- # with only the `before:` constraint this ran ~20 initializers too early, so
135
- # an output outside the conventional glob was eager loaded on top of
136
- # load_generated! and died on a redefined enum.
137
- initializer "graph_weaver.ignore_generated",
138
- after: :load_config_initializers, before: :setup_main_autoloader do
139
- # patterns, not paths generated_paths may be globs, and Zeitwerk
140
- # expands its own at setup (which is what this runs before)
141
- dirs = GraphWeaver::Internal::Util.generated_dirs.map { GraphWeaver::Railtie.autoload_path(_1) }
142
- GraphWeaver::Railtie.check_autoload_once!(dirs)
143
- GraphWeaver::Railtie.ignored_dirs = dirs
144
- Rails.autoloaders.each { |loader| dirs.each { |path| loader.ignore(path) } }
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, and Zeitwerk reads its ignore
148
- # list only at setup so the `loader.ignore` above, which runs after
149
- # config/initializers, hides nothing from it however the output is spelled.
150
- # The generic advice ("name it in GraphWeaver.generated_paths from
151
- # config/initializers") produced byte-identical output for this one, so it
152
- # gets its own refusal, naming the place that is still early enough.
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. after: :load_config_initializers,
238
- # since filter_parameter_logging.rb is where an app adds to it.
239
- initializer "graph_weaver.filter_parameters", after: :load_config_initializers do |app|
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
- next if filters.empty? || GraphWeaver.filter_parameters != GraphWeaver::DEFAULT_FILTER_PARAMETERS
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 to_prepare below regenerates before it loads. Off with
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
- # after: :load_config_initializers that's where an app moves
254
- # queries_paths, and the finisher that reads app.reloaders runs later still.
255
- #
256
- # to_prepare, not the initializer itself: a graph declared from one of those
257
- # (what the docs say to do when its block names an autoloaded constant) isn't
258
- # declared until every initializer has run, and a watcher built before it
259
- # watched the default queries_paths — an edit to that graph's .graphql
260
- # silently never regenerated. to_prepare blocks run in registration order and
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 to_prepare, so a watcher could only
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
- # the app's own to_prepare blocks (where extend_type/register_enum are told
326
- # to register, Codegen::AUTOLOAD_HINT) happen after config/initializers.
327
- # to_prepare, not `after:` a finisher initializer: naming one there makes
328
- # tsort hoist it ahead of the app's own config/initializers. Re-running on
329
- # each dev reload is free require is idempotent and picks up a module
330
- # generated since boot.
331
- initializer "graph_weaver.load_generated", after: :load_config_initializers do |app|
332
- app.config.to_prepare do
333
- # The graph_weaver tasks write these files and need none of them loaded.
334
- # Loading them would let a stale one block its own repair: a dropped
335
- # extend_type leaves a dangling include, and generate depends on
336
- # :environment, so boot failed before the task that would regenerate it.
337
- next if GraphWeaver.skip_generated_load
338
-
339
- # the app's own to_prepare blocks have run by now, so this is the first
340
- # point that sees every graph and the last before one of them loads
341
- GraphWeaver::Railtie.check_generated_ignored!
342
-
343
- # Regenerate first, then load — and here rather than in the watcher's own
344
- # to_run, so an extend_type or register_enum the app registers in its own
345
- # to_prepare is already in place (that block was registered at
346
- # :load_config_initializers, so it has run by now). A run that
347
- # regenerated has already reloaded what it wrote.
348
- next if GraphWeaver::Railtie.watcher&.execute_if_updated
349
-
350
- # entries may be globs, so Dir[] rather than Dir.exist?
351
- generated = GraphWeaver::Internal::Util.generated_dirs.any? do |dir|
352
- Dir[GraphWeaver::Internal::Util.resolve(dir)].any?
353
- end
354
- next unless generated
355
-
356
- # `require` no-ops on a file it has already read — which is what we want,
357
- # except when the constant that file defined is gone. A graph's
358
- # `namespace:` is normally a module Zeitwerk owns (app/graphql/accounts/
359
- # implies Accounts), and unloading it on a dev reload takes the generated
360
- # module nested inside it with it; require then restores nothing and every
361
- # request 500s on "uninitialized constant Accounts::PersonQuery" until a
362
- # .graphql edit happens to trigger the watcher. An un-namespaced module
363
- # defines a top-level constant Zeitwerk never manages, so it survives.
364
- if GraphWeaver.graphs.any?(&:namespace)
365
- GraphWeaver.reload_generated!
366
- else
367
- GraphWeaver.load_generated!
368
- end
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
@@ -1,3 +1,3 @@
1
1
  module GraphWeaver
2
- VERSION = "0.7.2"
2
+ VERSION = "0.7.3"
3
3
  end
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.2
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.2/CHANGELOG.md
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