Class: Capsium::Reactor::Mount
- Inherits:
-
Object
- Object
- Capsium::Reactor::Mount
- Defined in:
- lib/capsium/reactor/mount.rb,
sig/capsium/reactor/mount.rbs
Overview
One mounted package: the URL prefix it answers under plus its serving state. The reactor serves a list of mounts resolved by longest-prefix matching; default mount points are "/" for the first source and "/<metadata.name>/" for each additional one.
Defined Under Namespace
Classes: Entry
Constant Summary collapse
- ROOT_PATH =
"/"- SPEC_SEPARATOR =
"="
Instance Attribute Summary collapse
-
#overlay ⇒ Overlay?
readonly
Returns the value of attribute overlay.
-
#package ⇒ Package
readonly
Returns the value of attribute package.
-
#path ⇒ String
readonly
Returns the value of attribute path.
Class Method Summary collapse
-
.build(entries, store: nil, registry: nil) ⇒ Array[Mount]
Builds the mounts for a list of entries: loads each package, assigns default paths (first entry "/", each additional "/<metadata.name>/") and rejects duplicate prefixes with a MountConflictError.
-
.config_entries(config_file) ⇒ Array[Entry]
The mount entries of a JSON config file: [{"path": "/", "source": "dir-or.cap", "store": "..."]}.
-
.load_package(source, store, registry) ⇒ Package
Loads a mount source (a package directory, .cap file or an already-built Package) with the given resolution context.
-
.normalize_path(path) ⇒ String
Canonical mount prefix: leading "/", no trailing "/" (except the root itself).
-
.parse_spec(spec) ⇒ Entry
Parses a "--mount PATH=SOURCE" command-line value into an Entry.
Instance Method Summary collapse
-
#attach_workdir(workdir) ⇒ void
Attaches the reactor workdir (creates the writable overlay).
- #content_api ⇒ ContentApi
- #data_api ⇒ DataApi
-
#dataset_data(dataset) ⇒ Object
The dataset as served: base data merged with overlay mutations.
-
#graphql? ⇒ Boolean
Whether this mount exposes a GraphQL API (any file-backed dataset).
- #graphql_api ⇒ GraphqlApi
-
#initialize(path:, package:, store: nil, registry: nil, workdir: nil) ⇒ Mount
constructor
A new instance of Mount.
-
#inner_path(request_path) ⇒ String
The package-local path for a request path under this mount ("/" when the request hits the mount prefix itself).
-
#matches?(request_path) ⇒ Boolean
Whether this mount answers the given request path: the prefix itself or anything below it.
-
#merged_view ⇒ Package::MergedView
The merged content view; the overlay is always the topmost layer when a workdir is attached (hot-swap: content writes and tombstones resolve on the next request).
-
#reload ⇒ Package
Reloads the package from its (prepared) path, e.g.
- #routes ⇒ Package::Routes
-
#server_paths ⇒ Array[String]
The WEBrick mount points serving this mount.
-
#summary ⇒ String
The one-line "name version" summary used in reactor logs.
-
#writable? ⇒ Boolean
Writable unless the package declares "readOnly": true.
Constructor Details
#initialize(path:, package:, store: nil, registry: nil, workdir: nil) ⇒ Mount
Returns a new instance of Mount.
114 115 116 117 118 119 120 |
# File 'lib/capsium/reactor/mount.rb', line 114 def initialize(path:, package:, store: nil, registry: nil, workdir: nil) @path = self.class.normalize_path(path) @store = store @registry = registry @package = self.class.load_package(package, store, registry) attach_workdir(workdir) if workdir end |
Instance Attribute Details
#overlay ⇒ Overlay? (readonly)
Returns the value of attribute overlay.
26 27 28 |
# File 'lib/capsium/reactor/mount.rb', line 26 def end |
#package ⇒ Package (readonly)
Returns the value of attribute package.
26 27 28 |
# File 'lib/capsium/reactor/mount.rb', line 26 def package @package end |
#path ⇒ String (readonly)
Returns the value of attribute path.
26 27 28 |
# File 'lib/capsium/reactor/mount.rb', line 26 def path @path end |
Class Method Details
.build(entries, store: nil, registry: nil) ⇒ Array[Mount]
Builds the mounts for a list of entries: loads each package, assigns default paths (first entry "/", each additional "/<metadata.name>/") and rejects duplicate prefixes with a MountConflictError. On failure every package loaded so far is cleaned up again.
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/capsium/reactor/mount.rb', line 66 def self.build(entries, store: nil, registry: nil) built = [] entries.each_with_index do |entry, index| package_store = entry.store || store package = load_package(entry.source, package_store, registry) path = entry.path || default_path(index, package) built << new(path: path, package: package, store: package_store, registry: registry) end check_conflicts!(built) built rescue StandardError built.each { |mount| mount.package.cleanup } raise end |
.config_entries(config_file) ⇒ Array[Entry]
The mount entries of a JSON config file: [{"path": "/", "source": "dir-or.cap", "store": "..."]}
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/capsium/reactor/mount.rb', line 40 def self.config_entries(config_file) doc = JSON.parse(File.read(config_file)) mounts = doc.is_a?(Hash) ? doc["mounts"] : nil unless mounts.is_a?(Array) && mounts.all?(Hash) raise Error, "Invalid mount config #{config_file}: expected " \ '{"mounts": [{"path": ..., "source": ...}]}' end mounts.map do |entry| unless entry["source"].is_a?(String) raise Error, "Invalid mount config #{config_file}: every mount " \ "needs a \"source\"" end Entry.new(path: entry["path"], source: entry["source"], store: entry["store"]) end rescue JSON::ParserError => e raise Error, "Invalid mount config #{config_file}: #{e.message}" end |
.load_package(source, store, registry) ⇒ Package
Loads a mount source (a package directory, .cap file or an already-built Package) with the given resolution context.
84 85 86 87 88 |
# File 'lib/capsium/reactor/mount.rb', line 84 def self.load_package(source, store, registry) return source unless source.is_a?(String) Package.new(source, store: store, registry: registry) end |
.normalize_path(path) ⇒ String
Canonical mount prefix: leading "/", no trailing "/" (except the root itself).
107 108 109 110 111 112 |
# File 'lib/capsium/reactor/mount.rb', line 107 def self.normalize_path(path) return ROOT_PATH if path.nil? || path == ROOT_PATH normalized = path.start_with?(ROOT_PATH) ? path : "#{ROOT_PATH}#{path}" normalized.chomp(ROOT_PATH) end |
.parse_spec(spec) ⇒ Entry
Parses a "--mount PATH=SOURCE" command-line value into an Entry.
29 30 31 32 33 34 35 36 |
# File 'lib/capsium/reactor/mount.rb', line 29 def self.parse_spec(spec) path, separator, source = spec.to_s.partition(SPEC_SEPARATOR) if separator.empty? || path.empty? || source.empty? raise Error, "Invalid mount spec #{spec.inspect} (expected PATH=SOURCE)" end Entry.new(path: path, source: source, store: nil) end |
Instance Method Details
#attach_workdir(workdir) ⇒ void
This method returns an undefined value.
Attaches the reactor workdir (creates the writable overlay).
125 126 127 |
# File 'lib/capsium/reactor/mount.rb', line 125 def attach_workdir(workdir) = Overlay.new(root: File.join(workdir, "overlays", @package.name)) end |
#content_api ⇒ ContentApi
173 |
# File 'lib/capsium/reactor/mount.rb', line 173 def content_api = @content_api ||= ContentApi.new(self) |
#data_api ⇒ DataApi
171 |
# File 'lib/capsium/reactor/mount.rb', line 171 def data_api = @data_api ||= DataApi.new(self) |
#dataset_data(dataset) ⇒ Object
The dataset as served: base data merged with overlay mutations.
167 168 169 |
# File 'lib/capsium/reactor/mount.rb', line 167 def dataset_data(dataset) ? .dataset_data(dataset) : dataset.data end |
#graphql? ⇒ Boolean
Whether this mount exposes a GraphQL API (any file-backed dataset).
177 178 179 |
# File 'lib/capsium/reactor/mount.rb', line 177 def graphql? @package.storage.datasets.any? { |dataset| !dataset.config.sqlite? } end |
#graphql_api ⇒ GraphqlApi
181 |
# File 'lib/capsium/reactor/mount.rb', line 181 def graphql_api = @graphql_api ||= GraphqlApi.new(self) |
#inner_path(request_path) ⇒ String
The package-local path for a request path under this mount ("/" when the request hits the mount prefix itself).
144 145 146 147 148 149 150 |
# File 'lib/capsium/reactor/mount.rb', line 144 def inner_path(request_path) inner = request_path.delete_prefix(path) return ROOT_PATH if inner.empty? return inner if inner.start_with?(ROOT_PATH) "#{ROOT_PATH}#{inner}" end |
#matches?(request_path) ⇒ Boolean
Whether this mount answers the given request path: the prefix itself or anything below it. The root mount answers everything.
137 138 139 140 |
# File 'lib/capsium/reactor/mount.rb', line 137 def matches?(request_path) path == ROOT_PATH || request_path == path || request_path.start_with?("#{path}#{ROOT_PATH}") end |
#merged_view ⇒ Package::MergedView
The merged content view; the overlay is always the topmost layer when a workdir is attached (hot-swap: content writes and tombstones resolve on the next request).
157 158 159 160 161 162 163 |
# File 'lib/capsium/reactor/mount.rb', line 157 def merged_view @merged_view ||= if @package.merged_view(extra_layers: [.layer]) else @package.merged_view end end |
#reload ⇒ Package
Reloads the package from its (prepared) path, e.g. after the filesystem listener noticed changes. The overlay survives (it lives in the reactor workdir, not in the package).
199 200 201 202 203 |
# File 'lib/capsium/reactor/mount.rb', line 199 def reload @merged_view = nil @graphql_api = nil @package = Package.new(@package.path, store: @store, registry: @registry) end |
#routes ⇒ Package::Routes
152 |
# File 'lib/capsium/reactor/mount.rb', line 152 def routes = @package.routes |
#server_paths ⇒ Array[String]
The WEBrick mount points serving this mount.
190 191 192 193 194 |
# File 'lib/capsium/reactor/mount.rb', line 190 def server_paths return [path] unless path == ROOT_PATH routes.config.routes.map(&:serving_path) end |
#summary ⇒ String
The one-line "name version" summary used in reactor logs.
184 |
# File 'lib/capsium/reactor/mount.rb', line 184 def summary = "#{@package.name} #{@package.metadata.version}" |
#writable? ⇒ Boolean
Writable unless the package declares "readOnly": true.
131 132 133 |
# File 'lib/capsium/reactor/mount.rb', line 131 def writable? !.nil? && @package..read_only != true end |