Class: Textus::Store

Inherits:
Object
  • Object
show all
Defined in:
lib/textus/store.rb,
lib/textus/store/mover.rb,
lib/textus/store/reader.rb,
lib/textus/store/writer.rb,
lib/textus/store/audit_log.rb,
lib/textus/store/staleness.rb,
lib/textus/store/validator.rb

Defined Under Namespace

Classes: AuditLog, Mover, Reader, Staleness, Validator, Writer

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(root) ⇒ Store

Returns a new instance of Store.



39
40
41
42
43
44
45
46
47
48
49
# File 'lib/textus/store.rb', line 39

def initialize(root)
  @root = File.expand_path(root)
  @manifest = Manifest.load(@root)
  @bus = Hooks::Dispatcher.new(audit_log: audit_log)
  @registry = Hooks::Registry.new(dispatcher: @bus)
  @schemas = {}
  load_hooks
  @reader = Reader.new(self)
  @writer = Writer.new(self)
  fire_event(:loaded)
end

Instance Attribute Details

#busObject (readonly)

Returns the value of attribute bus.



6
7
8
# File 'lib/textus/store.rb', line 6

def bus
  @bus
end

#manifestObject (readonly)

Returns the value of attribute manifest.



6
7
8
# File 'lib/textus/store.rb', line 6

def manifest
  @manifest
end

#readerObject (readonly)

Returns the value of attribute reader.



6
7
8
# File 'lib/textus/store.rb', line 6

def reader
  @reader
end

#registryObject (readonly)

Returns the value of attribute registry.



6
7
8
# File 'lib/textus/store.rb', line 6

def registry
  @registry
end

#rootObject (readonly)

Returns the value of attribute root.



6
7
8
# File 'lib/textus/store.rb', line 6

def root
  @root
end

#writerObject (readonly)

Returns the value of attribute writer.



6
7
8
# File 'lib/textus/store.rb', line 6

def writer
  @writer
end

Class Method Details

.discover(start_dir = Dir.pwd, root: nil) ⇒ Object

Raises:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/textus/store.rb', line 15

def self.discover(start_dir = Dir.pwd, root: nil)
  explicit = root || ENV.fetch("TEXTUS_ROOT", nil)
  return discover_explicit(explicit) if explicit

  dir = File.expand_path(start_dir)
  loop do
    candidate = File.join(dir, ".textus")
    return new(candidate) if File.directory?(candidate) && File.exist?(File.join(candidate, "manifest.yaml"))

    parent = File.dirname(dir)
    break if parent == dir

    dir = parent
  end
  raise IoError.new("no .textus directory found from #{start_dir}")
end

.mint_uidObject

A Textus UID: 16 lowercase hex chars (SecureRandom.hex(8)). Not a UUID —short on purpose. Random enough for collision-never-in-practice within a single store.



11
12
13
# File 'lib/textus/store.rb', line 11

def self.mint_uid
  SecureRandom.hex(8)
end

Instance Method Details

#accept(key, as: Role::DEFAULT) ⇒ Object



109
110
111
112
# File 'lib/textus/store.rb', line 109

def accept(key, as: Role::DEFAULT)
  ctx = Textus::Composition.context(self, role: as)
  Textus::Composition.writes_accept(ctx).call(key)
end

#audit_logObject



132
133
134
# File 'lib/textus/store.rb', line 132

def audit_log
  @audit_log ||= Store::AuditLog.new(@root)
end

#delete(key, if_etag: nil, as: Role::DEFAULT, suppress_events: false) ⇒ Object

rubocop:enable Metrics/ParameterLists



99
100
101
102
# File 'lib/textus/store.rb', line 99

def delete(key, if_etag: nil, as: Role::DEFAULT, suppress_events: false)
  ctx = Textus::Composition.context(self, role: as)
  Textus::Composition.writes_delete(ctx).call(key, if_etag: if_etag, suppress_events: suppress_events)
end

#deps(key) ⇒ Object



116
# File 'lib/textus/store.rb', line 116

def deps(key)    = @reader.deps(key)

#fire_event(event) ⇒ Object



104
105
106
107
# File 'lib/textus/store.rb', line 104

def fire_event(event, **)
  view = Textus::Application::Context.new(store: self, role: "human")
  @bus.publish(event, store: view, **)
end

#get(key, as: Textus::Role::DEFAULT) ⇒ Object

Raises:



78
79
80
81
82
83
84
# File 'lib/textus/store.rb', line 78

def get(key, as: Textus::Role::DEFAULT)
  ctx = Textus::Composition.context(self, role: as)
  result = Textus::Composition.reads_get(ctx).call(key)
  raise UnknownKey.new(key, suggestions: manifest.suggestions_for(key)) if result.nil?

  result
end

#listObject



87
# File 'lib/textus/store.rb', line 87

def list(**) = @reader.list(**)

#load_hooksObject



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/textus/store.rb', line 51

def load_hooks
  Textus.with_registry(@registry) do
    Hooks::Builtin.register_all
    dir = File.join(@root, "hooks")
    return unless File.directory?(dir)

    Dir.glob(File.join(dir, "**/*.rb")).sort.each do |f| # rubocop:disable Lint/RedundantDirGlobSort
      begin
        load(f)
      rescue StandardError, ScriptError => e
        raise UsageError.new("failed loading hook #{File.basename(f)}: #{e.class}: #{e.message}")
      end
    end
  end
end

#mv(old_key, new_key, as: Role::DEFAULT, dry_run: false, correlation_id: nil) ⇒ Object

Move an entry from old_key to new_key within the same zone. Preserves uid (minting one first if absent), validates both keys against the manifest, refuses to clobber, and writes one mv audit row.



127
128
129
130
# File 'lib/textus/store.rb', line 127

def mv(old_key, new_key, as: Role::DEFAULT, dry_run: false, correlation_id: nil)
  Mover.new(store: self, reader: @reader, writer: @writer, manifest: @manifest, audit_log: audit_log)
       .call(old_key, new_key, as: as, dry_run: dry_run, correlation_id: correlation_id)
end

#publishedObject



118
# File 'lib/textus/store.rb', line 118

def published    = @reader.published

#put(key, meta: nil, body: nil, content: nil, if_etag: nil, as: Role::DEFAULT, suppress_events: false) ⇒ Object

rubocop:disable Metrics/ParameterLists



91
92
93
94
95
96
# File 'lib/textus/store.rb', line 91

def put(key, meta: nil, body: nil, content: nil, if_etag: nil, as: Role::DEFAULT, suppress_events: false)
  ctx = Textus::Composition.context(self, role: as)
  Textus::Composition.writes_put(ctx).call(
    key, meta: meta, body: body, content: content, if_etag: if_etag, suppress_events: suppress_events
  )
end

#rdeps(key) ⇒ Object



117
# File 'lib/textus/store.rb', line 117

def rdeps(key)   = @reader.rdeps(key)

#rejectObject



114
# File 'lib/textus/store.rb', line 114

def reject(...) = @writer.reject(...)

#schema_envelope(key) ⇒ Object



88
# File 'lib/textus/store.rb', line 88

def schema_envelope(key) = @reader.schema_envelope(key)

#schema_for(name) ⇒ Object



67
68
69
70
71
72
73
74
75
76
# File 'lib/textus/store.rb', line 67

def schema_for(name)
  return nil if name.nil?

  @schemas[name] ||= begin
    sp = File.join(@root, "schemas", "#{name}.yaml")
    raise IoError.new("schema not found: #{sp}") unless File.exist?(sp)

    Schema.load(sp)
  end
end

#staleObject



119
# File 'lib/textus/store.rb', line 119

def stale(**)    = @reader.stale(**)

#uid(key) ⇒ Object



122
# File 'lib/textus/store.rb', line 122

def uid(key) = @reader.uid(key)

#validate_allObject



120
# File 'lib/textus/store.rb', line 120

def validate_all = @reader.validate_all

#where(key) ⇒ Object



86
# File 'lib/textus/store.rb', line 86

def where(key) = @reader.where(key)