Class: Kobako::Catalog::Services

Inherits:
Object
  • Object
show all
Defined in:
lib/kobako/catalog/services.rb,
sig/kobako/catalog/services.rbs

Overview

Kobako::Catalog::Services — per-Sandbox registry of Service bindings keyed by their constant-path name. Holds the flat path→object table and the declared path set every invocation announces on Frame 1.

Public API:

services = Kobako::Catalog::Services.new
services.bind("MyService::KV", kv_object)  # => services (chainable)
services.paths                             # => ["MyService::KV"]
services.lookup("MyService::KV")           # => kv_object

Per-dispatch routing is +Kobako::Transport::Dispatcher+'s responsibility — the Dispatcher resolves a path against the invocation's Kobako::Context (which layers per-invocation providers and ctx.bind overrides over these base bindings) and the Catalog::Handles, both passed in the per-invocation dispatch Proc the Context hands to Runtime#eval / #run.

Constant Summary collapse

NAME_PATTERN =

Ruby constant-name pattern each +::+-separated bind-path segment must match.

Returns:

  • (Regexp)
/\A[A-Z]\w*\z/

Instance Method Summary collapse

Constructor Details

#initializeServices

Build a fresh registry.



31
32
33
34
# File 'lib/kobako/catalog/services.rb', line 31

def initialize
  @bindings = {} # : Hash[String, untyped]
  @sealed = false
end

Instance Method Details

#bind(path, object) ⇒ self

Bind object as the Service reachable at path — a Symbol or String of one or more +::+-separated constant-form segments (+"MyService::KV"+ or a top-level "File"). Returns self for chaining. Raises ArgumentError when a segment is malformed, when path collides with an existing binding (a name is a bound Service or a grouping prefix, never both), or when the owning Sandbox has been sealed by its first invocation.

Parameters:

  • path (Symbol, String)
  • object (Object)

Returns:

  • (self)

Raises:

  • (ArgumentError)


43
44
45
46
47
48
49
50
51
# File 'lib/kobako/catalog/services.rb', line 43

def bind(path, object)
  raise ArgumentError, "cannot bind after first Sandbox invocation" if @sealed

  path_str = validate_path!(path)
  raise ArgumentError, "Service path #{path_str} conflicts with an existing binding" if collision?(path_str)

  @bindings[path_str] = object
  self
end

#bound?(path) ⇒ Boolean

Returns true iff a Service is bound at path — the declared (Frame 1) key set a per-invocation ctx.bind override may target.

Parameters:

  • path (Symbol, String)

Returns:

  • (Boolean)


55
56
57
# File 'lib/kobako/catalog/services.rb', line 55

def bound?(path)
  @bindings.key?(path.to_s)
end

#collision?(path) ⇒ Boolean

A path collides when it equals, is a prefix of, or extends an existing binding on the :: segment boundary — the guardrail that keeps a name from being both a bound Service and a grouping prefix.

Parameters:

  • path (String)

Returns:

  • (Boolean)


104
105
106
107
108
109
110
# File 'lib/kobako/catalog/services.rb', line 104

def collision?(path)
  @bindings.each_key.any? do |existing|
    existing == path ||
      existing.start_with?("#{path}::") ||
      path.start_with?("#{existing}::")
  end
end

#lookup(target) ⇒ Object

Resolve a target constant path to the bound Service. Raises KeyError when no Service is bound at target.

Parameters:

  • target (Symbol, String)

Returns:

  • (Object)

Raises:

  • (KeyError)


61
62
63
64
65
66
# File 'lib/kobako/catalog/services.rb', line 61

def lookup(target)
  target_str = target.to_s
  raise KeyError, "no service bound at #{target_str.inspect}" unless @bindings.key?(target_str)

  @bindings[target_str]
end

#pathsArray[String]

The bound constant paths in bind order — +["MyService::KV", "File"]+ — which Runtime#eval / #run frame into the Frame 1 preamble. The registry holds the bindings; the wire layout is the native side's.

Returns:

  • (Array[String])


72
73
74
# File 'lib/kobako/catalog/services.rb', line 72

def paths
  @bindings.keys
end

#seal!self

Mark the registry as sealed so registration can never alter the declared path set afterwards. Called by Sandbox on the first invocation; afterwards #bind raises ArgumentError. Idempotent; returns self.

Returns:

  • (self)


80
81
82
83
# File 'lib/kobako/catalog/services.rb', line 80

def seal!
  @sealed = true
  self
end

#sealed?Boolean

Returns true when #seal! has been called, false otherwise.

Returns:

  • (Boolean)


86
87
88
# File 'lib/kobako/catalog/services.rb', line 86

def sealed?
  @sealed
end

#validate_path!(path) ⇒ String

Parameters:

  • path (Symbol, String)

Returns:

  • (String)

Raises:

  • (ArgumentError)


92
93
94
95
96
97
98
99
# File 'lib/kobako/catalog/services.rb', line 92

def validate_path!(path)
  path_str = path.to_s
  segments = path_str.split("::", -1)
  return path_str if !segments.empty? && segments.all? { |seg| NAME_PATTERN.match?(seg) }

  raise ArgumentError,
        "bind path must be constant-form segments joined by '::' (got #{path.inspect})"
end