Class: Capsium::Reactor::Overlay
- Inherits:
-
Object
- Object
- Capsium::Reactor::Overlay
- Defined in:
- lib/capsium/reactor/overlay.rb,
sig/capsium/reactor/overlay.rbs
Overview
The writable top overlay layer of a mounted package (ARCHITECTURE.md section 5a): an append-only record of writes in the reactor workdir (content files, tombstones, per-dataset mutation logs). The immutable package never changes on disk.
Defined Under Namespace
Classes: ConflictError, Error, ItemNotFoundError, SchemaViolationError, UnsupportedDatasetError
Constant Summary collapse
- TOMBSTONE_FILE =
Package::MergedView::TOMBSTONE_FILE
- SAFE_NAME_PATTERN =
/\A[\w.-]+\z/
Instance Attribute Summary collapse
-
#root ⇒ String
readonly
Returns the value of attribute root.
-
#tombstones ⇒ Set[String]
readonly
Returns the value of attribute tombstones.
Instance Method Summary collapse
-
#append_item(dataset, item) ⇒ String
Appends an item and returns its assigned id: the "id" field convention, else the 1-based index as a string.
- #content_root ⇒ String
- #data_root ⇒ String
-
#dataset_data(dataset) ⇒ Object
The dataset as served: base data merged with the mutation log.
-
#delete_content(content_relative) ⇒ void
Tombstones a content path: it resolves 404 even when a lower layer holds the file (ARCHITECTURE.md section 5a).
- #delete_item(dataset, id) ⇒ nil
-
#initialize(root:) ⇒ Overlay
constructor
A new instance of Overlay.
- #item(dataset, id) ⇒ Object
-
#item_id(item) ⇒ String?
The item id convention: an "id" field, else nil (positional).
-
#items(dataset) ⇒ Array[untyped]
The merged item collection.
-
#layer ⇒ Package::MergedView::Layer
The overlay as the topmost MergedView layer.
-
#mutations?(name) ⇒ Boolean
Whether the dataset has recorded mutations.
- #replace_item(dataset, id, item) ⇒ void
-
#write_content(content_relative, body) ⇒ void
Writes a content file (create/overwrite), clearing any tombstone for the path so it is served again.
Constructor Details
#initialize(root:) ⇒ Overlay
Returns a new instance of Overlay.
48 49 50 51 52 |
# File 'lib/capsium/reactor/overlay.rb', line 48 def initialize(root:) @root = root @tombstones = load_tombstones @logs = {} end |
Instance Attribute Details
#root ⇒ String (readonly)
Returns the value of attribute root.
46 47 48 |
# File 'lib/capsium/reactor/overlay.rb', line 46 def root @root end |
#tombstones ⇒ Set[String] (readonly)
Returns the value of attribute tombstones.
46 47 48 |
# File 'lib/capsium/reactor/overlay.rb', line 46 def tombstones @tombstones end |
Instance Method Details
#append_item(dataset, item) ⇒ String
Appends an item and returns its assigned id: the "id" field convention, else the 1-based index as a string.
110 111 112 113 114 115 116 117 118 119 120 |
# 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 |
#content_root ⇒ String
54 |
# File 'lib/capsium/reactor/overlay.rb', line 54 def content_root = File.join(@root, "content") |
#data_root ⇒ String
56 |
# File 'lib/capsium/reactor/overlay.rb', line 56 def data_root = File.join(@root, "data") |
#dataset_data(dataset) ⇒ Object
The dataset as served: base data merged with the mutation log.
86 87 88 89 90 |
# File 'lib/capsium/reactor/overlay.rb', line 86 def dataset_data(dataset) return dataset.data if mutation_log(dataset.name).empty? items(dataset) end |
#delete_content(content_relative) ⇒ void
This method returns an undefined value.
Tombstones a content path: it resolves 404 even when a lower layer holds the file (ARCHITECTURE.md section 5a).
78 79 80 81 82 |
# 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 |
#delete_item(dataset, id) ⇒ nil
136 137 138 139 140 141 142 143 |
# 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 |
#item(dataset, id) ⇒ Object
100 101 102 103 104 105 106 |
# 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 |
#item_id(item) ⇒ String?
The item id convention: an "id" field, else nil (positional).
146 147 148 |
# 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 |
#items(dataset) ⇒ Array[untyped]
The merged item collection.
94 95 96 97 98 |
# 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 |
#layer ⇒ Package::MergedView::Layer
The overlay as the topmost MergedView layer.
60 61 62 63 64 |
# File 'lib/capsium/reactor/overlay.rb', line 60 def layer Package::MergedView::Layer.new(root: content_root, visibility: "private", tombstones: @tombstones) end |
#mutations?(name) ⇒ Boolean
Whether the dataset has recorded mutations.
151 |
# File 'lib/capsium/reactor/overlay.rb', line 151 def mutations?(name) = !mutation_log(name).empty? |
#replace_item(dataset, id, item) ⇒ void
This method returns an undefined value.
122 123 124 125 126 127 128 129 130 131 132 133 134 |
# 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 |
#write_content(content_relative, body) ⇒ void
This method returns an undefined value.
Writes a content file (create/overwrite), clearing any tombstone for the path so it is served again.
68 69 70 71 72 73 74 |
# 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 |