@ensnode/ponder-metadata 0.1.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,105 @@
1
+ import { EnsRainbow } from '@ensnode/ensrainbow-sdk';
2
+ import { MiddlewareHandler } from 'hono';
3
+ import { ReadonlyDrizzle } from 'ponder';
4
+ import { PublicClient } from 'viem';
5
+
6
+ /**
7
+ * Basic information about a block.
8
+ */
9
+ interface BlockInfo {
10
+ /** block number */
11
+ number: number;
12
+ /** block unix timestamp */
13
+ timestamp: number;
14
+ }
15
+ /**
16
+ * Network indexing status for a chain.
17
+ */
18
+ interface NetworkIndexingStatus {
19
+ /**
20
+ * First block required to be indexed during the historical sync.
21
+ */
22
+ firstBlockToIndex: BlockInfo;
23
+ /**
24
+ * Latest block synced into indexer's RPC cache.
25
+ */
26
+ lastSyncedBlock: BlockInfo | null;
27
+ /**
28
+ * Last block processed & indexed by the indexer.
29
+ */
30
+ lastIndexedBlock: BlockInfo | null;
31
+ /**
32
+ * Latest safe block available on the chain.
33
+ */
34
+ latestSafeBlock: BlockInfo;
35
+ }
36
+
37
+ type PonderEnvVarsInfo = Record<string, unknown>;
38
+ interface PonderMetadataMiddlewareOptions<AppInfo, EnvVars extends PonderEnvVarsInfo> {
39
+ /** Database access object (readonly Drizzle) */
40
+ db: ReadonlyDrizzle<Record<string, unknown>>;
41
+ /** Application info */
42
+ app: AppInfo;
43
+ /** Environment settings info */
44
+ env: EnvVars;
45
+ /** Query methods */
46
+ query: {
47
+ /** Fetches prometheus metrics for Ponder application */
48
+ prometheusMetrics(): Promise<string>;
49
+ /** Fetches the first block do be indexed for a requested chain ID */
50
+ firstBlockToIndexByChainId(chainId: number, publicClient: PublicClient): Promise<BlockInfo>;
51
+ /** Fetches ENSRainbow version information */
52
+ ensRainbowVersion?(): Promise<EnsRainbow.VersionInfo>;
53
+ };
54
+ /** Public clients for each blockchain network fetching data */
55
+ publicClients: Record<number, PublicClient>;
56
+ }
57
+ interface PonderMetadataMiddlewareResponse<AppInfo, EnvVarsInfo extends PonderEnvVarsInfo, RuntimeInfo> {
58
+ /** Application info */
59
+ app: AppInfo;
60
+ /** Dependencies info */
61
+ deps: {
62
+ /** Ponder application version */
63
+ ponder: string;
64
+ /** Node.js runtime version */
65
+ nodejs: string;
66
+ };
67
+ /** Environment settings info */
68
+ env: EnvVarsInfo;
69
+ /** Runtime status info */
70
+ runtime: RuntimeInfo;
71
+ }
72
+
73
+ /**
74
+ * Ponder Metadata types definition.
75
+ */
76
+ interface PonderMetadataModule {
77
+ /** Application info */
78
+ AppInfo: {
79
+ /** Application name */
80
+ name: string;
81
+ /** Application version */
82
+ version: string;
83
+ };
84
+ /** Environment Variables info */
85
+ EnvVars: {
86
+ /** Database schema */
87
+ DATABASE_SCHEMA: string;
88
+ } & PonderEnvVarsInfo;
89
+ /** Runtime info */
90
+ RuntimeInfo: {
91
+ /**
92
+ * Application build id
93
+ * https://github.com/ponder-sh/ponder/blob/626e524/packages/core/src/build/index.ts#L425-L431
94
+ **/
95
+ codebaseBuildId: string;
96
+ /** Network indexing status by chain ID */
97
+ networkIndexingStatusByChainId: Record<number, NetworkIndexingStatus>;
98
+ /** ENSRainbow version info */
99
+ ensRainbow?: EnsRainbow.VersionInfo;
100
+ };
101
+ }
102
+ type MetadataMiddlewareResponse = PonderMetadataMiddlewareResponse<PonderMetadataModule["AppInfo"], PonderMetadataModule["EnvVars"], PonderMetadataModule["RuntimeInfo"]>;
103
+ declare function ponderMetadata<AppInfo extends PonderMetadataModule["AppInfo"], EnvVars extends PonderMetadataModule["EnvVars"]>({ app, db, env, query, publicClients, }: PonderMetadataMiddlewareOptions<AppInfo, EnvVars>): MiddlewareHandler;
104
+
105
+ export { type BlockInfo, type MetadataMiddlewareResponse, type NetworkIndexingStatus, type PonderMetadataMiddlewareResponse, ponderMetadata };
@@ -1,20 +1,23 @@
1
+ // src/middleware.ts
2
+ import { HTTPException } from "hono/http-exception";
3
+
1
4
  // src/db-helpers.ts
2
5
  import { pgSchema, pgTable } from "drizzle-orm/pg-core";
3
6
  import { eq } from "ponder";
4
- var getPonderMeta = (namespace) => {
5
- if (namespace === "public") {
7
+ var getPonderMetaTableSchema = (databaseNamespace) => {
8
+ if (databaseNamespace === "public") {
6
9
  return pgTable("_ponder_meta", (t) => ({
7
10
  key: t.text().primaryKey().$type(),
8
11
  value: t.jsonb().$type().notNull()
9
12
  }));
10
13
  }
11
- return pgSchema(namespace).table("_ponder_meta", (t) => ({
14
+ return pgSchema(databaseNamespace).table("_ponder_meta", (t) => ({
12
15
  key: t.text().primaryKey().$type(),
13
16
  value: t.jsonb().$type().notNull()
14
17
  }));
15
18
  };
16
- var getPonderStatus = (namespace) => {
17
- if (namespace === "public") {
19
+ var getPonderStatusTableSchema = (databaseNamespace) => {
20
+ if (databaseNamespace === "public") {
18
21
  return pgTable("_ponder_status", (t) => ({
19
22
  network_name: t.text().primaryKey(),
20
23
  block_number: t.bigint({ mode: "number" }),
@@ -22,7 +25,7 @@ var getPonderStatus = (namespace) => {
22
25
  ready: t.boolean().notNull()
23
26
  }));
24
27
  }
25
- return pgSchema(namespace).table("_ponder_status", (t) => ({
28
+ return pgSchema(databaseNamespace).table("_ponder_status", (t) => ({
26
29
  network_name: t.text().primaryKey(),
27
30
  block_number: t.bigint({ mode: "number" }),
28
31
  block_timestamp: t.bigint({ mode: "number" }),
@@ -30,15 +33,16 @@ var getPonderStatus = (namespace) => {
30
33
  }));
31
34
  };
32
35
  async function queryPonderStatus(namespace, db) {
33
- const PONDER_STATUS = getPonderStatus(namespace);
36
+ const PONDER_STATUS = getPonderStatusTableSchema(namespace);
34
37
  return db.select().from(PONDER_STATUS);
35
38
  }
36
39
  async function queryPonderMeta(namespace, db) {
37
- const PONDER_META = getPonderMeta(namespace);
38
- return db.select({ value: PONDER_META.value }).from(PONDER_META).where(eq(PONDER_META.key, "app")).limit(1).then((result) => {
39
- var _a;
40
- return (_a = result[0]) == null ? void 0 : _a.value;
41
- });
40
+ const PONDER_META = getPonderMetaTableSchema(namespace);
41
+ const [ponderAppMeta] = await db.select({ value: PONDER_META.value }).from(PONDER_META).where(eq(PONDER_META.key, "app")).limit(1);
42
+ if (!ponderAppMeta) {
43
+ throw new Error("Ponder metadata not found");
44
+ }
45
+ return ponderAppMeta.value;
42
46
  }
43
47
 
44
48
  // src/prometheus-metrics.ts
@@ -197,87 +201,131 @@ function ponderMetadata({
197
201
  app,
198
202
  db,
199
203
  env,
200
- fetchPrometheusMetrics,
204
+ query,
201
205
  publicClients
202
206
  }) {
203
207
  return async function ponderMetadataMiddleware(ctx) {
204
208
  const indexedChainIds = Object.keys(publicClients).map(Number);
205
209
  const ponderStatus = await queryPonderStatus(env.DATABASE_SCHEMA, db);
206
- const ponderMeta = await queryPonderMeta(env.DATABASE_SCHEMA, db);
207
- const metrics = PrometheusMetrics.parse(await fetchPrometheusMetrics());
208
- const networkIndexingStatus = {};
210
+ const metrics = PrometheusMetrics.parse(await query.prometheusMetrics());
211
+ const networkIndexingStatusByChainId = {};
209
212
  for (const indexedChainId of indexedChainIds) {
210
213
  const publicClient = publicClients[indexedChainId];
211
214
  if (!publicClient) {
212
- throw new Error(`No public client found for chainId ${indexedChainId}`);
215
+ throw new HTTPException(500, {
216
+ message: `No public client found for chainId ${indexedChainId}`
217
+ });
218
+ }
219
+ const fetchBlockMetadata = async (blockNumber) => {
220
+ const block = await publicClient.getBlock({
221
+ blockNumber: BigInt(blockNumber)
222
+ });
223
+ if (!block) {
224
+ throw new Error(
225
+ `Failed to fetch block metadata for block number ${blockNumber} with chain ID ${indexedChainId}`
226
+ );
227
+ }
228
+ return {
229
+ number: Number(block.number),
230
+ timestamp: Number(block.timestamp)
231
+ };
232
+ };
233
+ const latestSafeBlockData = await publicClient.getBlock();
234
+ if (!latestSafeBlockData) {
235
+ throw new HTTPException(500, {
236
+ message: `Failed to fetch latest safe block for chainId ${indexedChainId}`
237
+ });
213
238
  }
214
- const latestSafeBlock = await publicClient.getBlock();
239
+ const latestSafeBlock = {
240
+ number: Number(latestSafeBlockData.number),
241
+ timestamp: Number(latestSafeBlockData.timestamp)
242
+ };
215
243
  const network = indexedChainId.toString();
216
- const ponderStatusForNetwork = ponderStatus.find((s) => s.network_name === network);
217
- const lastIndexedBlock = mapPonderStatusBlockToBlockMetadata(ponderStatusForNetwork);
218
- networkIndexingStatus[network] = {
219
- totalBlocksCount: metrics.getValue("ponder_historical_total_blocks", {
220
- network
221
- }) ?? null,
222
- cachedBlocksCount: metrics.getValue("ponder_historical_cached_blocks", {
223
- network
224
- }) ?? null,
225
- lastSyncedBlock: blockInfo({
226
- number: metrics.getValue("ponder_sync_block", {
227
- network
228
- }) ?? 0,
229
- timestamp: 0
230
- }),
231
- latestSafeBlock: blockInfo({
232
- number: Number(latestSafeBlock.number),
233
- timestamp: Number(latestSafeBlock.timestamp)
234
- }),
244
+ const lastSyncedBlockHeight = metrics.getValue("ponder_sync_block", {
245
+ network
246
+ });
247
+ let lastSyncedBlock = null;
248
+ if (lastSyncedBlockHeight) {
249
+ try {
250
+ lastSyncedBlock = await fetchBlockMetadata(lastSyncedBlockHeight);
251
+ } catch (error) {
252
+ console.error("Failed to fetch block metadata for last synced block", error);
253
+ }
254
+ }
255
+ const ponderStatusForNetwork = ponderStatus.find(
256
+ (ponderStatusEntry) => ponderStatusEntry.network_name === network
257
+ );
258
+ let lastIndexedBlock = null;
259
+ if (ponderStatusForNetwork) {
260
+ lastIndexedBlock = ponderBlockInfoToBlockMetadata(ponderStatusForNetwork);
261
+ }
262
+ networkIndexingStatusByChainId[indexedChainId] = {
263
+ lastSyncedBlock,
235
264
  lastIndexedBlock,
236
- isRealtime: Boolean(metrics.getValue("ponder_sync_is_realtime", { network })),
237
- isComplete: Boolean(metrics.getValue("ponder_sync_is_complete", { network })),
238
- isQueued: lastIndexedBlock === null
265
+ latestSafeBlock,
266
+ firstBlockToIndex: await query.firstBlockToIndexByChainId(indexedChainId, publicClient)
239
267
  };
240
268
  }
241
- return ctx.json({
269
+ let ponderAppBuildId;
270
+ try {
271
+ ponderAppBuildId = (await queryPonderMeta(env.DATABASE_SCHEMA, db)).build_id;
272
+ } catch (error) {
273
+ console.error("Failed to fetch ponder metadata", error);
274
+ }
275
+ let ensRainbowVersionInfo = void 0;
276
+ if (query.ensRainbowVersion) {
277
+ try {
278
+ ensRainbowVersionInfo = await query.ensRainbowVersion();
279
+ } catch (error) {
280
+ console.error("Failed to fetch ENSRainbow version", error);
281
+ }
282
+ }
283
+ const response = {
242
284
  app,
243
- // application dependencies version
244
285
  deps: {
245
- ponder: metrics.getLabel("ponder_version_info", "version"),
246
- nodejs: metrics.getLabel("nodejs_version_info", "version")
286
+ ponder: formatTextMetricValue(metrics.getLabel("ponder_version_info", "version")),
287
+ nodejs: formatTextMetricValue(metrics.getLabel("nodejs_version_info", "version"))
247
288
  },
248
- // application environment variables
249
289
  env,
250
- // application runtime information
251
290
  runtime: {
252
- // application build id
253
- // https://github.com/ponder-sh/ponder/blob/626e524/packages/core/src/build/index.ts#L425-L431
254
- codebaseBuildId: ponderMeta == null ? void 0 : ponderMeta.build_id,
255
- // tableNames: meta?.table_names,
256
- networkIndexingStatus
291
+ codebaseBuildId: formatTextMetricValue(ponderAppBuildId),
292
+ networkIndexingStatusByChainId,
293
+ ensRainbow: ensRainbowVersionInfo
257
294
  }
258
- });
295
+ };
296
+ validateResponse(response);
297
+ return ctx.json(response);
259
298
  };
260
299
  }
261
- function blockInfo(block) {
262
- if (!block.number) {
263
- return null;
300
+ function validateResponse(response) {
301
+ const { networkIndexingStatusByChainId } = response.runtime;
302
+ if (Object.keys(networkIndexingStatusByChainId).length === 0) {
303
+ throw new HTTPException(500, {
304
+ message: "No network indexing status found"
305
+ });
264
306
  }
265
- return {
266
- height: block.number,
267
- timestamp: block.timestamp ?? 0,
268
- utc: block.timestamp ? new Date(block.timestamp * 1e3).toISOString() : ""
269
- };
307
+ if (Object.values(networkIndexingStatusByChainId).some((n) => n.firstBlockToIndex === null)) {
308
+ throw new HTTPException(500, {
309
+ message: "Failed to fetch first block to index for some networks"
310
+ });
311
+ }
312
+ }
313
+ function formatTextMetricValue(value) {
314
+ return value ?? "unknown";
270
315
  }
271
- function mapPonderStatusBlockToBlockMetadata(block) {
272
- if (!(block == null ? void 0 : block.block_number)) {
316
+ function ponderBlockInfoToBlockMetadata(block) {
317
+ if (!block) {
318
+ return null;
319
+ }
320
+ if (!block.block_number || !block.block_timestamp) {
273
321
  return null;
274
322
  }
275
- return blockInfo({
323
+ return {
276
324
  number: block.block_number,
277
325
  timestamp: block.block_timestamp
278
- });
326
+ };
279
327
  }
280
328
  export {
281
329
  ponderMetadata
282
330
  };
283
- //# sourceMappingURL=middleware.js.map
331
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/middleware.ts","../src/db-helpers.ts","../src/prometheus-metrics.ts"],"sourcesContent":["import type { EnsRainbow } from \"@ensnode/ensrainbow-sdk\";\nimport { MiddlewareHandler } from \"hono\";\nimport { HTTPException } from \"hono/http-exception\";\n\nimport { queryPonderMeta, queryPonderStatus } from \"./db-helpers\";\nimport { PrometheusMetrics } from \"./prometheus-metrics\";\nimport type {\n PonderEnvVarsInfo,\n PonderMetadataMiddlewareOptions,\n PonderMetadataMiddlewareResponse,\n} from \"./types/api\";\nimport type { BlockInfo, NetworkIndexingStatus, PonderBlockStatus } from \"./types/common\";\n\n/**\n * Ponder Metadata types definition.\n */\ninterface PonderMetadataModule {\n /** Application info */\n AppInfo: {\n /** Application name */\n name: string;\n /** Application version */\n version: string;\n };\n\n /** Environment Variables info */\n EnvVars: {\n /** Database schema */\n DATABASE_SCHEMA: string;\n } & PonderEnvVarsInfo;\n\n /** Runtime info */\n RuntimeInfo: {\n /**\n * Application build id\n * https://github.com/ponder-sh/ponder/blob/626e524/packages/core/src/build/index.ts#L425-L431\n **/\n codebaseBuildId: string;\n\n /** Network indexing status by chain ID */\n networkIndexingStatusByChainId: Record<number, NetworkIndexingStatus>;\n\n /** ENSRainbow version info */\n ensRainbow?: EnsRainbow.VersionInfo;\n };\n}\n\nexport type MetadataMiddlewareResponse = PonderMetadataMiddlewareResponse<\n PonderMetadataModule[\"AppInfo\"],\n PonderMetadataModule[\"EnvVars\"],\n PonderMetadataModule[\"RuntimeInfo\"]\n>;\n\nexport function ponderMetadata<\n AppInfo extends PonderMetadataModule[\"AppInfo\"],\n EnvVars extends PonderMetadataModule[\"EnvVars\"],\n>({\n app,\n db,\n env,\n query,\n publicClients,\n}: PonderMetadataMiddlewareOptions<AppInfo, EnvVars>): MiddlewareHandler {\n return async function ponderMetadataMiddleware(ctx) {\n const indexedChainIds = Object.keys(publicClients).map(Number);\n\n const ponderStatus = await queryPonderStatus(env.DATABASE_SCHEMA, db);\n const metrics = PrometheusMetrics.parse(await query.prometheusMetrics());\n\n const networkIndexingStatusByChainId: Record<number, NetworkIndexingStatus> = {};\n\n for (const indexedChainId of indexedChainIds) {\n const publicClient = publicClients[indexedChainId];\n\n if (!publicClient) {\n throw new HTTPException(500, {\n message: `No public client found for chainId ${indexedChainId}`,\n });\n }\n\n /**\n * Fetches block metadata from blockchain network for a given block number.\n * @param blockNumber\n * @returns block metadata\n * @throws {Error} if failed to fetch block metadata from blockchain network\n */\n const fetchBlockMetadata = async (blockNumber: number): Promise<BlockInfo> => {\n const block = await publicClient.getBlock({\n blockNumber: BigInt(blockNumber),\n });\n\n if (!block) {\n throw new Error(\n `Failed to fetch block metadata for block number ${blockNumber} with chain ID ${indexedChainId}`,\n );\n }\n\n return {\n number: Number(block.number),\n timestamp: Number(block.timestamp),\n } satisfies BlockInfo;\n };\n\n const latestSafeBlockData = await publicClient.getBlock();\n\n if (!latestSafeBlockData) {\n throw new HTTPException(500, {\n message: `Failed to fetch latest safe block for chainId ${indexedChainId}`,\n });\n }\n\n // mapping latest safe block\n const latestSafeBlock = {\n number: Number(latestSafeBlockData.number),\n timestamp: Number(latestSafeBlockData.timestamp),\n } satisfies BlockInfo;\n\n // mapping chain id to its string representation for metric queries\n const network = indexedChainId.toString();\n\n // mapping last synced block if available\n const lastSyncedBlockHeight = metrics.getValue(\"ponder_sync_block\", {\n network,\n });\n let lastSyncedBlock: BlockInfo | null = null;\n if (lastSyncedBlockHeight) {\n try {\n lastSyncedBlock = await fetchBlockMetadata(lastSyncedBlockHeight);\n } catch (error) {\n console.error(\"Failed to fetch block metadata for last synced block\", error);\n }\n }\n\n // mapping ponder status for current network\n const ponderStatusForNetwork = ponderStatus.find(\n (ponderStatusEntry) => ponderStatusEntry.network_name === network,\n );\n\n // mapping last indexed block if available\n let lastIndexedBlock: BlockInfo | null = null;\n if (ponderStatusForNetwork) {\n lastIndexedBlock = ponderBlockInfoToBlockMetadata(ponderStatusForNetwork);\n }\n\n networkIndexingStatusByChainId[indexedChainId] = {\n lastSyncedBlock,\n lastIndexedBlock,\n latestSafeBlock,\n firstBlockToIndex: await query.firstBlockToIndexByChainId(indexedChainId, publicClient),\n } satisfies NetworkIndexingStatus;\n }\n\n // mapping ponder app build id if available\n let ponderAppBuildId: string | undefined;\n try {\n ponderAppBuildId = (await queryPonderMeta(env.DATABASE_SCHEMA, db)).build_id;\n } catch (error) {\n console.error(\"Failed to fetch ponder metadata\", error);\n }\n\n // fetch ENSRainbow version if available\n let ensRainbowVersionInfo = undefined;\n if (query.ensRainbowVersion) {\n try {\n ensRainbowVersionInfo = await query.ensRainbowVersion();\n } catch (error) {\n console.error(\"Failed to fetch ENSRainbow version\", error);\n }\n }\n\n const response = {\n app,\n deps: {\n ponder: formatTextMetricValue(metrics.getLabel(\"ponder_version_info\", \"version\")),\n nodejs: formatTextMetricValue(metrics.getLabel(\"nodejs_version_info\", \"version\")),\n },\n env,\n runtime: {\n codebaseBuildId: formatTextMetricValue(ponderAppBuildId),\n networkIndexingStatusByChainId,\n ensRainbow: ensRainbowVersionInfo,\n },\n } satisfies MetadataMiddlewareResponse;\n\n // validate if response is in correct state\n validateResponse(response);\n\n return ctx.json(response);\n };\n}\n\n/**\n * Validates the metadata middleware response to ensure correct state.\n *\n * @param response The response to validate\n * @throws {HTTPException} if the response is in an invalid state\n */\nfunction validateResponse(response: MetadataMiddlewareResponse): void {\n const { networkIndexingStatusByChainId } = response.runtime;\n\n if (Object.keys(networkIndexingStatusByChainId).length === 0) {\n throw new HTTPException(500, {\n message: \"No network indexing status found\",\n });\n }\n\n if (Object.values(networkIndexingStatusByChainId).some((n) => n.firstBlockToIndex === null)) {\n throw new HTTPException(500, {\n message: \"Failed to fetch first block to index for some networks\",\n });\n }\n}\n\n/**\n * Formats a text metric value.\n * @param value\n * @returns\n */\nfunction formatTextMetricValue(value?: string): string {\n return value ?? \"unknown\";\n}\n\n/**\n * Converts a Ponder block status to a block info object.\n **/\nfunction ponderBlockInfoToBlockMetadata(block: PonderBlockStatus | undefined): BlockInfo | null {\n if (!block) {\n return null;\n }\n\n if (!block.block_number || !block.block_timestamp) {\n return null;\n }\n\n return {\n number: block.block_number,\n timestamp: block.block_timestamp,\n };\n}\n","import { pgSchema, pgTable } from \"drizzle-orm/pg-core\";\nimport { type ReadonlyDrizzle, eq } from \"ponder\";\n\n/**\n * Internal ponder metadata type.\n * Copied from https://github.com/ponder-sh/ponder/blob/32634897bf65e92a85dc4cccdaba70c9425d90f3/packages/core/src/database/index.ts#L94-L102\n */\ntype PonderAppMeta = {\n is_locked: 0 | 1;\n is_dev: 0 | 1;\n heartbeat_at: number;\n build_id: string;\n checkpoint: string;\n table_names: Array<string>;\n version: string;\n};\n\n/**\n * Get DB schema for _ponder_meta table.\n * Akin to https://github.com/ponder-sh/ponder/blob/32634897bf65e92a85dc4cccdaba70c9425d90f3/packages/core/src/database/index.ts#L129-L141\n *\n * @param databaseNamespace A namespace for the database.\n * @returns A table schema for _ponder_meta table.\n * */\nconst getPonderMetaTableSchema = (databaseNamespace: string) => {\n if (databaseNamespace === \"public\") {\n return pgTable(\"_ponder_meta\", (t) => ({\n key: t.text().primaryKey().$type<\"app\">(),\n value: t.jsonb().$type<PonderAppMeta>().notNull(),\n }));\n }\n\n return pgSchema(databaseNamespace).table(\"_ponder_meta\", (t) => ({\n key: t.text().primaryKey().$type<\"app\">(),\n value: t.jsonb().$type<PonderAppMeta>().notNull(),\n }));\n};\n\n/**\n * Get DB schema for _ponder_status table.\n * Akin to https://github.com/ponder-sh/ponder/blob/32634897bf65e92a85dc4cccdaba70c9425d90f3/packages/core/src/database/index.ts#L143-L159\n *\n * @param databaseNamespace A namespace for the database.\n * @returns A table schema for _ponder_status table.\n */\nconst getPonderStatusTableSchema = (databaseNamespace: string) => {\n if (databaseNamespace === \"public\") {\n return pgTable(\"_ponder_status\", (t) => ({\n network_name: t.text().primaryKey(),\n block_number: t.bigint({ mode: \"number\" }),\n block_timestamp: t.bigint({ mode: \"number\" }),\n ready: t.boolean().notNull(),\n }));\n }\n\n return pgSchema(databaseNamespace).table(\"_ponder_status\", (t) => ({\n network_name: t.text().primaryKey(),\n block_number: t.bigint({ mode: \"number\" }),\n block_timestamp: t.bigint({ mode: \"number\" }),\n ready: t.boolean().notNull(),\n }));\n};\n\ntype PonderStatusTableSchema = ReturnType<typeof getPonderStatusTableSchema>;\n\n/**\n * Get a list of ponder status entries for each network.\n *\n * @param namespace A namespace for the database (e.g. \"public\").\n * @param db Drizzle DB Client instance.\n * @returns a list of ponder status entries for each network.\n */\nexport async function queryPonderStatus(\n namespace: string,\n db: ReadonlyDrizzle<Record<string, unknown>>,\n): Promise<Array<PonderStatusTableSchema[\"$inferSelect\"]>> {\n const PONDER_STATUS = getPonderStatusTableSchema(namespace);\n\n return db.select().from(PONDER_STATUS);\n}\n\ntype PonderMetaTableSchema = ReturnType<typeof getPonderMetaTableSchema>;\n\n/**\n * Get ponder metadata for the app.\n *\n * @param namespace A namespace for the database (e.g. \"public\").\n * @param db Drizzle DB Client instance.\n * @returns ponder metadata for the app.\n * @throws Error if ponder metadata not found.\n */\nexport async function queryPonderMeta(\n namespace: string,\n db: ReadonlyDrizzle<Record<string, unknown>>,\n): Promise<PonderMetaTableSchema[\"$inferSelect\"][\"value\"]> {\n const PONDER_META = getPonderMetaTableSchema(namespace);\n\n const [ponderAppMeta] = await db\n .select({ value: PONDER_META.value })\n .from(PONDER_META)\n .where(eq(PONDER_META.key, \"app\"))\n .limit(1);\n\n if (!ponderAppMeta) {\n throw new Error(\"Ponder metadata not found\");\n }\n\n return ponderAppMeta.value;\n}\n","import parsePrometheusTextFormat, { type PrometheusMetric } from \"parse-prometheus-text-format\";\n// Ensures local declaration file is available to downstream consumers\nimport \"./types/parse-prometheus-text-format\";\n\ninterface ParsedPrometheusMetric extends Omit<PrometheusMetric, \"metrics\"> {\n metrics: Array<{\n value: number;\n labels?: Record<string, string>;\n }>;\n}\n\n/**\n * Converts Prometheus text format to JSON format compatible with prom2json\n * @param text Raw Prometheus metric text\n * @returns Array of metrics in prom2json compatible format\n * @example\n * ```ts\n * const metrics = parsePrometheusText(`\n * # HELP ponder_version_info Ponder version information\n * # TYPE ponder_version_info gauge\n * ponder_version_info{version=\"0.9.18\",major=\"0\",minor=\"9\",patch=\"18\"} 1\n * `);\n * // Returns:\n * // [{\n * // name: \"ponder_version_info\",\n * // help: \"Ponder version information\",\n * // type: \"gauge\",\n * // metrics: [{\n * // value: 1,\n * // labels: { version: \"0.9.18\", major: \"0\", minor: \"9\", patch: \"18\" }\n * // }]\n * // }]\n * ```\n */\nexport function parsePrometheusText(text: string): Array<ParsedPrometheusMetric> {\n return parsePrometheusTextFormat(text).map((metric) => ({\n name: metric.name,\n help: metric.help || \"\",\n type: metric.type.toLowerCase(),\n metrics: metric.metrics.map((m) => ({\n value: Number(m.value),\n ...(m.labels && Object.keys(m.labels).length > 0 ? { labels: m.labels } : {}),\n })),\n }));\n}\n\nexport class PrometheusMetrics {\n private constructor(private readonly metrics: Array<ParsedPrometheusMetric>) {}\n\n static parse(maybePrometheusMetricsText: string): PrometheusMetrics {\n return new PrometheusMetrics(parsePrometheusText(maybePrometheusMetricsText));\n }\n\n /**\n * Gets all metrics of a specific name\n * @param name Metric name\n * @returns Array of metrics or undefined if not found\n * @example\n * ```ts\n * const metrics = parser.get('ponder_historical_total_indexing_seconds');\n * // Returns: [\n * // { value: 251224935, labels: { network: \"1\" } },\n * // { value: 251224935, labels: { network: \"8453\" } }\n * // ]\n * ```\n */\n get(name: string): Array<{ value: number; labels?: Record<string, string> }> | undefined {\n const metric = this.metrics.find((m) => m.name === name);\n return metric?.metrics;\n }\n\n /**\n * Gets a single metric value, optionally filtered by labels\n * @param name Metric name\n * @param labelFilter Optional label key-value pairs to match\n * @returns Metric value or undefined if not found\n * @example\n * ```ts\n * // Get simple value\n * parser.getValue('ponder_historical_start_timestamp_seconds') // Returns: 1740391265\n *\n * // Get value with label filter\n * parser.getValue('ponder_historical_total_indexing_seconds', { network: '1' }) // Returns: 251224935\n * ```\n */\n getValue(name: string, labelFilter?: Record<string, string>): number | undefined {\n const metrics = this.get(name);\n\n if (!metrics || metrics.length === 0) {\n return undefined;\n }\n\n if (!labelFilter) {\n return metrics[0]?.value;\n }\n\n const metric = metrics.find(\n (m) => m.labels && Object.entries(labelFilter).every(([k, v]) => m.labels?.[k] === v),\n );\n\n return metric?.value;\n }\n\n /**\n * Gets a label value from a metric\n * @param name Metric name\n * @param label Label name to retrieve\n * @returns Label value or undefined if not found\n * @example\n * ```ts\n * parser.getLabel('ponder_version_info', 'version') // Returns: \"0.9.18\"\n * parser.getLabel('ponder_settings_info', 'ordering') // Returns: \"omnichain\"\n * ```\n */\n getLabel(name: string, label: string): string | undefined {\n return this.getLabels(name, label)[0];\n }\n\n /**\n * Gets all unique label values for a metric\n * @param name Metric name\n * @param label Label name to retrieve\n * @returns Array of unique label values\n * @example\n * ```ts\n * // Get all network IDs\n * parser.getLabels('ponder_historical_total_indexing_seconds', 'network')\n * // Returns: ['1', '8453']\n * ```\n */\n getLabels(name: string, label: string): string[] {\n const metrics = this.get(name);\n\n if (!metrics) return [];\n\n return [\n ...new Set(metrics.map((m) => m.labels?.[label]).filter((v): v is string => v !== undefined)),\n ];\n }\n\n /**\n * Gets help text for a metric\n * @param name Metric name\n * @returns Help text or undefined if not found\n * @example\n * ```ts\n * parser.getHelp('ponder_historical_start_timestamp_seconds')\n * // Returns: \"Timestamp at which historical indexing started\"\n * ```\n */\n getHelp(name: string): string | undefined {\n return this.metrics.find((m) => m.name === name)?.help;\n }\n\n /**\n * Gets metric type\n * @param name Metric name\n * @returns Metric type or undefined if not found\n * @example\n * ```ts\n * parser.getType('ponder_version_info') // Returns: \"gauge\"\n * parser.getType('ponder_postgres_query_total') // Returns: \"counter\"\n * ```\n */\n getType(name: string): string | undefined {\n return this.metrics.find((m) => m.name === name)?.type;\n }\n\n /**\n * Gets all metric names\n * @returns Array of metric names\n * @example\n * ```ts\n * parser.getMetricNames()\n * // Returns: [\n * // 'ponder_version_info',\n * // 'ponder_settings_info',\n * // 'ponder_historical_start_timestamp_seconds',\n * // 'ponder_historical_total_indexing_seconds'\n * // ]\n * ```\n */\n getMetricNames(): string[] {\n return this.metrics.map((m) => m.name);\n }\n}\n"],"mappings":"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","names":["_a"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ensnode/ponder-metadata",
3
- "version": "0.1.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "description": "A Hono middleware for making Ponder app metadata available to clients.",
6
6
  "license": "MIT",
@@ -13,36 +13,37 @@
13
13
  "keywords": [
14
14
  "Ponder"
15
15
  ],
16
- "exports": {
17
- "./middleware": {
18
- "types": "./dist/middleware.d.ts",
19
- "default": "./dist/middleware.js"
20
- }
21
- },
22
16
  "files": [
23
17
  "dist"
24
18
  ],
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "default": "./dist/index.js"
23
+ }
24
+ },
25
25
  "publishConfig": {
26
26
  "access": "public"
27
27
  },
28
28
  "dependencies": {
29
29
  "drizzle-orm": "^0.39.3",
30
30
  "parse-prometheus-text-format": "^1.1.1",
31
- "viem": "^2.22.13"
31
+ "viem": "^2.22.13",
32
+ "@ensnode/ensrainbow-sdk": "0.7.0"
32
33
  },
33
34
  "devDependencies": {
34
35
  "@biomejs/biome": "^1.9.4",
35
- "@ensnode/shared-configs": "",
36
36
  "@types/node": "^20.10.0",
37
37
  "hono": "^4.6.14",
38
- "ponder": "^0.9.18",
38
+ "ponder": "^0.9.27",
39
39
  "tsup": "^8.3.6",
40
40
  "typescript": "^5.7.3",
41
- "vitest": "^3.0.5"
41
+ "vitest": "^3.1.1",
42
+ "@ensnode/shared-configs": "0.7.0"
42
43
  },
43
44
  "peerDependencies": {
44
45
  "hono": "^4.6.14",
45
- "ponder": "^0.9.18"
46
+ "ponder": "^0.9.27"
46
47
  },
47
48
  "scripts": {
48
49
  "prepublish": "tsup",
@@ -51,7 +52,7 @@
51
52
  "lint": "biome check --write",
52
53
  "lint:ci": "biome ci"
53
54
  },
54
- "main": "./dist/middleware.js",
55
- "module": "./dist/middleware.mjs",
56
- "types": "./dist/middleware.d.ts"
55
+ "main": "./dist/index.js",
56
+ "module": "./dist/index.mjs",
57
+ "types": "./dist/index.d.ts"
57
58
  }
@@ -1,18 +0,0 @@
1
- import { MiddlewareHandler } from 'hono';
2
- import { ReadonlyDrizzle } from 'ponder';
3
- import { PublicClient } from 'viem';
4
-
5
- declare function ponderMetadata({ app, db, env, fetchPrometheusMetrics, publicClients, }: {
6
- db: ReadonlyDrizzle<Record<string, unknown>>;
7
- app: {
8
- name: string;
9
- version: string;
10
- };
11
- env: {
12
- DATABASE_SCHEMA: string;
13
- } & Record<string, unknown>;
14
- fetchPrometheusMetrics: () => Promise<string>;
15
- publicClients: Record<number, PublicClient>;
16
- }): MiddlewareHandler;
17
-
18
- export { ponderMetadata };
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/db-helpers.ts","../src/prometheus-metrics.ts","../src/middleware.ts"],"sourcesContent":["import { pgSchema, pgTable } from \"drizzle-orm/pg-core\";\nimport { type ReadonlyDrizzle, eq } from \"ponder\";\n\ntype NamespaceBuild = string;\n\ntype PonderApp = {\n is_locked: 0 | 1;\n is_dev: 0 | 1;\n heartbeat_at: number;\n build_id: string;\n checkpoint: string;\n table_names: string[];\n version: string;\n};\n\nconst getPonderMeta = (namespace: NamespaceBuild) => {\n if (namespace === \"public\") {\n return pgTable(\"_ponder_meta\", (t) => ({\n key: t.text().primaryKey().$type<\"app\">(),\n value: t.jsonb().$type<PonderApp>().notNull(),\n }));\n }\n\n return pgSchema(namespace).table(\"_ponder_meta\", (t) => ({\n key: t.text().primaryKey().$type<\"app\">(),\n value: t.jsonb().$type<PonderApp>().notNull(),\n }));\n};\n\nconst getPonderStatus = (namespace: NamespaceBuild) => {\n if (namespace === \"public\") {\n return pgTable(\"_ponder_status\", (t) => ({\n network_name: t.text().primaryKey(),\n block_number: t.bigint({ mode: \"number\" }),\n block_timestamp: t.bigint({ mode: \"number\" }),\n ready: t.boolean().notNull(),\n }));\n }\n\n return pgSchema(namespace).table(\"_ponder_status\", (t) => ({\n network_name: t.text().primaryKey(),\n block_number: t.bigint({ mode: \"number\" }),\n block_timestamp: t.bigint({ mode: \"number\" }),\n ready: t.boolean().notNull(),\n }));\n};\n\nexport async function queryPonderStatus(\n namespace: string,\n db: ReadonlyDrizzle<Record<string, unknown>>,\n) {\n const PONDER_STATUS = getPonderStatus(namespace);\n\n return db.select().from(PONDER_STATUS);\n}\n\nexport async function queryPonderMeta(\n namespace: string,\n db: ReadonlyDrizzle<Record<string, unknown>>,\n) {\n const PONDER_META = getPonderMeta(namespace);\n\n return db\n .select({ value: PONDER_META.value })\n .from(PONDER_META)\n .where(eq(PONDER_META.key, \"app\"))\n .limit(1)\n .then((result: any) => result[0]?.value);\n}\n","import parsePrometheusTextFormat from \"parse-prometheus-text-format\";\nimport type { PrometheusMetric } from \"parse-prometheus-text-format\";\n// Ensures local declaration file is available to downstream consumers\nimport \"./types/parse-prometheus-text-format\";\n\ninterface ParsedPrometheusMetric extends Omit<PrometheusMetric, \"metrics\"> {\n metrics: Array<{\n value: number;\n labels?: Record<string, string>;\n }>;\n}\n\n/**\n * Converts Prometheus text format to JSON format compatible with prom2json\n * @param text Raw Prometheus metric text\n * @returns Array of metrics in prom2json compatible format\n * @example\n * ```ts\n * const metrics = parsePrometheusText(`\n * # HELP ponder_version_info Ponder version information\n * # TYPE ponder_version_info gauge\n * ponder_version_info{version=\"0.9.18\",major=\"0\",minor=\"9\",patch=\"18\"} 1\n * `);\n * // Returns:\n * // [{\n * // name: \"ponder_version_info\",\n * // help: \"Ponder version information\",\n * // type: \"gauge\",\n * // metrics: [{\n * // value: 1,\n * // labels: { version: \"0.9.18\", major: \"0\", minor: \"9\", patch: \"18\" }\n * // }]\n * // }]\n * ```\n */\nexport function parsePrometheusText(text: string): Array<ParsedPrometheusMetric> {\n return parsePrometheusTextFormat(text).map((metric) => ({\n name: metric.name,\n help: metric.help || \"\",\n type: metric.type.toLowerCase(),\n metrics: metric.metrics.map((m) => ({\n value: Number(m.value),\n ...(m.labels && Object.keys(m.labels).length > 0 ? { labels: m.labels } : {}),\n })),\n }));\n}\n\nexport class PrometheusMetrics {\n private constructor(private readonly metrics: Array<ParsedPrometheusMetric>) {}\n\n static parse(maybePrometheusMetricsText: string): PrometheusMetrics {\n return new PrometheusMetrics(parsePrometheusText(maybePrometheusMetricsText));\n }\n\n /**\n * Gets all metrics of a specific name\n * @param name Metric name\n * @returns Array of metrics or undefined if not found\n * @example\n * ```ts\n * const metrics = parser.get('ponder_historical_total_indexing_seconds');\n * // Returns: [\n * // { value: 251224935, labels: { network: \"1\" } },\n * // { value: 251224935, labels: { network: \"8453\" } }\n * // ]\n * ```\n */\n get(name: string): Array<{ value: number; labels?: Record<string, string> }> | undefined {\n const metric = this.metrics.find((m) => m.name === name);\n return metric?.metrics;\n }\n\n /**\n * Gets a single metric value, optionally filtered by labels\n * @param name Metric name\n * @param labelFilter Optional label key-value pairs to match\n * @returns Metric value or undefined if not found\n * @example\n * ```ts\n * // Get simple value\n * parser.getValue('ponder_historical_start_timestamp_seconds') // Returns: 1740391265\n *\n * // Get value with label filter\n * parser.getValue('ponder_historical_total_indexing_seconds', { network: '1' }) // Returns: 251224935\n * ```\n */\n getValue(name: string, labelFilter?: Record<string, string>): number | undefined {\n const metrics = this.get(name);\n\n if (!metrics || metrics.length === 0) {\n return undefined;\n }\n\n if (!labelFilter) {\n return metrics[0]?.value;\n }\n\n const metric = metrics.find(\n (m) => m.labels && Object.entries(labelFilter).every(([k, v]) => m.labels?.[k] === v),\n );\n\n return metric?.value;\n }\n\n /**\n * Gets a label value from a metric\n * @param name Metric name\n * @param label Label name to retrieve\n * @returns Label value or undefined if not found\n * @example\n * ```ts\n * parser.getLabel('ponder_version_info', 'version') // Returns: \"0.9.18\"\n * parser.getLabel('ponder_settings_info', 'ordering') // Returns: \"omnichain\"\n * ```\n */\n getLabel(name: string, label: string): string | undefined {\n return this.getLabels(name, label)[0];\n }\n\n /**\n * Gets all unique label values for a metric\n * @param name Metric name\n * @param label Label name to retrieve\n * @returns Array of unique label values\n * @example\n * ```ts\n * // Get all network IDs\n * parser.getLabels('ponder_historical_total_indexing_seconds', 'network')\n * // Returns: ['1', '8453']\n * ```\n */\n getLabels(name: string, label: string): string[] {\n const metrics = this.get(name);\n\n if (!metrics) return [];\n\n return [\n ...new Set(metrics.map((m) => m.labels?.[label]).filter((v): v is string => v !== undefined)),\n ];\n }\n\n /**\n * Gets help text for a metric\n * @param name Metric name\n * @returns Help text or undefined if not found\n * @example\n * ```ts\n * parser.getHelp('ponder_historical_start_timestamp_seconds')\n * // Returns: \"Timestamp at which historical indexing started\"\n * ```\n */\n getHelp(name: string): string | undefined {\n return this.metrics.find((m) => m.name === name)?.help;\n }\n\n /**\n * Gets metric type\n * @param name Metric name\n * @returns Metric type or undefined if not found\n * @example\n * ```ts\n * parser.getType('ponder_version_info') // Returns: \"gauge\"\n * parser.getType('ponder_postgres_query_total') // Returns: \"counter\"\n * ```\n */\n getType(name: string): string | undefined {\n return this.metrics.find((m) => m.name === name)?.type;\n }\n\n /**\n * Gets all metric names\n * @returns Array of metric names\n * @example\n * ```ts\n * parser.getMetricNames()\n * // Returns: [\n * // 'ponder_version_info',\n * // 'ponder_settings_info',\n * // 'ponder_historical_start_timestamp_seconds',\n * // 'ponder_historical_total_indexing_seconds'\n * // ]\n * ```\n */\n getMetricNames(): string[] {\n return this.metrics.map((m) => m.name);\n }\n}\n","import { MiddlewareHandler } from \"hono\";\nimport { ReadonlyDrizzle } from \"ponder\";\nimport { PublicClient } from \"viem\";\nimport { queryPonderMeta, queryPonderStatus } from \"./db-helpers\";\nimport { PrometheusMetrics } from \"./prometheus-metrics\";\nimport type { BlockMetadata, NetworkIndexingStatus } from \"./types/common\";\n\nexport function ponderMetadata({\n app,\n db,\n env,\n fetchPrometheusMetrics,\n publicClients,\n}: {\n db: ReadonlyDrizzle<Record<string, unknown>>;\n app: {\n name: string;\n version: string;\n };\n env: {\n DATABASE_SCHEMA: string;\n } & Record<string, unknown>;\n fetchPrometheusMetrics: () => Promise<string>;\n publicClients: Record<number, PublicClient>;\n}): MiddlewareHandler {\n return async function ponderMetadataMiddleware(ctx) {\n const indexedChainIds = Object.keys(publicClients).map(Number);\n\n const ponderStatus = await queryPonderStatus(env.DATABASE_SCHEMA, db);\n const ponderMeta = await queryPonderMeta(env.DATABASE_SCHEMA, db);\n const metrics = PrometheusMetrics.parse(await fetchPrometheusMetrics());\n\n const networkIndexingStatus: Record<string, NetworkIndexingStatus> = {};\n\n for (const indexedChainId of indexedChainIds) {\n const publicClient = publicClients[indexedChainId];\n\n if (!publicClient) {\n throw new Error(`No public client found for chainId ${indexedChainId}`);\n }\n\n const latestSafeBlock = await publicClient.getBlock();\n\n const network = indexedChainId.toString();\n const ponderStatusForNetwork = ponderStatus.find((s) => s.network_name === network);\n const lastIndexedBlock = mapPonderStatusBlockToBlockMetadata(ponderStatusForNetwork);\n\n networkIndexingStatus[network] = {\n totalBlocksCount:\n metrics.getValue(\"ponder_historical_total_blocks\", {\n network,\n }) ?? null,\n cachedBlocksCount:\n metrics.getValue(\"ponder_historical_cached_blocks\", {\n network,\n }) ?? null,\n lastSyncedBlock: blockInfo({\n number:\n metrics.getValue(\"ponder_sync_block\", {\n network,\n }) ?? 0,\n timestamp: 0,\n }),\n latestSafeBlock: blockInfo({\n number: Number(latestSafeBlock.number),\n timestamp: Number(latestSafeBlock.timestamp),\n }),\n lastIndexedBlock,\n isRealtime: Boolean(metrics.getValue(\"ponder_sync_is_realtime\", { network })),\n isComplete: Boolean(metrics.getValue(\"ponder_sync_is_complete\", { network })),\n isQueued: lastIndexedBlock === null,\n } satisfies NetworkIndexingStatus;\n }\n\n return ctx.json({\n app,\n // application dependencies version\n deps: {\n ponder: metrics.getLabel(\"ponder_version_info\", \"version\"),\n nodejs: metrics.getLabel(\"nodejs_version_info\", \"version\"),\n },\n // application environment variables\n env,\n // application runtime information\n runtime: {\n // application build id\n // https://github.com/ponder-sh/ponder/blob/626e524/packages/core/src/build/index.ts#L425-L431\n codebaseBuildId: ponderMeta?.build_id,\n // tableNames: meta?.table_names,\n networkIndexingStatus,\n },\n });\n };\n}\n\nfunction blockInfo(block: {\n number: number | null;\n timestamp: number | null;\n}): BlockMetadata | null {\n if (!block.number) {\n return null;\n }\n\n return {\n height: block.number,\n timestamp: block.timestamp ?? 0,\n utc: block.timestamp ? new Date(block.timestamp * 1000).toISOString() : \"\",\n };\n}\n\nfunction mapPonderStatusBlockToBlockMetadata(\n block:\n | {\n block_number: number | null;\n block_timestamp: number | null;\n }\n | undefined,\n): BlockMetadata | null {\n if (!block?.block_number) {\n return null;\n }\n\n return blockInfo({\n number: block.block_number,\n timestamp: block.block_timestamp,\n });\n}\n"],"mappings":"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","names":["_a"]}