library

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 25, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

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

View Source
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

func ClearCache(dir string) (string, error)

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

func RetryAfter(err error) (time.Duration, bool)

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".

func Validate

func Validate(input string) error

Validate reports whether input is something Add could look up, without touching the network or the library. A front door calls it first, so that a typo is refused before anything is created — including, on a first run, the library itself.

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

type AmbiguousError struct {
	Title      string
	Candidates []Candidate
}

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 Exported

type Exported struct {
	Nodes, Edges int
}

Exported reports what an export wrote.

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 Open

func Open(ctx context.Context, cfg *config.Config, opts Options) (*Library, error)

Open opens the library cfg names, creating and migrating it on first use.

func (*Library) Add

func (l *Library) Add(ctx context.Context, input string, opts AddOptions) (Added, error)

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

func (l *Library) AddCandidate(ctx context.Context, c Candidate) (Added, error)

AddCandidate adds the title-search candidate the user chose. The candidate arrived fully fetched, so this makes no request.

func (*Library) Budget

func (l *Library) Budget() (maxNodes, maxDepth int)

Budget is the configured expansion budget: the default a front door shows when the user has not overridden it.

func (*Library) Close

func (l *Library) Close() error

Close closes the library.

func (*Library) Expand

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

func (l *Library) Find(ctx context.Context, input string) (model.Work, error)

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

func (l *Library) Neighbours(ctx context.Context, input string) (Neighbourhood, error)

Neighbours returns what a work in the library cites and what cites it.

func (*Library) Path

func (l *Library) Path() string

Path is where the library lives on disk.

func (*Library) PathBetween

func (l *Library) PathBetween(ctx context.Context, from, to string, opts PathOptions) (Path, error)

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

func (l *Library) Quota() (Quota, bool)

Quota returns the last OpenAlex allowance seen, and false before any request has reported one — including when every answer came from the cache.

func (*Library) Stats

func (l *Library) Stats(ctx context.Context) (Stats, error)

Stats reads the library's counts.

func (*Library) Summarise

func (l *Library) Summarise(ctx context.Context) (Summary, error)

Summarise counts the library.

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

type Quota = httpx.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.

Jump to

Keyboard shortcuts

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