Packages

Production-grade Elixir library for the Open Knowledge Format (OKF). Standards-compliant parsing, validation, lossless I/O, graph indexing, querying, and provenance-aware AI context assembly.

Current section

Files

Jump to
ex_okf lib ex_okf bundle.ex
Raw

lib/ex_okf/bundle.ex

defmodule ExOKF.Bundle do
@moduledoc """
An in-memory OKF knowledge bundle — the unit of distribution (OKF §3).
Holds concepts, reserved index/log documents, parse diagnostics, and an
optional graph index built from internal links.
## Fields
| Field | Type | Description |
|-------|------|-------------|
| `root` | `String.t()` | Absolute filesystem path to the bundle directory. |
| `okf_version` | `String.t() \\| nil` | Declared version from root `index.md` frontmatter, e.g. `"0.1"`. |
| `concepts` | `%{optional(String.t()) => ExOKF.Concept.t()}` | Map of concept id → concept. |
| `indexes` | `%{optional(String.t()) => ExOKF.Index.t()}` | Map of relative path → index doc. |
| `logs` | `%{optional(String.t()) => ExOKF.Log.t()}` | Map of relative path → log doc. |
| `diagnostics` | `ExOKF.Diagnostics.t()` | Parse/load diagnostics accumulated while reading. |
| `graph` | `map() \\| nil` | Opaque graph handle `%{backend: module(), data: term()}` after indexing. |
## Example
%ExOKF.Bundle{
root: "/data/acme-knowledge",
okf_version: "0.1",
concepts: %{"tables/orders" => %ExOKF.Concept{id: "tables/orders", path: "tables/orders.md", type: "BigQuery Table"}},
indexes: %{"index.md" => %ExOKF.Index{path: "index.md", directory: ""}},
logs: %{"log.md" => %ExOKF.Log{path: "log.md", directory: ""}},
diagnostics: %ExOKF.Diagnostics{entries: []},
graph: %{backend: ExOKF.Graph.Adjacency, data: %ExOKF.Graph.Adjacency{}}
}
"""
alias ExOKF.{Concept, Diagnostics, Index, Log}
@typedoc """
An loaded OKF bundle.
See the module documentation for field meanings and a structural example.
"""
@type t :: %__MODULE__{
root: String.t(),
okf_version: String.t() | nil,
concepts: %{optional(String.t()) => Concept.t()},
indexes: %{optional(String.t()) => Index.t()},
logs: %{optional(String.t()) => Log.t()},
diagnostics: Diagnostics.t(),
graph: map() | nil
}
@enforce_keys [:root]
defstruct root: nil,
okf_version: nil,
concepts: %{},
indexes: %{},
logs: %{},
diagnostics: %Diagnostics{},
graph: nil
@doc """
Returns the concept with the given id, or `nil` if absent.
## Parameters
* `bundle` (`t:t/0`) — loaded bundle
* `id` (`String.t()`) — concept id, e.g. `"tables/orders"`
## Examples
iex> bundle = ExOKF.TestSupport.minimal_bundle()
iex> ExOKF.Bundle.get_concept(bundle, "hello").type
"Reference"
iex> bundle = ExOKF.TestSupport.minimal_bundle()
iex> ExOKF.Bundle.get_concept(bundle, "missing")
nil
"""
@spec get_concept(t(), String.t()) :: Concept.t() | nil
def get_concept(%__MODULE__{concepts: concepts}, id), do: Map.get(concepts, id)
@doc """
Lists all concepts sorted by id.
## Parameters
* `bundle` (`t:t/0`) — loaded bundle
## Examples
iex> bundle = ExOKF.TestSupport.sample_bundle()
iex> ExOKF.Bundle.concepts(bundle) |> Enum.map(& &1.id)
["datasets/sales", "tables/customers", "tables/orders"]
"""
@spec concepts(t()) :: [Concept.t()]
def concepts(%__MODULE__{concepts: concepts}) do
concepts |> Map.values() |> Enum.sort_by(& &1.id)
end
@doc """
Returns `true` when the concept id exists in the bundle.
## Parameters
* `bundle` (`t:t/0`) — loaded bundle
* `id` (`String.t()`) — concept id
## Examples
iex> bundle = ExOKF.TestSupport.minimal_bundle()
iex> ExOKF.Bundle.has_concept?(bundle, "hello")
true
"""
@spec has_concept?(t(), String.t()) :: boolean()
def has_concept?(%__MODULE__{concepts: concepts}, id), do: Map.has_key?(concepts, id)
@doc """
Returns all concept ids present in the bundle, sorted.
## Parameters
* `bundle` (`t:t/0`) — loaded bundle
## Examples
iex> bundle = ExOKF.TestSupport.minimal_bundle()
iex> ExOKF.Bundle.concept_ids(bundle)
["hello"]
"""
@spec concept_ids(t()) :: [String.t()]
def concept_ids(%__MODULE__{concepts: concepts}), do: Map.keys(concepts) |> Enum.sort()
@doc """
Returns the number of concepts in the bundle.
## Parameters
* `bundle` (`t:t/0`) — loaded bundle
## Examples
iex> bundle = ExOKF.TestSupport.sample_bundle()
iex> ExOKF.Bundle.size(bundle)
3
"""
@spec size(t()) :: non_neg_integer()
def size(%__MODULE__{concepts: concepts}), do: map_size(concepts)
@doc """
Attaches or replaces the graph index on the bundle.
Prefer `ExOKF.Graph.index/2`, which builds the graph and calls this.
## Parameters
* `bundle` (`t:t/0`) — loaded bundle
* `graph` (`term()`) — opaque graph handle, typically `%{backend: module(), data: term()}`
## Examples
iex> bundle = ExOKF.TestSupport.minimal_bundle()
iex> indexed = ExOKF.Bundle.put_graph(bundle, %{backend: ExOKF.Graph.Adjacency, data: %{}})
iex> match?(%{backend: _, data: _}, indexed.graph)
true
"""
@spec put_graph(t(), term()) :: t()
def put_graph(%__MODULE__{} = bundle, graph), do: %{bundle | graph: graph}
@doc """
Merges additional diagnostics into the bundle.
## Parameters
* `bundle` (`t:t/0`) — loaded bundle
* `diags` (`ExOKF.Diagnostics.t()`) — diagnostics to append
## Examples
iex> bundle = ExOKF.TestSupport.minimal_bundle()
iex> extra = ExOKF.Diagnostics.new([
...> %ExOKF.Diagnostic{severity: :info, code: :note, message: "hello"}
...> ])
iex> updated = ExOKF.Bundle.put_diagnostics(bundle, extra)
iex> Enum.any?(updated.diagnostics.entries, &(&1.code == :note))
true
"""
@spec put_diagnostics(t(), Diagnostics.t()) :: t()
def put_diagnostics(%__MODULE__{} = bundle, %Diagnostics{} = diags) do
%{bundle | diagnostics: Diagnostics.concat(bundle.diagnostics, diags)}
end
end