@ensnode/ponder-metadata
Version:
A Hono middleware for making Ponder app metadata available to clients.
309 lines (306 loc) • 9.82 kB
JavaScript
// src/middleware.ts
import { HTTPException } from "hono/http-exception";
// src/db-helpers.ts
import { pgSchema, pgTable } from "drizzle-orm/pg-core";
import { eq } from "ponder";
var getPonderMetaTableSchema = (databaseNamespace) => {
if (databaseNamespace === "public") {
return pgTable("_ponder_meta", (t) => ({
key: t.text().primaryKey().$type(),
value: t.jsonb().$type().notNull()
}));
}
return pgSchema(databaseNamespace).table("_ponder_meta", (t) => ({
key: t.text().primaryKey().$type(),
value: t.jsonb().$type().notNull()
}));
};
async function queryPonderMeta(namespace, db) {
const PONDER_META = getPonderMetaTableSchema(namespace);
const [ponderAppMeta] = await db.select({ value: PONDER_META.value }).from(PONDER_META).where(eq(PONDER_META.key, "app")).limit(1);
if (!ponderAppMeta) {
throw new Error("Ponder metadata not found");
}
return ponderAppMeta.value;
}
// src/prometheus-metrics.ts
import parsePrometheusTextFormat from "parse-prometheus-text-format";
function parsePrometheusText(text) {
return parsePrometheusTextFormat(text).map((metric) => ({
name: metric.name,
help: metric.help || "",
type: metric.type.toLowerCase(),
metrics: metric.metrics.map((m) => ({
value: Number(m.value),
...m.labels && Object.keys(m.labels).length > 0 ? { labels: m.labels } : {}
}))
}));
}
var PrometheusMetrics = class _PrometheusMetrics {
constructor(metrics) {
this.metrics = metrics;
}
static parse(maybePrometheusMetricsText) {
return new _PrometheusMetrics(parsePrometheusText(maybePrometheusMetricsText));
}
/**
* Gets all metrics of a specific name
* @param name Metric name
* @returns Array of metrics or undefined if not found
* @example
* ```ts
* const metrics = parser.get('ponder_historical_total_indexing_seconds');
* // Returns: [
* // { value: 251224935, labels: { network: "1" } },
* // { value: 251224935, labels: { network: "8453" } }
* // ]
* ```
*/
get(name) {
const metric = this.metrics.find((m) => m.name === name);
return metric == null ? void 0 : metric.metrics;
}
/**
* Gets a single metric value, optionally filtered by labels
* @param name Metric name
* @param labelFilter Optional label key-value pairs to match
* @returns Metric value or undefined if not found
* @example
* ```ts
* // Get simple value
* parser.getValue('ponder_historical_start_timestamp_seconds') // Returns: 1740391265
*
* // Get value with label filter
* parser.getValue('ponder_historical_total_indexing_seconds', { network: '1' }) // Returns: 251224935
* ```
*/
getValue(name, labelFilter) {
var _a;
const metrics = this.get(name);
if (!metrics || metrics.length === 0) {
return void 0;
}
if (!labelFilter) {
return (_a = metrics[0]) == null ? void 0 : _a.value;
}
const metric = metrics.find(
(m) => m.labels && Object.entries(labelFilter).every(([k, v]) => {
var _a2;
return ((_a2 = m.labels) == null ? void 0 : _a2[k]) === v;
})
);
return metric == null ? void 0 : metric.value;
}
/**
* Gets a label value from a metric
* @param name Metric name
* @param label Label name to retrieve
* @returns Label value or undefined if not found
* @example
* ```ts
* parser.getLabel('ponder_version_info', 'version') // Returns: "0.9.18"
* parser.getLabel('ponder_settings_info', 'ordering') // Returns: "omnichain"
* ```
*/
getLabel(name, label) {
return this.getLabels(name, label)[0];
}
/**
* Gets all unique label values for a metric
* @param name Metric name
* @param label Label name to retrieve
* @returns Array of unique label values
* @example
* ```ts
* // Get all network IDs
* parser.getLabels('ponder_historical_total_indexing_seconds', 'network')
* // Returns: ['1', '8453']
* ```
*/
getLabels(name, label) {
const metrics = this.get(name);
if (!metrics) return [];
return [
...new Set(metrics.map((m) => {
var _a;
return (_a = m.labels) == null ? void 0 : _a[label];
}).filter((v) => v !== void 0))
];
}
/**
* Gets help text for a metric
* @param name Metric name
* @returns Help text or undefined if not found
* @example
* ```ts
* parser.getHelp('ponder_historical_start_timestamp_seconds')
* // Returns: "Timestamp at which historical indexing started"
* ```
*/
getHelp(name) {
var _a;
return (_a = this.metrics.find((m) => m.name === name)) == null ? void 0 : _a.help;
}
/**
* Gets metric type
* @param name Metric name
* @returns Metric type or undefined if not found
* @example
* ```ts
* parser.getType('ponder_version_info') // Returns: "gauge"
* parser.getType('ponder_postgres_query_total') // Returns: "counter"
* ```
*/
getType(name) {
var _a;
return (_a = this.metrics.find((m) => m.name === name)) == null ? void 0 : _a.type;
}
/**
* Gets all metric names
* @returns Array of metric names
* @example
* ```ts
* parser.getMetricNames()
* // Returns: [
* // 'ponder_version_info',
* // 'ponder_settings_info',
* // 'ponder_historical_start_timestamp_seconds',
* // 'ponder_historical_total_indexing_seconds'
* // ]
* ```
*/
getMetricNames() {
return this.metrics.map((m) => m.name);
}
};
// src/middleware.ts
function ponderMetadata({
app,
db,
env,
query,
publicClients
}) {
return async function ponderMetadataMiddleware(ctx) {
const indexedChainNames = Object.keys(publicClients);
const ponderStatus = await query.ponderStatus();
const metrics = PrometheusMetrics.parse(await query.prometheusMetrics());
const chainIndexingStatuses = {};
for (const indexedChainName of indexedChainNames) {
const publicClient = publicClients[indexedChainName];
if (!publicClient || typeof publicClient.chain === "undefined") {
throw new HTTPException(500, {
message: `No public client found for "${indexedChainName}" chain name`
});
}
const publicClientChainId = publicClient.chain.id;
const fetchBlockMetadata = async (blockNumber) => {
const block = await publicClient.getBlock({
blockNumber: BigInt(blockNumber)
});
if (!block) {
throw new Error(
`Failed to fetch block metadata for block number ${blockNumber} on chain ID "${publicClientChainId}"`
);
}
return {
number: Number(block.number),
timestamp: Number(block.timestamp)
};
};
const latestSafeBlockData = await publicClient.getBlock();
if (!latestSafeBlockData) {
throw new HTTPException(500, {
message: `Failed to fetch latest safe block for chain ID "${publicClientChainId}"`
});
}
const latestSafeBlock = {
number: Number(latestSafeBlockData.number),
timestamp: Number(latestSafeBlockData.timestamp)
};
const chain = indexedChainName;
const lastSyncedBlockHeight = metrics.getValue("ponder_sync_block", {
chain
});
let lastSyncedBlock = null;
if (lastSyncedBlockHeight) {
try {
lastSyncedBlock = await fetchBlockMetadata(lastSyncedBlockHeight);
} catch (error) {
console.error("Failed to fetch block metadata for last synced block", error);
}
}
const firstBlockToIndex = await query.firstBlockToIndexByChainId(
publicClientChainId,
publicClient
);
const ponderStatusForChain = Object.values(ponderStatus).find(
(ponderStatusEntry) => ponderStatusEntry.id === publicClientChainId
);
let lastIndexedBlock = null;
if (ponderStatusForChain) {
if (firstBlockToIndex.number < ponderStatusForChain.block.number) {
lastIndexedBlock = ponderStatusForChain.block;
}
}
chainIndexingStatuses[publicClientChainId] = {
chainId: publicClientChainId,
lastSyncedBlock,
lastIndexedBlock,
latestSafeBlock,
firstBlockToIndex
};
}
let ponderAppBuildId;
try {
ponderAppBuildId = (await queryPonderMeta(env.DATABASE_SCHEMA, db)).build_id;
} catch (error) {
console.error("Failed to fetch ponder metadata", error);
}
let ensRainbowVersionInfo = void 0;
if (query.ensRainbowVersion) {
try {
ensRainbowVersionInfo = await query.ensRainbowVersion();
} catch (error) {
console.error("Failed to fetch ENSRainbow version", error);
}
}
const response = {
app,
deps: {
ponder: formatTextMetricValue(metrics.getLabel("ponder_version_info", "version")),
nodejs: formatTextMetricValue(metrics.getLabel("nodejs_version_info", "version"))
},
env,
runtime: {
codebaseBuildId: formatTextMetricValue(ponderAppBuildId),
chainIndexingStatuses,
ensRainbow: ensRainbowVersionInfo
}
};
validateResponse(response);
return ctx.json(response);
};
}
function validateResponse(response) {
const { chainIndexingStatuses } = response.runtime;
if (Object.keys(chainIndexingStatuses).length === 0) {
throw new HTTPException(500, {
message: "No chain indexing status found"
});
}
if (Object.values(chainIndexingStatuses).some((n) => n.firstBlockToIndex === null)) {
throw new HTTPException(500, {
message: "Failed to fetch first block to index for some chains"
});
}
}
function formatTextMetricValue(value) {
return value ?? "unknown";
}
export {
PrometheusMetrics,
ponderMetadata,
queryPonderMeta
};
//# sourceMappingURL=index.js.map