Class: Kobako::Catalog::Extensions
- Inherits:
-
Object
- Object
- Kobako::Catalog::Extensions
- Defined in:
- lib/kobako/catalog/extensions.rb,
sig/kobako/catalog/extensions.rbs
Overview
Kobako::Catalog::Extensions — per-Sandbox registry of installed
Extensions. Composes each Extension onto the sibling registries at
install time (its source into Catalog::Snippets, its backend
path into Catalog::Services), asserts declared dependencies are
present when the Sandbox seals, and resolves each callable-backed
path to a fresh object at the start of every invocation.
Sealing and the reject-after-install guard are governed by the owning
Sandbox through Catalog::Services#sealed?, the shared seal signal;
this registry only records entries and enforces the Extension-shape
and dependency rules.
Instance Method Summary collapse
- #assert_dependencies! ⇒ void
- #callable?(provider) ⇒ Boolean
-
#initial_object(provider) ⇒ Object
A callable provider reserves its path with a placeholder that the per-invocation refresh replaces before any dispatch; a fixed provider is the bound object itself.
-
#initialize ⇒ Extensions
constructor
A new instance of Extensions.
-
#install(extension, snippets:, services:) ⇒ self
Install
extension: validate its shape, register itssourceas a preloaded snippet, and — when it carries abackend— reserve the backend path inservices. -
#refresh_backend(extension, services, resolved) ⇒ void
Resolve one Extension's callable-backed path against the shared per-invocation
resolvedcache (keyed by provider identity) and refresh it inservices. -
#refresh_backends!(services) ⇒ self
Resolve each callable-backed path to this invocation's object and refresh it behind its already-sealed path in
services. -
#seal! ⇒ self
Assert every installed Extension's
depends_onnames a fellow installed Extension. -
#symbolize(name) ⇒ Object
Match names and dependencies by Symbol so the Symbol-or-String forms of a constant token are interchangeable; a value that is neither falls through unchanged to the not-installed path.
-
#validate!(extension) ⇒ void
Enforce the Extension-shape checks
#preload/#binddo not: a mandatory Stringsource(the install/bind boundary) and abackendthat, when present, exposespathandprovider.
Constructor Details
#initialize ⇒ Extensions
Returns a new instance of Extensions.
17 18 19 20 |
# File 'lib/kobako/catalog/extensions.rb', line 17 def initialize @entries = [] # : Array[untyped] @asserted = false end |
Instance Method Details
#assert_dependencies! ⇒ void
This method returns an undefined value.
97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/kobako/catalog/extensions.rb', line 97 def assert_dependencies! names = @entries.map { |extension| symbolize(extension.name) } @entries.each do |extension| (extension.depends_on || []).each do |dependency| next if names.include?(symbolize(dependency)) raise ArgumentError, "Extension #{extension.name.inspect} depends on #{dependency.inspect}, which is not installed" end end end |
#callable?(provider) ⇒ Boolean
114 |
# File 'lib/kobako/catalog/extensions.rb', line 114 def callable?(provider) = provider.respond_to?(:call) |
#initial_object(provider) ⇒ Object
A callable provider reserves its path with a placeholder that the per-invocation refresh replaces before any dispatch; a fixed provider is the bound object itself.
119 |
# File 'lib/kobako/catalog/extensions.rb', line 119 def initial_object(provider) = callable?(provider) ? nil : provider |
#install(extension, snippets:, services:) ⇒ self
Install extension: validate its shape, register its source as a
preloaded snippet, and — when it carries a backend — reserve the
backend path in services. A callable provider reserves the path
with a placeholder the per-invocation refresh replaces; a fixed
provider binds its object directly. Raises ArgumentError for the two
shapes #validate! owns — source not a String, or a present
backend that omits path / provider. The Extension readers are
duck-typed, so an object missing them surfaces the underlying
NoMethodError, and a malformed name or backend.path surfaces
through the snippets / services registration it routes to.
32 33 34 35 36 37 38 39 |
# File 'lib/kobako/catalog/extensions.rb', line 32 def install(extension, snippets:, services:) validate!(extension) snippets.register(code: extension.source, name: extension.name) backend = extension.backend services.bind(backend.path, initial_object(backend.provider)) if backend @entries << extension self end |
#refresh_backend(extension, services, resolved) ⇒ void
This method returns an undefined value.
Resolve one Extension's callable-backed path against the shared
per-invocation resolved cache (keyed by provider identity) and
refresh it in services. A fixed provider is skipped — its object
stays as bound at install.
72 73 74 75 76 77 78 79 80 81 |
# File 'lib/kobako/catalog/extensions.rb', line 72 def refresh_backend(extension, services, resolved) backend = extension.backend return unless backend provider = backend.provider return unless callable?(provider) object = resolved.fetch(provider) { resolved[provider] = provider.call } services.refresh(backend.path, object) end |
#refresh_backends!(services) ⇒ self
Resolve each callable-backed path to this invocation's object and
refresh it behind its already-sealed path in services. Distinct
providers yield distinct objects; one provider shared by several
Extensions is invoked once and its result shared, so provider
identity is resource identity. Fixed providers are left untouched —
they stay the object bound at install.
59 60 61 62 63 64 |
# File 'lib/kobako/catalog/extensions.rb', line 59 def refresh_backends!(services) resolved = {} # : Hash[untyped, untyped] resolved.compare_by_identity @entries.each { |extension| refresh_backend(extension, services, resolved) } self end |
#seal! ⇒ self
Assert every installed Extension's depends_on names a fellow
installed Extension. Runs once, when the Sandbox first seals; an
unmet dependency raises ArgumentError naming the gap, before the
guest runs. Idempotent across later invocations.
45 46 47 48 49 50 51 |
# File 'lib/kobako/catalog/extensions.rb', line 45 def seal! return self if @asserted @asserted = true assert_dependencies! self end |
#symbolize(name) ⇒ Object
Match names and dependencies by Symbol so the Symbol-or-String forms of a constant token are interchangeable; a value that is neither falls through unchanged to the not-installed path.
112 |
# File 'lib/kobako/catalog/extensions.rb', line 112 def symbolize(name) = name.is_a?(String) ? name.to_sym : name |
#validate!(extension) ⇒ void
This method returns an undefined value.
Enforce the Extension-shape checks #preload / #bind do not: a
mandatory String source (the install/bind boundary) and a
backend that, when present, exposes path and provider.
86 87 88 89 90 91 92 93 94 95 |
# File 'lib/kobako/catalog/extensions.rb', line 86 def validate!(extension) source = extension.source raise ArgumentError, "Extension #source must be a String, got #{source.class}" unless source.is_a?(String) backend = extension.backend return if backend.nil? return if backend.respond_to?(:path) && backend.respond_to?(:provider) raise ArgumentError, "Extension #backend must expose #path and #provider" end |