Files
analiticaNucleo/nuxt4-app/app/stores/metadata.ts
2025-09-29 20:57:27 -06:00

252 lines
6.2 KiB
TypeScript

import { defineStore } from 'pinia'
import { useRequestFetch } from '#imports'
export interface TableMetadata {
table: string
rowCount: number
primaryKey: string
approxSizeBytes: number
columns: string[]
createdAtRange?: {
from: string
to: string
}
lastRefreshed?: string
sampleRow?: Record<string, unknown>
}
export interface MetadataState {
metadata: TableMetadata[]
loading: boolean
error: string | null
lastUpdated: string | null
initialized: boolean
}
export const useMetadataStore = defineStore('metadata', {
state: (): MetadataState => ({
metadata: [],
loading: false,
error: null,
lastUpdated: null,
initialized: false
}),
getters: {
/**
* Get metadata for all tables
*/
allTables: (state): TableMetadata[] => state.metadata,
/**
* Get metadata for a specific table
*/
getTableMetadata: (state) => (tableName: string): TableMetadata | undefined => {
return state.metadata.find(meta => meta.table === tableName)
},
/**
* Get total number of tables
*/
totalTables: (state): number => state.metadata.length,
/**
* Get total number of records across all tables
*/
totalRecords: (state): number => {
return state.metadata.reduce((sum, meta) => sum + (meta.rowCount || 0), 0)
},
/**
* Get list of all table names
*/
tableNames: (state): string[] => {
return state.metadata.map(meta => meta.table)
},
/**
* Check if metadata is available
*/
hasMetadata: (state): boolean => state.metadata.length > 0,
/**
* Check if metadata is currently loading
*/
isLoading: (state): boolean => state.loading,
/**
* Check if there's an error
*/
hasError: (state): boolean => !!state.error,
/**
* Get formatted last updated time
*/
formattedLastUpdated: (state): string => {
if (!state.lastUpdated) return 'Nunca'
try {
return new Date(state.lastUpdated).toLocaleString('es-ES', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})
} catch {
return 'Fecha inválida'
}
},
/**
* Check if metadata is stale (older than 5 minutes)
*/
isStale: (state): boolean => {
if (!state.lastUpdated) return true
const lastUpdate = new Date(state.lastUpdated)
const now = new Date()
const fiveMinutes = 5 * 60 * 1000
return (now.getTime() - lastUpdate.getTime()) > fiveMinutes
}
},
actions: {
/**
* Load metadata lazily (only if not already loaded)
*/
async loadMetadata(force = false): Promise<void> {
// Don't load if already loading
if (this.loading) return
// Don't load if already initialized and not forced
if (this.initialized && !force && !this.isStale) return
await this.fetchMetadata()
},
/**
* Force refresh metadata
*/
async refreshMetadata(): Promise<void> {
await this.fetchMetadata()
},
/**
* Internal method to fetch metadata from API
*/
async fetchMetadata(): Promise<void> {
this.loading = true
this.error = null
try {
const requestFetch = useRequestFetch()
const response = await requestFetch('/api/metadata')
if (Array.isArray(response)) {
this.metadata = response.map((meta: any) => ({
...meta,
lastRefreshed: meta.lastRefreshed || new Date().toISOString()
}))
this.lastUpdated = new Date().toISOString()
this.initialized = true
// Persist to localStorage for offline access
if (process.client) {
try {
localStorage.setItem('metadata-cache', JSON.stringify({
metadata: this.metadata,
lastUpdated: this.lastUpdated
}))
} catch (error) {
console.warn('Failed to persist metadata to localStorage:', error)
}
}
} else {
throw new Error('Invalid metadata response format')
}
} catch (error: any) {
this.error = this.extractErrorMessage(error)
console.error('Error fetching metadata:', error)
// Try to load from cache if available
if (process.client && !this.hasMetadata) {
this.loadFromCache()
}
} finally {
this.loading = false
}
},
/**
* Load metadata from localStorage cache
*/
loadFromCache(): void {
if (!process.client) return
try {
const cached = localStorage.getItem('metadata-cache')
if (cached) {
const parsedCache = JSON.parse(cached)
this.metadata = parsedCache.metadata || []
this.lastUpdated = parsedCache.lastUpdated || null
this.initialized = true
}
} catch (error) {
console.warn('Failed to load metadata from cache:', error)
}
},
/**
* Clear all metadata
*/
clearMetadata(): void {
this.metadata = []
this.error = null
this.lastUpdated = null
this.initialized = false
if (process.client) {
try {
localStorage.removeItem('metadata-cache')
} catch (error) {
console.warn('Failed to clear metadata cache:', error)
}
}
},
/**
* Extract error message from various error types
*/
extractErrorMessage(error: unknown): string {
if (error && typeof error === 'object' && 'statusMessage' in error) {
return String((error as { statusMessage: string }).statusMessage)
}
if (error instanceof Error) {
return error.message
}
return 'Error inesperado al cargar metadatos'
},
/**
* Initialize store (called on app startup)
*/
async initialize(): Promise<void> {
// Load from cache first for immediate availability
this.loadFromCache()
// Then try to fetch fresh data
await this.loadMetadata()
}
},
// Enable persistence for better UX
persist: process.client ? {
key: 'metadata-store',
storage: localStorage,
pick: ['metadata', 'lastUpdated', 'initialized']
} : false
})