Class: TrakFlow::Storage::Jsonl
- Inherits:
-
Object
- Object
- TrakFlow::Storage::Jsonl
- Defined in:
- lib/trak_flow/storage/jsonl.rb
Overview
JSONL (JSON Lines) persistence layer for Git integration This is the git-tracked source of truth stored in .trak_flow/tasks.jsonl One JSON entity per line makes diffs readable and merges usually automatic
Constant Summary collapse
- ENTITY_TYPES =
%w[task dependency label comment].freeze
Instance Attribute Summary collapse
-
#path ⇒ Object
readonly
Returns the value of attribute path.
Instance Method Summary collapse
-
#changed_since?(timestamp) ⇒ Boolean
Check if JSONL file has changed since last import.
-
#content_hash ⇒ Object
Get content hash of the JSONL file.
-
#exists? ⇒ Boolean
Check if file exists.
-
#export(db) ⇒ Object
Export all data from database to JSONL file - Plans are exported (persistent blueprints) - Ephemeral Workflows are NOT exported (temporary only).
-
#import(db, orphan_handling: nil, error_policy: nil) ⇒ Object
Import all data from JSONL file to database.
-
#incremental_export(db, changed_ids) ⇒ Object
Incremental export - only export changed entities.
-
#initialize(path = nil) ⇒ Jsonl
constructor
A new instance of Jsonl.
-
#read_entities ⇒ Object
Read raw entities from file.
-
#write_entities(entities) ⇒ Object
Write entities to file.
Constructor Details
#initialize(path = nil) ⇒ Jsonl
Returns a new instance of Jsonl.
13 14 15 |
# File 'lib/trak_flow/storage/jsonl.rb', line 13 def initialize(path = nil) @path = path || TrakFlow.jsonl_path end |
Instance Attribute Details
#path ⇒ Object (readonly)
Returns the value of attribute path.
11 12 13 |
# File 'lib/trak_flow/storage/jsonl.rb', line 11 def path @path end |
Instance Method Details
#changed_since?(timestamp) ⇒ Boolean
Check if JSONL file has changed since last import
120 121 122 123 124 |
# File 'lib/trak_flow/storage/jsonl.rb', line 120 def changed_since?() return true unless File.exist?(path) File.mtime(path) > end |
#content_hash ⇒ Object
Get content hash of the JSONL file
127 128 129 130 131 |
# File 'lib/trak_flow/storage/jsonl.rb', line 127 def content_hash return nil unless File.exist?(path) Digest::SHA256.hexdigest(File.read(path))[0, 16] end |
#exists? ⇒ Boolean
Check if file exists
134 135 136 |
# File 'lib/trak_flow/storage/jsonl.rb', line 134 def exists? File.exist?(path) end |
#export(db) ⇒ Object
Export all data from database to JSONL file
- Plans are exported (persistent blueprints)
- Ephemeral Workflows are NOT exported (temporary only)
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/trak_flow/storage/jsonl.rb', line 20 def export(db) # Export regular tasks (excluding ephemeral) and include Plans entities = db.list_tasks(include_ephemeral: false, include_plans: true, include_tombstones: true) .map { |task| { type: "task", data: task.to_h } } db.all_task_ids.each do |task_id| db.find_dependencies(task_id, direction: :outgoing).each do |dep| entities << { type: "dependency", data: dep.to_h } end db.find_labels(task_id).each do |label| entities << { type: "label", data: label.to_h } end db.find_comments(task_id).each do |comment| entities << { type: "comment", data: comment.to_h } end end write_entities(entities) db.mark_clean! end |
#import(db, orphan_handling: nil, error_policy: nil) ⇒ Object
Import all data from JSONL file to database
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/trak_flow/storage/jsonl.rb', line 47 def import(db, orphan_handling: nil, error_policy: nil) orphan_handling ||= TrakFlow.config.get("import.orphan_handling") error_policy ||= TrakFlow.config.get("import.error_policy") || "warn" models = build_import_models(read_entities) tasks = handle_orphans(models[:task], orphan_handling) db.import_tasks(tasks) import_errors = import_entities(db, :add_dependency, models[:dependency], error_policy) import_errors += import_entities(db, :add_label, models[:label], error_policy) import_errors += import_entities(db, :add_comment, models[:comment], error_policy) raise_if_strict_errors(import_errors, error_policy) end |
#incremental_export(db, changed_ids) ⇒ Object
Incremental export - only export changed entities
174 175 176 177 178 179 180 181 182 |
# File 'lib/trak_flow/storage/jsonl.rb', line 174 def incremental_export(db, changed_ids) return export(db) unless File.exist?(path) entities_by_key = index_entities(read_entities) changed_ids.each { |task_id| apply_task_change(db, task_id, entities_by_key) } write_entities(entities_by_key.values) db.mark_clean! end |
#read_entities ⇒ Object
Read raw entities from file
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/trak_flow/storage/jsonl.rb', line 139 def read_entities return [] unless File.exist?(path) entities = [] File.readlines(path).each_with_index do |line, index| line = line.strip next if line.empty? || line.start_with?("#") begin data = Oj.load(line, mode: :compat, symbol_keys: true) entities << data if valid_entity?(data) rescue Oj::ParseError => e debug_me "Warning: Could not parse line #{index + 1}: #{e.message}" end end entities end |
#write_entities(entities) ⇒ Object
Write entities to file
159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/trak_flow/storage/jsonl.rb', line 159 def write_entities(entities) FileUtils.mkdir_p(File.dirname(path)) File.open(path, "w") do |f| f.puts "# TrakFlow task tracker data" f.puts "# Generated at #{Time.now.utc.iso8601}" f.puts "" entities.each do |entity| f.puts Oj.dump(entity, mode: :compat) end end end |