Class: Kobako::Catalog::Services
- Inherits:
-
Object
- Object
- Kobako::Catalog::Services
- 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 receives this registry and the
Catalog::Handles as arguments from the Runtime#on_dispatch Proc
that Kobako::Sandbox#initialize installs. The registry holds an
injected Catalog::Handles reference so dispatch target resolution
and host→guest auto-wrap share the same Sandbox-owned allocator.
Constant Summary collapse
- NAME_PATTERN =
Ruby constant-name pattern each +::+-separated bind-path segment must match.
/\A[A-Z]\w*\z/
Instance Method Summary collapse
-
#bind(path, object) ⇒ self
Bind
objectas the Service reachable atpath— aSymbolorStringof one or more +::+-separated constant-form segments (+"MyService::KV"+ or a top-level"File"). -
#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. -
#encode ⇒ String
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"]. -
#initialize(handler: Catalog::Handles.new) ⇒ Services
constructor
Build a fresh registry.
-
#lookup(target) ⇒ Object
Resolve a
targetconstant path to the bound Service. -
#seal! ⇒ self
Mark the registry as sealed.
-
#sealed? ⇒ Boolean
Returns
truewhen #seal! has been called,falseotherwise. - #validate_path!(path) ⇒ String
Constructor Details
#initialize(handler: Catalog::Handles.new) ⇒ Services
Build a fresh registry. handler is an internal seam that injects
a pre-configured Catalog::Handles; tests pass one whose next_id
is pinned near MAX_ID to exercise the cap-exhaustion path
without 2³¹ allocations. Production callers leave it at the default.
35 36 37 38 39 40 |
# File 'lib/kobako/catalog/services.rb', line 35 def initialize(handler: Catalog::Handles.new) @bindings = {} # : Hash[String, untyped] @handler = handler @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.
49 50 51 52 53 54 55 56 57 |
# File 'lib/kobako/catalog/services.rb', line 49 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 |
#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.
114 115 116 117 118 119 120 |
# File 'lib/kobako/catalog/services.rb', line 114 def collision?(path) @bindings.each_key.any? do |existing| existing == path || existing.start_with?("#{path}::") || path.start_with?("#{existing}::") end end |
#encode ⇒ String
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.
Once sealed, the bytes are computed once and reused for every
subsequent invocation: sealing freezes Service registration at the
first invocation, so a bind reaching the registry after the seal
raises ArgumentError and never alters Frame 1.
79 80 81 82 83 84 85 |
# File 'lib/kobako/catalog/services.rb', line 79 def encode return @encoded if @encoded bytes = Codec::Encoder.encode(@bindings.keys).freeze @encoded = bytes if @sealed bytes end |
#lookup(target) ⇒ Object
Resolve a target constant path to the bound Service. Raises
KeyError when no Service is bound at target.
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 |
#seal! ⇒ self
Mark the registry as sealed. Called by Sandbox on the first
invocation; afterwards #bind raises ArgumentError. Idempotent;
returns self.
90 91 92 93 |
# File 'lib/kobako/catalog/services.rb', line 90 def seal! @sealed = true self end |
#sealed? ⇒ Boolean
Returns true when #seal! has been called, false otherwise.
96 97 98 |
# File 'lib/kobako/catalog/services.rb', line 96 def sealed? @sealed end |
#validate_path!(path) ⇒ String
102 103 104 105 106 107 108 109 |
# File 'lib/kobako/catalog/services.rb', line 102 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 |