import type { OxySDK } from "@oxy-hq/sdk";
export interface MonthlyMetric {
period: string; // Date string (e.g., "2024-11-01")
value: number; // Metric value
}
// BigInt-safe JSON serializer
function safeStringify(obj: any): string {
return JSON.stringify(
obj,
(_key, value) =>
typeof value === "bigint" ? `BigInt(${value.toString()})` : value,
2,
);
}
export async function fetchMonthlyMetrics(
sdk: OxySDK,
appPath: string = "apps/monthly_metrics_starter.app.yml",
): Promise<MonthlyMetric[]> {
try {
console.log("[Oxy] Loading app data...");
await sdk.loadAppData(appPath);
console.log("[Oxy] Querying table...");
const result = await sdk.query("SELECT * FROM monthly_values");
console.log("[Oxy] Columns:", result.columns);
console.log("[Oxy] Number of rows:", result.rows?.length);
// Log every row safely (BigInt-safe)
result.rows.forEach((row: any[], i: number) => {
const cellInfo = row.map((cell: any, j: number) => {
const t = typeof cell;
let val: string;
if (cell === null || cell === undefined) val = "NULL";
else if (t === "bigint") val = `BigInt(${cell.toString()})`;
else if (t === "object") {
try {
val = safeStringify(cell);
} catch {
val = "[unserializable object]";
}
} else val = String(cell);
return `col[${j}](${t}): ${val}`;
});
console.log(`[Oxy] Row ${i}: ${cellInfo.join(" | ")}`);
});
const metrics: MonthlyMetric[] = result.rows.map((row: any[]) => {
// Handle epoch date objects from DuckDB
let period: string;
const rawDate = row[0];
if (rawDate && typeof rawDate === "object" && rawDate.epoch) {
const epochValue =
typeof rawDate.epoch === "bigint"
? Number(rawDate.epoch)
: typeof rawDate.epoch === "object" &&
rawDate.epoch._type === "BigInt"
? Number(rawDate.epoch.value)
: Number(rawDate.epoch);
const ms = epochValue < 1e12 ? epochValue * 1000 : epochValue / 1000;
period = new Date(ms).toISOString().split("T")[0];
} else {
period = String(rawDate);
}
// Try all columns for a numeric value (skip the date column)
let value = 0;
for (let i = 1; i < row.length; i++) {
const rawValue = row[i];
if (rawValue == null) continue;
if (typeof rawValue === "bigint") {
value = Number(rawValue);
break;
} else if (typeof rawValue === "number") {
value = rawValue;
break;
} else if (typeof rawValue === "string") {
const parsed = Number(rawValue.replace(/[$,]/g, ""));
if (!isNaN(parsed) && parsed !== 0) {
value = parsed;
break;
}
} else if (typeof rawValue === "object") {
// Could be a DuckDB decimal or other structured type
console.log(
` [Oxy] col[${i}] object keys:`,
Object.keys(rawValue),
safeStringify(rawValue),
);
if (rawValue.value !== undefined) value = Number(rawValue.value);
else if (rawValue.epoch !== undefined)
value = Number(
typeof rawValue.epoch === "bigint"
? rawValue.epoch
: rawValue.epoch,
);
if (value !== 0) break;
}
}
return { period, value };
});
metrics.sort((a, b) => a.period.localeCompare(b.period));
return metrics;
} catch (error) {
console.error("[Oxy] Error:", error);
throw error;
}
}