class Capsium::Reactor::Overlay
The writable top overlay layer of a mounted package (ARCHITECTURE.md section 5a): an append-only record of writes in the reactor workdir. The immutable package on disk never changes; content writes land under content/, deletions are tombstones in .capsium-tombstones, and dataset mutations are a per-dataset JSON operation log under data/. Everything is visible on the next request (hot-swap) and reloads from disk when the reactor starts over the same workdir.
Constants
- SAFE_NAME_PATTERN
- TOMBSTONE_FILE
Attributes
String
Set[String]
Public Class Methods
(root: String root) → void
Source
# File lib/capsium/reactor/overlay.rb, line 48 def initialize(root:) @root = root @tombstones = load_tombstones @logs = {} end
Public Instance Methods
(Package::Dataset dataset, untyped item) → String
Source
# File lib/capsium/reactor/overlay.rb, line 110 def append_item(dataset, item) current = items(dataset) explicit = item_id(item) if explicit && find_index(current, explicit) raise ConflictError, "item id #{explicit} already exists in #{dataset.name}" end validate_candidate!(dataset, current + [item]) record_op(dataset.name, "op" => "append", "item" => item) explicit || (current.size + 1).to_s end
Appends an item and returns its assigned id: the “id” field convention, else the 1-based index as a string.
() → String
Source
# File lib/capsium/reactor/overlay.rb, line 54 def content_root = File.join(@root, "content")
() → String
Source
# File lib/capsium/reactor/overlay.rb, line 56 def data_root = File.join(@root, "data")
(Package::Dataset dataset) → untyped
Source
# File lib/capsium/reactor/overlay.rb, line 86 def dataset_data(dataset) return dataset.data if mutation_log(dataset.name).empty? items(dataset) end
The dataset as served: the base data untouched when no mutations were recorded, the merged collection otherwise.
(String content_relative) → void
Source
# File lib/capsium/reactor/overlay.rb, line 78 def delete_content(content_relative) FileUtils.rm_f(safe_content_path(content_relative)) @tombstones.add(content_relative) persist_tombstones end
Tombstones a content path: it resolves 404 even when a lower layer holds the file (ARCHITECTURE.md section 5a).
(Package::Dataset dataset, String id) → nil
Source
# File lib/capsium/reactor/overlay.rb, line 136 def delete_item(dataset, id) unless find_index(items(dataset), id) raise ItemNotFoundError, "no item #{id} in dataset #{dataset.name}" end record_op(dataset.name, "op" => "delete", "id" => id) nil end
(Package::Dataset dataset, String id) → untyped
Source
# File lib/capsium/reactor/overlay.rb, line 100 def item(dataset, id) current = items(dataset) found = find_index(current, id) raise ItemNotFoundError, "no item #{id} in dataset #{dataset.name}" unless found current[found] end
(untyped item) → String?
Source
# File lib/capsium/reactor/overlay.rb, line 146 def item_id(item) item["id"].to_s if item.is_a?(Hash) && item.key?("id") end
The item id convention: an “id” field, else nil (positional).
(Package::Dataset dataset) → Array[untyped]
Source
# File lib/capsium/reactor/overlay.rb, line 94 def items(dataset) mutation_log(dataset.name).inject(base_items(dataset)) do |items, record| apply_op(items, record) end end
The merged item collection: base data plus the replayed mutation log.
() → Package::MergedView::Layer
Source
# File lib/capsium/reactor/overlay.rb, line 60 def layer Package::MergedView::Layer.new(root: content_root, visibility: "private", tombstones: @tombstones) end
The overlay as the topmost MergedView layer. The tombstones set is live: deletions are visible on the next resolve.
(String name) → bool
Source
# File lib/capsium/reactor/overlay.rb, line 151 def mutations?(name) = !mutation_log(name).empty?
Whether the dataset has recorded mutations.
(Package::Dataset dataset, String id, untyped item) → void
Source
# File lib/capsium/reactor/overlay.rb, line 122 def replace_item(dataset, id, item) current = items(dataset) index = find_index(current, id) raise ItemNotFoundError, "no item #{id} in dataset #{dataset.name}" unless index explicit = item_id(item) if explicit && explicit != id raise ConflictError, "body id #{explicit} does not match #{id}" end validate_candidate!(dataset, replaced(current, index, item)) record_op(dataset.name, "op" => "replace", "id" => id, "item" => item) end
(String content_relative, String body) → void
Source
# File lib/capsium/reactor/overlay.rb, line 68 def write_content(content_relative, body) path = safe_content_path(content_relative) FileUtils.mkdir_p(File.dirname(path)) File.write(path, body) @tombstones.delete(content_relative) persist_tombstones end
Writes a content file (create/overwrite), clearing any tombstone for the path so it is served again.