Documentation
¶
Overview ¶
Package library is the core of Filiation. Every user-visible operation — add, expand, search, ask, path — is a function here.
Everything else is arranged around this package:
- The front doors (cmd/, internal/mcpsrv, internal/web) are translation layers. Each parses a request, calls one function here, and formats the result. If a front door imports internal/store, that is a bug.
- This package orchestrates: it calls store, sources, graph, retrieve and jobs. Those packages do not call back into it.
- It is the composition root: the one place that builds a store and a source side by side. It never moves data between them — that is internal/graph's job — so each stays swappable on its own.
Nothing a front door needs from here is a store type. Results come back as model types or types declared in this package, and errors as errs sentinels, so cmd/ can branch on every outcome without importing store.
The practical test: adding a command to the CLI, an MCP tool and a web endpoint for the same feature should mean one function here and three thin wrappers, not three implementations.
Index ¶
- Constants
- func ClearCache(dir string) (string, error)
- func RetryAfter(err error) (time.Duration, bool)
- func Validate(input string) error
- type AddOptions
- type Added
- type AmbiguousError
- type Candidate
- type DuplicateGroup
- type ExpandOptions
- type ExportOptions
- type Exported
- type Library
- func (l *Library) Add(ctx context.Context, input string, opts AddOptions) (Added, error)
- func (l *Library) AddCandidate(ctx context.Context, c Candidate) (Added, error)
- func (l *Library) Budget() (maxNodes, maxDepth int)
- func (l *Library) Close() error
- func (l *Library) Expand(ctx context.Context, opts ExpandOptions) (model.ExpansionResult, error)
- func (l *Library) ExportGraphML(ctx context.Context, w io.Writer, opts ExportOptions) (Exported, error)
- func (l *Library) Find(ctx context.Context, input string) (model.Work, error)
- func (l *Library) Neighbours(ctx context.Context, input string) (Neighbourhood, error)
- func (l *Library) Path() string
- func (l *Library) PathBetween(ctx context.Context, from, to string, opts PathOptions) (Path, error)
- func (l *Library) ProbableDuplicates(ctx context.Context) ([]DuplicateGroup, error)
- func (l *Library) Quota() (Quota, bool)
- func (l *Library) Stats(ctx context.Context) (Stats, error)
- func (l *Library) Summarise(ctx context.Context) (Summary, error)
- type Neighbourhood
- type Options
- type Path
- type PathOptions
- type Quota
- type Stats
- type Summary
Constants ¶
const DefaultMaxHops = 6
DefaultMaxHops is how far Path searches by default. Six citation steps reaches across almost any library; a chain longer than that says little about how two papers are related.
Variables ¶
This section is empty.
Functions ¶
func ClearCache ¶
ClearCache deletes every cached HTTP response under dir, or under config.DefaultCacheDir if dir is empty, and returns the directory it cleared. It is `fil cache clear`, and does not need the library open.
func RetryAfter ¶
RetryAfter returns how long OpenAlex asked us to wait, when err is a transient failure that carried the request. A front door uses it to say "try again in an hour — today's allowance is spent" rather than just "failed".
Types ¶
type AddOptions ¶
type AddOptions struct {
// AcceptFirst takes the best title-search candidate without asking. It is
// --accept-first, for scripts, and it is the only way a title seeds a
// graph unattended (ADR-005).
AcceptFirst bool
// SearchLimit is how many title candidates to offer. Zero means the
// source's default.
SearchLimit int
}
AddOptions controls Add.
type Added ¶
type Added struct {
// Work is the seed as stored.
Work model.Work
// AlreadySeed reports the work was a seed before this call, so nothing
// about it changed. The CLI says "already in your library" rather than
// printing zeros that look like a failure.
AlreadySeed bool
// Refs is how many works the seed cites. NewStubs of them were not yet in
// the library, and NewEdges citation edges were recorded.
Refs int
NewStubs int
NewEdges int
// DeadEnd reports the seed cites nothing OpenAlex could resolve, so the
// graph cannot grow from it. In arts and humanities that is most works
// (D12). It is reported, not raised: it is the data, not the tool.
DeadEnd bool
}
Added reports what Add did.
type AmbiguousError ¶
AmbiguousError is returned by Add when a title matched candidates and no choice was made. It matches errs.ErrAmbiguous.
func (*AmbiguousError) Error ¶
func (e *AmbiguousError) Error() string
func (*AmbiguousError) Is ¶
func (e *AmbiguousError) Is(target error) bool
Is makes an *AmbiguousError match errs.ErrAmbiguous.
type Candidate ¶
type Candidate struct {
Work model.Work
// TitleMatch flags a candidate whose title is, allowing for case,
// punctuation and a typo, the one typed. Such candidates come first. It is
// a hint for the person choosing, never a choice (ADR-005).
TitleMatch bool
// contains filtered or unexported fields
}
Candidate is one title-search result offered to the user.
type DuplicateGroup ¶
type DuplicateGroup = graph.DuplicateGroup
DuplicateGroup is a set of fetched works that share a title: probably one paper OpenAlex holds as more than one record.
type ExpandOptions ¶
type ExpandOptions struct {
MaxNodes int
MaxDepth int
// Progress, if set, is called after each batch is committed.
Progress func(model.ExpansionResult)
}
ExpandOptions bound one expansion. Zero fields take the configured value — max_nodes, max_depth and max_refs_per_work from the config file, or their defaults — so a front door passes only what the user overrode.
type ExportOptions ¶
type ExportOptions struct {
// IncludeStubs exports every work, including the many known only by ID.
// Without it the export holds fetched works and the citations between
// them: after one expansion that is ~500 readable nodes rather than ~500
// buried among ~7,000 that have no title, year or type.
IncludeStubs bool
}
ExportOptions control an export.
type Library ¶
type Library struct {
// contains filtered or unexported fields
}
Library is an open library: the store, and the sources that grow it.
This is the composition root. It is the one place that builds both a store and a source — but it never moves data between them. That is internal/graph's job, so the two stay swappable independently.
func (*Library) Add ¶
Add adds a paper to the library as a seed: the work, a stub for everything it cites, and the edges between them.
input is whatever the user typed. A DOI, arXiv ID, PMID or OpenAlex ID resolves directly. A title is searched, and unless opts.AcceptFirst is set the candidates come back in an *AmbiguousError and nothing is written — the user chooses and the front door calls AddCandidate. Errors to expect, all compared with errors.Is:
errs.ErrInvalidInput the input is not something that can be looked up errs.ErrUnresolved OpenAlex has no such work (or no title candidates) errs.ErrAmbiguous a title matched candidates; see *AmbiguousError errs.ErrTransient the network failed; trying again later may work
func (*Library) AddCandidate ¶
AddCandidate adds the title-search candidate the user chose. The candidate arrived fully fetched, so this makes no request.
func (*Library) Budget ¶
Budget is the configured expansion budget: the default a front door shows when the user has not overridden it.
func (*Library) Expand ¶
func (l *Library) Expand(ctx context.Context, opts ExpandOptions) (model.ExpansionResult, error)
Expand grows the graph outward from everything already in the library, best first, until the budget is spent, the frontier is empty, the depth limit is reached, or ctx is cancelled. Each of those is a normal outcome, reported in the result's StoppedBecause with a nil error. Run it again to continue: the frontier is persisted state, so a stopped run resumes where it left off.
An error means something failed — most often the network, wrapping errs.ErrTransient — and the result still reports what was saved before it.
func (*Library) ExportGraphML ¶
func (l *Library) ExportGraphML(ctx context.Context, w io.Writer, opts ExportOptions) (Exported, error)
ExportGraphML writes the library as GraphML to w, for Gephi, Cytoscape, NetworkX and the rest. It streams, so a library of any size exports in constant memory. An arrow means "cites".
func (*Library) Find ¶
Find resolves what the user typed to a work already in the library. It never asks OpenAlex: a paper that has not been added has no neighbours and no paths, and saying so is more useful than fetching it behind the user's back.
A DOI, arXiv ID, PMID or OpenAlex ID is looked up directly. A title is matched against the titles in the library, allowing for case, punctuation and a typo; one match is used, several come back in an *AmbiguousError. For a read-only question that is safe in a way it is not for Add: nothing is written, and the answer names the work it used.
A work that is not there wraps errs.ErrNotFound.
func (*Library) Neighbours ¶
Neighbours returns what a work in the library cites and what cites it.
func (*Library) PathBetween ¶
PathBetween finds the shortest chain of citations between two works in the library.
By default it looks for lineage — the project's name for it (D6): a chain of references from one work to the other, so that one descends from the other. It tries from the first to the second, then the reverse, and always reports the chain in the order asked. With AnyDirection it finds any connection.
func (*Library) ProbableDuplicates ¶
func (l *Library) ProbableDuplicates(ctx context.Context) ([]DuplicateGroup, error)
ProbableDuplicates lists fetched works that share a title. It reports and never merges: a matching title is evidence, not proof — a book review carries the title of the book it reviews (D14).
func (*Library) Quota ¶
Quota returns the last OpenAlex allowance seen, and false before any request has reported one — including when every answer came from the cache.
type Neighbourhood ¶
type Neighbourhood struct {
Work model.Work
// Cites is the work's references, fetched ones first.
Cites []model.Work
// CitedBy is the works in this library that cite it — never a global
// count; see Work.CitedByCount for that.
CitedBy []model.Work
}
Neighbourhood is a work and the works on either side of it.
type Options ¶
type Options struct {
// CacheDir is where HTTP responses are cached. Empty means
// config.DefaultCacheDir.
CacheDir string
// NoCache turns the cache off: --no-cache, which ADR-004 requires from day
// one because a stale cache can hide a bug.
NoCache bool
// Transport replaces the network. Tests replay recorded responses through
// it; front doors leave it nil.
Transport http.RoundTripper
}
Options are the choices a front door makes that are not configuration.
type Path ¶
type Path struct {
// Found reports whether a chain exists within the step limit. Not finding
// one is an answer, not an error.
Found bool
// Works runs from the first work asked about to the second.
Works []model.Work
// Cites[i] reports that Works[i] cites Works[i+1]; false means it is the
// other way round.
Cites []bool
}
Path is a chain of citations between two works.
type PathOptions ¶
type PathOptions struct {
// MaxHops bounds the search. Zero means DefaultMaxHops.
MaxHops int
// AnyDirection connects the two works through citations in either
// direction. Without it, Path looks for lineage: one work reaching the
// other by following references.
AnyDirection bool
}
PathOptions control Path.
type Quota ¶
Quota is the OpenAlex allowance as last reported: requests left today and how long until it resets.
type Stats ¶
type Stats struct {
Works int // every work, stubs included
Stubs int // works known only by ID so far
Edges int // citations
}
Stats counts what the library holds.
type Summary ¶
type Summary struct {
Works int // every work, stubs included
Fetched int // metadata fetched
Stubs int // known only by ID so far
Unresolved int // of the stubs, those OpenAlex has no record of
Seeds int // papers the user added
Citations int
// DeadEnds is how many fetched works have no reference list, so the graph
// cannot grow through them (D12).
DeadEnds int
}
Summary is the library at a glance.