dry-system 0.19.2 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +472 -1
  3. data/LICENSE +1 -1
  4. data/README.md +4 -3
  5. data/dry-system.gemspec +16 -15
  6. data/lib/dry/system/auto_registrar.rb +1 -13
  7. data/lib/dry/system/component.rb +104 -47
  8. data/lib/dry/system/component_dir.rb +88 -47
  9. data/lib/dry/system/components.rb +8 -4
  10. data/lib/dry/system/config/component_dir.rb +141 -53
  11. data/lib/dry/system/config/component_dirs.rb +176 -70
  12. data/lib/dry/system/config/namespace.rb +76 -0
  13. data/lib/dry/system/config/namespaces.rb +208 -0
  14. data/lib/dry/system/constants.rb +2 -2
  15. data/lib/dry/system/container.rb +279 -201
  16. data/lib/dry/system/errors.rb +72 -61
  17. data/lib/dry/system/identifier.rb +99 -79
  18. data/lib/dry/system/importer.rb +83 -12
  19. data/lib/dry/system/indirect_component.rb +65 -0
  20. data/lib/dry/system/loader.rb +8 -4
  21. data/lib/dry/system/{manual_registrar.rb → manifest_registrar.rb} +12 -13
  22. data/lib/dry/system/plugins/bootsnap.rb +3 -2
  23. data/lib/dry/system/plugins/dependency_graph/strategies.rb +37 -1
  24. data/lib/dry/system/plugins/dependency_graph.rb +26 -20
  25. data/lib/dry/system/plugins/env.rb +3 -2
  26. data/lib/dry/system/plugins/logging.rb +9 -5
  27. data/lib/dry/system/plugins/monitoring.rb +1 -1
  28. data/lib/dry/system/plugins/notifications.rb +1 -1
  29. data/lib/dry/system/plugins/zeitwerk/compat_inflector.rb +22 -0
  30. data/lib/dry/system/plugins/zeitwerk.rb +109 -0
  31. data/lib/dry/system/plugins.rb +8 -7
  32. data/lib/dry/system/provider/source.rb +324 -0
  33. data/lib/dry/system/provider/source_dsl.rb +94 -0
  34. data/lib/dry/system/provider.rb +264 -24
  35. data/lib/dry/system/provider_registrar.rb +276 -0
  36. data/lib/dry/system/provider_source_registry.rb +70 -0
  37. data/lib/dry/system/provider_sources/settings/config.rb +86 -0
  38. data/lib/dry/system/provider_sources/settings/loader.rb +53 -0
  39. data/lib/dry/system/provider_sources/settings.rb +40 -0
  40. data/lib/dry/system/provider_sources.rb +5 -0
  41. data/lib/dry/system/stubs.rb +1 -1
  42. data/lib/dry/system/version.rb +1 -1
  43. data/lib/dry/system.rb +45 -13
  44. metadata +25 -22
  45. data/lib/dry/system/booter/component_registry.rb +0 -35
  46. data/lib/dry/system/booter.rb +0 -200
  47. data/lib/dry/system/components/bootable.rb +0 -289
  48. data/lib/dry/system/components/config.rb +0 -35
  49. data/lib/dry/system/lifecycle.rb +0 -135
  50. data/lib/dry/system/provider_registry.rb +0 -27
  51. data/lib/dry/system/settings/file_loader.rb +0 -30
  52. data/lib/dry/system/settings/file_parser.rb +0 -51
  53. data/lib/dry/system/settings.rb +0 -67
  54. data/lib/dry/system/system_components/settings.rb +0 -11
@@ -5,19 +5,21 @@ require "pathname"
5
5
  require "dry-auto_inject"
6
6
  require "dry-configurable"
7
7
  require "dry-container"
8
- require "dry/inflector"
9
-
10
8
  require "dry/core/deprecations"
9
+ require "dry/inflector"
11
10
 
12
- require "dry/system"
13
- require "dry/system/errors"
14
- require "dry/system/booter"
15
11
  require "dry/system/auto_registrar"
16
- require "dry/system/manual_registrar"
17
- require "dry/system/importer"
18
12
  require "dry/system/component"
19
13
  require "dry/system/constants"
14
+ require "dry/system/errors"
15
+ require "dry/system/identifier"
16
+ require "dry/system/importer"
17
+ require "dry/system/indirect_component"
18
+ require "dry/system/manifest_registrar"
20
19
  require "dry/system/plugins"
20
+ require "dry/system/provider_registrar"
21
+ require "dry/system/provider"
22
+ require "dry/system/provider/source"
21
23
 
22
24
  require_relative "component_dir"
23
25
  require_relative "config/component_dirs"
@@ -48,7 +50,7 @@ module Dry
48
50
  #
49
51
  # Every container needs to be configured with following settings:
50
52
  #
51
- # * `:name` - a unique container identifier
53
+ # * `:name` - a unique container name
52
54
  # * `:root` - a system root directory (defaults to `pwd`)
53
55
  #
54
56
  # @example
@@ -73,63 +75,98 @@ module Dry
73
75
  extend Dry::System::Plugins
74
76
 
75
77
  setting :name
76
- setting(:root, Pathname.pwd.freeze) { |path| Pathname(path) }
77
- setting :system_dir, "system"
78
- setting :bootable_dirs, ["system/boot"]
79
- setting :registrations_dir, "container"
80
- setting :component_dirs, Config::ComponentDirs.new, cloneable: true
81
- setting :inflector, Dry::Inflector.new
82
- setting :booter, Dry::System::Booter
83
- setting :auto_registrar, Dry::System::AutoRegistrar
84
- setting :manual_registrar, Dry::System::ManualRegistrar
85
- setting :importer, Dry::System::Importer
86
- setting(:components, {}, reader: true, &:dup)
78
+ setting :root, default: Pathname.pwd.freeze, constructor: -> path { Pathname(path) }
79
+ setting :provider_dirs, default: ["system/providers"]
80
+ setting :bootable_dirs # Deprecated for provider_dirs, see .provider_paths below
81
+ setting :registrations_dir, default: "system/registrations"
82
+ setting :component_dirs, default: Config::ComponentDirs.new, cloneable: true
83
+ setting :exports, reader: true
84
+ setting :inflector, default: Dry::Inflector.new
85
+ setting :auto_registrar, default: Dry::System::AutoRegistrar
86
+ setting :manifest_registrar, default: Dry::System::ManifestRegistrar
87
+ setting :provider_registrar, default: Dry::System::ProviderRegistrar
88
+ setting :importer, default: Dry::System::Importer
89
+
90
+ # We presume "." as key namespace separator. This is not intended to be
91
+ # user-configurable.
92
+ config.namespace_separator = KEY_SEPARATOR
87
93
 
88
94
  class << self
89
- def strategies(value = nil)
90
- if value
91
- @strategies = value
92
- else
93
- @strategies ||= Dry::AutoInject::Strategies
94
- end
95
- end
96
-
97
95
  extend Dry::Core::Deprecations["Dry::System::Container"]
98
96
 
99
- # Define a new configuration setting
97
+ # @!method config
98
+ # Returns the configuration for the container
100
99
  #
101
- # @see https://dry-rb.org/gems/dry-configurable
100
+ # @example
101
+ # container.config.root = "/path/to/app"
102
+ # container.config.root # => #<Pathname:/path/to/app>
102
103
  #
103
- # @api public
104
- def setting(name, *args, &block)
105
- super(name, *args, &block)
106
- # TODO: dry-configurable needs a public API for this
107
- config._settings << _settings[name]
108
- self
109
- end
110
- ruby2_keywords(:setting) if respond_to?(:ruby2_keywords, true)
104
+ # @return [Dry::Configurable::Config]
105
+ #
106
+ # @api public
111
107
 
112
- # Configures the container
108
+ # Yields a configuration object for the container, which you can use to modify the
109
+ # configuration, then runs the after-`configured` hooks and finalizes (freezes)
110
+ # the {config}.
111
+ #
112
+ # Does not finalize the config when given `finalize_config: false`
113
113
  #
114
114
  # @example
115
115
  # class MyApp < Dry::System::Container
116
116
  # configure do |config|
117
117
  # config.root = Pathname("/path/to/app")
118
118
  # config.name = :my_app
119
- # config.auto_register = %w(lib/apis lib/core)
120
119
  # end
121
120
  # end
122
121
  #
122
+ # @param finalize_config [Boolean]
123
+ #
123
124
  # @return [self]
124
125
  #
126
+ # @see after
127
+ #
125
128
  # @api public
126
- def configure(&block)
127
- hooks[:before_configure].each { |hook| instance_eval(&hook) }
129
+ def configure(finalize_config: true, &block)
128
130
  super(&block)
131
+
132
+ unless configured?
133
+ hooks[:after_configure].each { |hook| instance_eval(&hook) }
134
+ config.finalize! if finalize_config
135
+ @__configured__ = true
136
+ end
137
+
138
+ self
139
+ end
140
+
141
+ # Marks the container as configured, runs the after-`configured` hooks, then
142
+ # finalizes (freezes) the {config}.
143
+ #
144
+ # This method is useful to call if you're modifying the container's {config}
145
+ # directly, rather than via the config object yielded when calling {configure}.
146
+ #
147
+ # Does not finalize the config if given `finalize_config: false`.
148
+ #
149
+ # @param finalize_config [Boolean]
150
+ #
151
+ # @return [self]
152
+ #
153
+ # @see after
154
+ #
155
+ # @api public
156
+ def configured!(finalize_config: true)
157
+ return self if configured?
158
+
129
159
  hooks[:after_configure].each { |hook| instance_eval(&hook) }
160
+ config.finalize! if finalize_config
161
+ @__configured__ = true
162
+
130
163
  self
131
164
  end
132
165
 
166
+ def configured?
167
+ @__configured__.equal?(true)
168
+ end
169
+
133
170
  # Registers another container for import
134
171
  #
135
172
  # @example
@@ -156,124 +193,142 @@ module Dry
156
193
  # @param other [Hash, Dry::Container::Namespace]
157
194
  #
158
195
  # @api public
159
- def import(other)
160
- case other
161
- when Hash then importer.register(other)
162
- when Dry::Container::Namespace then super
163
- else
164
- raise ArgumentError, <<-STR
165
- +other+ must be a hash of names and systems, or a Dry::Container namespace
166
- STR
196
+ def import(keys: nil, from: nil, as: nil, **deprecated_import_hash)
197
+ if deprecated_import_hash.any?
198
+ Dry::Core::Deprecations.announce(
199
+ "Dry::System::Container.import with {namespace => container} hash",
200
+ "Use Dry::System::Container.import(from: container, as: namespace) instead",
201
+ tag: "dry-system",
202
+ uplevel: 1
203
+ )
204
+
205
+ deprecated_import_hash.each do |namespace, container|
206
+ importer.register(container: container, namespace: namespace)
207
+ end
208
+ return self
209
+ elsif from.nil? || as.nil?
210
+ # These keyword arguments can become properly required in the params list once
211
+ # we remove the deprecation shim above
212
+ raise ArgumentError, "required keyword arguments: :from, :as"
167
213
  end
214
+
215
+ importer.register(container: from, namespace: as, keys: keys)
216
+
217
+ self
168
218
  end
169
219
 
170
- # Registers finalization function for a bootable component
220
+ # rubocop:disable Layout/LineLength
221
+
222
+ # @overload register_provider(name, namespace: nil, from: nil, source: nil, if: true, &block)
223
+ # Registers a provider and its lifecycle hooks
171
224
  #
172
- # By convention, boot files for components should be placed in a
173
- # `bootable_dirs` entry and they will be loaded on demand when
174
- # components are loaded in isolation, or during the finalization
175
- # process.
225
+ # By convention, you should place a file for each provider in one of the
226
+ # configured `provider_dirs`, and they will be loaded on demand when components
227
+ # are loaded in isolation, or during container finalization.
176
228
  #
177
- # @example
178
- # # system/container.rb
179
- # class MyApp < Dry::System::Container
180
- # configure do |config|
181
- # config.root = Pathname("/path/to/app")
182
- # config.name = :core
183
- # config.auto_register = %w(lib/apis lib/core)
229
+ # @example
230
+ # # system/container.rb
231
+ # class MyApp < Dry::System::Container
232
+ # configure do |config|
233
+ # config.root = Pathname("/path/to/app")
234
+ # end
184
235
  # end
185
236
  #
186
- # # system/boot/db.rb
187
- # #
188
- # # Simple component registration
189
- # MyApp.boot(:db) do |container|
190
- # require 'db'
191
- #
192
- # container.register(:db, DB.new)
193
- # end
194
- #
195
- # # system/boot/db.rb
196
- # #
197
- # # Component registration with lifecycle triggers
198
- # MyApp.boot(:db) do |container|
199
- # init do
200
- # require 'db'
201
- # DB.configure(ENV['DB_URL'])
202
- # container.register(:db, DB.new)
237
+ # # system/providers/db.rb
238
+ # #
239
+ # # Simple provider registration
240
+ # MyApp.register_provider(:db) do
241
+ # start do
242
+ # require "db"
243
+ # register("db", DB.new)
244
+ # end
203
245
  # end
204
246
  #
205
- # start do
206
- # db.establish_connection
247
+ # # system/providers/db.rb
248
+ # #
249
+ # # Provider registration with lifecycle triggers
250
+ # MyApp.register_provider(:db) do |container|
251
+ # init do
252
+ # require "db"
253
+ # DB.configure(ENV["DB_URL"])
254
+ # container.register("db", DB.new)
255
+ # end
256
+ #
257
+ # start do
258
+ # container["db"].establish_connection
259
+ # end
260
+ #
261
+ # stop do
262
+ # container["db"].close_connection
263
+ # end
207
264
  # end
208
265
  #
209
- # stop do
210
- # db.close_connection
266
+ # # system/providers/db.rb
267
+ # #
268
+ # # Provider registration which uses another provider
269
+ # MyApp.register_provider(:db) do |container|
270
+ # start do
271
+ # use :logger
272
+ #
273
+ # require "db"
274
+ # DB.configure(ENV['DB_URL'], logger: logger)
275
+ # container.register("db", DB.new)
276
+ # end
211
277
  # end
212
- # end
213
278
  #
214
- # # system/boot/db.rb
215
- # #
216
- # # Component registration which uses another bootable component
217
- # MyApp.boot(:db) do |container|
218
- # use :logger
219
- #
220
- # start do
221
- # require 'db'
222
- # DB.configure(ENV['DB_URL'], logger: logger)
223
- # container.register(:db, DB.new)
279
+ # # system/providers/db.rb
280
+ # #
281
+ # # Provider registration under a namespace. This will register the
282
+ # # db object with the "persistence.db" key
283
+ # MyApp.register_provider(:persistence, namespace: "db") do
284
+ # start do
285
+ # require "db"
286
+ # DB.configure(ENV["DB_URL"])
287
+ # register("db", DB.new)
288
+ # end
224
289
  # end
225
- # end
226
- #
227
- # # system/boot/db.rb
228
- # #
229
- # # Component registration under a namespace. This will register the
230
- # # db object under `persistence.db` key
231
- # MyApp.namespace(:persistence) do |persistence|
232
- # require 'db'
233
- # DB.configure(ENV['DB_URL'], logger: logger)
234
- # persistence.register(:db, DB.new)
235
- # end
236
290
  #
237
- # @param name [Symbol] a unique identifier for a bootable component
291
+ # @param name [Symbol] a unique name for the provider
292
+ # @param namespace [String, nil] the key namespace to use for any registrations
293
+ # made during the provider's lifecycle
294
+ # @param from [Symbol, nil] the group for the external provider source (with the
295
+ # provider source name inferred from `name` or passsed explicitly as
296
+ # `source:`)
297
+ # @param source [Symbol, nil] the name of the external provider source to use
298
+ # (if different from the value provided as `name`)
299
+ # @param if [Boolean] a boolean to determine whether to register the provider
238
300
  #
239
- # @see Lifecycle
301
+ # @see Provider
302
+ # @see Provider::Source
240
303
  #
241
- # @return [self]
304
+ # @return [self]
242
305
  #
243
- # @api public
244
- def boot(name, **opts, &block)
245
- if components.key?(name)
246
- raise DuplicatedComponentKeyError, <<-STR
247
- Bootable component #{name.inspect} was already registered
248
- STR
249
- end
250
-
251
- component =
252
- if opts[:from]
253
- boot_external(name, **opts, &block)
254
- else
255
- boot_local(name, **opts, &block)
256
- end
257
-
258
- booter.register_component component
259
-
260
- components[name] = component
306
+ # @api public
307
+ def register_provider(...)
308
+ providers.register_provider(...)
261
309
  end
262
- deprecate :finalize, :boot
263
310
 
264
- # @api private
265
- def boot_external(identifier, from:, key: nil, namespace: nil, &block)
266
- System.providers[from].component(
267
- identifier, key: key, namespace: namespace, finalize: block, container: self
311
+ # rubocop:enable Layout/LineLength
312
+
313
+ # @see .register_provider
314
+ # @api public
315
+ def boot(name, **opts, &block)
316
+ Dry::Core::Deprecations.announce(
317
+ "Dry::System::Container.boot",
318
+ "Use `Dry::System::Container.register_provider` instead",
319
+ tag: "dry-system",
320
+ uplevel: 1
268
321
  )
269
- end
270
322
 
271
- # @api private
272
- def boot_local(identifier, namespace: nil, &block)
273
- Components::Bootable.new(
274
- identifier, container: self, namespace: namespace, &block
323
+ register_provider(
324
+ name,
325
+ namespace: opts[:namespace],
326
+ from: opts[:from],
327
+ source: opts[:key],
328
+ &block
275
329
  )
276
330
  end
331
+ deprecate :finalize, :boot
277
332
 
278
333
  # Return if a container was finalized
279
334
  #
@@ -316,71 +371,77 @@ module Dry
316
371
  def finalize!(freeze: true, &block)
317
372
  return self if finalized?
318
373
 
374
+ configured!
375
+
376
+ hooks[:before_finalize].each { |hook| instance_eval(&hook) }
319
377
  yield(self) if block
320
378
 
321
379
  importer.finalize!
322
- booter.finalize!
323
- manual_registrar.finalize!
380
+ providers.finalize!
381
+ manifest_registrar.finalize!
324
382
  auto_registrar.finalize!
325
383
 
326
384
  @__finalized__ = true
327
385
 
328
386
  self.freeze if freeze
387
+ hooks[:after_finalize].each { |hook| instance_eval(&hook) }
329
388
  self
330
389
  end
331
390
 
332
- # Boots a specific component
391
+ # Starts a provider
333
392
  #
334
- # As a result, `init` and `start` lifecycle triggers are called
393
+ # As a result, the provider's `prepare` and `start` lifecycle triggers are called
335
394
  #
336
395
  # @example
337
396
  # MyApp.start(:persistence)
338
397
  #
339
- # @param name [Symbol] the name of a registered bootable component
398
+ # @param name [Symbol] the name of a registered provider to start
340
399
  #
341
400
  # @return [self]
342
401
  #
343
402
  # @api public
344
403
  def start(name)
345
- booter.start(name)
404
+ providers.start(name)
346
405
  self
347
406
  end
348
407
 
349
- # Boots a specific component but calls only `init` lifecycle trigger
408
+ # Prepares a provider using its `prepare` lifecycle trigger
350
409
  #
351
- # This way of booting is useful in places where a heavy dependency is
352
- # needed but its started environment is not required
410
+ # Preparing (as opposed to starting) a provider is useful in places where some
411
+ # aspects of a heavier dependency are needed, but its fully started environment
353
412
  #
354
413
  # @example
355
- # MyApp.init(:persistence)
414
+ # MyApp.prepare(:persistence)
356
415
  #
357
- # @param [Symbol] name The name of a registered bootable component
416
+ # @param name [Symbol] The name of the registered provider to prepare
358
417
  #
359
418
  # @return [self]
360
419
  #
361
420
  # @api public
362
- def init(name)
363
- booter.init(name)
421
+ def prepare(name)
422
+ providers.prepare(name)
364
423
  self
365
424
  end
425
+ deprecate :init, :prepare
366
426
 
367
427
  # Stop a specific component but calls only `stop` lifecycle trigger
368
428
  #
369
429
  # @example
370
430
  # MyApp.stop(:persistence)
371
431
  #
372
- # @param [Symbol] name The name of a registered bootable component
432
+ # @param name [Symbol] The name of a registered bootable component
373
433
  #
374
434
  # @return [self]
375
435
  #
376
436
  # @api public
377
437
  def stop(name)
378
- booter.stop(name)
438
+ providers.stop(name)
379
439
  self
380
440
  end
381
441
 
442
+ # @api public
382
443
  def shutdown!
383
- booter.shutdown
444
+ providers.shutdown
384
445
  self
385
446
  end
386
447
 
@@ -395,7 +456,7 @@ module Dry
395
456
  # add_to_load_path!('lib')
396
457
  # end
397
458
  #
398
- # @param [Array<String>] dirs
459
+ # @param dirs [Array<String>]
399
460
  #
400
461
  # @return [self]
401
462
  #
@@ -409,7 +470,7 @@ module Dry
409
470
 
410
471
  # @api public
411
472
  def load_registrations!(name)
412
- manual_registrar.(name)
473
+ manifest_registrar.(name)
413
474
  self
414
475
  end
415
476
 
@@ -438,8 +499,8 @@ module Dry
438
499
  # @param options [Hash] injector options
439
500
  #
440
501
  # @api public
441
- def injector(options = {strategies: strategies})
442
- Dry::AutoInject(self, options)
502
+ def injector(**options)
503
+ Dry::AutoInject(self, **options)
443
504
  end
444
505
 
445
506
  # Requires one or more files relative to the container's root
@@ -491,7 +552,7 @@ module Dry
491
552
  #
492
553
  # @!method registered?(key)
493
554
  # Whether a +key+ is registered (doesn't trigger loading)
494
- # @param [String,Symbol] key Identifier
555
+ # @param key [String,Symbol] The key
495
556
  # @return [Boolean]
496
557
  # @api public
497
558
  #
@@ -499,7 +560,7 @@ module Dry
499
560
  # Check if identifier is registered.
500
561
  # If not, try to load the component
501
562
  #
502
- # @param [String,Symbol] key Identifier
563
+ # @param key [String,Symbol] Identifier
503
564
  # @return [Boolean]
504
565
  #
505
566
  # @api public
@@ -518,22 +579,10 @@ module Dry
518
579
  end
519
580
 
520
581
  # @api private
521
- def booter
522
- @booter ||= config.booter.new(boot_paths)
523
- end
524
-
525
- # @api private
526
- def boot_paths
527
- config.bootable_dirs.map { |dir|
528
- dir = Pathname(dir)
529
-
530
- if dir.relative?
531
- root.join(dir)
532
- else
533
- dir
534
- end
535
- }
582
+ def providers
583
+ @providers ||= config.provider_registrar.new(self)
536
584
  end
585
+ deprecate :booter, :providers
537
586
 
538
587
  # @api private
539
588
  def auto_registrar
@@ -541,8 +590,8 @@ module Dry
541
590
  end
542
591
 
543
592
  # @api private
544
- def manual_registrar
545
- @manual_registrar ||= config.manual_registrar.new(self)
593
+ def manifest_registrar
594
+ @manifest_registrar ||= config.manifest_registrar.new(self)
546
595
  end
547
596
 
548
597
  # @api private
@@ -550,13 +599,45 @@ module Dry
550
599
  @importer ||= config.importer.new(self)
551
600
  end
552
601
 
553
- # @api private
554
- def after(event, &block)
555
- hooks[:"after_#{event}"] << block
556
- end
557
-
602
+ # Registers a callback hook to run before container lifecycle events.
603
+ #
604
+ # Currently, the only supported event is `:finalized`. This hook is called when
605
+ # you run `{finalize!}`.
606
+ #
607
+ # When the given block is called, `self` is the container class, and no block
608
+ # arguments are given.
609
+ #
610
+ # @param event [Symbol] the event name
611
+ # @param block [Proc] the callback hook to run
612
+ #
613
+ # @return [self]
614
+ #
615
+ # @api public
558
616
  def before(event, &block)
559
617
  hooks[:"before_#{event}"] << block
618
+ self
619
+ end
620
+
621
+ # Registers a callback hook to run after container lifecycle events.
622
+ #
623
+ # The supported events are:
624
+ #
625
+ # - `:configured`, called when you run {configure} or {configured!}, or when
626
+ # running {finalize!} and neither of the prior two methods have been called.
627
+ # - `:finalized`, called when you run {finalize!}.
628
+ #
629
+ # When the given block is called, `self` is the container class, and no block
630
+ # arguments are given.
631
+ #
632
+ # @param event [Symbol] the event name
633
+ # @param block [Proc] the callback hook to run
634
+ #
635
+ # @return [self]
636
+ #
637
+ # @api public
638
+ def after(event, &block)
639
+ hooks[:"after_#{event}"] << block
640
+ self
560
641
  end
561
642
 
562
643
  # @api private
@@ -570,6 +651,7 @@ module Dry
570
651
  klass.hooks[event].concat blocks.dup
571
652
  end
572
653
 
654
+ klass.instance_variable_set(:@__configured__, false)
573
655
  klass.instance_variable_set(:@__finalized__, false)
574
656
 
575
657
  super
@@ -581,21 +663,21 @@ module Dry
581
663
  def load_component(key)
582
664
  return self if registered?(key)
583
665
 
584
- component = component(key)
585
-
586
- if component.bootable?
587
- booter.start(component)
666
+ if (provider = providers.find_and_load_provider(key))
667
+ provider.start
588
668
  return self
589
669
  end
590
670
 
591
- booter.boot_dependency(component)
671
+ component = find_component(key)
672
+
673
+ providers.start_provider_dependency(component)
592
674
  return self if registered?(key)
593
675
 
594
- if component.file_exists?
676
+ if component.loadable?
595
677
  load_local_component(component)
596
- elsif manual_registrar.file_exists?(component)
597
- manual_registrar.(component)
598
- elsif importer.key?(component.identifier.root_key)
678
+ elsif manifest_registrar.file_exists?(component)
679
+ manifest_registrar.(component)
680
+ elsif importer.namespace?(component.identifier.root_key)
599
681
  load_imported_component(component.identifier)
600
682
  end
601
683
 
@@ -613,27 +695,23 @@ module Dry
613
695
  def load_imported_component(identifier)
614
696
  import_namespace = identifier.root_key
615
697
 
616
- container = importer[import_namespace]
698
+ return unless importer.namespace?(import_namespace)
617
699
 
618
- container.load_component(identifier.dequalified(import_namespace).key)
700
+ import_key = identifier.namespaced(from: import_namespace, to: nil).key
619
701
 
620
- importer.(import_namespace, container)
702
+ importer.import(import_namespace, keys: [import_key])
621
703
  end
622
704
 
623
- def component(identifier)
624
- if (bootable_component = booter.find_component(identifier))
625
- return bootable_component
626
- end
627
-
705
+ def find_component(key)
628
706
  # Find the first matching component from within the configured component dirs.
629
- # If no matching component is found, return a plain component instance with no
630
- # associated file path. This fallback is important because the component may
631
- # still be loadable via the manual registrar or an imported container.
707
+ # If no matching component is found, return a null component; this fallback is
708
+ # important because the component may still be loadable via the manifest
709
+ # registrar or an imported container.
632
710
  component_dirs.detect { |dir|
633
- if (component = dir.component_for_identifier(identifier))
711
+ if (component = dir.component_for_key(key))
634
712
  break component
635
713
  end
636
- } || Component.new(identifier)
714
+ } || IndirectComponent.new(Identifier.new(key))
637
715
  end
638
716
  end
639
717