provisioning-core/nulib/domain/config/cache/metadata.nu

277 lines
6.7 KiB
Text
Raw Permalink Normal View History

# Cache Metadata Management
# Handles metadata creation, loading, validation, and mtime comparison
# Follows Nushell 0.109.0+ guidelines
# Helper: Get current ISO8601 timestamp
def get-iso-timestamp [] {
date now | format date "%Y-%m-%dT%H:%M:%SZ"
}
# Helper: Parse ISO8601 timestamp
def parse-iso-timestamp [timestamp: string] {
# Convert ISO8601 to Unix timestamp
$timestamp
}
# Helper: Check if timestamp is expired
def is-expired [
expires_at: string
now: string
] {
$now > $expires_at
}
# ============================================================================
# PUBLIC API: Metadata Operations
# ============================================================================
# Create metadata for cache entry
export def create-metadata [
source_files: list # List of file paths to track
ttl_seconds: int # Time-to-live in seconds
data_hash: string # Hash of cached data
] {
let now = (get-iso-timestamp)
let expires_at = (((date now) + ($ttl_seconds | into duration)) | format date "%Y-%m-%dT%H:%M:%SZ")
mut source_mtimes = {}
for file in $source_files {
let mtime = if ($file | path exists) {
$file | stat | get modified | into int
} else {
(-1)
}
$source_mtimes = ($source_mtimes | insert $file $mtime)
}
{
created_at: $now,
ttl_seconds: $ttl_seconds,
expires_at: $expires_at,
source_files: $source_files,
source_mtimes: $source_mtimes,
hash: $data_hash,
cache_version: "1.0"
}
}
# Load and validate metadata
export def load-metadata [
meta_file: string
] {
let load_result = (do {
if ($meta_file | path exists) {
open $meta_file
} else {
error make { msg: $"Metadata file not found: ($meta_file)" }
}
} | complete)
if $load_result.exit_code != 0 {
return {
valid: false,
reason: "metadata_load_error",
data: null
}
}
{
valid: true,
reason: "metadata_loaded",
data: $load_result.stdout
}
}
# Validate metadata (check version, format, etc.)
export def validate-metadata [
metadata: record
] {
# Check required fields
let required_fields = ["created_at" "ttl_seconds" "expires_at" "source_files" "hash" "cache_version"]
for field in $required_fields {
if not ($metadata | has -c $field) {
return {
valid: false,
reason: $"missing_field: ($field)"
}
}
}
# Check cache version compatibility
if $metadata.cache_version != "1.0" {
return {
valid: false,
reason: "incompatible_cache_version"
}
}
{
valid: true,
reason: "metadata_valid"
}
}
# Get file modification times
export def get-source-mtimes [
source_files: list
] {
mut mtimes = {}
for file in $source_files {
let mtime_result = (do {
if ($file | path exists) {
$file | stat | get modified | into int
} else {
-1
}
} | complete)
if $mtime_result.exit_code == 0 {
$mtimes = ($mtimes | insert $file $mtime_result.stdout)
} else {
$mtimes = ($mtimes | insert $file (-1))
}
}
$mtimes
}
# Compare cached vs current mtimes
export def compare-mtimes [
cached_mtimes: record
current_mtimes: record
] {
mut all_match = true
mut mismatches = []
for file in ($cached_mtimes | columns) {
let cached = ($cached_mtimes | get $file)
let current = ($current_mtimes | get --optional $file | default (-1))
if $cached != $current {
$all_match = false
$mismatches = ($mismatches | append {
file: $file,
cached: $cached,
current: $current
})
}
}
{
match: $all_match,
mismatches: $mismatches
}
}
# Check if metadata is expired
export def is-metadata-expired [
metadata: record
now: string
] {
is-expired $metadata.expires_at $now
}
# Get metadata expiration time remaining
export def get-metadata-ttl-remaining [
metadata: record
now: string
] {
# Parse both timestamps and calculate difference
let now_ts = (parse-iso-timestamp $now)
let expires_ts = (parse-iso-timestamp $metadata.expires_at)
if $expires_ts > $now_ts {
$expires_ts - $now_ts
} else {
0
}
}
# Get comprehensive metadata status
export def get-metadata-status [
metadata: record
current_source_mtimes: record
] {
let now = (get-iso-timestamp)
let is_expired = (is-metadata-expired $metadata $now)
let mtime_comparison = (compare-mtimes $metadata.source_mtimes $current_source_mtimes)
let ttl_remaining = (get-metadata-ttl-remaining $metadata $now)
{
valid: ((not $is_expired) and $mtime_comparison.match),
expired: $is_expired,
ttl_remaining_seconds: $ttl_remaining,
mtime_status: $mtime_comparison,
created_at: $metadata.created_at,
expires_at: $metadata.expires_at,
source_files_count: ($metadata.source_files | length),
mtime_matches: (($mtime_comparison.mismatches | length) == 0)
}
}
# Update metadata (refresh TTL)
export def update-metadata [
metadata: record
ttl_seconds: int = 0 # If 0, keep original TTL
] {
let new_ttl = if $ttl_seconds > 0 {
$ttl_seconds
} else {
$metadata.ttl_seconds
}
let now = (get-iso-timestamp)
let expires_at = (((date now) + ($new_ttl | into duration)) | format date "%Y-%m-%dT%H:%M:%SZ")
$metadata
| insert created_at $now
| insert ttl_seconds $new_ttl
| insert expires_at $expires_at
}
# Rotate metadata (create new with updated mtimes)
export def rotate-metadata [
metadata: record
new_source_mtimes: record
] {
let now = (get-iso-timestamp)
let expires_at = (((date now) + ($metadata.ttl_seconds | into duration)) | format date "%Y-%m-%dT%H:%M:%SZ")
$metadata
| insert created_at $now
| insert expires_at $expires_at
| insert source_mtimes $new_source_mtimes
}
# Export metadata to JSON
export def export-metadata [
metadata: record
--pretty = false
] {
if $pretty {
$metadata | to json --indent 2
} else {
$metadata | to json
}
}
# Import metadata from JSON
export def import-metadata [
json_string: string
] {
let result = (do {
$json_string | from json
} | complete)
if $result.exit_code == 0 {
$result.stdout
} else {
error make { msg: "Failed to parse metadata JSON" }
}
}