flake-explorer docs

Data schema

The JSON contract between the extraction CLI and the SPA lives in a single file, crates/extract/src/schema.rs. There are exactly two document kinds:

Document File Cost Lifecycle
Manifest manifest.json Cheap Always regenerated on every run (see Extraction pipeline)
ConfigData config/<kind>.<name>.json Expensive (full options eval) Extracted on demand, cached by extractor fingerprint + flake identity + lock hash + nix version

storePath is the universal join key: FileEntry.storePath matches the file strings in each option's declarations and definitions.

Versioning

Constant Value Gates
SCHEMA_VERSION 1 Stamped into both documents; the SPA rejects a ConfigData blob whose version mismatches (per its JSDoc in crates/extract/src/schema.rs)

There is no manually-bumped extractor version: the cache key's code half is a content hash of the extraction sources plus the crate's resolved dependency closure (crates/extract/build.rs), so any change to the flake-explorer-extract crate — crates/extract/src/schema.rs included — or to a dependency it actually links makes every cached blob stale at once. Changes to the CLI, server and exporter in the root crate deliberately do not, which is what the crate split is for (see The extraction crate boundary). reconcile in crates/extract/src/cache.rs additionally requires the sidecar's flake identity (narHash, or the self store path when absent), resolved-input lockHash, and nixVersion to match — see Extraction pipeline.

Type overview

classDiagram
  class Manifest {
    version
    extractor
    outputs OutputNode
    outputNames
    warnings
  }
  class FlakeInfo {
    ref
    path
    rev
    narHash
  }
  class InputInfo {
    name
    nodeKey
    transitive
    storePath
    follows
  }
  class FileEntry {
    id
    relPath
    origin FileOrigin
    storePath
    git
  }
  class ImportEdge {
    from
    to
  }
  class InputRef {
    file
    input
  }
  class InputFollow {
    name
    target
  }
  class ConfigRef {
    id
    kind
    dataFile
    status
  }
  class GraftInfo {
    output
    input
    added
    inherited
  }
  class ConfigData {
    version
    id
    fileIndex
  }
  class OptionEntry {
    loc
    type
    isDefined
    highestPrio
    customized
    value
  }
  class DeclarationRef {
    file
    line
    column
  }
  class DefinitionRef {
    file
    value
    valueError
    valueSkipped
    prio
  }
  class FileOptionRefs {
    defines
    declares
  }
  Manifest --> FlakeInfo : flake
  Manifest --> "many" InputInfo : inputs
  Manifest --> "many" FileEntry : files
  Manifest --> "many" ImportEdge : importEdges
  Manifest --> "many" InputRef : inputRefs
  Manifest --> "many" InputFollow : inputFollows
  Manifest --> "many" ConfigRef : configurations
  Manifest --> "many" GraftInfo : grafts
  ConfigData --> "many" OptionEntry : options
  ConfigData --> "many" FileOptionRefs : fileIndex
  OptionEntry --> "many" DeclarationRef : declarations
  OptionEntry --> "many" DefinitionRef : definitions

OutputNode is a recursive union (attrset | leaf | omitted | unknown) normalized from nix flake show --json; FileOrigin is self | input (optionally patched) | unknown.

Join keys and the file id codec

Definition priorities

PRIO records the well-known lib.mkOverride values (lower wins):

Name Value Meaning
mkForce 50 lib.mkForce
plain 100 An ordinary definition
mkDefault 1000 lib.mkDefault
optionDefault 1500 The option's own declared default (lib.mkOptionDefault)

An option is customized when isDefined && highestPrio !== null && highestPrio < PRIO.optionDefault (see to_entry in crates/extract/src/options.rs) — a real definition beat the declared default. fileIndex.defines only counts customized definitions, because every defaulted option carries a mkOptionDefault definition pointing at its declaring module, which would otherwise make nixpkgs "define" everything.

Besides the option-level highestPrio, a DefinitionRef may carry its own prio, lifted by to_entry from a {mkOverride, content} envelope (scrub's rendering of a lib.mkOverride wrapper) in the raw definition value. On the real module system this rarely fires: lib.filterOverrides strips the wrapper and drops losing-priority definitions entirely before definitionsWithLocations is exposed, so every listed definition merged at the option's highestPrio and absent prio means exactly that. The lift matters on module systems that expose raw definition values (hand-rolled fixtures, older lib versions). Skipped definitions (package-typed options, degraded chunks) carry valueSkipped instead of a value.

Declaration positions and skip flags

Sentinels and grafts

Reference

The full generated API reference is at https://kris.net/flake-explorer/docs/api/ (CI-generated, site only). The source of truth is crates/extract/src/schema.rs.