Help us shape the future of Go! Take the 2026 Go Developer Survey

userdata

package
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 26, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package userdata is the state dbc keeps for the user between sessions: the query history, the editor buffer and the assistant's conversations, all under ~/.config/dbc.

It started life inside the former tview UI (package ui, since removed), and kept that UI's on-disk format — same paths, same JSON-lines shape — so history and the scratchpad written by older dbc builds still load.

Index

Constants

View Source
const (
	// MaxChats caps how many conversations the directory keeps, trimmed
	// from the oldest. The feature is "the last few chats": an unbounded
	// archive would be a directory growing forever and a picker nobody can
	// scan. Thirty is a working week or two of questions.
	MaxChats = 30

	// MaxChatTitle bounds a derived title, in runes. The picker row also
	// carries a date, a message count and sometimes a connection name, and
	// a title long enough to push those off the row costs more than it says.
	MaxChatTitle = 72
)

Variables

This section is empty.

Functions

func BufferFile

func BufferFile() string

BufferFile is where the editor buffer persists between sessions, under the user's config directory. It returns "" when no home directory is known, and the buffer simply does not persist.

func ChatTitle

func ChatTitle(msgs []ChatMsg) string

ChatTitle picks what a conversation is remembered by: the first line of the first thing the USER asked. Not the answer — the question is what the user scans a list for, and an answer's first line is usually a restatement or a preamble. Returns "" when there is no question; the caller decides how an untitled row reads.

func ChatsDir

func ChatsDir() string

ChatsDir is where conversations persist, beside the history and the editor buffer. It returns "" when no home directory is known, and conversations then live only for the session.

func HistoryFile

func HistoryFile() string

HistoryFile is where the history persists, beside the editor buffer. It returns "" when no home directory is known, and history lives only for the session.

func LoadBuffer

func LoadBuffer(path string) string

LoadBuffer returns the persisted editor buffer, or "" when there is none.

func NewChatID

func NewChatID(now time.Time) string

NewChatID mints a conversation id from a wall-clock instant: sortable by construction, which lets a listing fall back on id order when a file's Updated stamp is missing, and legible to a user browsing the directory.

The tail is the full nanosecond field. ⟲ new saves the old conversation and the next save mints the new one's id moments later; a coarser tail could collide there, and a collision would overwrite the earlier file.

func RemoveChat

func RemoveChat(dir, id string) error

RemoveChat deletes one conversation. A missing file is success: the caller wanted it gone.

func SaveBuffer

func SaveBuffer(path, text string) error

SaveBuffer persists the editor buffer. The file is user-only: query scratchpads have a way of accumulating real values.

func SaveChat

func SaveChat(dir string, c Chat) error

SaveChat writes c into dir as <id>.json, then trims the directory back to MaxChats. Rewriting the same id is the normal case — the live conversation is re-saved after every answer — so the write goes to a temp file that is renamed over the old one: a crash mid-write must not leave a half-written transcript where a readable one used to be.

An empty dir, id or message list is a no-op rather than an error. The caller saves unconditionally at teardown, and "there was no conversation" is not a failure.

The files are user-only, like the history: a conversation about a query carries the query, and on connections with ai_rows its result rows.

Types

type Chat

type Chat struct {
	ID      string    `json:"id"`
	Title   string    `json:"title"`
	Agent   string    `json:"agent,omitempty"` // the backend's display name
	Model   string    `json:"model,omitempty"` // the model's display name
	Conn    string    `json:"conn,omitempty"`  // the active connection at the last save
	Started time.Time `json:"started"`
	Updated time.Time `json:"updated"`
	Msgs    []ChatMsg `json:"msgs"`
}

Chat is one archived conversation.

func LoadChat

func LoadChat(dir, id string) (Chat, error)

LoadChat reads one conversation by id.

func (Chat) RestoreNote

func (c Chat) RestoreNote() string

RestoreNote is the transcript line shown when a saved conversation is reopened: what the user got back, and what they did not. A reopened conversation is a transcript, not a resumed agent session, and silence about that would let a follow-up get a confidently unrelated answer from a model that never saw the conversation. Both UIs show these words.

type ChatMeta

type ChatMeta struct {
	ID      string
	Title   string
	Agent   string
	Conn    string
	Updated time.Time
	Count   int // messages, so a row can say how much is in there
}

ChatMeta is the listing view: what a picker row needs, without the transcript. ListChats still reads whole files — they are few and small, and a partial parse would need a second schema to stay honest — but returning only this keeps a caller from holding every conversation in memory to draw a list of titles.

func ListChats

func ListChats(dir string) ([]ChatMeta, error)

ListChats returns the archived conversations newest first. A file that will not parse costs itself and nothing else: one truncated or hand-edited transcript must not take the whole list down with it. A missing directory is the common case — a user who has never asked anything — and is not an error.

type ChatMsg

type ChatMsg struct {
	Role string `json:"role"`
	Text string `json:"text"`
}

ChatMsg is one transcript entry. Role is a string ("user", "agent", "tool", "note", "info", "error") rather than the TUI's integer: an archive outlives the enum, and a reordered constant block must not silently restyle every stored conversation.

type Entry

type Entry struct {
	At   time.Time `json:"at"`
	Conn string    `json:"conn"`
	SQL  string    `json:"sql"`
}

Entry is one statement the user ran — one line of the history file.

func MatchHistory

func MatchHistory(entries []Entry, q string) []Entry

MatchHistory keeps the entries whose SQL or connection name holds q, case-insensitively. An empty query keeps everything.

type History

type History struct {
	// contains filtered or unexported fields
}

History is the statements the user has run, oldest first, backed by a JSON-lines file: one object per line carries the multi-line SQL that a plain-text history would have to escape, and appending one entry costs one write rather than a rewrite of the file.

It is safe for concurrent use. The TUI touches it from one goroutine, but `dbc web` gives every browser tab its own workspace over ONE shared History (so a query run in any tab, or in the terminal's history file, is recallable from all of them), and those tabs record from their own request goroutines. mu also serializes the file appends, so two tabs recording at once cannot interleave half-lines.

func LoadHistory

func LoadHistory(path string) *History

LoadHistory reads the history file, skipping lines it cannot parse — a half-written last line from a killed process must not cost the whole history. An over-long file is trimmed back to histMax here, which is the only point at which the file is rewritten rather than appended to.

func (*History) Add

func (h *History) Add(conn, sql string, at time.Time) error

Add records a statement, unless it repeats the one just recorded — running the same query twice while watching a table change should not bury the rest of the history. The entry is kept in memory whether or not the file write works, so a read-only config directory costs persistence, not the session.

func (*History) Recent

func (h *History) Recent() []Entry

Recent returns the entries newest first — the order they are picked in.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL