Class: TrakFlow::Storage::Jsonl

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

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

#pathObject (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

Returns:



120
121
122
123
124
# File 'lib/trak_flow/storage/jsonl.rb', line 120

def changed_since?(timestamp)
  return true unless File.exist?(path)

  File.mtime(path) > timestamp
end

#content_hashObject

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

Returns:



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

Parameters:

  • the database to import into

  • (defaults to: nil)

    how to handle orphaned tasks

  • (defaults to: nil)

    how to handle import errors: "warn", "strict", or "ignore"



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_entitiesObject

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