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 preamble emitted on Frame 1.

Public API:

services = Kobako::Catalog::Services.new
services.bind("MyService::KV", kv_object)  # => services (chainable)
services.encode                            # => msgpack bytes for Frame 1
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
35
# File 'lib/kobako/catalog/services.rb', line 31

def initialize
  @bindings = {} # : Hash[String, untyped]
  @sealed = false
  @encoded = nil # : String?
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)


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

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)


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

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)


112
113
114
115
116
117
118
# File 'lib/kobako/catalog/services.rb', line 112

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

#encodeString

Encode the preamble as msgpack bytes for stdin Frame 1 delivery — a flat array of the bound constant paths, in bind order: ["MyService::KV", "File"]. Routes through Kobako::Codec::Encoder like every other host-side wire encode; the preamble carries only Strings, so none of the kobako ext types fire. Returns a binary String of msgpack bytes.

The bytes are pinned eagerly by #seal! at the first invocation, so every subsequent call is a pure read of the frozen preamble — a bind can never reach the registry after the seal to alter Frame 1.

Returns:

  • (String)


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

def encode
  @encoded || Codec::Encoder.encode(@bindings.keys).freeze
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)


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

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

#seal!self

Mark the registry as sealed and pin the Frame 1 preamble bytes so registration can never alter them afterwards. Called by Sandbox on the first invocation; afterwards #bind raises ArgumentError. Idempotent; returns self.

Returns:

  • (self)


87
88
89
90
91
# File 'lib/kobako/catalog/services.rb', line 87

def seal!
  @sealed = true
  @encoded ||= Codec::Encoder.encode(@bindings.keys).freeze
  self
end

#sealed?Boolean

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

Returns:

  • (Boolean)


94
95
96
# File 'lib/kobako/catalog/services.rb', line 94

def sealed?
  @sealed
end

#validate_path!(path) ⇒ String

Parameters:

  • path (Symbol, String)

Returns:

  • (String)

Raises:

  • (ArgumentError)


100
101
102
103
104
105
106
107
# File 'lib/kobako/catalog/services.rb', line 100

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