keg

package
v0.15.0 Latest Latest
Warning

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

Go to latest
Published: Mar 24, 2026 License: Apache-2.0 Imports: 34 Imported by: 0

Documentation

Index

Constants

View Source
const (
	MarkdownContentFilename = "README.md"
	YAMLMetaFilename        = "meta.yaml"
	JSONStatsFilename       = "stats.json"
	KegCurrentEnvKey        = "KEG_CURRENT"
	KegLockFile             = ".keg-lock"
	NodeImagesDir           = "images"
	NodeAttachmentsDir      = "assets"
)
View Source
const DefaultLockTTL = 5 * time.Minute

DefaultLockTTL is the default time-to-live for a cross-process lock.

View Source
const (
	KegConfigSchemaURL = "https://raw.githubusercontent.com/jlrickert/tapper/main/schemas/keg-config.json"
)
View Source
const (
	KegCrossLockFile = ".keg-cross-lock"
)

Variables

View Source
var (
	// ConfigV1VersionString is the initial KEG configuration version identifier.
	ConfigV1VersionString = "2023-01"

	// ConfigV2VersionString is the current KEG configuration version identifier.
	ConfigV2VersionString = "2025-07"

	// FormatMarkdown is the short format identifier for Markdown content.
	FormatMarkdown = "markdown"

	// FormatRST is the short format identifier for reStructuredText content.
	FormatRST = "rst"
)
View Source
var (
	ErrInvalid       = os.ErrInvalid    // invalid argument
	ErrExist         = os.ErrExist      // file already exists
	ErrNotExist      = os.ErrNotExist   // file does not exist
	ErrPermission    = os.ErrPermission // permission denied
	ErrParse         = errors.New("unable to parse")
	ErrConflict      = errors.New("conflict")
	ErrQuotaExceeded = errors.New("quota exceeded")
	ErrRateLimited   = errors.New("rate limited")
	ErrNotSupported  = errors.New("not supported")

	// ErrDestinationExists is returned when a move/rename cannot proceed because
	// the destination node id already exists. Prefer returning a typed
	// DestinationExistsError that unwraps to this sentinel when callers may need
	// structured information.
	ErrDestinationExists = errors.New("destination already exists")

	// ErrLockTimeout indicates acquiring a repository or node lock timed out or
	// was canceled. Lock-acquiring helpers should wrap context/cancellation
	// information while preserving this sentinel for callers that need to detect
	// timeout semantics via errors.Is.
	ErrLockTimeout = errors.New("lock acquire timeout")

	// ErrLock indicates a generic failure to acquire a repository or node
	// lock. Use errors.Is(err, ErrLock) to detect non-timeout lock acquisition
	// failures.
	ErrLock = errors.New("cannot acquire lock")
)

Sentinel errors used for simple equality-style checks.

View Source
var (
	// ErrUnauthorized indicates the API request lacked valid authentication
	// credentials (HTTP 401).
	ErrUnauthorized = errors.New("unauthorized")

	// ErrForbidden indicates the authenticated user lacks permission for the
	// requested operation (HTTP 403).
	ErrForbidden = errors.New("forbidden")
)

Sentinel errors for API-specific failure conditions.

View Source
var ErrLockTokenMismatch = errors.New("lock token mismatch")

ErrLockTokenMismatch indicates the provided token does not match the held lock.

View Source
var ErrNotLocked = errors.New("node is not locked")

ErrNotLocked indicates no lock is held on the node.

View Source
var RawZeroNodeContent = `` /* 229-byte string literal not displayed */

RawZeroNodeContent is the fallback content used when a node has no content. It serves as a friendly placeholder indicating the content is planned but not yet available. Callers may display this as the node README. If you want the content created sooner, open an issue describing the request.

Functions

func EvaluateTagExpression added in v0.2.0

func EvaluateTagExpression(
	expr TagExpr,
	universe map[string]struct{},
	resolve func(tag string) map[string]struct{},
) map[string]struct{}

EvaluateTagExpression evaluates expr against a universe of string identifiers. universe is the full candidate set (e.g. node paths). resolve maps a tag name to the subset of universe that carries that tag. Returns the subset of universe that satisfies the expression.

func IsBackendError

func IsBackendError(err error) bool

IsBackendError reports whether err is (or wraps) a BackendError.

func IsConflict

func IsConflict(err error) bool

IsConflict returns true if err is a conflict error.

func IsCoreIndex added in v0.2.0

func IsCoreIndex(name string) bool

IsCoreIndex reports whether the given index file path (as used in a keg config Indexes entry, e.g. "dex/changes.md") is one of the built-in protected index names.

func IsDestinationExists

func IsDestinationExists(err error) bool

IsDestinationExists returns true if err represents a destination-exists condition.

func IsInvalidConfig

func IsInvalidConfig(err error) bool

IsInvalidConfig reports whether err is (or wraps) an invalid-config condition.

func IsPermissionDenied

func IsPermissionDenied(err error) bool

IsPermissionDenied returns true if err indicates a permission problem.

func IsRetryable

func IsRetryable(err error) bool

IsRetryable inspects the error chain for a Retryable() bool implementation and returns its result (false if none found).

func IsTemporary

func IsTemporary(err error) bool

IsTemporary inspects the error chain for a Temporary() bool implementation and returns its result (false if none found).

func NewAliasNotFoundError

func NewAliasNotFoundError(alias string) error

NewAliasNotFoundError constructs a typed AliasNotFoundError.

func NewBackendError

func NewBackendError(backend, op string, status int, cause error, transient bool) error

NewBackendError constructs a *BackendError describing an operation against a backend.

func NewInvalidConfigError

func NewInvalidConfigError(msg string) error

NewInvalidConfigError creates an InvalidConfigError with a human message.

func NewRateLimitError

func NewRateLimitError(retryAfter time.Duration, msg string, cause error) error

NewRateLimitError constructs a *RateLimitError with a suggested retry duration.

func NewTransientError

func NewTransientError(cause error) error

NewTransientError constructs a *TransientError wrapping the provided cause.

func NormalizeTag

func NormalizeTag(s string) string

NormalizeTag normalizeTag lowercases, trims, and tokenizes a tag string into a hyphen-separated token.

func NormalizeTags

func NormalizeTags(tags []string) []string

func ParseTags

func ParseTags(raw string) []string

ParseTags accepts a comma/semicolon/newline separated list of tags (or a whitespace-separated string when no explicit separators are present) and returns a normalized, deduplicated, sorted slice of tags.

Behavior: - Trims whitespace around tokens. - Lowercases tokens and converts internal whitespace to hyphens via NormalizeTag. - Splits on commas, semicolons, CR/LF, or newlines when present; otherwise splits on whitespace. - Deduplicates tokens and returns them in lexicographic order.

func RandomCode

func RandomCode(context.Context) string

func RenderMarkdown added in v0.11.0

func RenderMarkdown(src []byte, opts RenderOptions) ([]byte, error)

RenderMarkdown converts raw markdown bytes to HTML, rewriting ../N node links to site-relative paths. The returned bytes are the inner HTML content (no <html> or <body> wrapper).

func RepoContainsKeg

func RepoContainsKeg(ctx context.Context, repo Repository) (bool, error)

RepoContainsKeg checks if a keg has been properly initialized within a repository. It verifies both that a keg config exists and that a zero node (node ID 0) is present. Returns true only if both conditions are met, indicating a fully initialized keg.

Types

type AliasNotFoundError

type AliasNotFoundError struct {
	Alias string
}

AliasNotFoundError is a typed error that carries the missing alias for callers that need richer diagnostic information.

func (*AliasNotFoundError) Error

func (e *AliasNotFoundError) Error() string

type ApiRepo added in v0.11.0

type ApiRepo struct {
	// BaseURL is the full URL prefix including the keg path, for example
	// "https://hub.example.com/api/v1/kegs/@mykeg". No trailing slash.
	BaseURL string

	// Token is the bearer token sent in the Authorization header on every
	// request. It is resolved from the target's Token or TokenEnv field.
	Token string

	// Client is the HTTP client used for all requests. When nil,
	// http.DefaultClient is used.
	Client *http.Client
	// contains filtered or unexported fields
}

ApiRepo implements Repository using tapper-hub's REST API as the storage backend. It maps each Repository method to an HTTP call against `/api/v1/kegs/@{keg}/...` endpoints, with bearer token authentication on every request.

ApiRepo also maintains a per-node ETag cache for optimistic concurrency control, following the decision in KEG node 307. Reads populate the cache, and writes include an `If-Match` header when an ETag is available. A 409 response maps to ErrConflict.

func NewApiRepo added in v0.11.0

func NewApiRepo(baseURL, token string) *ApiRepo

NewApiRepo constructs an ApiRepo for the given base URL and bearer token.

func (*ApiRepo) ClearIndexes added in v0.11.0

func (a *ApiRepo) ClearIndexes(ctx context.Context) error

ClearIndexes implements Repository.

func (*ApiRepo) DeleteFile added in v0.11.0

func (a *ApiRepo) DeleteFile(ctx context.Context, id NodeId, name string) error

DeleteFile implements RepositoryFiles.

func (*ApiRepo) DeleteImage added in v0.11.0

func (a *ApiRepo) DeleteImage(ctx context.Context, id NodeId, name string) error

DeleteImage implements RepositoryImages.

func (*ApiRepo) DeleteNode added in v0.11.0

func (a *ApiRepo) DeleteNode(ctx context.Context, id NodeId) error

DeleteNode implements Repository.

func (*ApiRepo) GetIndex added in v0.11.0

func (a *ApiRepo) GetIndex(ctx context.Context, name string) ([]byte, error)

GetIndex implements Repository.

func (*ApiRepo) HasNode added in v0.11.0

func (a *ApiRepo) HasNode(ctx context.Context, id NodeId) (bool, error)

HasNode implements Repository.

func (*ApiRepo) ListFiles added in v0.11.0

func (a *ApiRepo) ListFiles(ctx context.Context, id NodeId) ([]string, error)

ListFiles implements RepositoryFiles.

func (*ApiRepo) ListImages added in v0.11.0

func (a *ApiRepo) ListImages(ctx context.Context, id NodeId) ([]string, error)

ListImages implements RepositoryImages.

func (*ApiRepo) ListIndexes added in v0.11.0

func (a *ApiRepo) ListIndexes(ctx context.Context) ([]string, error)

ListIndexes implements Repository.

func (*ApiRepo) ListNodes added in v0.11.0

func (a *ApiRepo) ListNodes(ctx context.Context) ([]NodeId, error)

ListNodes implements Repository.

func (*ApiRepo) MoveNode added in v0.11.0

func (a *ApiRepo) MoveNode(ctx context.Context, id NodeId, dst NodeId) error

MoveNode implements Repository.

func (*ApiRepo) Name added in v0.11.0

func (a *ApiRepo) Name() string

Name implements Repository.

func (*ApiRepo) Next added in v0.11.0

func (a *ApiRepo) Next(ctx context.Context) (NodeId, error)

Next implements Repository.

func (*ApiRepo) ReadConfig added in v0.11.0

func (a *ApiRepo) ReadConfig(ctx context.Context) (*Config, error)

ReadConfig implements Repository.

func (*ApiRepo) ReadContent added in v0.11.0

func (a *ApiRepo) ReadContent(ctx context.Context, id NodeId) ([]byte, error)

ReadContent implements Repository.

func (*ApiRepo) ReadFile added in v0.11.0

func (a *ApiRepo) ReadFile(ctx context.Context, id NodeId, name string) ([]byte, error)

ReadFile implements RepositoryFiles.

func (*ApiRepo) ReadImage added in v0.11.0

func (a *ApiRepo) ReadImage(ctx context.Context, id NodeId, name string) ([]byte, error)

ReadImage implements RepositoryImages.

func (*ApiRepo) ReadMeta added in v0.11.0

func (a *ApiRepo) ReadMeta(ctx context.Context, id NodeId) ([]byte, error)

ReadMeta implements Repository.

func (*ApiRepo) ReadStats added in v0.11.0

func (a *ApiRepo) ReadStats(ctx context.Context, id NodeId) (*NodeStats, error)

ReadStats implements Repository.

func (*ApiRepo) WithNodeLock added in v0.11.0

func (a *ApiRepo) WithNodeLock(ctx context.Context, id NodeId, fn func(context.Context) error) error

WithNodeLock implements Repository. The lock is managed server-side using a lease mechanism. The client acquires a lease, executes the callback, and releases the lease.

func (*ApiRepo) WriteConfig added in v0.11.0

func (a *ApiRepo) WriteConfig(ctx context.Context, config *Config) error

WriteConfig implements Repository.

func (*ApiRepo) WriteContent added in v0.11.0

func (a *ApiRepo) WriteContent(ctx context.Context, id NodeId, data []byte) error

WriteContent implements Repository.

func (*ApiRepo) WriteFile added in v0.11.0

func (a *ApiRepo) WriteFile(ctx context.Context, id NodeId, name string, data []byte) error

WriteFile implements RepositoryFiles.

func (*ApiRepo) WriteImage added in v0.11.0

func (a *ApiRepo) WriteImage(ctx context.Context, id NodeId, name string, data []byte) error

WriteImage implements RepositoryImages.

func (*ApiRepo) WriteIndex added in v0.11.0

func (a *ApiRepo) WriteIndex(ctx context.Context, name string, data []byte) error

WriteIndex implements Repository.

func (*ApiRepo) WriteMeta added in v0.11.0

func (a *ApiRepo) WriteMeta(ctx context.Context, id NodeId, data []byte) error

WriteMeta implements Repository.

func (*ApiRepo) WriteStats added in v0.11.0

func (a *ApiRepo) WriteStats(ctx context.Context, id NodeId, stats *NodeStats) error

WriteStats implements Repository.

type AssetKind

type AssetKind string

AssetKind identifies an asset namespace for a node.

const (
	AssetKindImage AssetKind = "image"
	AssetKindItem  AssetKind = "item"
)

type BackendError

type BackendError struct {
	Backend    string // e.g. "s3", "http", "postgres", "fs"
	Op         string // operation, e.g. "WriteContent", "GetMeta"
	StatusCode int    // optional HTTP / backend status
	Cause      error
	Transient  bool // whether this is a transient error (retryable)
}

BackendError wraps errors coming from an external backend (API, DB, object store). It exposes Retryable() to indicate transient failures.

func ParseBackendError

func ParseBackendError(err error) *BackendError

func (*BackendError) Error

func (e *BackendError) Error() string

func (*BackendError) Retryable

func (e *BackendError) Retryable() bool

Retryable reports whether the backend error is transient.

func (*BackendError) Unwrap

func (e *BackendError) Unwrap() error

Unwrap returns the wrapped cause.

type BacklinkIndex

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

BacklinkIndex maps a destination node path to the list of source nodes that link to that destination. The underlying map keys are node.Path() values (string). The index is used to construct the "backlinks" index artifact.

The type is intended for in-memory, single-process use. Concurrency control is the caller's responsibility.

func ParseBacklinksIndex

func ParseBacklinksIndex(ctx context.Context, data []byte) (*BacklinkIndex, error)

ParseBacklinksIndex parses the raw bytes of a backlinks index into a BacklinkIndex.

Expected on-disk format is one line per destination:

"<dst>\t<src1> <src2> ...\n"

Behavior:

  • Empty or nil input yields an empty BacklinkIndex with no error.
  • Lines are split on tab to separate destination from space-separated sources.
  • Duplicate sources for a destination are tolerated and may be deduped by callers of Data.
  • This function does not modify any external state.

func (*BacklinkIndex) Add

func (idx *BacklinkIndex) Add(ctx context.Context, data *NodeData) error

Add incorporates backlink information derived from the provided NodeData. For each outgoing link listed in data.Links the function will add the source node (data.ID) to the corresponding destination entry in the index.

Behavior expectations:

  • If idx is nil the call is a no-op and returns nil.
  • If idx.data is nil it will be initialized.
  • The method should avoid introducing duplicate source entries for a given destination when possible.

This method only mutates in-memory state and does not perform I/O.

func (*BacklinkIndex) Data

func (idx *BacklinkIndex) Data(ctx context.Context) ([]byte, error)

Data serializes the index into the canonical on-disk format.

Serialization rules:

  • Each non-empty destination produces a line: "<dst>\t<src1> <src2> ...\n"
  • Source lists are deduplicated and sorted in a deterministic order.
  • Destination keys are emitted in a deterministic, parse-aware order (numeric node ids sorted numerically when possible, otherwise lexicographic).
  • An empty index returns an empty byte slice.

The returned bytes are owned by the caller and may be written atomically by the repository layer.

func (*BacklinkIndex) Rm

func (idx *BacklinkIndex) Rm(ctx context.Context, node NodeId) error

Rm removes any backlink references introduced by the given node. It removes the node as a source from any destination lists and may remove the entry for a destination if it ends up with no sources.

Behavior expectations:

  • If idx is nil the call is a no-op and returns nil.
  • If idx.data is nil it will be initialized to an empty map.
  • After removal, entries with no sources may either remain as empty slices or be deleted; callers should tolerate either representation.

This method only mutates in-memory state and does not perform I/O.

type ChangesIndex added in v0.2.0

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

ChangesIndex is an in-memory index of all nodes sorted by updated time in reverse-chronological order (newest first). It is used to build the dex/changes.md index artifact.

Concurrency note: ChangesIndex does not perform internal synchronization. Callers that require concurrent access should guard an instance with a mutex.

func ParseChangesIndex added in v0.2.0

func ParseChangesIndex(ctx context.Context, data []byte) (ChangesIndex, error)

ParseChangesIndex parses the serialized dex/changes.md bytes into a ChangesIndex. Each non-empty line must be in the format:

  • YYYY-MM-DD HH:MM:SSZ [TITLE](../ID)

Malformed lines are silently skipped. An empty input yields an empty ChangesIndex with no error.

func (*ChangesIndex) Add added in v0.2.0

func (idx *ChangesIndex) Add(ctx context.Context, data *NodeData) error

Add inserts or updates the node in the index, maintaining reverse- chronological sort order (newest Updated first). If a node with the same ID already exists it is replaced.

func (*ChangesIndex) Clear added in v0.2.0

func (idx *ChangesIndex) Clear(ctx context.Context) error

Clear resets the index to an empty state.

func (*ChangesIndex) Data added in v0.2.0

func (idx *ChangesIndex) Data(ctx context.Context) ([]byte, error)

Data serializes the ChangesIndex to the canonical dex/changes.md format. Each entry is emitted as:

  • YYYY-MM-DD HH:MM:SSZ [TITLE](../ID)

Entries are in reverse-chronological order (newest first). An empty index returns an empty byte slice.

func (*ChangesIndex) Rm added in v0.2.0

func (idx *ChangesIndex) Rm(ctx context.Context, node NodeId) error

Rm removes the node identified by node from the index. If the node is not present the call is a no-op.

type Config

type Config = ConfigV2

Config KegConfig is an alias for the latest configuration version. Update this alias when introducing a newer configuration version.

func NewConfig

func NewConfig(options ...ConfigOption) *Config

func ParseKegConfig

func ParseKegConfig(data []byte) (*Config, error)

ParseKegConfig parses raw YAML config data into the latest Config version. It detects the "kegv" version field and performs migration from earlier versions when necessary.

func (*Config) AddEntity

func (kc *Config) AddEntity(name string, id int, summary string) error

AddEntity adds or updates an entity entry by entity name.

func (*Config) AddTag

func (kc *Config) AddTag(name, summary string) error

AddTag adds or updates a tag summary by tag name.

func (*Config) Location added in v0.15.0

func (kc *Config) Location() *time.Location

Location returns the *time.Location for the configured Timezone. It returns time.UTC if the Timezone field is empty or invalid.

func (*Config) ResolveAlias

func (kc *Config) ResolveAlias(alias string) (*kegurl.Target, error)

func (*Config) String

func (kc *Config) String() string

func (*Config) ToJSON

func (kc *Config) ToJSON() ([]byte, error)

ToJSON serializes the Config to JSON.

func (*Config) ToYAML

func (kc *Config) ToYAML() ([]byte, error)

ToYAML serializes the Config to YAML.

func (*Config) Touch

func (kc *Config) Touch(t time.Time)

type ConfigOption

type ConfigOption = func(cfg *Config)

type ConfigV1

type ConfigV1 struct {
	// Kegv is the version of the specification.
	Kegv string `yaml:"kegv"`

	// Updated indicates when the keg was last indexed.
	Updated string `yaml:"updated,omitempty"`

	// Title is the title of the KEG worklog or project.
	Title string `yaml:"title,omitempty"`

	// URL is the main URL where the KEG can be found.
	URL string `yaml:"url,omitempty"`

	// Creator is the URL or identifier of the creator of the KEG.
	Creator string `yaml:"creator,omitempty"`

	// State indicates the current state of the KEG (e.g., living, archived).
	State string `yaml:"state,omitempty"`

	// Summary provides a brief description or summary of the KEG content.
	Summary string `yaml:"summary,omitempty"`

	// Indexes is a list of index entries that link to related files or nodes.
	Indexes []IndexEntry `yaml:"indexes,omitempty"`
	// contains filtered or unexported fields
}

ConfigV1 KegConfigV1 represents the initial version of the KEG configuration specification.

type ConfigV2

type ConfigV2 struct {
	// Kegv is the version of the specification.
	Kegv string `yaml:"kegv"`

	// Updated indicates when the keg was last indexed.
	Updated string `yaml:"updated,omitempty"`

	// Title is the title of the KEG worklog or project.
	Title string `yaml:"title,omitempty"`

	// URL is the main URL where the KEG can be found.
	URL string `yaml:"url,omitempty"`

	// Creator is the URL or identifier of the creator of the KEG.
	Creator string `yaml:"creator,omitempty"`

	// State indicates the current state of the KEG (e.g., living, archived).
	State string `yaml:"state,omitempty"`

	// Summary provides a brief description or summary of the KEG content.
	Summary string `yaml:"summary,omitempty"`

	// Links holds a list of LinkEntry objects representing related links or
	// references in the configuration.
	Links []LinkEntry `yaml:"links,omitempty"`

	// Indexes is a list of index entries that link to related files or nodes.
	Indexes []IndexEntry `yaml:"indexes,omitempty"`

	Entities map[string]EntityEntry `yaml:"entities,omitempty"`

	Tags map[string]string `yaml:"tags,omitempty"`

	// Timezone is the IANA timezone for resolving ambiguous timestamps
	// within this keg (e.g. "America/Chicago"). Defaults to "UTC".
	Timezone string `yaml:"timezone,omitempty"`

	// Doctor holds `tap doctor` check configuration.
	Doctor *DoctorConfig `yaml:"doctor,omitempty"`

	// Site holds static site generation defaults for `tap site`.
	Site *SiteConfig `yaml:"site,omitempty"`
	// contains filtered or unexported fields
}

ConfigV2 KegConfigV2 represents the second (current) version of the KEG configuration specification. It extends V1 with additional fields such as Links.

type CreateOptions

type CreateOptions struct {
	// Title is the human-readable title for the node
	Title string
	// Lead is a one-line summary
	Lead string
	// Tags are searchable labels for the node
	Tags []string
	// Body is the raw markdown content; if empty, default content is generated from Title/Lead
	Body []byte
	// Attrs are arbitrary key-value attributes attached to the node
	Attrs map[string]any
}

CreateOptions specifies parameters for creating a new node

type Dex

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

Dex provides a high-level, in-memory view of the repository's generated dex indices: nodes, tags, links, backlinks, and changes. It is a convenience wrapper used by index builders and other tooling to read or inspect index data without dealing directly with repository I/O. Dex does not perform any I/O itself; callers are responsible for providing a Repository when writing indices.

func NewDexFromRepo

func NewDexFromRepo(ctx context.Context, repo Repository, opts ...DexOption) (*Dex, error)

NewDexFromRepo loads available index artifacts ("nodes.tsv", "tags", "links", "backlinks", "changes.md") from the provided repository and returns a Dex populated with parsed indexes. Missing or empty index files are treated as empty datasets and do not cause an error. Additional DexOptions (e.g. WithConfig) can be supplied to configure optional behaviour such as tag-filtered custom indexes.

All 5 index files are read and parsed concurrently for faster loading.

func (*Dex) Add

func (dex *Dex) Add(ctx context.Context, data *NodeData) error

Add adds the provided node to all managed indexes. This implements the IndexBuilder contract for convenience when using Dex as an aggregated builder.

func (dex *Dex) Backlinks(ctx context.Context, node NodeId) ([]NodeId, bool)

Backlinks returns the parsed backlinks index (map[dst] -> []src). NOTE: not intended to be mutated

func (*Dex) Clear

func (dex *Dex) Clear(ctx context.Context)

Clear resets all in-memory index data held by the Dex instance.

func (*Dex) GetRef

func (dex *Dex) GetRef(ctx context.Context, id NodeId) *NodeIndexEntry
func (dex *Dex) Links(ctx context.Context, node NodeId) ([]NodeId, bool)

Links returns the parsed outgoing links index (map[src] -> []dst).

func (*Dex) NextNode

func (dex *Dex) NextNode(ctx context.Context) NodeId

func (*Dex) Nodes

func (dex *Dex) Nodes(ctx context.Context) []NodeIndexEntry

Nodes returns a copy of the parsed nodes index (slice of NodeRef).

func (*Dex) Remove

func (dex *Dex) Remove(ctx context.Context, node NodeId) error

Remove removes the node identified by id from all managed indexes. This implements the IndexBuilder contract for convenience when using Dex.

func (dex *Dex) TagLinks(ctx context.Context, node NodeId) ([]NodeId, bool)

TagLinks Tags returns the parsed tags index (map[tag] -> []NodeID).

func (*Dex) TagList

func (dex *Dex) TagList(ctx context.Context) []string

func (*Dex) TagNodes

func (dex *Dex) TagNodes(ctx context.Context, tag string) ([]NodeId, bool)

TagNodes returns the parsed tags index entry for tag (map[tag] -> []NodeID).

func (*Dex) Write

func (dex *Dex) Write(ctx context.Context, repo Repository) error

Write serializes the in-memory indexes and writes them atomically to the provided repository using WriteIndex. If any write operation fails the error chain is returned (errors.Join is used to aggregate multiple errors).

Serialization is performed under a read lock so concurrent readers are not blocked. The actual file writes happen after the lock is released, since they operate on independent byte buffers and repository WriteIndex calls are self-synchronizing (atomic file writes).

type DexOption added in v0.2.0

type DexOption func(*Dex) error

DexOption is a functional option for NewDexFromRepo.

func WithConfig added in v0.2.0

func WithConfig(cfg *Config) DexOption

WithConfig builds DexOptions from a keg Config. It iterates cfg.Indexes and creates a QueryFilteredIndex for each entry that:

  • has a non-empty Query (or deprecated Tags) field, and
  • is not one of the core protected index names.

The short file name used with repo.WriteIndex is derived by stripping any leading "dex/" prefix from entry.File.

By default, the index evaluates tag expressions against node tag sets. To support richer query terms (e.g. key=value attribute predicates), pass WithQueryResolver to inject a custom resolver callback.

func WithQueryResolver added in v0.15.0

func WithQueryResolver(resolve func(term string, data *NodeData) bool) DexOption

WithQueryResolver sets a custom query term resolver for config-driven custom indexes. When set, each term in a query expression is resolved by calling resolve(term, data) for each node, instead of the default tag-only resolver. This enables key=value attribute predicates and other term types defined in higher-level packages (e.g. pkg/tapper).

type DoctorConfig added in v0.14.0

type DoctorConfig struct {
	// EntityCheck enables per-node entity attribute validation.
	// When true, doctor reports nodes that lack an `entity` attribute in meta.
	EntityCheck bool `yaml:"entityCheck,omitempty" json:"entityCheck,omitempty"`

	// TagCheck enables per-node tag validation against the keg config's tag map.
	// When true, doctor warns about tags used in node metadata that are not
	// documented in the keg config.
	TagCheck bool `yaml:"tagCheck,omitempty" json:"tagCheck,omitempty"`
}

DoctorConfig holds options that control which checks `tap doctor` performs.

type EntityEntry

type EntityEntry struct {
	ID      int    `yaml:"id"`
	Summary string `yaml:"summary"`
}

type FsRepo

type FsRepo struct {
	// Root is the base directory path containing all KEG node directories
	Root string
	// ContentFilename specifies the filename for node content (typically README.md)
	ContentFilename string
	// MetaFilename specifies the filename for node metadata (typically meta.yaml)
	MetaFilename  string
	StatsFilename string
	// SnapshotCheckpointInterval controls how many patch revisions may occur
	// after a checkpoint before the next snapshot is stored as a full blob.
	SnapshotCheckpointInterval int
	// contains filtered or unexported fields
}

FsRepo implements Repository using the local filesystem as storage. It manages KEG nodes as directories under [Root], with each node containing content files, metadata, and optional attachments. Thread-safe operations are coordinated through the embedded mutex.

func NewFsRepo

func NewFsRepo(root string, rt *toolkit.Runtime) *FsRepo

NewFsRepo constructs a filesystem repository with the provided root/runtime.

func NewFsRepoFromEnvOrSearch

func NewFsRepoFromEnvOrSearch(ctx context.Context, rt *toolkit.Runtime) (*FsRepo, error)

NewFsRepoFromEnvOrSearch tries to locate a keg file using the order: 1) KEG_CURRENT env var (file or directory) 2) current working directory 3) if inside a git project, search the project tree for a keg file 4) recursive search from current working directory 5) fallback to default config location (~/.config/keg or XDG equivalent)

Returns a pointer to an initialized FsRepo and the path of the discovered keg file (or "" if using fallback path).

func (*FsRepo) AcquireLock added in v0.11.0

func (f *FsRepo) AcquireLock(ctx context.Context, id NodeId) (LockToken, error)

AcquireLock implements RepositoryLock.

func (*FsRepo) AppendSnapshot added in v0.4.0

func (f *FsRepo) AppendSnapshot(ctx context.Context, id NodeId, in SnapshotWrite) (Snapshot, error)

func (*FsRepo) ClearIndexes

func (f *FsRepo) ClearIndexes(ctx context.Context) error

func (*FsRepo) ContentFilePath added in v0.6.0

func (f *FsRepo) ContentFilePath(id NodeId) string

ContentFilePath returns the absolute filesystem path to a node's content file.

func (*FsRepo) DeleteAsset

func (f *FsRepo) DeleteAsset(ctx context.Context, id NodeId, kind AssetKind, name string) error

DeleteAsset implements Repository.

func (*FsRepo) DeleteFile

func (f *FsRepo) DeleteFile(ctx context.Context, id NodeId, name string) error

func (*FsRepo) DeleteImage

func (f *FsRepo) DeleteImage(ctx context.Context, id NodeId, name string) error

func (*FsRepo) DeleteNode

func (f *FsRepo) DeleteNode(ctx context.Context, id NodeId) error

DeleteNode implements Repository.

func (*FsRepo) ForceReleaseLock added in v0.11.0

func (f *FsRepo) ForceReleaseLock(ctx context.Context, id NodeId) error

ForceReleaseLock implements RepositoryLock.

func (*FsRepo) GetIndex

func (f *FsRepo) GetIndex(ctx context.Context, name string) ([]byte, error)

GetIndex implements Repository.

func (*FsRepo) GetSnapshot added in v0.4.0

func (f *FsRepo) GetSnapshot(ctx context.Context, id NodeId, rev RevisionID, opts SnapshotReadOptions) (Snapshot, []byte, []byte, *NodeStats, error)

func (*FsRepo) HasNode

func (f *FsRepo) HasNode(ctx context.Context, id NodeId) (bool, error)

func (*FsRepo) ListAssets

func (f *FsRepo) ListAssets(ctx context.Context, id NodeId, kind AssetKind) ([]string, error)

ListAssets implements Repository.

func (*FsRepo) ListFiles

func (f *FsRepo) ListFiles(ctx context.Context, id NodeId) ([]string, error)

func (*FsRepo) ListImages

func (f *FsRepo) ListImages(ctx context.Context, id NodeId) ([]string, error)

func (*FsRepo) ListIndexes

func (f *FsRepo) ListIndexes(ctx context.Context) ([]string, error)

ListIndexes implements Repository.

func (*FsRepo) ListNodes

func (f *FsRepo) ListNodes(ctx context.Context) ([]NodeId, error)

func (*FsRepo) ListSnapshots added in v0.4.0

func (f *FsRepo) ListSnapshots(ctx context.Context, id NodeId) ([]Snapshot, error)

func (*FsRepo) LockStatus added in v0.11.0

func (f *FsRepo) LockStatus(ctx context.Context, id NodeId) (LockInfo, error)

LockStatus implements RepositoryLock.

func (*FsRepo) MetaFilePath added in v0.6.0

func (f *FsRepo) MetaFilePath(id NodeId) string

MetaFilePath returns the absolute filesystem path to a node's metadata file.

func (*FsRepo) MoveNode

func (f *FsRepo) MoveNode(ctx context.Context, id NodeId, dst NodeId) error

MoveNode implements Repository.

func (*FsRepo) Name

func (f *FsRepo) Name() string

func (*FsRepo) Next

func (f *FsRepo) Next(ctx context.Context) (NodeId, error)

func (*FsRepo) NodeDirPath added in v0.6.0

func (f *FsRepo) NodeDirPath(id NodeId) string

NodeDirPath returns the absolute filesystem path to a node's directory.

func (*FsRepo) NodeFilesExist

func (f *FsRepo) NodeFilesExist(ctx context.Context, id NodeId) (bool, bool, error)

func (*FsRepo) ReadConfig

func (f *FsRepo) ReadConfig(ctx context.Context) (*Config, error)

ReadConfig implements Repository.

func (*FsRepo) ReadContent

func (f *FsRepo) ReadContent(ctx context.Context, id NodeId) ([]byte, error)

ReadContent implements Repository.

func (*FsRepo) ReadContentAt added in v0.4.0

func (f *FsRepo) ReadContentAt(ctx context.Context, id NodeId, rev RevisionID) ([]byte, error)

func (*FsRepo) ReadFile added in v0.2.0

func (f *FsRepo) ReadFile(ctx context.Context, id NodeId, name string) ([]byte, error)

func (*FsRepo) ReadImage added in v0.2.0

func (f *FsRepo) ReadImage(ctx context.Context, id NodeId, name string) ([]byte, error)

func (*FsRepo) ReadMeta

func (f *FsRepo) ReadMeta(ctx context.Context, id NodeId) ([]byte, error)

ReadMeta implements Repository.

func (*FsRepo) ReadStats

func (f *FsRepo) ReadStats(ctx context.Context, id NodeId) (*NodeStats, error)

ReadStats implements Repository.

func (*FsRepo) ReleaseLock added in v0.11.0

func (f *FsRepo) ReleaseLock(ctx context.Context, id NodeId, token LockToken) error

ReleaseLock implements RepositoryLock.

func (*FsRepo) RestoreSnapshot added in v0.4.0

func (f *FsRepo) RestoreSnapshot(ctx context.Context, id NodeId, rev RevisionID, createRestoreSnapshot bool) error

func (*FsRepo) Runtime

func (f *FsRepo) Runtime() *toolkit.Runtime

func (*FsRepo) WatchEvents added in v0.6.0

func (f *FsRepo) WatchEvents() (RepositoryEvents, error)

WatchEvents returns a RepositoryEvents implementation for the FsRepo. Each call creates a fresh watcher; callers must call Close when done.

func (*FsRepo) WithNodeLock

func (f *FsRepo) WithNodeLock(ctx context.Context, id NodeId, fn func(context.Context) error) error

WithNodeLock executes fn while holding an exclusive lock for node id. The lock uses atomic mkdir with optional process metadata for stale lock detection. When process info is available (via runtime.Process()), a JSON metadata file is written inside the lock directory. If the lock directory already exists and the owning process is dead, the stale lock is removed and acquisition is retried.

func (*FsRepo) WriteAsset

func (f *FsRepo) WriteAsset(ctx context.Context, id NodeId, kind AssetKind, name string, data []byte) error

WriteAsset implements Repository.

func (*FsRepo) WriteConfig

func (f *FsRepo) WriteConfig(ctx context.Context, config *Config) error

WriteConfig implements Repository.

func (*FsRepo) WriteContent

func (f *FsRepo) WriteContent(ctx context.Context, id NodeId, data []byte) error

WriteContent implements Repository.

func (*FsRepo) WriteFile

func (f *FsRepo) WriteFile(ctx context.Context, id NodeId, name string, data []byte) error

func (*FsRepo) WriteImage

func (f *FsRepo) WriteImage(ctx context.Context, id NodeId, name string, data []byte) error

func (*FsRepo) WriteIndex

func (f *FsRepo) WriteIndex(ctx context.Context, name string, data []byte) error

WriteIndex implements Repository.

func (*FsRepo) WriteMeta

func (f *FsRepo) WriteMeta(ctx context.Context, id NodeId, data []byte) error

WriteMeta implements Repository.

func (*FsRepo) WriteStats

func (f *FsRepo) WriteStats(ctx context.Context, id NodeId, stats *NodeStats) error

WriteStats implements Repository.

type IndexBuilder

type IndexBuilder interface {
	// Name returns the canonical index filename (for example "dex/tags").
	Name() string

	// Add incorporates information from a node into the index's in-memory state.
	Add(ctx context.Context, node *NodeData) error

	// Remove deletes node-related state from the index.
	Remove(ctx context.Context, node NodeId) error

	// Clear resets the index to an empty state.
	Clear(ctx context.Context) error

	// Data returns the serialized index bytes to be written to storage.
	Data(ctx context.Context) ([]byte, error)
}

IndexBuilder is an interface for constructing a single index artifact (for example: nodes.tsv, tags, links, backlinks). Implementations maintain in-memory state via Add / Remove / Clear and produce the serialized bytes to write via Data.

type IndexEntry

type IndexEntry struct {
	File    string `yaml:"file"`
	Summary string `yaml:"summary"`
	Query   string `yaml:"query,omitempty"` // boolean query expression; omit for core/unfiltered indexes
	Tags    string `yaml:"tags,omitempty"`  // deprecated: use query instead
}

IndexEntry represents an entry in the indexes list in the KEG configuration. The Query field holds a boolean query expression used to filter index contents (tag names, key=value attribute predicates, boolean operators). The deprecated Tags field is accepted for backward compatibility; Query takes precedence when both are present.

func (*IndexEntry) QueryOrTags added in v0.15.0

func (ie *IndexEntry) QueryOrTags() string

QueryOrTags returns the effective query string for the index entry. It prefers Query when set, falling back to the deprecated Tags field.

type IndexOptions

type IndexOptions struct {
	NoUpdate bool
}

type InvalidConfigError

type InvalidConfigError struct {
	Msg string
}

InvalidConfigError represents a validation or parse failure for tapper config.

func (*InvalidConfigError) Error

func (e *InvalidConfigError) Error() string

func (*InvalidConfigError) Is

func (e *InvalidConfigError) Is(target error) bool

func (*InvalidConfigError) Unwrap

func (e *InvalidConfigError) Unwrap() error

type Keg

type Keg struct {
	// Target is the keg URL/location (nil for memory-backed kegs)
	Target *kegurl.Target
	// Repo is the storage backend implementation
	Repo Repository
	// Runtime provides clock/hash/fs helpers used by high-level keg operations.
	Runtime *toolkit.Runtime
	// contains filtered or unexported fields
}

Keg is a concrete high-level service providing KEG node operations backed by a Repository. It abstracts storage implementation details, allowing operations over nodes to work uniformly across memory, filesystem, and remote backends. Keg delegates low-level storage operations to its underlying repository and maintains an in-memory dex for indexing.

func NewKeg

func NewKeg(repo Repository, rt *toolkit.Runtime, opts ...Option) *Keg

NewKeg returns a Keg service backed by the provided repository. Functional options can be provided to customize Keg behavior.

func NewKegFromTarget

func NewKegFromTarget(ctx context.Context, target kegurl.Target, rt *toolkit.Runtime) (*Keg, error)

NewKegFromTarget constructs a Keg from a kegurl.Target. It automatically selects the appropriate repository implementation based on the target's scheme:

  • memory:// targets use an in-memory repository
  • file:// targets use a filesystem repository
  • http:// and https:// targets use an API repository (ApiRepo)
  • registry targets use an API repository resolved from repo/user/keg fields

Returns an error if the target scheme is not supported.

func (*Keg) AppendSnapshot added in v0.4.0

func (k *Keg) AppendSnapshot(ctx context.Context, id NodeId, msg string) (Snapshot, error)

func (*Keg) Commit

func (k *Keg) Commit(ctx context.Context, id NodeId) error

Commit finalizes a temporary node by allocating a permanent ID and moving it from its temporary location (with Code suffix) to the canonical numeric ID. For nodes without a Code (already permanent), Commit is a no-op.

func (*Keg) Config

func (k *Keg) Config(ctx context.Context) (*Config, error)

Config returns the keg's configuration.

func (*Keg) Create

func (k *Keg) Create(ctx context.Context, opts *CreateOptions) (NodeId, error)

Create creates a new node: allocates an ID, parses content, generates metadata, and indexes the node in the dex. The node is immediately persisted to the repository. If Body is empty, default markdown content is generated from Title and Lead.

func (*Keg) Dex

func (k *Keg) Dex(ctx context.Context) (*Dex, error)

Dex returns the keg's index, loading it from the repository on first access. The dex is lazily loaded and cached in memory for efficient access. Config-driven query-filtered indexes are applied automatically via WithConfig.

func (*Keg) DexFresh added in v0.15.0

func (k *Keg) DexFresh(ctx context.Context) (*Dex, error)

DexFresh returns the keg's index, reloading from disk if the on-disk index files have changed since the last load. This is the correct method for long-lived processes (serve handlers, MCP servers) where another process may update the dex between calls. For FsRepo backends, it compares the mtime of dex/nodes.tsv; for MemoryRepo (single-process) it behaves identically to Dex.

func (*Keg) GetContent

func (k *Keg) GetContent(ctx context.Context, id NodeId) ([]byte, error)

GetContent retrieves the raw markdown content for a node.

func (*Keg) GetMeta

func (k *Keg) GetMeta(ctx context.Context, id NodeId) (*NodeMeta, error)

GetMeta retrieves the parsed metadata for a node.

func (*Keg) GetStats

func (k *Keg) GetStats(ctx context.Context, id NodeId) (*NodeStats, error)

GetStats retrieves programmatic node stats for a node.

func (*Keg) Index

func (k *Keg) Index(ctx context.Context, opts IndexOptions) error

Index rebuilds all keg indices from scratch. Every node is scanned, metadata and stats are refreshed (unless NoUpdate is set), and the full dex is regenerated.

func (*Keg) IndexNode

func (k *Keg) IndexNode(ctx context.Context, id NodeId) error

IndexNode updates a node's metadata by re-parsing its content and extracting properties like title, lead, and content hash. The dex is also updated to reflect any changes. If content hasn't changed, this is a no-op.

func (*Keg) Init

func (k *Keg) Init(ctx context.Context) error

Init initializes a new keg by creating the config file, zero node with default content, and updating the dex. It returns an error if the keg already exists. Init is idempotent in the sense that it checks for existing kegs first.

func (*Keg) InvalidateDex added in v0.13.0

func (k *Keg) InvalidateDex()

InvalidateDex clears the cached dex so the next Dex() call reloads from the repository. This is useful when external processes may have modified the index files.

func (*Keg) ListSnapshots added in v0.4.0

func (k *Keg) ListSnapshots(ctx context.Context, id NodeId) ([]Snapshot, error)

func (*Keg) Move

func (k *Keg) Move(ctx context.Context, src NodeId, dst NodeId) error

Move renames a node from src to dst and rewrites in-content links that target src (../N) across the keg.

func (*Keg) Next

func (k *Keg) Next(ctx context.Context) (NodeId, error)

Next reserves and returns the next available node ID from the repository.

func (*Keg) Node

func (k *Keg) Node(id NodeId) *Node

Node retrieves complete node data including content, metadata, items, and images for a given node ID. Returns an error if any component fails to load.

func (*Keg) ReadContentAt added in v0.4.0

func (k *Keg) ReadContentAt(ctx context.Context, id NodeId, rev RevisionID) ([]byte, error)

func (*Keg) Remove

func (k *Keg) Remove(ctx context.Context, id NodeId) error

Remove deletes a node from the repository and updates dex/config artifacts.

func (*Keg) RestoreSnapshot added in v0.4.0

func (k *Keg) RestoreSnapshot(ctx context.Context, id NodeId, rev RevisionID) error

func (*Keg) SetConfig

func (k *Keg) SetConfig(ctx context.Context, data []byte) error

SetConfig parses and writes keg configuration from raw bytes. Prefer UpdateConfig for most use cases as it handles read-modify-write atomically.

func (*Keg) SetContent

func (k *Keg) SetContent(ctx context.Context, id NodeId, data []byte) error

SetContent writes content for a node and updates its metadata by re-indexing. This ensures the node's title, lead, and other metadata are kept in sync with content changes.

func (*Keg) SetExtraDexOpts added in v0.15.0

func (k *Keg) SetExtraDexOpts(opts ...DexOption)

SetExtraDexOpts stores additional DexOptions that will be included whenever the dex is loaded or refreshed. These options are prepended before WithConfig so that injected resolvers (e.g. WithQueryResolver) are available when WithConfig creates QueryFilteredIndex instances.

This is the injection point for higher-level packages (e.g. pkg/tapper) to provide capabilities that pkg/keg cannot import directly.

func (*Keg) SetMeta

func (k *Keg) SetMeta(ctx context.Context, id NodeId, meta *NodeMeta) error

SetMeta writes metadata for a node and updates the dex. If the new meta bytes are identical to the existing on-disk meta, the write and dex/config update are skipped entirely.

func (*Keg) Touch

func (k *Keg) Touch(ctx context.Context, id NodeId) error

Touch updates the access time of a node to the current time.

func (*Keg) UpdateConfig

func (k *Keg) UpdateConfig(ctx context.Context, f func(*Config)) error

UpdateConfig reads the keg config, applies the provided mutation function, and writes the result back to the repository. This is the preferred way to modify keg configuration to ensure updates are atomically persisted.

func (*Keg) UpdateMeta

func (k *Keg) UpdateMeta(ctx context.Context, id NodeId, f func(*NodeMeta)) error

UpdateMeta reads the node's metadata, applies the provided mutation function, and writes the result back to the repository with dex updates.

type LinkEntry

type LinkEntry struct {
	Alias string `json:"alias"` // Alias for the link
	URL   string `json:"url"`   // URL of the link
}

LinkEntry represents a named link in the KEG configuration.

type LinkIndex

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

LinkIndex maps a source node path to the list of destination nodes that the source links to. It is used to construct the "links" index artifact.

The underlying map keys are node.Path() values (string). The index is expected to be small enough to be kept in memory for index-building tooling.

The type has unexported fields and is safe for in-memory, single-process use. Concurrency control is the caller's responsibility.

func ParseLinkIndex

func ParseLinkIndex(ctx context.Context, data []byte) (LinkIndex, error)

ParseLinkIndex parses the raw bytes of a links index into a LinkIndex. The expected on-disk format is one line per source:

"<src>\t<dst1> <dst2> ...\n"

Behavior:

  • Empty or nil input yields an empty LinkIndex with no error.
  • Lines are split on tab to separate source from space-separated destinations.
  • Duplicate destinations for a source are tolerated and may be deduped by callers of Data.

This function does not modify any external state.

func (*LinkIndex) Add

func (idx *LinkIndex) Add(ctx context.Context, data *NodeData) error

Add incorporates link information from the provided NodeData into the index. The NodeData.ID value is used as the source key (via NodeId.Path semantics) and NodeData.Links is treated as the list of destination nodes.

Behavior expectations (not enforced here but callers may rely on them):

  • If idx is nil the call is a no-op and returns nil.
  • If idx.data is nil it will be initialized.
  • The method should avoid introducing duplicate destination entries for a given source when possible.

This method only mutates in-memory state and does not perform I/O.

func (*LinkIndex) Data

func (idx *LinkIndex) Data(ctx context.Context) ([]byte, error)

Data serializes the index into the canonical on-disk format.

Serialization rules:

  • Each non-empty source produces a line: "<src>\t<dst1> <dst2> ...\n"
  • Destination lists are deduplicated and sorted in a deterministic order.
  • Source keys are emitted in a deterministic, parse-aware order (numeric node ids sorted numerically when possible, otherwise lexicographic).
  • An empty index returns an empty byte slice.

The returned bytes are owned by the caller and may be written atomically by the repository layer.

func (*LinkIndex) Rm

func (idx *LinkIndex) Rm(ctx context.Context, node NodeId) error

Rm removes any references introduced by the given node as a source and removes the node from any destination lists where it appears.

Behavior expectations:

  • If idx is nil the call is a no-op and returns nil.
  • If idx.data is nil it will be initialized to an empty map.
  • After removal, entries with no destinations may either remain as empty slices or be deleted; callers should tolerate either representation.

This method only mutates in-memory state and does not perform I/O.

type LockInfo added in v0.11.0

type LockInfo struct {
	Token      LockToken `json:"token"`
	AcquiredAt time.Time `json:"acquired_at"`
	TTLSeconds int       `json:"ttl_seconds"`
	Holder     string    `json:"holder"`
}

LockInfo describes the current state of a cross-process node lock.

func (LockInfo) IsStale added in v0.11.0

func (li LockInfo) IsStale(now time.Time) bool

IsStale reports whether the lock has expired based on its TTL.

type LockToken added in v0.11.0

type LockToken string

LockToken is an opaque string identifying lock ownership.

type MemoryRepo

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

MemoryRepo is an in-memory implementation of Repository intended for tests and lightweight tooling that doesn't require persistent storage.

Concurrency / locking:

  • MemoryRepo uses an internal sync.RWMutex (mu) to guard all internal maps and per-node structures. Readers should use RLock/RUnlock; mutating operations use Lock/Unlock.
  • The implementation is safe for concurrent use by multiple goroutines.

Semantics / behavior:

  • NodeId entries are created on demand when writing content, meta, items, or images.
  • Index files are kept in-memory by name (for example "nodes.tsv") and are accessible via WriteIndex/GetIndex.
  • Methods return sentinel or typed errors defined in the package to match the Repository contract (for example NewNodeNotFoundError, ErrNotFound).

func NewMemoryRepo

func NewMemoryRepo(rt *toolkit.Runtime) *MemoryRepo

NewMemoryRepo constructs a ready-to-use in-memory repository.

func (*MemoryRepo) AcquireLock added in v0.11.0

func (r *MemoryRepo) AcquireLock(ctx context.Context, id NodeId) (LockToken, error)

AcquireLock implements RepositoryLock.

func (*MemoryRepo) AppendSnapshot added in v0.4.0

func (r *MemoryRepo) AppendSnapshot(ctx context.Context, id NodeId, in SnapshotWrite) (Snapshot, error)

func (*MemoryRepo) ClearDex

func (r *MemoryRepo) ClearDex() error

ClearDex removes all stored index artifacts.

func (*MemoryRepo) ClearIndexes

func (r *MemoryRepo) ClearIndexes(ctx context.Context) error

ClearIndexes removes all stored index artifacts.

func (*MemoryRepo) ClearNodeLock

func (r *MemoryRepo) ClearNodeLock(ctx context.Context, id NodeId) error

ClearNodeLock removes an active per-node lock marker.

func (*MemoryRepo) DeleteAsset

func (r *MemoryRepo) DeleteAsset(ctx context.Context, id NodeId, kind AssetKind, name string) error

DeleteAsset removes an asset by name for a node.

func (*MemoryRepo) DeleteFile

func (r *MemoryRepo) DeleteFile(ctx context.Context, id NodeId, name string) error

func (*MemoryRepo) DeleteImage

func (r *MemoryRepo) DeleteImage(ctx context.Context, id NodeId, name string) error

func (*MemoryRepo) DeleteNode

func (r *MemoryRepo) DeleteNode(ctx context.Context, id NodeId) error

DeleteNode removes the node and all associated content/metadata/items. If the node does not exist, NewNodeNotFoundError is returned.

func (*MemoryRepo) ForceReleaseLock added in v0.11.0

func (r *MemoryRepo) ForceReleaseLock(ctx context.Context, id NodeId) error

ForceReleaseLock implements RepositoryLock.

func (*MemoryRepo) GetIndex

func (r *MemoryRepo) GetIndex(ctx context.Context, name string) ([]byte, error)

GetIndex reads a stored index by name. If not present, ErrNotFound is returned. The returned bytes are a copy.

func (*MemoryRepo) GetSnapshot added in v0.4.0

func (r *MemoryRepo) GetSnapshot(ctx context.Context, id NodeId, rev RevisionID, opts SnapshotReadOptions) (Snapshot, []byte, []byte, *NodeStats, error)

func (*MemoryRepo) HasNode

func (r *MemoryRepo) HasNode(ctx context.Context, id NodeId) (bool, error)

func (*MemoryRepo) ListAssets

func (r *MemoryRepo) ListAssets(ctx context.Context, id NodeId, kind AssetKind) ([]string, error)

ListAssets lists asset names for a node and asset kind, sorted lexicographically.

func (*MemoryRepo) ListFiles

func (r *MemoryRepo) ListFiles(ctx context.Context, id NodeId) ([]string, error)

func (*MemoryRepo) ListImages

func (r *MemoryRepo) ListImages(ctx context.Context, id NodeId) ([]string, error)

func (*MemoryRepo) ListIndexes

func (r *MemoryRepo) ListIndexes(ctx context.Context) ([]string, error)

ListIndexes returns the names of stored index files sorted lexicographically.

func (*MemoryRepo) ListNodes

func (r *MemoryRepo) ListNodes(ctx context.Context) ([]NodeId, error)

ListNodes returns all known NodeIDs sorted in ascending numeric order.

func (*MemoryRepo) ListSnapshots added in v0.4.0

func (r *MemoryRepo) ListSnapshots(ctx context.Context, id NodeId) ([]Snapshot, error)

func (*MemoryRepo) LockNode

func (r *MemoryRepo) LockNode(ctx context.Context, id NodeId, retryInterval time.Duration) (func() error, error)

LockNode attempts to acquire a per-node lock. It will retry at the provided retryInterval until the context is cancelled. On success it returns an unlock function which the caller MUST call to release the lock.

Behavior notes:

- If retryInterval <= 0, a sensible default is used. - If ctx is cancelled while waiting, ErrLockTimeout is returned.

func (*MemoryRepo) LockStatus added in v0.11.0

func (r *MemoryRepo) LockStatus(ctx context.Context, id NodeId) (LockInfo, error)

LockStatus implements RepositoryLock.

func (*MemoryRepo) MoveNode

func (r *MemoryRepo) MoveNode(ctx context.Context, id NodeId, dst NodeId) error

MoveNode renames or moves a node from id to dst.

- If the source node does not exist, ErrNodeNotFound is returned. - If the destination already exists, a DestinationExistsError is returned. The move is performed by transferring the in-memory node pointer.

func (*MemoryRepo) Name

func (r *MemoryRepo) Name() string

func (*MemoryRepo) Next

func (r *MemoryRepo) Next(ctx context.Context) (NodeId, error)

Next returns a new NodeID and reserves it by inserting an empty node entry. This prevents concurrent callers from receiving the same ID.

func (*MemoryRepo) NodeFilesExist

func (r *MemoryRepo) NodeFilesExist(ctx context.Context, id NodeId) (bool, bool, error)

func (*MemoryRepo) ReadConfig

func (r *MemoryRepo) ReadConfig(ctx context.Context) (*Config, error)

ReadConfig returns the repository-level config previously written with WriteConfig. If no config has been written, ErrNotFound is returned. A copy of the stored Config is returned to avoid external mutation.

func (*MemoryRepo) ReadContent

func (r *MemoryRepo) ReadContent(ctx context.Context, id NodeId) ([]byte, error)

ReadContent returns the primary content for the given node id.

- If the node does not exist, ErrNodeNotFound is returned. - If the node exists but has no content, (nil, nil) is returned. - The returned slice is a copy to prevent caller-visible mutation.

func (*MemoryRepo) ReadContentAt added in v0.4.0

func (r *MemoryRepo) ReadContentAt(ctx context.Context, id NodeId, rev RevisionID) ([]byte, error)

func (*MemoryRepo) ReadFile added in v0.2.0

func (r *MemoryRepo) ReadFile(ctx context.Context, id NodeId, name string) ([]byte, error)

func (*MemoryRepo) ReadImage added in v0.2.0

func (r *MemoryRepo) ReadImage(ctx context.Context, id NodeId, name string) ([]byte, error)

func (*MemoryRepo) ReadMeta

func (r *MemoryRepo) ReadMeta(ctx context.Context, id NodeId) ([]byte, error)

ReadMeta returns the serialized node metadata (usually meta.yaml).

- If the node does not exist, ErrNodeNotFound is returned. - If meta is absent, ErrNotFound is returned. - The returned bytes are a copy.

func (*MemoryRepo) ReadStats

func (r *MemoryRepo) ReadStats(ctx context.Context, id NodeId) (*NodeStats, error)

ReadStats returns parsed programmatic stats for a node.

func (*MemoryRepo) ReleaseLock added in v0.11.0

func (r *MemoryRepo) ReleaseLock(ctx context.Context, id NodeId, token LockToken) error

ReleaseLock implements RepositoryLock.

func (*MemoryRepo) RestoreSnapshot added in v0.4.0

func (r *MemoryRepo) RestoreSnapshot(ctx context.Context, id NodeId, rev RevisionID, createRestoreSnapshot bool) error

func (*MemoryRepo) Runtime

func (r *MemoryRepo) Runtime() *toolkit.Runtime

func (*MemoryRepo) WatchEvents added in v0.6.0

func (r *MemoryRepo) WatchEvents() *MemoryRepoWatcher

WatchEvents returns a RepositoryEvents implementation for the MemoryRepo.

func (*MemoryRepo) WithNodeLock

func (r *MemoryRepo) WithNodeLock(ctx context.Context, id NodeId, fn func(context.Context) error) error

WithNodeLock executes fn while holding an exclusive lock for node id.

func (*MemoryRepo) WriteAsset

func (r *MemoryRepo) WriteAsset(ctx context.Context, id NodeId, kind AssetKind, name string, data []byte) error

WriteAsset stores a named asset blob for a node.

func (*MemoryRepo) WriteConfig

func (r *MemoryRepo) WriteConfig(ctx context.Context, config *Config) error

WriteConfig stores the provided Config in-memory. A copy of the value is kept.

func (*MemoryRepo) WriteContent

func (r *MemoryRepo) WriteContent(ctx context.Context, id NodeId, data []byte) error

WriteContent writes the primary content for the given node id, creating the node if necessary.

Note: this implementation stores the provided slice reference in-memory. Callers should avoid mutating the provided slice after calling this method.

func (*MemoryRepo) WriteFile

func (r *MemoryRepo) WriteFile(ctx context.Context, id NodeId, name string, data []byte) error

func (*MemoryRepo) WriteImage

func (r *MemoryRepo) WriteImage(ctx context.Context, id NodeId, name string, data []byte) error

func (*MemoryRepo) WriteIndex

func (r *MemoryRepo) WriteIndex(ctx context.Context, name string, data []byte) error

WriteIndex writes or replaces an in-memory index file.

func (*MemoryRepo) WriteMeta

func (r *MemoryRepo) WriteMeta(ctx context.Context, id NodeId, data []byte) error

WriteMeta sets the node metadata (meta.yaml bytes), creating the node if needed.

Note: the provided slice is stored as-is in-memory; do not modify it after writing.

func (*MemoryRepo) WriteStats

func (r *MemoryRepo) WriteStats(ctx context.Context, id NodeId, stats *NodeStats) error

WriteStats writes programmatic stats while preserving manually edited meta fields.

type MemoryRepoWatcher added in v0.6.0

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

MemoryRepoWatcher implements RepositoryEvents for MemoryRepo, enabling event-driven testing without filesystem dependencies. The Emit method allows test code to simulate repository changes.

func (*MemoryRepoWatcher) Close added in v0.6.0

func (w *MemoryRepoWatcher) Close() error

Close releases all watcher resources and closes subscriber channels.

func (*MemoryRepoWatcher) Emit added in v0.6.0

func (w *MemoryRepoWatcher) Emit(ev NodeEvent)

Emit sends a NodeEvent to all active subscribers whose filters match. This is intended to be called from test code to simulate repository changes.

func (*MemoryRepoWatcher) Watch added in v0.6.0

func (w *MemoryRepoWatcher) Watch(ctx context.Context, ids ...NodeId) (<-chan NodeEvent, error)

Watch begins observing changes for the specified node IDs (or all nodes when no IDs are given).

type Node

type Node struct {
	ID      NodeId
	Repo    Repository
	Runtime *toolkit.Runtime
	// contains filtered or unexported fields
}

Node provides operations and lifecycle management for a single KEG node. It holds the node identifier, repository reference, and lazily-loaded node data.

func (*Node) Accessed

func (n *Node) Accessed(ctx context.Context) (time.Time, error)

func (*Node) Changed

func (n *Node) Changed(ctx context.Context) (bool, error)

func (*Node) ClearCache

func (n *Node) ClearCache()

func (*Node) Created

func (n *Node) Created(ctx context.Context) (time.Time, error)

func (*Node) Init

func (n *Node) Init(ctx context.Context) error

Init loads and initializes the node data from the repository including content, metadata, items, and images. Returns an error if the repository is not set or if any repository operation fails.

func (*Node) Lead

func (n *Node) Lead(ctx context.Context) (string, error)
func (n *Node) Links(ctx context.Context) ([]NodeId, error)

func (*Node) ListImages

func (n *Node) ListImages(ctx context.Context) ([]string, error)

func (*Node) ListItems

func (n *Node) ListItems(ctx context.Context) ([]string, error)

func (*Node) Ref

func (n *Node) Ref(ctx context.Context) (NodeIndexEntry, error)

func (*Node) Save

func (n *Node) Save(ctx context.Context) error

func (*Node) Stats

func (n *Node) Stats(ctx context.Context) (*NodeStats, error)

func (*Node) String

func (n *Node) String() string

func (*Node) Tags

func (n *Node) Tags(ctx context.Context) ([]string, error)

func (*Node) Touch

func (n *Node) Touch(ctx context.Context) error

func (*Node) Update

func (n *Node) Update(ctx context.Context) error

func (*Node) Updated

func (n *Node) Updated(ctx context.Context) (time.Time, error)

type NodeContent

type NodeContent struct {
	// Hash is the stable content hash computed by the repository hasher.
	Hash string

	// Title is the canonical title for the content. For Markdown this is the
	// first H1; for RST it is the detected title.
	Title string

	// Lead is the first paragraph immediately following the title. It is used
	// as a short summary or preview of the content.
	Lead string

	// Links is the list of numeric outgoing node links discovered in the
	// content (for example "../42"). Entries are normalized NodeId values.
	Links []NodeId

	// Format is a short hint of the detected format. Typical values are
	// "markdown", "rst", or "empty".
	Format string

	// Body is the content body with Markdown frontmatter removed when present.
	// For non-Markdown formats this is the original file content.
	Body string

	// Frontmatter is the parsed YAML frontmatter when present. It is non-nil
	// only for Markdown documents that include a leading YAML block.
	Frontmatter map[string]any
}

NodeContent holds the extracted pieces of a node's primary content file (README.md or README.rst).

Fields:

  • Hash: stable content hash computed by the repository hasher.
  • Title: canonical title (first H1 for Markdown, or RST title detected).
  • Lead: first paragraph immediately following the title (used as a short summary).
  • Links: numeric outgoing node links discovered in the content (../N).
  • Format: short hint of the detected format ("markdown", "rst", or "empty").
  • Frontmatter: parsed YAML frontmatter when present (Markdown only).
  • Body: the raw body bytes of the content file with frontmatter removed for Markdown (or the original bytes for other formats), represented as a string.

func ParseContent

func ParseContent(rt *toolkit.Runtime, data []byte, format string) (*NodeContent, error)

ParseContent extracts a NodeContent value from raw file bytes.

The format parameter is a filename hint (e.g., "README.md", "README.rst"). When format is ambiguous the function applies simple heuristics to choose between Markdown and reStructuredText. The returned NodeContent contains a deterministic, deduplicated, sorted list of discovered numeric links.

ParseContent uses the provided runtime hasher to compute content Hash. If the input is empty or only whitespace, a NodeContent with Format == "empty" is returned.

type NodeData

type NodeData struct {
	// ID is the node identifier as a string (for example "42" or "42-0001").
	// Keep this lightweight while other fields are exposed via accessors.
	ID      NodeId
	Content *NodeContent
	Meta    *NodeMeta
	Stats   *NodeStats

	// Ancillary names (attachments and images). Implementations may populate these
	// from the repository.
	Items  []string
	Images []string
}

NodeData is a high-level representation of a KEG node. Implementations may compose this from repository pieces such as meta, content, and ancillary items.

func (*NodeData) Accessed

func (n *NodeData) Accessed() time.Time

Accessed returns the accessed timestamp from stats when available.

func (*NodeData) ContentChanged

func (n *NodeData) ContentChanged() bool

NodeContent has previously changed

func (*NodeData) ContentHash

func (n *NodeData) ContentHash() string

ContentHash returns the content hash if content is present, otherwise the empty string.

func (*NodeData) Created

func (n *NodeData) Created() time.Time

Created returns the created timestamp from stats when available.

func (*NodeData) Format

func (n *NodeData) Format() string

Format returns the content format hint (for example "markdown" or "rst").

func (*NodeData) Lead

func (n *NodeData) Lead() string

Lead returns the short lead/summary for the node. Prefer stats then content.

func (n *NodeData) Links() []NodeId

Links returns the outgoing links discovered for the node. Prefer stats and fall back to parsed content links when stats are unavailable.

func (*NodeData) MetaHash

func (n *NodeData) MetaHash() string

MetaHash returns the stored programmatic hash when available.

func (*NodeData) Ref

func (n *NodeData) Ref() NodeIndexEntry

Ref builds a NodeIndexEntry from the NodeData. If the NodeData.ID is malformed ParseNode may fail and the function will fall back to a zero NodeId.

func (*NodeData) Tags

func (n *NodeData) Tags() []string

Tags returns a copy of the normalized tag list from metadata or nil if not set.

func (*NodeData) Title

func (n *NodeData) Title() string

Title returns the canonical title for the node. Prefer stats title and fall back to parsed content title when available.

func (*NodeData) Touch

func (n *NodeData) Touch(ctx context.Context, now *time.Time)

func (*NodeData) UpdateMeta

func (n *NodeData) UpdateMeta(ctx context.Context, now *time.Time) error

func (*NodeData) Updated

func (n *NodeData) Updated() time.Time

Updated returns the updated timestamp from stats when available.

type NodeEvent added in v0.6.0

type NodeEvent struct {
	Kind   NodeEventKind
	NodeID NodeId
	Field  string // "content", "meta", "stats", or ""
}

NodeEvent describes a single change or access observed on a node. Field identifies which part of the node was affected: "content" for README.md, "meta" for meta.yaml, "stats" for stats.json, or "" when the event applies to the node as a whole (e.g. creation or deletion of the entire directory).

type NodeEventKind added in v0.6.0

type NodeEventKind int

NodeEventKind identifies the type of change that occurred on a node.

const (
	// NodeEventCreated indicates a new node was created.
	NodeEventCreated NodeEventKind = iota + 1
	// NodeEventModified indicates an existing node file was modified.
	NodeEventModified
	// NodeEventDeleted indicates a node or node file was removed.
	NodeEventDeleted
	// NodeEventAccessed indicates a node's content or metadata was read.
	NodeEventAccessed
)

func (NodeEventKind) String added in v0.6.0

func (k NodeEventKind) String() string

String returns a human-readable label for the event kind.

type NodeId

type NodeId struct {
	ID    int
	Alias string
	// Code is an additional random identifier used to signify an uncommitted node.
	Code string
}

NodeId is the stable numeric identifier for a KEG node. The ID field is the canonical non-negative integer identifier. The optional Code field is a zero-padded 4-digit numeric suffix used to represent an uncommitted or temporary variant of the node.

func NewTempNode

func NewTempNode(ctx context.Context, id string) *NodeId

NewTempNode creates a new NodeId using the provided base id string and a 4-digit numeric code. The function attempts to parse the base id via ParseNode; if that fails it will try to parse the string as a non-negative integer. If the id is empty or cannot be parsed as a non-negative integer the returned NodeId will have ID set to 0.

The Code is generated with crypto/rand when available and falls back to the current nanotime if random bytes cannot be obtained. The code is returned as a zero-padded 4-digit string.

The context parameter is accepted to allow future callers to pass context without changing the signature. It is not used by the current implementation.

func ParseNode

func ParseNode(s string) (*NodeId, error)

ParseNode converts a string into a *NodeId.

Accepted forms:

  • "0" or a non-negative integer without leading zeros (for example "1", "23")

  • "<id>-<code>" where <id> follows the rules above and <code> is exactly 4 digits

  • "keg:<alias>/<id>" or "keg:<alias>/<id>-<code>" to include an alias.

Examples:

"42"               -> &NodeId{ID:42, Code:""}, nil
"42-0001"          -> &NodeId{ID:42, Code:"0001"}, nil
"keg:work/23"      -> &NodeId{ID:23, Keg:"work"}, nil
"keg:work/23-0001" -> &NodeId{ID:23, Keg:"work", Code:"0001"}, nil
"0023"             -> nil, error (leading zeros not allowed)
""                 -> nil, error

func (NodeId) Compare

func (n NodeId) Compare(other NodeId) int

Compare returns -1 if n < other, 1 if n > other, and 0 if they are equal.

func (NodeId) Equals

func (n NodeId) Equals(other NodeId) bool

Equals reports whether two Nodes are identical in ID and Code.

func (NodeId) Gt

func (n NodeId) Gt(other NodeId) bool

Gt reports whether n is strictly greater than other using ID then Code.

func (NodeId) Gte

func (n NodeId) Gte(other NodeId) bool

Gte reports whether n is greater than or equal to other.

func (NodeId) Increment

func (n NodeId) Increment() NodeId

Increment returns a new NodeId with the ID value increased by one while preserving the Code.

func (NodeId) Lt

func (n NodeId) Lt(other NodeId) bool

Lt reports whether n is strictly less than other using ID then Code.

func (NodeId) Lte

func (n NodeId) Lte(other NodeId) bool

Lte reports whether n is less than or equal to other.

func (NodeId) Path

func (id NodeId) Path() string

Path returns the path component for this NodeId suitable for use in file names or URLs.

Examples:

NodeId{ID:42, Code:""}      -> "42"
NodeId{ID:42, Code:"0001"}  -> "42-0001"
NodeId{ID:42, Keg:"work"} -> "keg:work/42"

func (NodeId) String

func (id NodeId) String() string

func (NodeId) Valid

func (id NodeId) Valid() bool

Valid reports whether the NodeId ID is a non-negative integer.

type NodeIndex

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

NodeIndex is an in-memory index of node descriptors used to construct the `nodes.tsv` index artifact.

The index stores a slice of `NodeIndexEntry` values in a deterministic order (ascending by numeric node id, with codes used to break ties). It provides helpers to parse a serialized index, mutate the in-memory list, and produce the canonical serialized bytes.

Concurrency note: NodeIndex itself does not perform internal synchronization. Callers that require concurrent access should guard an instance with a mutex.

func ParseNodeIndex

func ParseNodeIndex(ctx context.Context, data []byte) (NodeIndex, error)

ParseNodeIndex parses the serialized nodes index bytes into a NodeIndex.

Expected input is zero or more lines separated by newline. Each non-empty line represents a node entry in the canonical TSV format used by the repo. Parsers should tolerate empty input and skip malformed lines while continuing to parse the remainder. An empty input yields an empty NodeIndex and no error.

Parsing rules and leniency:

  • Each valid line is expected to contain at least the ID field. Additional columns (for example updated timestamp and title) are accepted when present.
  • Lines that cannot be parsed into a valid NodeIndexEntry are skipped and do not cause the entire parse to fail. This allows forward compatibility when new columns are added to the on-disk format.
  • The returned NodeIndex contains entries in the order they were parsed; it is the caller's responsibility to sort or normalize ordering if desired.

Returns:

  • a NodeIndex containing parsed NodeIndexEntry values.
  • a non nil error only for unexpected conditions preventing parsing of the entire input (for example severe encoding issues). Minor line-level parse problems are tolerated and do not cause an error.

Example input (5-column, current format):

"42\t2025-01-02T15:04:05Z\t2024-06-01T10:00:00Z\t2025-01-03T08:00:00Z\tMy Title\n"

Legacy input (3-column, backward compatible):

"42\t2025-01-02T15:04:05Z\tMy Title\n"

Column order (5-col): id<TAB>updated<TAB>created<TAB>accessed<TAB>title Column order (3-col): id<TAB>updated<TAB>title

func (*NodeIndex) Add

func (idx *NodeIndex) Add(ctx context.Context, data *NodeData) error

Add inserts the provided node into the index. The index should remain sorted by ascending node id after the operation.

Behavior expectations:

  • If idx is nil the call is a no-op and returns nil.
  • The method should ensure idx.data is initialized when first used.
  • Adding an existing node id should be idempotent: the existing entry should be updated or replaced rather than producing duplicates.
  • The operation is in-memory only and does not perform I/O.

Typical callers: - Index builders that aggregate node metadata into the nodes index. - Tests that need to construct an in-memory nodes list.

Note: This method does not acquire any synchronization; callers should hold a lock if concurrent mutations are possible.

Implementation note:

The function should insert or update the NodeIndexEntry derived from the
supplied NodeData. After modification, idx.data must be ordered so that
Next and serialized output are stable and deterministic.

func (*NodeIndex) Data

func (idx *NodeIndex) Data(ctx context.Context) ([]byte, error)

Data serializes the NodeIndex into the canonical on-disk TSV representation.

Serialization rules:

  • Each entry produces a single line in the form used by the repository's nodes index. Column order is: id<TAB>updated<TAB>created<TAB>accessed<TAB>title<LF>.
  • Entries must be emitted in ascending node id order.
  • An empty index returns an empty byte slice.

The returned bytes are owned by the caller and may be written atomically by the repository layer. The function should not modify idx.data.

Implementation note:

The function should not rely on external state. It must produce stable,
deterministic output suitable for writing to an index file.

func (*NodeIndex) Get

func (idx *NodeIndex) Get(ctx context.Context, node NodeId) *NodeIndexEntry

Get returns the NodeIndexEntry pointer for the provided node if present. The lookup uses node.Path() to match the ID field of entries.

Returns:

  • *NodeIndexEntry when the entry is present.
  • nil when the entry is not present or idx is nil.

The returned pointer points into the internal slice. Callers that need to modify the entry should copy it first to avoid data races.

func (*NodeIndex) List

func (idx *NodeIndex) List(ctx context.Context) []NodeIndexEntry

List returns the in-memory slice of NodeIndexEntry. The returned slice is the underlying data and callers should not mutate it to avoid data races.

func (*NodeIndex) Next

func (idx *NodeIndex) Next(ctx context.Context) NodeId

Next returns the next available NodeId id based on the current index contents.

Semantics:

  • If the index is empty, Next returns NodeId{ID:0, Code:""} (the zeroth id).
  • Otherwise Next returns a NodeId whose ID is one greater than the highest numeric ID present in the index. If entries contain code suffixes the numeric portion is used for ordering.
  • The function does not modify the index.

Implementation note:

The function should examine idx.data to determine the maximal numeric id and
return the subsequent id. It should not allocate or write any external state.

func (*NodeIndex) Rm

func (idx *NodeIndex) Rm(ctx context.Context, node NodeId) error

Rm removes the node identified by id from the index.

Behavior expectations: - If idx is nil the call is a no-op and returns nil. - If the node is not present the call should not error. - After removal the index slice should remain in a stable, sorted state. - This method only mutates in-memory state and does not perform I/O.

Typical callers: - Index maintenance routines that remove entries for deleted nodes. - Tests cleaning up expected state.

Implementation note:

The function should locate the entry whose ID equals node.Path() and remove
it from the slice. The remaining slice should preserve deterministic order.

type NodeIndexEntry

type NodeIndexEntry struct {
	ID       string    `json:"id" yaml:"id"`
	Title    string    `json:"title" yaml:"title"`
	Updated  time.Time `json:"updated" yaml:"updated"`
	Created  time.Time `json:"created" yaml:"created"`
	Accessed time.Time `json:"accessed" yaml:"accessed"`
}

NodeIndexEntry is a small descriptor for a node used by repository listings and indices. It contains the node id as a string, a human-friendly title, and timestamps for updated, created, and accessed.

type NodeMeta

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

NodeMeta holds manually edited node metadata and helpers to read/update it.

Programmatic fields (title/hash/timestamps/lead/links) are represented by NodeStats. NodeMeta focuses on human-editable yaml data and comment-preserving writes.

func NewMeta

func NewMeta(ctx context.Context, now time.Time) *NodeMeta

NewMeta constructs an empty NodeMeta.

func ParseMeta

func ParseMeta(ctx context.Context, raw []byte) (*NodeMeta, error)

ParseMeta parses raw yaml bytes into NodeMeta. Empty input returns an empty NodeMeta.

func (*NodeMeta) AddTag

func (m *NodeMeta) AddTag(tag string)

func (*NodeMeta) Get

func (m *NodeMeta) Get(key string) (string, bool)

Get retrieves scalar metadata fields by key.

func (*NodeMeta) RmTag

func (m *NodeMeta) RmTag(tag string)

func (*NodeMeta) Set

func (m *NodeMeta) Set(ctx context.Context, key string, val any) error

Set updates known NodeMeta keys (tags) and preserves unknown keys in the yaml node when available.

func (*NodeMeta) SetAttrs

func (m *NodeMeta) SetAttrs(ctx context.Context, attrs map[string]any) error

func (*NodeMeta) SetTags

func (m *NodeMeta) SetTags(tags []string)

func (*NodeMeta) Tags

func (m *NodeMeta) Tags() []string

func (*NodeMeta) ToYAML

func (m *NodeMeta) ToYAML() string

ToYAML serializes only manually edited metadata fields.

func (*NodeMeta) ToYAMLWithStats

func (m *NodeMeta) ToYAMLWithStats(stats *NodeStats) string

ToYAMLWithStats serializes metadata while optionally merging programmatic NodeStats fields into the emitted yaml.

type NodeStats

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

NodeStats contains programmatic node data derived by tooling.

func NewStats

func NewStats(now time.Time) *NodeStats

func ParseStats

func ParseStats(ctx context.Context, raw []byte) (*NodeStats, error)

ParseStats extracts programmatic node stats from raw bytes. The canonical encoding is JSON; YAML is accepted as a compatibility fallback.

func (*NodeStats) AccessCount

func (s *NodeStats) AccessCount() int

func (*NodeStats) Accessed

func (s *NodeStats) Accessed() time.Time

func (*NodeStats) Created

func (s *NodeStats) Created() time.Time

func (*NodeStats) EnsureTimes

func (s *NodeStats) EnsureTimes(now time.Time)

func (*NodeStats) Hash

func (s *NodeStats) Hash() string

func (*NodeStats) IncrementAccessCount

func (s *NodeStats) IncrementAccessCount()

func (*NodeStats) Lead

func (s *NodeStats) Lead() string
func (s *NodeStats) Links() []NodeId

func (*NodeStats) SetAccessCount

func (s *NodeStats) SetAccessCount(count int)

func (*NodeStats) SetAccessed

func (s *NodeStats) SetAccessed(t time.Time)

func (*NodeStats) SetCreated

func (s *NodeStats) SetCreated(t time.Time)

func (*NodeStats) SetHash

func (s *NodeStats) SetHash(hash string, now *time.Time)

func (*NodeStats) SetLead

func (s *NodeStats) SetLead(lead string)
func (s *NodeStats) SetLinks(links []NodeId)

func (*NodeStats) SetTitle

func (s *NodeStats) SetTitle(title string)

func (*NodeStats) SetUpdated

func (s *NodeStats) SetUpdated(t time.Time)

func (*NodeStats) Title

func (s *NodeStats) Title() string

func (*NodeStats) ToJSON

func (s *NodeStats) ToJSON() ([]byte, error)

func (*NodeStats) UpdateFromContent

func (s *NodeStats) UpdateFromContent(content *NodeContent, now *time.Time)

func (*NodeStats) Updated

func (s *NodeStats) Updated() time.Time

type Option

type Option func(*Keg)

Option is a functional option for configuring Keg behavior

type QueryFilteredIndex added in v0.15.0

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

QueryFilteredIndex is an in-memory index of nodes that match a boolean query expression. It supports the full query expression system: tag names, key=value attribute predicates, boolean operators (and/or/not), and parenthesized grouping.

The resolve callback, when non-nil, is called for each term in the query expression with each candidate node. This allows higher-level packages (e.g. pkg/tapper) to inject attribute predicate support without creating a dependency from pkg/keg to pkg/tapper.

When resolve is nil, the index falls back to tag-only matching (each term is evaluated as a tag name against the node's tag set).

Concurrency note: QueryFilteredIndex does not perform internal synchronization. Callers should guard access with a mutex when needed.

func NewQueryFilteredIndex added in v0.15.0

func NewQueryFilteredIndex(name, query string, resolve func(term string, data *NodeData) bool) (*QueryFilteredIndex, error)

NewQueryFilteredIndex creates a QueryFilteredIndex for the given index file name and boolean query string. The optional resolve callback enables key=value attribute predicates and other term types.

When resolve is nil, terms are evaluated as tag names against the node's tag set (equivalent to TagFilteredIndex behavior).

name should be the short filename (without the "dex/" prefix) used when writing to the repository, e.g. "golang.md".

func (*QueryFilteredIndex) Add added in v0.15.0

func (idx *QueryFilteredIndex) Add(ctx context.Context, data *NodeData) error

Add evaluates the query expression against the node and, if it matches, inserts or updates the node entry maintaining reverse-chronological order.

func (*QueryFilteredIndex) Clear added in v0.15.0

func (idx *QueryFilteredIndex) Clear(ctx context.Context) error

Clear resets the index to an empty state.

func (*QueryFilteredIndex) Data added in v0.15.0

func (idx *QueryFilteredIndex) Data(ctx context.Context) ([]byte, error)

Data serializes the QueryFilteredIndex to the same markdown format as ChangesIndex.Data. Entries are in reverse-chronological order.

func (*QueryFilteredIndex) Name added in v0.15.0

func (idx *QueryFilteredIndex) Name() string

Name returns the short index filename used with repo.WriteIndex.

func (*QueryFilteredIndex) Remove added in v0.15.0

func (idx *QueryFilteredIndex) Remove(ctx context.Context, node NodeId) error

Remove removes the node identified by node from the index. If the node is not present the call is a no-op.

type RateLimitError

type RateLimitError struct {
	RetryAfter time.Duration // suggested wait time
	Message    string
	Cause      error
}

RateLimitError represents a throttling response that includes a suggested RetryAfter duration and an optional message. It is always considered retryable.

func (*RateLimitError) Error

func (e *RateLimitError) Error() string

func (*RateLimitError) Retryable

func (e *RateLimitError) Retryable() bool

func (*RateLimitError) Unwrap

func (e *RateLimitError) Unwrap() error

type RenderOptions added in v0.11.0

type RenderOptions struct {
	// BaseURL is the site base URL prefix for rewritten node links.
	// Defaults to "/".
	BaseURL string
}

RenderOptions configures markdown-to-HTML rendering.

type Repository

type Repository interface {

	// Name returns a short, human-friendly backend identifier.
	Name() string

	// HasNode reports whether id exists as a node in the backend.
	// Missing nodes should return (false, nil). Backend/storage failures should
	// be returned as non-nil errors.
	HasNode(ctx context.Context, id NodeId) (bool, error)
	// Next returns the next available node id allocation candidate.
	// Implementations should honor ctx cancellation where applicable.
	Next(ctx context.Context) (NodeId, error)
	// ListNodes returns all node ids present in the backend.
	// Returned ids should be deterministic (stable ordering) when possible.
	ListNodes(ctx context.Context) ([]NodeId, error)
	// MoveNode renames or relocates a node from id to dst.
	// Implementations should return typed/sentinel errors when source is missing
	// or destination already exists.
	MoveNode(ctx context.Context, id NodeId, dst NodeId) error
	// DeleteNode removes the node and all associated persisted data.
	// If id does not exist, implementations should return a typed/sentinel
	// not-exist error.
	DeleteNode(ctx context.Context, id NodeId) error

	// WithNodeLock executes fn while holding an exclusive lock for node id.
	// Implementations should block until the lock is acquired or ctx is
	// canceled, and must release the lock after fn returns.
	WithNodeLock(ctx context.Context, id NodeId, fn func(context.Context) error) error
	// ReadContent reads the primary node content bytes (for example README.md).
	// Missing nodes should return a typed/sentinel not-exist error.
	ReadContent(ctx context.Context, id NodeId) ([]byte, error)
	// WriteContent writes primary node content bytes for id.
	// Implementations should perform atomic writes when possible.
	WriteContent(ctx context.Context, id NodeId, data []byte) error
	// ReadMeta reads raw node metadata bytes (for example meta.yaml).
	// Missing nodes should return a typed/sentinel not-exist error.
	ReadMeta(ctx context.Context, id NodeId) ([]byte, error)
	// WriteMeta writes raw node metadata bytes.
	// Implementations should preserve atomicity when possible.
	WriteMeta(ctx context.Context, id NodeId, data []byte) error
	// ReadStats returns parsed programmatic node stats for id.
	// Backends that persist stats inside meta.yaml should parse and return those
	// fields while preserving any manual metadata concerns at higher layers.
	ReadStats(ctx context.Context, id NodeId) (*NodeStats, error)
	// WriteStats writes programmatic node stats for id.
	// Implementations should preserve manually edited metadata fields when stats
	// and metadata share a storage representation.
	WriteStats(ctx context.Context, id NodeId, stats *NodeStats) error

	// GetIndex reads an index artifact by name (for example "nodes.tsv").
	// Callers should treat returned bytes as immutable.
	GetIndex(ctx context.Context, name string) ([]byte, error)
	// WriteIndex writes an index artifact by name.
	// Implementations should prefer atomic file replacement semantics.
	WriteIndex(ctx context.Context, name string, data []byte) error
	// ListIndexes returns available index artifact names.
	// Results should be deterministic when possible.
	ListIndexes(ctx context.Context) ([]string, error)
	// ClearIndexes removes or resets index artifacts in the backend.
	// This method should be idempotent and context-aware.
	ClearIndexes(ctx context.Context) error

	// ReadConfig reads repository-level keg configuration.
	// Missing config should return typed/sentinel not-exist errors.
	ReadConfig(ctx context.Context) (*Config, error)
	// WriteConfig persists repository-level keg configuration.
	// Implementations should perform atomic writes when possible.
	WriteConfig(ctx context.Context, config *Config) error
}

Repository is the storage backend contract used by KEG. Implementations are responsible for moving node data between storage and the service layer.

type RepositoryEvents added in v0.6.0

type RepositoryEvents interface {
	Watch(ctx context.Context, ids ...NodeId) (<-chan NodeEvent, error)
	// Emit sends a NodeEvent to all active subscribers whose filters match.
	// This is used for programmatic events (e.g. access tracking) that
	// cannot be detected by filesystem watchers.
	Emit(ev NodeEvent)
	Close() error
}

RepositoryEvents is an optional interface that Repository implementations may satisfy to provide live change notifications. The editing layer uses a type assertion to check whether the underlying repository supports events.

Watch begins observing changes for the specified node IDs (or all nodes when no IDs are given). Events are delivered on the returned channel until the context is cancelled or Close is called. Implementations must close the channel when observation ends.

Close releases all watcher resources. After Close returns, event channels are closed and no further events are delivered.

type RepositoryFiles

type RepositoryFiles interface {
	// ListFiles lists file attachment names for a node.
	ListFiles(ctx context.Context, id NodeId) ([]string, error)
	// ReadFile reads a file attachment for a node.
	ReadFile(ctx context.Context, id NodeId, name string) ([]byte, error)
	// WriteFile stores a file attachment for a node.
	WriteFile(ctx context.Context, id NodeId, name string, data []byte) error
	// DeleteFile removes a file attachment from a node.
	DeleteFile(ctx context.Context, id NodeId, name string) error
}

RepositoryFiles provides optional per-node file attachment access.

type RepositoryImages

type RepositoryImages interface {
	// ListImages lists image names for a node.
	ListImages(ctx context.Context, id NodeId) ([]string, error)
	// ReadImage reads an image payload for a node.
	ReadImage(ctx context.Context, id NodeId, name string) ([]byte, error)
	// WriteImage stores an image payload for a node.
	WriteImage(ctx context.Context, id NodeId, name string, data []byte) error
	// DeleteImage removes an image from a node.
	DeleteImage(ctx context.Context, id NodeId, name string) error
}

RepositoryImages provides optional per-node image access.

type RepositoryLock added in v0.11.0

type RepositoryLock interface {
	// AcquireLock acquires a cross-process lock on a node. Returns a token
	// that proves ownership. If the node is already locked by a non-stale
	// lock, blocks until the lock is released or ctx is canceled.
	AcquireLock(ctx context.Context, id NodeId) (LockToken, error)

	// ReleaseLock releases a cross-process lock. The token must match the
	// token returned by AcquireLock. Returns an error if the token does not
	// match or no lock is held.
	ReleaseLock(ctx context.Context, id NodeId, token LockToken) error

	// LockStatus returns the current lock state for a node. If no lock is
	// held (or the lock is stale), returns a zero LockInfo with no error.
	LockStatus(ctx context.Context, id NodeId) (LockInfo, error)

	// ForceReleaseLock unconditionally removes a lock regardless of token
	// ownership. Use as an escape hatch for stuck or stale locks.
	ForceReleaseLock(ctx context.Context, id NodeId) error
}

RepositoryLock provides cross-process token-based node locking. This is an optional interface (like RepositoryFiles, RepositoryImages, RepositorySnapshots). Not all repository implementations need cross-process locking.

Cross-process locks are separate from the process-scoped WithNodeLock on the core Repository interface. WithNodeLock serializes concurrent goroutines within a single process; RepositoryLock coordinates across separate CLI invocations or MCP server sessions.

type RepositorySnapshots

type RepositorySnapshots interface {
	// AppendSnapshot appends a new revision with optimistic parent check.
	// Implementations should preserve SnapshotWrite.CreatedAt when supplied and
	// otherwise stamp the revision with the current runtime clock time.
	AppendSnapshot(ctx context.Context, id NodeId, in SnapshotWrite) (Snapshot, error)

	// GetSnapshot returns snapshot metadata and optional state payloads.
	// When opts.ResolveContent is true, returned content must be fully materialized.
	GetSnapshot(ctx context.Context, id NodeId, rev RevisionID, opts SnapshotReadOptions) (snap Snapshot, content []byte, meta []byte, stats *NodeStats, err error)

	// ListSnapshots returns revisions for a node in deterministic order.
	ListSnapshots(ctx context.Context, id NodeId) ([]Snapshot, error)

	// ReadContentAt reconstructs content at a specific revision.
	ReadContentAt(ctx context.Context, id NodeId, rev RevisionID) ([]byte, error)

	// RestoreSnapshot restores live node state to rev. Implementations may append
	// a restore snapshot when createRestoreSnapshot is true.
	RestoreSnapshot(ctx context.Context, id NodeId, rev RevisionID, createRestoreSnapshot bool) error
}

RepositorySnapshots provides revision-based history operations.

type RevisionID

type RevisionID int64

type SiteConfig added in v0.11.0

type SiteConfig struct {
	// Output is the default output directory.
	Output string `yaml:"output,omitempty" json:"output,omitempty"`
	// Title is the site title.
	Title string `yaml:"title,omitempty" json:"title,omitempty"`
	// BaseURL is the base URL prefix for absolute links.
	BaseURL string `yaml:"baseUrl,omitempty" json:"baseUrl,omitempty"`
	// Search enables or disables Pagefind search indexing.
	Search *bool `yaml:"search,omitempty" json:"search,omitempty"`
}

SiteConfig holds static site generation defaults stored in the keg config.

type Snapshot

type Snapshot struct {
	ID        RevisionID
	Node      NodeId
	Parent    RevisionID // 0 for root
	CreatedAt time.Time
	Message   string

	// Integrity + retrieval hints
	ContentHash  string
	MetaHash     string
	StatsHash    string
	IsCheckpoint bool // full content stored instead of patch
}

type SnapshotContentKind

type SnapshotContentKind string

SnapshotContentKind describes how snapshot content bytes are stored.

const (
	// SnapshotContentKindPatch stores content as a diff from a base revision.
	SnapshotContentKindPatch SnapshotContentKind = "patch"
	// SnapshotContentKindFull stores full reconstructed content bytes.
	SnapshotContentKindFull SnapshotContentKind = "full"
)

type SnapshotContentWrite

type SnapshotContentWrite struct {
	Kind SnapshotContentKind
	Base RevisionID

	// Algorithm identifies the patch format, for example "xdiff-v1".
	Algorithm string
	Data      []byte

	// Hash is the digest of fully materialized content at this revision.
	Hash string
}

SnapshotContentWrite describes content payload for a new snapshot revision.

type SnapshotReadOptions

type SnapshotReadOptions struct {
	// ResolveContent reconstructs full content bytes for the selected revision.
	ResolveContent bool
}

SnapshotReadOptions configures how snapshots are loaded.

type SnapshotWrite

type SnapshotWrite struct {
	ExpectedParent RevisionID
	Message        string
	// CreatedAt preserves an externally supplied revision timestamp. When zero,
	// repositories stamp the snapshot with the current runtime clock time.
	CreatedAt time.Time

	Meta  []byte
	Stats *NodeStats

	Content SnapshotContentWrite
}

SnapshotWrite describes append parameters for a new node snapshot.

type TagExpr added in v0.2.0

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

TagExpr is an opaque compiled tag boolean expression. Callers obtain one via ParseTagExpression and pass it to EvaluateTagExpression. The underlying AST is unexported; external packages cannot inspect or implement it.

func ParseTagExpression added in v0.2.0

func ParseTagExpression(raw string) (TagExpr, error)

ParseTagExpression compiles raw into a TagExpr that can be evaluated with EvaluateTagExpression. Returns an error if raw is empty or syntactically invalid.

type TagFilteredIndex deprecated added in v0.2.0

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

TagFilteredIndex is an in-memory index of nodes that match a boolean tag expression. It is used to build custom dex/NAME.md index artifacts driven by keg config Indexes entries with a non-empty Tags field.

Deprecated: Use QueryFilteredIndex instead, which supports the full query expression system including key=value attribute predicates.

Concurrency note: TagFilteredIndex does not perform internal synchronization. Callers should guard access with a mutex when needed.

func NewTagFilteredIndex deprecated added in v0.2.0

func NewTagFilteredIndex(name, tagQuery string) (*TagFilteredIndex, error)

NewTagFilteredIndex creates a TagFilteredIndex for the given index file name and boolean tag query string. Returns an error if tagQuery fails to parse.

Deprecated: Use NewQueryFilteredIndex instead.

name should be the short filename (without the "dex/" prefix) used when writing to the repository, e.g. "golang.md".

func (*TagFilteredIndex) Add added in v0.2.0

func (idx *TagFilteredIndex) Add(ctx context.Context, data *NodeData) error

Add evaluates the tag expression against the node and, if it matches, inserts or updates the node entry maintaining reverse-chronological order. A node matches when EvaluateTagExpression returns a non-empty set.

func (*TagFilteredIndex) Clear added in v0.2.0

func (idx *TagFilteredIndex) Clear(ctx context.Context) error

Clear resets the index to an empty state.

func (*TagFilteredIndex) Data added in v0.2.0

func (idx *TagFilteredIndex) Data(ctx context.Context) ([]byte, error)

Data serializes the TagFilteredIndex to the same markdown format as ChangesIndex.Data. Entries are in reverse-chronological order.

func (*TagFilteredIndex) Name added in v0.2.0

func (idx *TagFilteredIndex) Name() string

Name returns the short index filename used with repo.WriteIndex.

func (*TagFilteredIndex) Remove added in v0.2.0

func (idx *TagFilteredIndex) Remove(ctx context.Context, node NodeId) error

Remove removes the node identified by node from the index. If the node is not present the call is a no-op.

type TagIndex

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

TagIndex is an in-memory index mapping a normalized tag string to the list of nodes that declare that tag.

The index format (used by ParseTagIndex and Data) is line-oriented. Each line represents a tag and its node list in the form:

<tag>\t<node1> <node2> ...\n

Where <nodeN> is the node.Path() string representation (for example "42" or "42-0001"). Parsers should tolerate empty input and skip empty lines. When serializing, the implementation should produce stable output by sorting tag keys and de-duplicating and sorting node lists.

Note: TagIndex does not perform internal synchronization. Callers that need concurrent access should guard the index with a mutex.

func ParseTagIndex

func ParseTagIndex(ctx context.Context, data []byte) (TagIndex, error)

ParseTagIndex parses the serialized tag index bytes into a TagIndex.

Expected input is zero or more lines separated by newline. Each non-empty line must contain a tag, a tab, and a space-separated list of node ids. Invalid or malformed lines should be handled gracefully by ignoring the offending line and continuing parsing. An empty input yields an empty TagIndex and no error.

func (*TagIndex) Add

func (idx *TagIndex) Add(ctx context.Context, data *NodeData) error

Add incorporates the node into the index for each tag present on the node.

Behavior notes: - If idx is nil this is a no-op. - The method should ensure idx.data is initialized when first used. - Duplicate entries for a given tag should be avoided (idempotent add). - The node should be added using node.Path() as the identifier.

func (*TagIndex) Data

func (idx *TagIndex) Data(ctx context.Context) ([]byte, error)

Data serializes the TagIndex to the canonical byte representation described for ParseTagIndex.

Serialization requirements:

  • Tags (map keys) must be emitted in a stable, deterministic order. When a tag token can be parsed as a NodeId id it may be ordered numerically; otherwise fall back to lexicographic ordering.
  • NodeId lists for each tag must be de-duplicated and sorted by numeric id then by code (the same ordering ParseNode/NodeId.Compare implies).
  • Lines must use a single tab between tag and the node list, and a single space between node ids. Each line must be terminated with a newline.
  • If the index is empty return an empty byte slice and no error.

func (*TagIndex) Rm

func (idx *TagIndex) Rm(ctx context.Context, node NodeId) error

Rm removes the node from all tag lists in the index.

Behavior notes:

  • If idx is nil this is a no-op.
  • If a tag has no remaining nodes after removal it should be removed from the map to avoid emitting empty tag lines when serialized.

type TransientError

type TransientError struct {
	Cause error
}

TransientError marks a transient (retryable) failure, e.g. network timeout, DB deadlock. It implements both Temporary() and Retryable().

func (*TransientError) Error

func (e *TransientError) Error() string

func (*TransientError) Retryable

func (e *TransientError) Retryable() bool

func (*TransientError) Temporary

func (e *TransientError) Temporary() bool

func (*TransientError) Unwrap

func (e *TransientError) Unwrap() error

Jump to

Keyboard shortcuts

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