Class: Textus::Store::Writer
- Inherits:
-
Object
- Object
- Textus::Store::Writer
- Defined in:
- lib/textus/store/writer.rb
Overview
rubocop:disable Metrics/ParameterLists
Instance Method Summary collapse
- #accept(key, as:) ⇒ Object
-
#delete(key, if_etag: nil, as: Role::DEFAULT, suppress_events: false) ⇒ Object
Backward-compat shim — orchestration now lives in Application::Writes::Delete.
-
#delete_envelope_from_disk(key, if_etag: nil, as: Role::DEFAULT, correlation_id: nil) ⇒ Object
Pure I/O: resolve path, validate etag, delete from disk, audit.
- #enforce_name_match!(path, meta, format) ⇒ Object
- #ensure_uid(format, meta, content, existing_uid) ⇒ Object
- #existing_uid_for(mentry, path) ⇒ Object
-
#initialize(store) ⇒ Writer
constructor
A new instance of Writer.
-
#put(key, meta: nil, body: nil, content: nil, if_etag: nil, as: Role::DEFAULT, suppress_events: false) ⇒ Object
Backward-compat shim — orchestration now lives in Application::Writes::Put.
- #reject(pending_key, as: Role::DEFAULT) ⇒ Object
- #serialize_for_put(mentry:, path:, strategy:, meta:, body:, content:) ⇒ Object
-
#write_envelope_to_disk(key, mentry:, meta: nil, body: nil, content: nil, if_etag: nil, as: Role::DEFAULT, correlation_id: nil) ⇒ Object
Pure I/O: validate, serialize, etag-check, write to disk, audit.
Constructor Details
#initialize(store) ⇒ Writer
Returns a new instance of Writer.
7 8 9 10 11 |
# File 'lib/textus/store/writer.rb', line 7 def initialize(store) @store = store @manifest = store.manifest @reader = store.reader end |
Instance Method Details
#accept(key, as:) ⇒ Object
147 148 149 |
# File 'lib/textus/store/writer.rb', line 147 def accept(key, as:) Proposal.accept(@store, key, as: as) end |
#delete(key, if_etag: nil, as: Role::DEFAULT, suppress_events: false) ⇒ Object
Backward-compat shim — orchestration now lives in Application::Writes::Delete.
122 123 124 125 126 127 |
# File 'lib/textus/store/writer.rb', line 122 def delete(key, if_etag: nil, as: Role::DEFAULT, suppress_events: false) ctx = Textus::Application::Context.new(store: @store, role: as) Textus::Application::Writes::Delete.new(ctx: ctx, bus: @store.bus).call( key, if_etag: if_etag, suppress_events: suppress_events ) end |
#delete_envelope_from_disk(key, if_etag: nil, as: Role::DEFAULT, correlation_id: nil) ⇒ Object
Pure I/O: resolve path, validate etag, delete from disk, audit. No permission check and no event firing — those are handled by the caller (Application::Writes::Delete).
132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/textus/store/writer.rb', line 132 def delete_envelope_from_disk(key, if_etag: nil, as: Role::DEFAULT, correlation_id: nil) _, path, = @manifest.resolve(key) raise UnknownKey.new(key, suggestions: @manifest.suggestions_for(key)) unless File.exist?(path) etag_before = Etag.for_file(path) raise EtagMismatch.new(key, if_etag, etag_before) if if_etag && if_etag != etag_before File.delete(path) @store.audit_log.append( role: as, verb: "delete", key: key, etag_before: etag_before, etag_after: nil, extras: correlation_id ? { "correlation_id" => correlation_id } : nil ) end |
#enforce_name_match!(path, meta, format) ⇒ Object
86 87 88 89 90 91 92 93 94 95 |
# File 'lib/textus/store/writer.rb', line 86 def enforce_name_match!(path, , format) return unless %w[markdown json yaml].include?(format) return unless .is_a?(Hash) && ["name"] ext = Entry.for_format(format).extensions.first basename = File.basename(path, ext) return if ["name"] == basename raise BadFrontmatter.new(path, "name '#{["name"]}' does not match basename '#{basename}'") end |
#ensure_uid(format, meta, content, existing_uid) ⇒ Object
75 76 77 78 79 80 81 82 83 84 |
# File 'lib/textus/store/writer.rb', line 75 def ensure_uid(format, , content, existing_uid) case format when "markdown", "json", "yaml" m = .is_a?(Hash) ? .dup : {} m["uid"] = existing_uid || Store.mint_uid unless m["uid"].is_a?(String) && !m["uid"].empty? [m, content] else [, content] end end |
#existing_uid_for(mentry, path) ⇒ Object
65 66 67 68 69 70 71 72 73 |
# File 'lib/textus/store/writer.rb', line 65 def existing_uid_for(mentry, path) return nil unless File.exist?(path) raw = File.binread(path) parsed = Entry.for_format(mentry.format).parse(raw, path: path) Envelope.extract_uid(parsed["_meta"]) rescue StandardError nil end |
#put(key, meta: nil, body: nil, content: nil, if_etag: nil, as: Role::DEFAULT, suppress_events: false) ⇒ Object
Backward-compat shim — orchestration now lives in Application::Writes::Put.
14 15 16 17 18 19 |
# File 'lib/textus/store/writer.rb', line 14 def put(key, meta: nil, body: nil, content: nil, if_etag: nil, as: Role::DEFAULT, suppress_events: false) ctx = Textus::Application::Context.new(store: @store, role: as) Textus::Application::Writes::Put.new(ctx: ctx, bus: @store.bus).call( key, meta: , body: body, content: content, if_etag: if_etag, suppress_events: suppress_events ) end |
#reject(pending_key, as: Role::DEFAULT) ⇒ Object
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/textus/store/writer.rb', line 151 def reject(pending_key, as: Role::DEFAULT) raise ProposalError.new("only human role can reject proposals; got '#{as}'") unless as == "human" mentry, = @store.manifest.resolve(pending_key) raise ProposalError.new("reject: '#{pending_key}' is not in a proposal zone (zone=#{mentry.zone})") unless mentry.in_proposal_zone? env = @store.get(pending_key) proposal = env.dig("_meta", "proposal") or raise ProposalError.new("entry has no proposal block: #{pending_key}") target_key = proposal["target_key"] or raise ProposalError.new("proposal missing target_key") delete(pending_key, as: as) @store.fire_event(:reject, key: pending_key, target_key: target_key) { "protocol" => PROTOCOL, "rejected" => pending_key, "target_key" => target_key } end |
#serialize_for_put(mentry:, path:, strategy:, meta:, body:, content:) ⇒ Object
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/textus/store/writer.rb', line 97 def serialize_for_put(mentry:, path:, strategy:, meta:, body:, content:) case mentry.format when "markdown", "text" bytes = strategy.serialize(meta: , body: body.to_s) [bytes, , body.to_s, nil] when "json", "yaml" raise UsageError.new("put for #{mentry.format} requires content: or body:") if content.nil? && (body.nil? || body.to_s.empty?) if content.nil? begin parsed = strategy.parse(body.to_s, path: path) rescue BadFrontmatter => e raise BadContent.new(path, "bad_content: #{e.}") end [body.to_s, parsed["_meta"], body.to_s, parsed["content"]] else bytes = strategy.serialize(meta: , body: "", content: content) [bytes, , bytes, content] end else raise UsageError.new("unknown format #{mentry.format.inspect}") end end |
#write_envelope_to_disk(key, mentry:, meta: nil, body: nil, content: nil, if_etag: nil, as: Role::DEFAULT, correlation_id: nil) ⇒ Object
Pure I/O: validate, serialize, etag-check, write to disk, audit. No permission check and no event firing — those are handled by the caller (Application::Writes::Put).
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/textus/store/writer.rb', line 24 def write_envelope_to_disk(key, mentry:, meta: nil, body: nil, content: nil, if_etag: nil, as: Role::DEFAULT, correlation_id: nil) _, path, = @manifest.resolve(key) ||= {} strategy = Entry.for_format(mentry.format) existing_uid = existing_uid_for(mentry, path) , content = ensure_uid(mentry.format, , content, existing_uid) bytes, , eff_body, eff_content = serialize_for_put( mentry: mentry, path: path, strategy: strategy, meta: , body: body, content: content ) enforce_name_match!(path, , mentry.format) schema = @store.schema_for(mentry.schema) if schema Entry.for_format(mentry.format).validate_against( schema, { "_meta" => , "content" => eff_content }, ) end etag_before = File.exist?(path) ? Etag.for_file(path) : nil raise EtagMismatch.new(key, if_etag, etag_before) if if_etag && (etag_before != if_etag) FileUtils.mkdir_p(File.dirname(path)) File.binwrite(path, bytes) etag_after = Etag.for_bytes(bytes) @store.audit_log.append( role: as, verb: "put", key: key, etag_before: etag_before, etag_after: etag_after, extras: correlation_id ? { "correlation_id" => correlation_id } : nil ) Envelope.build( key: key, mentry: mentry, path: path, meta: , body: eff_body, etag: etag_after, content: eff_content ) end |