Documentation
¶
Overview ¶
Package store implements sync82's SQLite-backed persistence layer: projects (each optionally holding subprojects), overwrite-style documents, append-only entries, and case-insensitive substring search across both. Manager caches one *Store per vault path so repeated calls against the same vault reuse a connection pool; Open handles per-vault schema migrations and connection setup. Every *Store method is safe for concurrent use. Lookup failures wrap ErrNotFound, naming collisions wrap ErrAlreadyExists, and Open failures wrap ErrOpenFailed; check them with errors.Is. Messages of all errors except ErrOpenFailed contain no filesystem paths.
Index ¶
- Variables
- type ArchiveResult
- type Entry
- type EntrySection
- type KindMetadata
- type KindStorage
- type KindWrite
- type Manager
- type Project
- type SearchResult
- type SearchScope
- type Store
- func (s *Store) AppendEntry(ctx context.Context, project, subproject, kind, entryDate, body string) error
- func (s *Store) ArchiveEntries(ctx context.Context, project, subproject, kind, cutoff string) (ArchiveResult, error)
- func (s *Store) Close() error
- func (s *Store) CreateProject(ctx context.Context, name string, parentID *int64) (*Project, error)
- func (s *Store) DeleteKind(ctx context.Context, project, subproject, kind string) error
- func (s *Store) DeleteProject(ctx context.Context, name string) error
- func (s *Store) DeleteSubproject(ctx context.Context, parentName, subName string) error
- func (s *Store) EnsureProject(ctx context.Context, project, subproject string) (proj *Project, created bool, err error)
- func (s *Store) FindProjectByName(ctx context.Context, name string, parentID *int64) (*Project, error)
- func (s *Store) IsBlankOrTemplate(ctx context.Context, project, subproject, kind string) (bool, error)
- func (s *Store) KindExists(ctx context.Context, project, subproject, kind string) (bool, error)
- func (s *Store) KindMode(ctx context.Context, project, subproject, kind string) (KindStorage, error)
- func (s *Store) ListKinds(ctx context.Context, project, subproject string, includeArchived bool) ([]string, error)
- func (s *Store) ListSubprojects(ctx context.Context, parentID int64) ([]Project, error)
- func (s *Store) ListTopLevelProjects(ctx context.Context) ([]Project, error)
- func (s *Store) Metadata(ctx context.Context, project, subproject, kind string) (KindMetadata, error)
- func (s *Store) ProjectExists(ctx context.Context, project, subproject string) (bool, error)
- func (s *Store) PromoteAllSubprojects(ctx context.Context, parentName string) ([]string, error)
- func (s *Store) PromoteSubproject(ctx context.Context, parentName, subName string) error
- func (s *Store) ReadContent(ctx context.Context, project, subproject, kind string) (content string, ok bool, err error)
- func (s *Store) ReadDocument(ctx context.Context, project, subproject, kind string) (content string, ok bool, err error)
- func (s *Store) ReadEntries(ctx context.Context, project, subproject, kind string, includeArchived bool) ([]Entry, error)
- func (s *Store) ReadEntriesSince(ctx context.Context, project, subproject, kind, since string, maxEntries int) ([]Entry, error)
- func (s *Store) RenameProject(ctx context.Context, project, subproject, newName string) error
- func (s *Store) ReplaceAllEntries(ctx context.Context, project, subproject, kind string, sections []EntrySection) error
- func (s *Store) ReplaceArchivedEntries(ctx context.Context, project, subproject, kind string, sections []EntrySection) error
- func (s *Store) SearchText(ctx context.Context, query string, scope SearchScope, ...) ([]SearchResult, bool, error)
- func (s *Store) WriteDocument(ctx context.Context, project, subproject, kind, content string) error
- func (s *Store) WriteKinds(ctx context.Context, project, subproject string, writes []KindWrite) error
Constants ¶
This section is empty.
Variables ¶
var ErrAlreadyExists = errors.New("already exists")
ErrAlreadyExists is wrapped when an operation would create a naming collision, for example promoting a subproject to a name already used at the vault root.
var ErrManagerClosed = errors.New("store manager is closed")
ErrManagerClosed is returned by Get once Close has been called, so a goroutine still running during shutdown cannot open a connection that Close would never release.
var ErrNotFound = errors.New("not found")
ErrNotFound is wrapped by any lookup that fails to find its target (project, subproject, document kind, or entries kind). Check for it with errors.Is; the error message describes what was not found without including filesystem paths.
var ErrOpenFailed = errors.New("could not open vault")
ErrOpenFailed is wrapped by Open when the vault directory, file, database connection or migration step fails. Unlike every other store error, its messages embed a raw filesystem path, so callers that return errors to remote clients should detect it with errors.Is and substitute a generic message.
var ErrVaultNotFound = errors.New("vault not found")
ErrVaultNotFound is wrapped by Manager.GetExisting when no vault file exists at the requested path.
Functions ¶
This section is empty.
Types ¶
type ArchiveResult ¶
ArchiveResult summarizes the outcome of an ArchiveEntries call: Archived is the number of entries archived by the call, Kept the number of non-archived entries remaining afterwards, and NoDate how many of those are undated.
type Entry ¶
Entry is a single row of an append-only log. EntryDate is empty when the entry is undated.
type EntrySection ¶
type EntrySection struct {
Date string // "YYYY-MM-DD", empty for undated content
Body string
// Preamble marks undated content that precedes the first dated section
// (such as a title). It is stored with position preamblePosition so it
// reads back first instead of after every dated entry.
Preamble bool
}
EntrySection is a single dated or undated chunk of append-only content, such as one "## YYYY-MM-DD" section of a log. The store persists already-parsed sections and does not split raw text itself.
type KindMetadata ¶
KindMetadata describes the size and freshness of a kind's content. SizeBytes is the content length in bytes, EstimatedTokens a rough estimate (SizeBytes / 4), and LastModified the date of the last change.
type KindStorage ¶
type KindStorage string
KindStorage names the table a kind's data lives in for a project.
const ( // KindStorageNone means the kind has no data yet. KindStorageNone KindStorage = "" // KindStorageDocument means the kind is a single overwrite-style // documents row. KindStorageDocument KindStorage = "document" // KindStorageEntries means the kind is a log of entries rows // (archived or not). KindStorageEntries KindStorage = "entries" )
type KindWrite ¶
type KindWrite struct {
Kind string
Document *string
Sections []EntrySection
Archived bool
}
KindWrite is one kind's new content in a WriteKinds batch: Document for an overwrite-style kind, or Sections replacing its entries (the non-archived ones, or the archived ones when Archived is set). Document takes precedence when non-nil.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager caches one Store per vault path, so repeated calls against the same vault reuse one connection pool instead of reopening the database and re-running the migration check each time. A process may use several vault paths, hence one Store per path. A Manager is safe for concurrent use.
func (*Manager) Close ¶
Close closes every cached Store, empties the cache and marks the Manager closed, so later Get calls fail with ErrManagerClosed. It returns the first error reported while closing a Store, after attempting to close all of them.
func (*Manager) Get ¶
Get returns the Store for path, opening and caching it on first use. path is made absolute first, so equivalent spellings share one Store. It returns ErrManagerClosed once Close has run, and any error from Open (wrapping ErrOpenFailed) when the vault cannot be opened.
Open runs outside m.mu because it performs file I/O and migrations; a slow first open of one vault must not block calls against other vaults. The lock only guards the cache lookup and the reconciliation after Open returns.
func (*Manager) GetExisting ¶
GetExisting is Get for callers that must not create a vault: when no file exists at path it returns an error wrapping ErrVaultNotFound instead of creating an empty vault there. Other errors are those of Get.
type Project ¶
Project is a row of the projects table: a top-level project (ParentID not valid) or a subproject (ParentID holds the parent's ID). CreatedAt is an RFC 3339 timestamp.
type SearchResult ¶
type SearchResult struct {
Project string
Subproject string
Kind string
EntryDate string
LineNumber int
Line string
ContextBefore []string
ContextAfter []string
}
SearchResult is a single matching line within a document or entry. Project is the top-level project name and Subproject the subproject name (empty for a top-level project). For an entry, EntryDate is its date ("" if undated); LineNumber is the 1-based line within the document or entry body. Line is the matching line, and ContextBefore and ContextAfter hold the surrounding lines when context was requested.
type SearchScope ¶
SearchScope narrows a search to a project (and optionally a specific subproject). An empty Project means the whole vault; a Project with an empty Subproject means that project plus all of its subprojects.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is a SQLite-backed vault: one physical file holds every project's documents and entries. A Store is safe for concurrent use by multiple goroutines.
func Open ¶
Open opens (creating if necessary) the SQLite database at path and applies pending schema migrations. ctx bounds the initial ping and the migration, so a cancelled or expired context stops Open from waiting on a slow disk or filesystem mount.
It returns an error wrapping ErrOpenFailed when path contains '?' or '#', when the vault directory or file cannot be created, or when opening, pinging or migrating the database fails (including a vault whose schema is newer than this binary supports).
SQLite pragmas are connection-scoped, not database-scoped, so foreign_keys and journal_mode are set via DSN query parameters (modernc.org/sqlite applies "_pragma" params to every new physical connection the pool creates) rather than a one-time PRAGMA statement — that guarantees every connection enforces foreign keys and uses WAL, including a connection database/sql reopens after closing one.
The pool holds a single connection, so the operations of one Store run one at a time and only other processes contend for the file lock.
func (*Store) AppendEntry ¶
func (s *Store) AppendEntry(ctx context.Context, project, subproject, kind, entryDate, body string) error
AppendEntry inserts one new, non-archived entry with the given date (empty for undated) and body at the next insertion position for its (project, kind) pair. The position is computed by a subquery in the same INSERT statement, so concurrent appends cannot read the same position. It returns an error wrapping ErrNotFound when the project or subproject does not exist, or a database error.
func (*Store) ArchiveEntries ¶
func (s *Store) ArchiveEntries(ctx context.Context, project, subproject, kind, cutoff string) (ArchiveResult, error)
ArchiveEntries marks every non-archived, dated entry older than cutoff (format "YYYY-MM-DD") as archived. Undated entries are never archived. It returns the counts as an ArchiveResult. It returns an error wrapping ErrNotFound when the project or subproject does not exist, or a database error.
func (*Store) Close ¶
Close closes the underlying database connection pool and returns any error reported by the driver.
func (*Store) CreateProject ¶
CreateProject inserts a new project row named name (normalized), top-level when parentID is nil and otherwise a child of parentID. It returns the created Project, or a database error; a duplicate name in the same scope fails with a UNIQUE constraint error. The existence of parentID is checked by the foreign key.
func (*Store) DeleteKind ¶
DeleteKind deletes every row (document and entries, archived or not) of a kind in one transaction, so a kind is never left half-deleted. The store does not protect any kind from deletion. It returns an error wrapping ErrNotFound when the project or subproject does not exist or the kind has no rows, or a database error.
func (*Store) DeleteProject ¶
DeleteProject deletes a top-level project by name, cascading to its subprojects, documents and entries via ON DELETE CASCADE. It returns an error wrapping ErrNotFound when no such project exists, or a database error.
func (*Store) DeleteSubproject ¶
DeleteSubproject deletes the subproject subName of parentName, cascading to its documents and entries. It returns an error wrapping ErrNotFound when the parent or subproject does not exist, or a database error.
func (*Store) EnsureProject ¶
func (s *Store) EnsureProject(ctx context.Context, project, subproject string) (proj *Project, created bool, err error)
EnsureProject returns the project or subproject, creating it (and its parent project when missing) if needed. subproject may be empty for a top-level project. created reports whether the leaf (project or subproject) was created by this call; it does not report whether a missing parent was also created. It returns a database error on failure.
The check-then-create sequence is not a single transaction, so concurrent calls for the same new name can both attempt the insert. The UNIQUE index decides the winner, and createOrRecoverExisting turns the loser's constraint violation into the existing row.
func (*Store) FindProjectByName ¶
func (s *Store) FindProjectByName(ctx context.Context, name string, parentID *int64) (*Project, error)
FindProjectByName looks up a project by name within the given parent scope (parentID nil means top-level). The name is normalized. It returns (nil, nil) when no matching row exists, and a database error otherwise.
func (*Store) IsBlankOrTemplate ¶
func (s *Store) IsBlankOrTemplate(ctx context.Context, project, subproject, kind string) (bool, error)
IsBlankOrTemplate reports whether the document of the given kind is blank: either no row exists or its content is empty or whitespace-only. Errors are those of ReadDocument.
func (*Store) KindExists ¶
KindExists reports whether a kind has any data for the project: a documents row (even with empty content) or at least one entries row, archived or not. Errors are those of ReadDocument and ReadEntries.
func (*Store) KindMode ¶
func (s *Store) KindMode(ctx context.Context, project, subproject, kind string) (KindStorage, error)
KindMode reports which table holds a kind's data for the project, or KindStorageNone when it has none. A kind present in both tables reports KindStorageDocument, the table ReadContent reads first. It returns an error wrapping ErrNotFound when the project or subproject does not exist, or a database error.
func (*Store) ListKinds ¶
func (s *Store) ListKinds(ctx context.Context, project, subproject string, includeArchived bool) ([]string, error)
ListKinds returns every kind known for the project, the union of document kinds and distinct entries kinds, sorted by name. A kind whose entries are all archived is included only when includeArchived is true. It returns an error wrapping ErrNotFound when the project or subproject does not exist, or a database error.
func (*Store) ListSubprojects ¶
ListSubprojects returns every project whose parent is parentID, sorted by name. It returns a database error on failure.
func (*Store) ListTopLevelProjects ¶
ListTopLevelProjects returns every project with no parent, sorted by name. It returns a database error on failure.
func (*Store) Metadata ¶
func (s *Store) Metadata(ctx context.Context, project, subproject, kind string) (KindMetadata, error)
Metadata computes KindMetadata for a document or entries kind. For a document it uses the content size and update date; for entries it sums the sizes of the non-archived bodies and uses the latest creation date of the non-archived entries, or of all entries when every one is archived (size 0), so a kind KindExists reports always has metadata. Sizes use length(CAST(x AS BLOB)) because SQLite's length() counts characters, not bytes, for TEXT values. It returns an error wrapping ErrNotFound when the project does not exist or the kind has no document and no entries, or a database error.
func (*Store) ProjectExists ¶
ProjectExists reports whether the (project, subproject) pair exists. subproject may be empty to check the top-level project itself. It returns an error only for database failures.
func (*Store) PromoteAllSubprojects ¶
PromoteAllSubprojects promotes every subproject of parentName to the vault root in a single transaction: either all of them are promoted or none is. The parent and its subprojects are read inside that transaction, so the promoted set is the one present when the writes are applied. It returns the promoted names (nil when there are none). It returns an error wrapping ErrNotFound when the parent does not exist, an error wrapping ErrAlreadyExists when any name collides with a top-level project (rolling the whole batch back), or a database error.
func (*Store) PromoteSubproject ¶
PromoteSubproject moves the subproject subName of parentName to the vault root as a top-level project. It returns an error wrapping ErrNotFound when the parent or subproject does not exist, an error wrapping ErrAlreadyExists when a top-level project with that name exists, or a database error.
func (*Store) ReadContent ¶
func (s *Store) ReadContent(ctx context.Context, project, subproject, kind string) (content string, ok bool, err error)
ReadContent returns the readable content of a kind, from whichever table holds it. A document takes precedence; otherwise the bodies of the non-archived entries are joined with a blank line between them. ok is false when the kind has neither a document nor non-archived entries. It returns an error wrapping ErrNotFound when the project or subproject does not exist, or a database error.
func (*Store) ReadDocument ¶
func (s *Store) ReadDocument(ctx context.Context, project, subproject, kind string) (content string, ok bool, err error)
ReadDocument returns the raw content of the overwrite-style document of the given kind for project/subproject. ok is false, with no error, when the document row does not exist. It returns an error wrapping ErrNotFound when the project or subproject does not exist, or a database error.
func (*Store) ReadEntries ¶
func (s *Store) ReadEntries(ctx context.Context, project, subproject, kind string, includeArchived bool) ([]Entry, error)
ReadEntries returns entries for (project, kind) in reading order: a preamble first, then dated entries by date, then other undated entries, ties broken by insertion position. Archived entries are excluded unless includeArchived is true. A kind with no entries yields an empty slice and no error. It returns an error wrapping ErrNotFound when the project or subproject does not exist, or a database error.
func (*Store) ReadEntriesSince ¶
func (s *Store) ReadEntriesSince(ctx context.Context, project, subproject, kind, since string, maxEntries int) ([]Entry, error)
ReadEntriesSince returns non-archived entries for (project, kind), filtered in SQL so the full history is never loaded just to return recent entries.
Both filters apply to dated entries only; undated entries (a preamble, undated notes) are always kept, as in ArchiveEntries. since, if non-empty (format "YYYY-MM-DD"), keeps only dated entries on or after it. maxEntries, if > 0, then keeps only the most recent maxEntries of those dated entries. The result is in the same reading order ReadEntries uses. It returns an error wrapping ErrNotFound when the project or subproject does not exist, or a database error.
func (*Store) RenameProject ¶
RenameProject renames a top-level project (subproject == "") or a specific subproject in place to newName (normalized). It returns an error wrapping ErrNotFound when the project or subproject does not exist, an error wrapping ErrAlreadyExists when a sibling in the same scope already has newName, or a database error. Only the database is changed.
func (*Store) ReplaceAllEntries ¶
func (s *Store) ReplaceAllEntries(ctx context.Context, project, subproject, kind string, sections []EntrySection) error
ReplaceAllEntries atomically replaces every non-archived entries row for (project, kind) with the given sections, in order. Archived rows are left untouched. The result reads the same as appending the sections one by one with AppendEntry. It returns an error wrapping ErrNotFound when the project or subproject does not exist, or a database error.
func (*Store) ReplaceArchivedEntries ¶
func (s *Store) ReplaceArchivedEntries(ctx context.Context, project, subproject, kind string, sections []EntrySection) error
ReplaceArchivedEntries atomically replaces every archived entries row for (project, kind) with the given sections, stored as archived. Non-archived rows are left untouched. It returns an error wrapping ErrNotFound when the project or subproject does not exist, or a database error.
func (*Store) SearchText ¶
func (s *Store) SearchText(ctx context.Context, query string, scope SearchScope, offset, limit, contextLines int) ([]SearchResult, bool, error)
SearchText performs a case-insensitive substring search over every document and non-archived entries row in scope, matching query literally (no tokenization or wildcards). Case folding is Unicode-aware ("DECISÃO" matches "decisão"). contextLines, when positive, adds that many lines before and after each match.
It returns the matching lines skipping the first offset, up to limit+1 of them: the extra one lets the caller detect that more results exist. The boolean reports whether the scan stopped at maxScannedRows or maxScannedBytes, in which case later matches are missing. Matches are ordered by project, subproject, kind, then row reading order (entries in reading order), then line number, so pagination is stable. It returns an error wrapping ErrNotFound when the scoped project or subproject does not exist, or a database error.
func (*Store) WriteDocument ¶
WriteDocument sets the content of the overwrite-style document of the given kind for project/subproject, inserting it when it does not exist yet. The names are normalized. It returns an error wrapping ErrNotFound when the project or subproject does not exist, or a database error. A single upsert statement keeps the write atomic.
func (*Store) WriteKinds ¶
func (s *Store) WriteKinds(ctx context.Context, project, subproject string, writes []KindWrite) error
WriteKinds applies every write to (project, subproject) in one transaction: either all of them take effect or none does. Kind names are normalized. It returns an error wrapping ErrNotFound when the project or subproject does not exist, or a database error.