Jesús Pérez 0396e4037b
Some checks failed
Nickel Type Check / Nickel Type Checking (push) Has been cancelled
Rust CI / Security Audit (push) Has been cancelled
Rust CI / Check + Test + Lint (nightly) (push) Has been cancelled
Rust CI / Check + Test + Lint (stable) (push) Has been cancelled
chore: add ontology and reflection
2026-03-13 00:21:04 +00:00

307 lines
11 KiB
Plaintext

#!/usr/bin/env nu
# store.nu — Nushell client for ontoref-daemon HTTP API.
#
# Provides cached nickel export via daemon (with subprocess fallback),
# plus query/sync/nodes/dimensions when daemon has DB enabled.
#
# All HTTP calls use ^curl (external command) because Nushell's internal
# http get/post cannot be captured with `| complete` on connection errors.
#
# Usage:
# use ../modules/store.nu *
#
# daemon-export ".ontology/core.ncl" # cached export
# daemon-export ".ontology/core.ncl" --import-path $ip # with NICKEL_IMPORT_PATH
# store nodes --level Axiom # query ontology nodes
# store dimensions # query dimensions
# daemon-health # check daemon status
# ── Utilities (self-contained to avoid circular imports with shared.nu) ─────────
def project-root []: nothing -> string {
let pr = ($env.ONTOREF_PROJECT_ROOT? | default "")
if ($pr | is-not-empty) and ($pr != $env.ONTOREF_ROOT) { $pr } else { $env.ONTOREF_ROOT }
}
def nickel-import-path [root: string]: nothing -> string {
let entries = [
$"($root)/.ontology"
$"($root)/adrs"
$"($root)/.ontoref/ontology/schemas"
$"($root)/.ontoref/adrs"
$"($root)/.onref"
$root
$"($env.ONTOREF_ROOT)/ontology"
$"($env.ONTOREF_ROOT)/ontology/schemas"
$"($env.ONTOREF_ROOT)/adrs"
$env.ONTOREF_ROOT
]
let valid = ($entries | where { |p| $p | path exists } | uniq)
let existing = ($env.NICKEL_IMPORT_PATH? | default "")
if ($existing | is-not-empty) {
($valid | append $existing) | str join ":"
} else {
$valid | str join ":"
}
}
# ── Configuration ────────────────────────────────────────────────────────────────
def daemon-url []: nothing -> string {
$env.ONTOREF_DAEMON_URL? | default "http://127.0.0.1:7891"
}
# Load project config to check if DB is enabled
def project-config-db-status []: nothing -> record<enabled: bool, url: string, namespace: string> {
let root = (project-root)
let config_path = $"($root)/.ontoref/config.ncl"
if not ($config_path | path exists) {
return { enabled: false, url: "", namespace: "" }
}
# Export config and extract db section
let result = (do { ^nickel export $config_path } | complete)
if $result.exit_code != 0 {
return { enabled: false, url: "", namespace: "" }
}
# Parse JSON safely
let parse_result = (do { $result.stdout | from json } | complete)
if $parse_result.exit_code != 0 {
return { enabled: false, url: "", namespace: "" }
}
let config = $parse_result.stdout
let db = ($config.db? | default {})
{
enabled: ($db.enabled? | default false),
url: ($db.url? | default ""),
namespace: ($db.namespace? | default "ontoref"),
}
}
# ── HTTP helpers (external ^curl) ────────────────────────────────────────────────
def http-get [url: string]: nothing -> record {
do { ^curl -sf $url } | complete
}
def http-post-json [url: string, body: string]: nothing -> record {
do { ^curl -sf -X POST -H "Content-Type: application/json" -d $body $url } | complete
}
# ── Availability check ───────────────────────────────────────────────────────────
# Check if daemon is reachable. Caches "true" in env var for the session.
# A "false" result is NOT cached — allows recovery when daemon starts mid-session.
export def --env daemon-available []: nothing -> bool {
let cached = ($env.ONTOREF_DAEMON_AVAILABLE? | default "")
if $cached == "true" { return true }
let url = $"(daemon-url)/health"
let result = (http-get $url)
if $result.exit_code == 0 {
$env.ONTOREF_DAEMON_AVAILABLE = "true"
true
} else {
false
}
}
# ── Health ───────────────────────────────────────────────────────────────────────
# Show daemon health status with optional DB info from config.
# Returns record with { status, uptime_secs, cache_*, db_enabled?, db_config? }
# or null if daemon unreachable or response is not valid JSON.
export def daemon-health []: nothing -> any {
let url = $"(daemon-url)/health"
let result = (http-get $url)
if $result.exit_code != 0 {
null
} else {
let body = ($result.stdout | str trim)
if not ($body | str starts-with "{") {
null
} else {
let parse_result = (do { $body | from json } | complete)
if $parse_result.exit_code != 0 {
null
} else {
let health = $parse_result.stdout
let db_config = (project-config-db-status)
$health | insert db_config $db_config
}
}
}
}
# ── NCL Export (core function) ───────────────────────────────────────────────────
# Export a Nickel file to JSON via daemon (cached) with subprocess fallback.
#
# When daemon is available: POST /nickel/export → cached result.
# When daemon is unreachable: falls back to ^nickel export subprocess.
# System works identically either way — just slower without daemon.
export def --env daemon-export [
file: string,
--import-path: string = "",
]: nothing -> any {
let ip = if ($import_path | is-not-empty) {
$import_path
} else {
nickel-import-path (project-root)
}
if (daemon-available) {
let result = (daemon-export-http $file $ip)
if $result != null { return $result }
# HTTP call failed despite health check — clear cache to re-probe next call
$env.ONTOREF_DAEMON_AVAILABLE = ""
}
daemon-export-subprocess $file $ip
}
# Safe version: returns null on failure instead of throwing.
# Use for call sites that handle missing data gracefully (return [] or {}).
export def --env daemon-export-safe [
file: string,
--import-path: string = "",
]: nothing -> any {
if not ($file | path exists) { return null }
let ip = if ($import_path | is-not-empty) {
$import_path
} else {
nickel-import-path (project-root)
}
if (daemon-available) {
let result = (daemon-export-http $file $ip)
if $result != null { return $result }
$env.ONTOREF_DAEMON_AVAILABLE = ""
}
let result = do { with-env { NICKEL_IMPORT_PATH: $ip } { ^nickel export $file } } | complete
if $result.exit_code != 0 { return null }
$result.stdout | from json
}
# ── Daemon HTTP calls ────────────────────────────────────────────────────────────
def daemon-export-http [file: string, import_path: string]: nothing -> any {
let url = $"(daemon-url)/nickel/export"
let body = if ($import_path | is-not-empty) {
{ path: $file, import_path: $import_path } | to json
} else {
{ path: $file } | to json
}
let result = (http-post-json $url $body)
if $result.exit_code != 0 { return null }
let response = ($result.stdout | from json)
$response.data? | default null
}
# ── Subprocess fallback ──────────────────────────────────────────────────────────
def daemon-export-subprocess [file: string, import_path: string]: nothing -> any {
let ip = if ($import_path | is-not-empty) {
$import_path
} else {
let root = (project-root)
nickel-import-path $root
}
let result = do { with-env { NICKEL_IMPORT_PATH: $ip } { ^nickel export $file } } | complete
if $result.exit_code != 0 {
error make { msg: $"nickel export failed for ($file): ($result.stderr)" }
}
$result.stdout | from json
}
# ── Store query commands ─────────────────────────────────────────────────────────
# Query ontology nodes. Returns empty list on failure.
export def --env "store nodes" [
--level: string = "",
]: nothing -> list {
let root = (project-root)
let core_file = $"($root)/.ontology/core.ncl"
if not ($core_file | path exists) { return [] }
let data = (daemon-export-safe $core_file)
if $data == null { return [] }
let nodes = ($data.nodes? | default [])
if ($level | is-not-empty) {
$nodes | where { |n| ($n.level? | default "") == $level }
} else {
$nodes
}
}
# Query ontology dimensions. Returns empty list on failure.
export def --env "store dimensions" []: nothing -> list {
let root = (project-root)
let state_file = $"($root)/.ontology/state.ncl"
if not ($state_file | path exists) { return [] }
let data = (daemon-export-safe $state_file)
if $data == null { return [] }
$data.dimensions? | default []
}
# Query membranes. Returns empty list on failure.
export def --env "store membranes" [
--all = false,
]: nothing -> list {
let root = (project-root)
let gate_file = $"($root)/.ontology/gate.ncl"
if not ($gate_file | path exists) { return [] }
let data = (daemon-export-safe $gate_file)
if $data == null { return [] }
let membranes = ($data.membranes? | default [])
if $all {
$membranes
} else {
$membranes | where { |m| ($m.active? | default false) == true }
}
}
# ── Cache management ─────────────────────────────────────────────────────────────
# Show daemon cache statistics.
export def --env "store cache-stats" []: nothing -> any {
if not (daemon-available) {
print " daemon not available"
return null
}
let url = $"(daemon-url)/cache/stats"
let result = (http-get $url)
if $result.exit_code == 0 {
$result.stdout | from json
} else {
null
}
}
# Invalidate daemon cache (all entries or by prefix/file).
export def --env "store cache-invalidate" [
--prefix: string = "",
--file: string = "",
--all = false,
]: nothing -> any {
if not (daemon-available) {
print " daemon not available"
return null
}
let url = $"(daemon-url)/cache/invalidate"
if (not $all) and ($prefix | is-empty) and ($file | is-empty) {
error make { msg: "cache-invalidate requires --all, --prefix, or --file" }
}
let body = if $all {
{ all: true } | to json
} else if ($prefix | is-not-empty) {
{ prefix: $prefix } | to json
} else {
{ file: $file } | to json
}
let result = (http-post-json $url $body)
if $result.exit_code == 0 {
$result.stdout | from json
} else {
null
}
}