Class: Kobako::Catalog::Extensions

Inherits:
Object
  • Object
show all
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

Constructor Details

#initializeExtensions

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

Parameters:

  • provider (Object)

Returns:

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

Parameters:

  • provider (Object)

Returns:

  • (Object)


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.

Parameters:

Returns:

  • (self)


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.

Parameters:



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.

Parameters:

Returns:

  • (self)


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.

Returns:

  • (self)


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.

Parameters:

  • name (Object)

Returns:

  • (Object)


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.

Parameters:

  • extension (Object)

Raises:

  • (ArgumentError)


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