Files
metabuilder/packages/package_validator/seed/scripts/db/operations.lua
2025-12-30 22:06:55 +00:00

336 lines
8.7 KiB
Lua

-- package_validator/seed/scripts/db/operations.lua
-- DBAL operations for Package validation results
-- @module package_validator.db.operations
local M = {}
local json = require('json')
---------------------------------------------------------------------------
-- VALIDATION RUN OPERATIONS
---------------------------------------------------------------------------
---@class ValidationRunParams
---@field tenantId string
---@field packageName string
---@field packageVersion string|nil
---@field status string (pending, running, completed, failed)
---@field results table|nil Validation results
---Create validation run
---@param dbal table DBAL client instance
---@param params ValidationRunParams
---@return table Created run
function M.createRun(dbal, params)
return dbal:create('ValidationRun', {
tenantId = params.tenantId,
packageName = params.packageName,
packageVersion = params.packageVersion,
status = params.status or 'pending',
results = params.results and json.encode(params.results) or nil,
startedAt = os.time() * 1000,
})
end
---Get validation run by ID
---@param dbal table
---@param runId string
---@return table|nil Run
function M.getRun(dbal, runId)
local run = dbal:read('ValidationRun', runId)
if run and run.results then
run.results = json.decode(run.results)
end
return run
end
---Update run status
---@param dbal table
---@param runId string
---@param status string
---@param results table|nil
function M.updateRunStatus(dbal, runId, status, results)
local updates = {
status = status,
}
if results then
updates.results = json.encode(results)
end
if status == 'completed' or status == 'failed' then
updates.completedAt = os.time() * 1000
end
return dbal:update('ValidationRun', runId, updates)
end
---List runs for package
---@param dbal table
---@param tenantId string
---@param packageName string
---@param take number|nil
---@return table[] Runs
function M.listRuns(dbal, tenantId, packageName, take)
local result = dbal:list('ValidationRun', {
where = { tenantId = tenantId, packageName = packageName },
orderBy = { startedAt = 'desc' },
take = take or 20,
})
return result.items or {}
end
---Get latest run for package
---@param dbal table
---@param tenantId string
---@param packageName string
---@return table|nil Latest run
function M.getLatestRun(dbal, tenantId, packageName)
local runs = M.listRuns(dbal, tenantId, packageName, 1)
if #runs > 0 then
local run = runs[1]
if run.results then
run.results = json.decode(run.results)
end
return run
end
return nil
end
---------------------------------------------------------------------------
-- VALIDATION ISSUE OPERATIONS
---------------------------------------------------------------------------
---@class ValidationIssue
---@field runId string
---@field severity string (error, warning, info)
---@field code string Issue code
---@field message string
---@field file string|nil
---@field line number|nil
---@field suggestion string|nil
---Add validation issue
---@param dbal table
---@param issue ValidationIssue
---@return table Created issue
function M.addIssue(dbal, issue)
return dbal:create('ValidationIssue', {
runId = issue.runId,
severity = issue.severity,
code = issue.code,
message = issue.message,
file = issue.file,
line = issue.line,
suggestion = issue.suggestion,
createdAt = os.time() * 1000,
})
end
---Add multiple issues
---@param dbal table
---@param runId string
---@param issues table[] Array of issues
function M.addIssues(dbal, runId, issues)
for _, issue in ipairs(issues) do
issue.runId = runId
M.addIssue(dbal, issue)
end
end
---Get issues for run
---@param dbal table
---@param runId string
---@param severity string|nil Filter by severity
---@return table[] Issues
function M.getIssues(dbal, runId, severity)
local where = { runId = runId }
local result = dbal:list('ValidationIssue', {
where = where,
orderBy = { severity = 'asc' },
take = 500,
})
local issues = result.items or {}
if severity then
local filtered = {}
for _, issue in ipairs(issues) do
if issue.severity == severity then
table.insert(filtered, issue)
end
end
issues = filtered
end
return issues
end
---Count issues by severity
---@param dbal table
---@param runId string
---@return table Counts by severity
function M.countIssues(dbal, runId)
local issues = M.getIssues(dbal, runId)
local counts = {
error = 0,
warning = 0,
info = 0,
total = #issues,
}
for _, issue in ipairs(issues) do
local sev = issue.severity
if counts[sev] then
counts[sev] = counts[sev] + 1
end
end
return counts
end
---------------------------------------------------------------------------
-- VALIDATION RULE OPERATIONS
---------------------------------------------------------------------------
---@class ValidationRuleParams
---@field tenantId string
---@field name string
---@field code string Unique rule code
---@field severity string
---@field description string
---@field check string Lua function body
---@field enabled boolean|nil
---Create validation rule
---@param dbal table
---@param params ValidationRuleParams
---@return table Created rule
function M.createRule(dbal, params)
return dbal:create('ValidationRule', {
tenantId = params.tenantId,
name = params.name,
code = params.code,
severity = params.severity,
description = params.description,
check = params.check,
enabled = params.enabled ~= false,
createdAt = os.time() * 1000,
updatedAt = os.time() * 1000,
})
end
---Get rule by code
---@param dbal table
---@param tenantId string
---@param code string
---@return table|nil Rule
function M.getRule(dbal, tenantId, code)
return dbal:findFirst('ValidationRule', {
where = { tenantId = tenantId, code = code },
})
end
---List enabled rules
---@param dbal table
---@param tenantId string
---@return table[] Rules
function M.listEnabledRules(dbal, tenantId)
local result = dbal:list('ValidationRule', {
where = { tenantId = tenantId, enabled = true },
orderBy = { code = 'asc' },
take = 100,
})
return result.items or {}
end
---Update rule
---@param dbal table
---@param tenantId string
---@param code string
---@param updates table
function M.updateRule(dbal, tenantId, code, updates)
local rule = M.getRule(dbal, tenantId, code)
if rule then
updates.updatedAt = os.time() * 1000
return dbal:update('ValidationRule', rule.id, updates)
end
return nil
end
---Toggle rule enabled status
---@param dbal table
---@param tenantId string
---@param code string
---@param enabled boolean
function M.setRuleEnabled(dbal, tenantId, code, enabled)
return M.updateRule(dbal, tenantId, code, { enabled = enabled })
end
---------------------------------------------------------------------------
-- PACKAGE REGISTRY OPERATIONS
---------------------------------------------------------------------------
---Get package validation summary
---@param dbal table
---@param tenantId string
---@param packageName string
---@return table Summary
function M.getPackageSummary(dbal, tenantId, packageName)
local latestRun = M.getLatestRun(dbal, tenantId, packageName)
if not latestRun then
return {
packageName = packageName,
status = 'never_validated',
lastValidated = nil,
issueCounts = nil,
}
end
local counts = M.countIssues(dbal, latestRun.id)
return {
packageName = packageName,
status = latestRun.status,
lastValidated = latestRun.completedAt or latestRun.startedAt,
issueCounts = counts,
hasErrors = counts.error > 0,
}
end
---Get all packages validation status
---@param dbal table
---@param tenantId string
---@return table[] Package summaries
function M.getAllPackageStatus(dbal, tenantId)
-- Get unique package names from runs
local result = dbal:list('ValidationRun', {
where = { tenantId = tenantId },
orderBy = { packageName = 'asc' },
take = 10000,
})
local packageNames = {}
local seen = {}
for _, run in ipairs(result.items or {}) do
if not seen[run.packageName] then
seen[run.packageName] = true
table.insert(packageNames, run.packageName)
end
end
local summaries = {}
for _, name in ipairs(packageNames) do
table.insert(summaries, M.getPackageSummary(dbal, tenantId, name))
end
return summaries
end
return M