dry-system 0.19.2 → 0.23.0

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.
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