Documentation
¶
Overview ¶
Package notes implements the disk-backed notes subsystem ported from kgraph. Notes are Markdown files on disk (source of truth) with an optional YAML frontmatter block. An FTS5 index in the per-project SQLite database is maintained as a derived, rebuildable view.
Index ¶
- Constants
- Variables
- func Delete(notesDir, key string) error
- func EncodeFrontmatter(data map[string]any, body []byte) ([]byte, error)
- func ExtractWikilinks(body []byte) []string
- func ListKeys(notesDir string) ([]string, error)
- func ParseFrontmatter(raw []byte) (map[string]any, []byte, error)
- func Related(g *Graph, key string) []string
- func ValidateKey(key string) error
- func Watch(notesDir string, onChange func(key string)) (func(), error)
- func Write(notesDir string, n *Note) error
- type Edge
- type Graph
- type HistoryEntry
- type Note
- type NoteNode
- type TreeNode
- type Wikilink
Constants ¶
const MaxKeyLen = 512
MaxKeyLen caps note keys. Keeps paths well under filesystem limits even with the `.md` suffix and nested dirs. Phase-2 decision.
Variables ¶
var ErrInvalidKey = errors.New("invalid note key")
ErrInvalidKey is returned when a key contains path-traversal components, absolute path prefixes, null bytes, or exceeds MaxKeyLen.
var ErrNotFound = errors.New("note not found")
ErrNotFound is returned when a note key does not exist on disk.
Functions ¶
func Delete ¶
Delete removes the note file. Returns ErrNotFound if missing. Serialized via the per-project mutex (see Write).
func EncodeFrontmatter ¶
EncodeFrontmatter emits `---\n<yaml>\n---\n<body>`. An empty map emits the body verbatim (no leading delimiter). Keys are sorted by yaml.v3's default encoder (alphabetical); kgraph's gray-matter also sorts, so round-trips are stable.
func ExtractWikilinks ¶
ExtractWikilinks returns the de-duplicated list of note keys referenced by `[[wikilink]]` or `[[wikilink|alias]]` occurrences in body, in the order of first appearance. Whitespace inside a target is preserved verbatim (Obsidian-style keys can contain spaces, though we normalize them to paths at the call site).
func ListKeys ¶
ListKeys is the cheap variant of List: it walks the tree once and returns keys only. Useful for the lean `GET /api/projects/.../notes` endpoint where frontmatter isn't needed.
func ParseFrontmatter ¶
ParseFrontmatter splits raw note bytes into a frontmatter map and a body. If the input does not begin with a `---` delimiter line the whole input is returned as the body with an empty map — this matches the kgraph gray-matter behavior (no frontmatter is not an error).
A body that happens to contain `---` on its own line elsewhere is preserved verbatim; only the FIRST delimiter pair is consumed.
func Related ¶
Related returns the set of note keys directly connected to `key`, in either direction (outlinks + backlinks). Self-links are deduped. The result is sorted for deterministic API output.
func ValidateKey ¶
ValidateKey enforces the invariants documented on ErrInvalidKey. Public so handlers can short-circuit on bad input before touching the filesystem.
func Watch ¶
Watch scans notesDir every second and fires onChange(key) whenever a `.md` file's mtime changes, a new `.md` file appears, or an existing one is removed. The returned `stop` func halts the background goroutine.
This is a polling watcher — chosen over fsnotify to keep the watcher dependency-free and cross-platform. kgraph uses fs.watch (Node), which is similarly coarse in practice; the 1-second cadence is adequate for the "re-index after a user edits a note in their IDE" use case.
func Write ¶
Write persists n to disk atomically (temp file + rename). Parent directories are created as needed. The frontmatter embedded in the file is built from n.Frontmatter, then overlaid with Author, Tags, CreatedAt, UpdatedAt so the struct fields win over stale map entries.
Acquires the per-project mutex for the whole write+auto-commit sequence so concurrent writes to the same notesDir are serialized — without this, write N's bytes could land on disk before write N-1's commit runs, causing the earlier commit to record the later content.
Types ¶
type Edge ¶
type Edge struct {
Source string `json:"source"`
Target string `json:"target"`
CrossProject bool `json:"cross_project,omitempty"`
}
Edge represents a directed `[[wikilink]]` reference from Source → Target. The optional CrossProject field is true when Source and Target belong to different projects.
type Graph ¶
Graph is the wikilink-derived relation between notes in a project.
func BuildGraph ¶
BuildGraph walks every note in notesDir, extracts wikilinks from the body, and returns the resulting node+edge graph. Edges may point to nonexistent targets (dangling wikilinks) — that is intentional, and matches kgraph's behavior.
projectsRoot, when non-empty, is the parent directory that contains all project data dirs (i.e. the directory whose children are <slug>/notes/). It is used to resolve cross-project wikilinks. Pass "" to disable cross-project resolution (backward-compatible: same-project only).
type HistoryEntry ¶
type HistoryEntry struct {
Commit string `json:"commit"`
Author string `json:"author"`
Time time.Time `json:"time"`
Message string `json:"message"`
}
HistoryEntry is a single commit entry in a note's auto-commit log.
func History ¶
func History(notesDir, key string, limit int) ([]HistoryEntry, error)
History returns recent commits touching the given note key, newest first. If git is not installed or the repo has no history for the file, returns an empty slice with nil error — the endpoint should not 500 just because a project has never been committed to.
limit <= 0 is treated as "no cap".
type Note ¶
type Note struct {
Key string `json:"key"`
Content string `json:"content"`
Author string `json:"author,omitempty"`
Tags []string `json:"tags,omitempty"`
Frontmatter map[string]any `json:"frontmatter,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
Note is the in-memory representation of a Markdown note.
Frontmatter is the raw YAML map; Author/Tags are convenience copies hoisted from common keys. Timestamps are derived from filesystem mtime on Read and from time.Now() on Write.
type NoteNode ¶
type NoteNode struct {
Key string `json:"key"`
Title string `json:"title"`
Folder string `json:"folder"`
Tags []string `json:"tags,omitempty"`
Project string `json:"project,omitempty"` // non-empty for cross-project nodes
Missing bool `json:"missing,omitempty"` // true when target file is absent
}
NoteNode is a lean projection of a Note for graph rendering. The optional Project field is non-empty only for cross-project nodes. The optional Missing field is true when the target note does not exist on disk.
type TreeNode ¶
type TreeNode struct {
Name string `json:"name"`
Path string `json:"path"`
Type string `json:"type"` // "folder" | "note"
Children []*TreeNode `json:"children,omitempty"`
}
TreeNode is the recursive folder/file tree returned by Tree().
type Wikilink ¶
type Wikilink struct {
Target string // raw target string, unchanged
Project string // "" for same-project; slug of the target project otherwise
Key string // the note key within that project (or the raw target for same-project)
CrossProject bool // true iff target is a cross-project reference
}
Wikilink holds a parsed wikilink target broken into its constituent parts.
func ParseWikilink ¶
ParseWikilink parses a raw wikilink target (as returned by ExtractWikilinks) into a Wikilink. A target matches the cross-project pattern when it is of the form "projects/<slug>/<rest>" and <slug> is a non-empty string of alphanumerics, hyphens, and underscores. Any other shape is treated as a same-project key.