@latticexyz/store-indexer 2.0.10-main-51b137d3 → 2.0.10-main-0ae9189c

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.
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{b as m,c as u}from"../chunk-7B4KE2DO.js";import"dotenv/config";import{z as a}from"zod";import{eq as C}from"drizzle-orm";import{createPublicClient as g,fallback as _,webSocket as h,http as A}from"viem";import{isDefined as E}from"@latticexyz/common/utils";import{combineLatest as R,filter as S,first as O}from"rxjs";import{drizzle as k}from"drizzle-orm/postgres-js";import w from"postgres";import{cleanDatabase as B,createStorageAdapter as P,shouldCleanDatabase as K}from"@latticexyz/store-sync/postgres";import{createStoreSync as N}from"@latticexyz/store-sync";var t=u(a.intersection(m,a.object({DATABASE_URL:a.string(),HEALTHCHECK_HOST:a.string().optional(),HEALTHCHECK_PORT:a.coerce.number().optional()}))),D=[t.RPC_WS_URL?h(t.RPC_WS_URL):void 0,t.RPC_HTTP_URL?A(t.RPC_HTTP_URL):void 0].filter(E),n=g({transport:_(D),pollingInterval:t.POLLING_INTERVAL}),T=await n.getChainId(),i=k(w(t.DATABASE_URL,{prepare:!1}));await K(i,T)&&(console.log("outdated database detected, clearing data to start fresh"),await B(i));var{storageAdapter:v,tables:p}=await P({database:i,publicClient:n}),s=t.START_BLOCK;async function c(){try{return(await i.select().from(p.configTable).where(C(p.configTable.chainId,T)).limit(1).execute().then(o=>o.find(()=>!0)))?.blockNumber}catch{}}async function y(){let[e,o]=await Promise.all([c(),n.getBlock({blockTag:t.FOLLOW_BLOCK_TAG})]);return(e??-1n)-o.number}var d=await c();d!=null&&(s=d+1n,console.log("resuming from block number",s));var{latestBlockNumber$:U,storedBlockLogs$:f}=await N({storageAdapter:v,publicClient:n,followBlockTag:t.FOLLOW_BLOCK_TAG,startBlock:s,maxBlockRange:t.MAX_BLOCK_RANGE,address:t.STORE_ADDRESS});f.subscribe();var l=!1;R([U,f]).pipe(S(([e,{blockNumber:o}])=>e===o),O()).subscribe(()=>{l=!0,console.log("all caught up")});if(t.HEALTHCHECK_HOST!=null||t.HEALTHCHECK_PORT!=null){let{default:e}=await import("koa"),{default:o}=await import("@koa/cors"),{healthcheck:b}=await import("../healthcheck-7XXWJH5U.js"),{metrics:H}=await import("../metrics-Q6NJ3DNN.js"),{helloWorld:L}=await import("../helloWorld-BMBNVEA7.js"),r=new e;r.use(o()),r.use(b({isReady:()=>l})),r.use(H({isHealthy:()=>!0,isReady:()=>l,getLatestStoredBlockNumber:c,getDistanceFromFollowBlock:y,followBlockTag:t.FOLLOW_BLOCK_TAG})),r.use(L()),r.listen({host:t.HEALTHCHECK_HOST,port:t.HEALTHCHECK_PORT}),console.log(`postgres indexer healthcheck server listening on http://${t.HEALTHCHECK_HOST}:${t.HEALTHCHECK_PORT}`)}
2
+ import{b as m,c as u}from"../chunk-7B4KE2DO.js";import"dotenv/config";import{z as a}from"zod";import{eq as C}from"drizzle-orm";import{createPublicClient as g,fallback as _,webSocket as h,http as A}from"viem";import{isDefined as E}from"@latticexyz/common/utils";import{combineLatest as R,filter as S,first as O}from"rxjs";import{drizzle as k}from"drizzle-orm/postgres-js";import w from"postgres";import{cleanDatabase as B,createStorageAdapter as P,shouldCleanDatabase as K}from"@latticexyz/store-sync/postgres";import{createStoreSync as N}from"@latticexyz/store-sync";var t=u(a.intersection(m,a.object({DATABASE_URL:a.string(),HEALTHCHECK_HOST:a.string().optional(),HEALTHCHECK_PORT:a.coerce.number().optional()}))),D=[t.RPC_WS_URL?h(t.RPC_WS_URL):void 0,t.RPC_HTTP_URL?A(t.RPC_HTTP_URL):void 0].filter(E),n=g({transport:_(D),pollingInterval:t.POLLING_INTERVAL}),T=await n.getChainId(),i=k(w(t.DATABASE_URL,{prepare:!1}));await K(i,T)&&(console.log("outdated database detected, clearing data to start fresh"),await B(i));var{storageAdapter:v,tables:p}=await P({database:i,publicClient:n}),s=t.START_BLOCK;async function c(){try{return(await i.select().from(p.configTable).where(C(p.configTable.chainId,T)).limit(1).execute().then(o=>o.find(()=>!0)))?.blockNumber}catch{}}async function y(){let[e,o]=await Promise.all([c(),n.getBlock({blockTag:t.FOLLOW_BLOCK_TAG})]);return o.number-(e??-1n)}var d=await c();d!=null&&(s=d+1n,console.log("resuming from block number",s));var{latestBlockNumber$:U,storedBlockLogs$:f}=await N({storageAdapter:v,publicClient:n,followBlockTag:t.FOLLOW_BLOCK_TAG,startBlock:s,maxBlockRange:t.MAX_BLOCK_RANGE,address:t.STORE_ADDRESS});f.subscribe();var l=!1;R([U,f]).pipe(S(([e,{blockNumber:o}])=>e===o),O()).subscribe(()=>{l=!0,console.log("all caught up")});if(t.HEALTHCHECK_HOST!=null||t.HEALTHCHECK_PORT!=null){let{default:e}=await import("koa"),{default:o}=await import("@koa/cors"),{healthcheck:b}=await import("../healthcheck-7XXWJH5U.js"),{metrics:H}=await import("../metrics-Q6NJ3DNN.js"),{helloWorld:L}=await import("../helloWorld-BMBNVEA7.js"),r=new e;r.use(o()),r.use(b({isReady:()=>l})),r.use(H({isHealthy:()=>!0,isReady:()=>l,getLatestStoredBlockNumber:c,getDistanceFromFollowBlock:y,followBlockTag:t.FOLLOW_BLOCK_TAG})),r.use(L()),r.listen({host:t.HEALTHCHECK_HOST,port:t.HEALTHCHECK_PORT}),console.log(`postgres indexer healthcheck server listening on http://${t.HEALTHCHECK_HOST}:${t.HEALTHCHECK_PORT}`)}
3
3
  //# sourceMappingURL=postgres-indexer.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../bin/postgres-indexer.ts"],"sourcesContent":["#!/usr/bin/env node\nimport \"dotenv/config\";\nimport { z } from \"zod\";\nimport { eq } from \"drizzle-orm\";\nimport { createPublicClient, fallback, webSocket, http, Transport } from \"viem\";\nimport { isDefined } from \"@latticexyz/common/utils\";\nimport { combineLatest, filter, first } from \"rxjs\";\nimport { drizzle } from \"drizzle-orm/postgres-js\";\nimport postgres from \"postgres\";\nimport { cleanDatabase, createStorageAdapter, shouldCleanDatabase } from \"@latticexyz/store-sync/postgres\";\nimport { createStoreSync } from \"@latticexyz/store-sync\";\nimport { indexerEnvSchema, parseEnv } from \"./parseEnv\";\n\nconst env = parseEnv(\n z.intersection(\n indexerEnvSchema,\n z.object({\n DATABASE_URL: z.string(),\n HEALTHCHECK_HOST: z.string().optional(),\n HEALTHCHECK_PORT: z.coerce.number().optional(),\n }),\n ),\n);\n\nconst transports: Transport[] = [\n // prefer WS when specified\n env.RPC_WS_URL ? webSocket(env.RPC_WS_URL) : undefined,\n // otherwise use or fallback to HTTP\n env.RPC_HTTP_URL ? http(env.RPC_HTTP_URL) : undefined,\n].filter(isDefined);\n\nconst publicClient = createPublicClient({\n transport: fallback(transports),\n pollingInterval: env.POLLING_INTERVAL,\n});\n\nconst chainId = await publicClient.getChainId();\nconst database = drizzle(postgres(env.DATABASE_URL, { prepare: false }));\n\nif (await shouldCleanDatabase(database, chainId)) {\n console.log(\"outdated database detected, clearing data to start fresh\");\n await cleanDatabase(database);\n}\n\nconst { storageAdapter, tables } = await createStorageAdapter({ database, publicClient });\n\nlet startBlock = env.START_BLOCK;\n\nasync function getLatestStoredBlockNumber(): Promise<bigint | undefined> {\n // Fetch latest block stored in DB. This will throw if the DB doesn't exist yet, so we wrap in a try/catch and ignore the error.\n // TODO: query if the DB exists instead of try/catch\n try {\n const chainState = await database\n .select()\n .from(tables.configTable)\n .where(eq(tables.configTable.chainId, chainId))\n .limit(1)\n .execute()\n // Get the first record in a way that returns a possible `undefined`\n // TODO: move this to `.findFirst` after upgrading drizzle or `rows[0]` after enabling `noUncheckedIndexedAccess: true`\n .then((rows) => rows.find(() => true));\n\n return chainState?.blockNumber;\n } catch (error) {\n // ignore errors for now\n }\n}\n\nasync function getDistanceFromFollowBlock(): Promise<bigint> {\n const [latestStoredBlockNumber, latestFollowBlock] = await Promise.all([\n getLatestStoredBlockNumber(),\n publicClient.getBlock({ blockTag: env.FOLLOW_BLOCK_TAG }),\n ]);\n return (latestStoredBlockNumber ?? -1n) - latestFollowBlock.number;\n}\n\nconst latestStoredBlockNumber = await getLatestStoredBlockNumber();\nif (latestStoredBlockNumber != null) {\n startBlock = latestStoredBlockNumber + 1n;\n console.log(\"resuming from block number\", startBlock);\n}\n\nconst { latestBlockNumber$, storedBlockLogs$ } = await createStoreSync({\n storageAdapter,\n publicClient,\n followBlockTag: env.FOLLOW_BLOCK_TAG,\n startBlock,\n maxBlockRange: env.MAX_BLOCK_RANGE,\n address: env.STORE_ADDRESS,\n});\n\nstoredBlockLogs$.subscribe();\n\nlet isCaughtUp = false;\ncombineLatest([latestBlockNumber$, storedBlockLogs$])\n .pipe(\n filter(\n ([latestBlockNumber, { blockNumber: lastBlockNumberProcessed }]) =>\n latestBlockNumber === lastBlockNumberProcessed,\n ),\n first(),\n )\n .subscribe(() => {\n isCaughtUp = true;\n console.log(\"all caught up\");\n });\n\nif (env.HEALTHCHECK_HOST != null || env.HEALTHCHECK_PORT != null) {\n const { default: Koa } = await import(\"koa\");\n const { default: cors } = await import(\"@koa/cors\");\n const { healthcheck } = await import(\"../src/koa-middleware/healthcheck\");\n const { metrics } = await import(\"../src/koa-middleware/metrics\");\n const { helloWorld } = await import(\"../src/koa-middleware/helloWorld\");\n\n const server = new Koa();\n\n server.use(cors());\n server.use(\n healthcheck({\n isReady: () => isCaughtUp,\n }),\n );\n server.use(\n metrics({\n isHealthy: () => true,\n isReady: () => isCaughtUp,\n getLatestStoredBlockNumber,\n getDistanceFromFollowBlock,\n followBlockTag: env.FOLLOW_BLOCK_TAG,\n }),\n );\n server.use(helloWorld());\n\n server.listen({ host: env.HEALTHCHECK_HOST, port: env.HEALTHCHECK_PORT });\n console.log(\n `postgres indexer healthcheck server listening on http://${env.HEALTHCHECK_HOST}:${env.HEALTHCHECK_PORT}`,\n );\n}\n"],"mappings":"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","names":["z","eq","createPublicClient","fallback","webSocket","http","isDefined","combineLatest","filter","first","drizzle","postgres","cleanDatabase","createStorageAdapter","shouldCleanDatabase","createStoreSync","env","parseEnv","z","indexerEnvSchema","transports","webSocket","http","isDefined","publicClient","createPublicClient","fallback","chainId","database","drizzle","postgres","shouldCleanDatabase","cleanDatabase","storageAdapter","tables","createStorageAdapter","startBlock","getLatestStoredBlockNumber","eq","rows","getDistanceFromFollowBlock","latestStoredBlockNumber","latestFollowBlock","latestBlockNumber$","storedBlockLogs$","createStoreSync","isCaughtUp","combineLatest","filter","latestBlockNumber","lastBlockNumberProcessed","first","Koa","cors","healthcheck","metrics","helloWorld","server"]}
1
+ {"version":3,"sources":["../../bin/postgres-indexer.ts"],"sourcesContent":["#!/usr/bin/env node\nimport \"dotenv/config\";\nimport { z } from \"zod\";\nimport { eq } from \"drizzle-orm\";\nimport { createPublicClient, fallback, webSocket, http, Transport } from \"viem\";\nimport { isDefined } from \"@latticexyz/common/utils\";\nimport { combineLatest, filter, first } from \"rxjs\";\nimport { drizzle } from \"drizzle-orm/postgres-js\";\nimport postgres from \"postgres\";\nimport { cleanDatabase, createStorageAdapter, shouldCleanDatabase } from \"@latticexyz/store-sync/postgres\";\nimport { createStoreSync } from \"@latticexyz/store-sync\";\nimport { indexerEnvSchema, parseEnv } from \"./parseEnv\";\n\nconst env = parseEnv(\n z.intersection(\n indexerEnvSchema,\n z.object({\n DATABASE_URL: z.string(),\n HEALTHCHECK_HOST: z.string().optional(),\n HEALTHCHECK_PORT: z.coerce.number().optional(),\n }),\n ),\n);\n\nconst transports: Transport[] = [\n // prefer WS when specified\n env.RPC_WS_URL ? webSocket(env.RPC_WS_URL) : undefined,\n // otherwise use or fallback to HTTP\n env.RPC_HTTP_URL ? http(env.RPC_HTTP_URL) : undefined,\n].filter(isDefined);\n\nconst publicClient = createPublicClient({\n transport: fallback(transports),\n pollingInterval: env.POLLING_INTERVAL,\n});\n\nconst chainId = await publicClient.getChainId();\nconst database = drizzle(postgres(env.DATABASE_URL, { prepare: false }));\n\nif (await shouldCleanDatabase(database, chainId)) {\n console.log(\"outdated database detected, clearing data to start fresh\");\n await cleanDatabase(database);\n}\n\nconst { storageAdapter, tables } = await createStorageAdapter({ database, publicClient });\n\nlet startBlock = env.START_BLOCK;\n\nasync function getLatestStoredBlockNumber(): Promise<bigint | undefined> {\n // Fetch latest block stored in DB. This will throw if the DB doesn't exist yet, so we wrap in a try/catch and ignore the error.\n // TODO: query if the DB exists instead of try/catch\n try {\n const chainState = await database\n .select()\n .from(tables.configTable)\n .where(eq(tables.configTable.chainId, chainId))\n .limit(1)\n .execute()\n // Get the first record in a way that returns a possible `undefined`\n // TODO: move this to `.findFirst` after upgrading drizzle or `rows[0]` after enabling `noUncheckedIndexedAccess: true`\n .then((rows) => rows.find(() => true));\n\n return chainState?.blockNumber;\n } catch (error) {\n // ignore errors for now\n }\n}\n\nasync function getDistanceFromFollowBlock(): Promise<bigint> {\n const [latestStoredBlockNumber, latestFollowBlock] = await Promise.all([\n getLatestStoredBlockNumber(),\n publicClient.getBlock({ blockTag: env.FOLLOW_BLOCK_TAG }),\n ]);\n return latestFollowBlock.number - (latestStoredBlockNumber ?? -1n);\n}\n\nconst latestStoredBlockNumber = await getLatestStoredBlockNumber();\nif (latestStoredBlockNumber != null) {\n startBlock = latestStoredBlockNumber + 1n;\n console.log(\"resuming from block number\", startBlock);\n}\n\nconst { latestBlockNumber$, storedBlockLogs$ } = await createStoreSync({\n storageAdapter,\n publicClient,\n followBlockTag: env.FOLLOW_BLOCK_TAG,\n startBlock,\n maxBlockRange: env.MAX_BLOCK_RANGE,\n address: env.STORE_ADDRESS,\n});\n\nstoredBlockLogs$.subscribe();\n\nlet isCaughtUp = false;\ncombineLatest([latestBlockNumber$, storedBlockLogs$])\n .pipe(\n filter(\n ([latestBlockNumber, { blockNumber: lastBlockNumberProcessed }]) =>\n latestBlockNumber === lastBlockNumberProcessed,\n ),\n first(),\n )\n .subscribe(() => {\n isCaughtUp = true;\n console.log(\"all caught up\");\n });\n\nif (env.HEALTHCHECK_HOST != null || env.HEALTHCHECK_PORT != null) {\n const { default: Koa } = await import(\"koa\");\n const { default: cors } = await import(\"@koa/cors\");\n const { healthcheck } = await import(\"../src/koa-middleware/healthcheck\");\n const { metrics } = await import(\"../src/koa-middleware/metrics\");\n const { helloWorld } = await import(\"../src/koa-middleware/helloWorld\");\n\n const server = new Koa();\n\n server.use(cors());\n server.use(\n healthcheck({\n isReady: () => isCaughtUp,\n }),\n );\n server.use(\n metrics({\n isHealthy: () => true,\n isReady: () => isCaughtUp,\n getLatestStoredBlockNumber,\n getDistanceFromFollowBlock,\n followBlockTag: env.FOLLOW_BLOCK_TAG,\n }),\n );\n server.use(helloWorld());\n\n server.listen({ host: env.HEALTHCHECK_HOST, port: env.HEALTHCHECK_PORT });\n console.log(\n `postgres indexer healthcheck server listening on http://${env.HEALTHCHECK_HOST}:${env.HEALTHCHECK_PORT}`,\n );\n}\n"],"mappings":";gDACA,MAAO,gBACP,OAAS,KAAAA,MAAS,MAClB,OAAS,MAAAC,MAAU,cACnB,OAAS,sBAAAC,EAAoB,YAAAC,EAAU,aAAAC,EAAW,QAAAC,MAAuB,OACzE,OAAS,aAAAC,MAAiB,2BAC1B,OAAS,iBAAAC,EAAe,UAAAC,EAAQ,SAAAC,MAAa,OAC7C,OAAS,WAAAC,MAAe,0BACxB,OAAOC,MAAc,WACrB,OAAS,iBAAAC,EAAe,wBAAAC,EAAsB,uBAAAC,MAA2B,kCACzE,OAAS,mBAAAC,MAAuB,yBAGhC,IAAMC,EAAMC,EACVC,EAAE,aACAC,EACAD,EAAE,OAAO,CACP,aAAcA,EAAE,OAAO,EACvB,iBAAkBA,EAAE,OAAO,EAAE,SAAS,EACtC,iBAAkBA,EAAE,OAAO,OAAO,EAAE,SAAS,CAC/C,CAAC,CACH,CACF,EAEME,EAA0B,CAE9BJ,EAAI,WAAaK,EAAUL,EAAI,UAAU,EAAI,OAE7CA,EAAI,aAAeM,EAAKN,EAAI,YAAY,EAAI,MAC9C,EAAE,OAAOO,CAAS,EAEZC,EAAeC,EAAmB,CACtC,UAAWC,EAASN,CAAU,EAC9B,gBAAiBJ,EAAI,gBACvB,CAAC,EAEKW,EAAU,MAAMH,EAAa,WAAW,EACxCI,EAAWC,EAAQC,EAASd,EAAI,aAAc,CAAE,QAAS,EAAM,CAAC,CAAC,EAEnE,MAAMe,EAAoBH,EAAUD,CAAO,IAC7C,QAAQ,IAAI,0DAA0D,EACtE,MAAMK,EAAcJ,CAAQ,GAG9B,GAAM,CAAE,eAAAK,EAAgB,OAAAC,CAAO,EAAI,MAAMC,EAAqB,CAAE,SAAAP,EAAU,aAAAJ,CAAa,CAAC,EAEpFY,EAAapB,EAAI,YAErB,eAAeqB,GAA0D,CAGvE,GAAI,CAWF,OAVmB,MAAMT,EACtB,OAAO,EACP,KAAKM,EAAO,WAAW,EACvB,MAAMI,EAAGJ,EAAO,YAAY,QAASP,CAAO,CAAC,EAC7C,MAAM,CAAC,EACP,QAAQ,EAGR,KAAMY,GAASA,EAAK,KAAK,IAAM,EAAI,CAAC,IAEpB,WACrB,MAAE,CAEF,CACF,CAEA,eAAeC,GAA8C,CAC3D,GAAM,CAACC,EAAyBC,CAAiB,EAAI,MAAM,QAAQ,IAAI,CACrEL,EAA2B,EAC3Bb,EAAa,SAAS,CAAE,SAAUR,EAAI,gBAAiB,CAAC,CAC1D,CAAC,EACD,OAAO0B,EAAkB,QAAUD,GAA2B,CAAC,GACjE,CAEA,IAAMA,EAA0B,MAAMJ,EAA2B,EAC7DI,GAA2B,OAC7BL,EAAaK,EAA0B,GACvC,QAAQ,IAAI,6BAA8BL,CAAU,GAGtD,GAAM,CAAE,mBAAAO,EAAoB,iBAAAC,CAAiB,EAAI,MAAMC,EAAgB,CACrE,eAAAZ,EACA,aAAAT,EACA,eAAgBR,EAAI,iBACpB,WAAAoB,EACA,cAAepB,EAAI,gBACnB,QAASA,EAAI,aACf,CAAC,EAED4B,EAAiB,UAAU,EAE3B,IAAIE,EAAa,GACjBC,EAAc,CAACJ,EAAoBC,CAAgB,CAAC,EACjD,KACCI,EACE,CAAC,CAACC,EAAmB,CAAE,YAAaC,CAAyB,CAAC,IAC5DD,IAAsBC,CAC1B,EACAC,EAAM,CACR,EACC,UAAU,IAAM,CACfL,EAAa,GACb,QAAQ,IAAI,eAAe,CAC7B,CAAC,EAEH,GAAI9B,EAAI,kBAAoB,MAAQA,EAAI,kBAAoB,KAAM,CAChE,GAAM,CAAE,QAASoC,CAAI,EAAI,KAAM,QAAO,KAAK,EACrC,CAAE,QAASC,CAAK,EAAI,KAAM,QAAO,WAAW,EAC5C,CAAE,YAAAC,CAAY,EAAI,KAAM,QAAO,4BAAmC,EAClE,CAAE,QAAAC,CAAQ,EAAI,KAAM,QAAO,wBAA+B,EAC1D,CAAE,WAAAC,CAAW,EAAI,KAAM,QAAO,2BAAkC,EAEhEC,EAAS,IAAIL,EAEnBK,EAAO,IAAIJ,EAAK,CAAC,EACjBI,EAAO,IACLH,EAAY,CACV,QAAS,IAAMR,CACjB,CAAC,CACH,EACAW,EAAO,IACLF,EAAQ,CACN,UAAW,IAAM,GACjB,QAAS,IAAMT,EACf,2BAAAT,EACA,2BAAAG,EACA,eAAgBxB,EAAI,gBACtB,CAAC,CACH,EACAyC,EAAO,IAAID,EAAW,CAAC,EAEvBC,EAAO,OAAO,CAAE,KAAMzC,EAAI,iBAAkB,KAAMA,EAAI,gBAAiB,CAAC,EACxE,QAAQ,IACN,2DAA2DA,EAAI,oBAAoBA,EAAI,kBACzF","names":["z","eq","createPublicClient","fallback","webSocket","http","isDefined","combineLatest","filter","first","drizzle","postgres","cleanDatabase","createStorageAdapter","shouldCleanDatabase","createStoreSync","env","parseEnv","z","indexerEnvSchema","transports","webSocket","http","isDefined","publicClient","createPublicClient","fallback","chainId","database","drizzle","postgres","shouldCleanDatabase","cleanDatabase","storageAdapter","tables","createStorageAdapter","startBlock","getLatestStoredBlockNumber","eq","rows","getDistanceFromFollowBlock","latestStoredBlockNumber","latestFollowBlock","latestBlockNumber$","storedBlockLogs$","createStoreSync","isCaughtUp","combineLatest","filter","latestBlockNumber","lastBlockNumberProcessed","first","Koa","cors","healthcheck","metrics","helloWorld","server"]}
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{a as g,c as _}from"../chunk-ZS3IQEZ4.js";import{a as O}from"../chunk-VCBWGHIO.js";import{a as R,b as N,c as B}from"../chunk-7B4KE2DO.js";import{a as w}from"../chunk-KDDXIBYJ.js";import{a as C}from"../chunk-OUZYPRYF.js";import{a as E}from"../chunk-GQCEMYLA.js";import"dotenv/config";import Z from"node:fs";import{z as p}from"zod";import{eq as ee}from"drizzle-orm";import{drizzle as te}from"drizzle-orm/better-sqlite3";import re from"better-sqlite3";import{createPublicClient as oe,fallback as ae,webSocket as ne,http as se}from"viem";import ie from"koa";import le from"@koa/cors";import{createKoaMiddleware as ce}from"trpc-koa-adapter";import{createAppRouter as me}from"@latticexyz/store-sync/trpc-indexer";import{chainState as W,schemaVersion as F,syncToSqlite as ue}from"@latticexyz/store-sync/sqlite";import{asc as H,eq as I}from"drizzle-orm";import{buildTable as K,chainState as A,getTables as j}from"@latticexyz/store-sync/sqlite";import{getAddress as D}from"viem";import{decodeDynamicField as G}from"@latticexyz/protocol-parser/internal";function d(t,{chainId:a,address:r,filters:s=[]}){let i=t.select().from(A).where(I(A.chainId,a)).limit(1).all().find(()=>!0),o=Array.from(new Set(s.map(n=>n.tableId))),y=j(t).filter(n=>r==null||D(r)===D(n.address)).filter(n=>!o.length||o.includes(n.tableId)).map(n=>{let h=K(n),L=t.select().from(h).where(I(h.__isDeleted,!1)).orderBy(H(h.__lastUpdatedBlockNumber)).all(),M=s.length?L.filter(f=>{let l=G("bytes32[]",f.__key);return s.some(u=>u.tableId===n.tableId&&(u.key0==null||u.key0===l[0])&&(u.key1==null||u.key1===l[1]))}):L;return{...n,records:M.map(f=>({key:Object.fromEntries(Object.entries(n.keySchema).map(([l])=>[l,f[l]])),value:Object.fromEntries(Object.entries(n.valueSchema).map(([l])=>[l,f[l]]))}))}});return{blockNumber:i?.lastUpdatedBlockNumber??null,tables:y}}import{tablesWithRecordsToLogs as V}from"@latticexyz/store-sync";async function P(t){return{async getLogs(r){let{blockNumber:s,tables:i}=d(t,r),o=V(i);return{blockNumber:s??0n,logs:o}},async findAll(r){return d(t,r)}}}import{isDefined as de}from"@latticexyz/common/utils";import{combineLatest as pe,filter as fe,first as be}from"rxjs";import x from"@koa/router";import J from"koa-compose";import{input as $}from"@latticexyz/store-sync/indexer-client";import{storeTables as z,tablesWithRecordsToLogs as Y}from"@latticexyz/store-sync";import{createBenchmark as X}from"@latticexyz/common";function Q(t){let a=new x;return a.get("/api/logs",_(),async r=>{let s=X("sqlite:logs"),i;try{i=$.parse(typeof r.query.input=="string"?JSON.parse(r.query.input):{})}catch(o){r.status=400,r.body=JSON.stringify(o),g(o);return}try{i.filters=i.filters.length>0?[...i.filters,{tableId:z.Tables.tableId}]:[],s("parse config");let{blockNumber:o,tables:k}=d(t,i);s("query tables with records");let y=Y(k);s("convert records to logs"),r.body=JSON.stringify({blockNumber:o?.toString()??"-1",logs:y}),r.status=200}catch(o){r.status=500,r.body=JSON.stringify(o),g(o)}}),J([a.routes(),a.allowedMethods()])}var e=B(p.intersection(p.intersection(N,R),p.object({SQLITE_FILENAME:p.string().default("indexer.db"),SENTRY_DSN:p.string().optional()}))),ye=[e.RPC_WS_URL?ne(e.RPC_WS_URL):void 0,e.RPC_HTTP_URL?se(e.RPC_HTTP_URL):void 0].filter(de),S=oe({transport:ae(ye),pollingInterval:e.POLLING_INTERVAL}),he=await S.getChainId(),b=te(new re(e.SQLITE_FILENAME)),U=e.START_BLOCK;async function v(){try{return b.select().from(W).where(ee(W.chainId,he)).all()[0]}catch{}}async function q(){return(await v())?.lastUpdatedBlockNumber??void 0}async function ge(){let[t,a]=await Promise.all([q(),S.getBlock({blockTag:e.FOLLOW_BLOCK_TAG})]);return(t??-1n)-a.number}var m=await v();m&&(m.schemaVersion!=F?(console.log("schema version changed from",m.schemaVersion,"to",F,"recreating database"),Z.truncateSync(e.SQLITE_FILENAME)):m.lastUpdatedBlockNumber!=null&&(console.log("resuming from block number",m.lastUpdatedBlockNumber+1n),U=m.lastUpdatedBlockNumber+1n));var{latestBlockNumber$:Se,storedBlockLogs$:Te}=await ue({database:b,publicClient:S,followBlockTag:e.FOLLOW_BLOCK_TAG,startBlock:U,maxBlockRange:e.MAX_BLOCK_RANGE,address:e.STORE_ADDRESS}),T=!1;pe([Se,Te]).pipe(fe(([t,{blockNumber:a}])=>t===a),be()).subscribe(()=>{T=!0,console.log("all caught up")});var c=new ie;e.SENTRY_DSN&&c.use(O(e.SENTRY_DSN));c.use(le());c.use(w({isReady:()=>T}));c.use(E({isHealthy:()=>!0,isReady:()=>T,getLatestStoredBlockNumber:q,getDistanceFromFollowBlock:ge,followBlockTag:e.FOLLOW_BLOCK_TAG}));c.use(C());c.use(Q(b));c.use(ce({prefix:"/trpc",router:me(),createContext:async()=>({queryAdapter:await P(b)})}));c.listen({host:e.HOST,port:e.PORT});console.log(`sqlite indexer frontend listening on http://${e.HOST}:${e.PORT}`);
2
+ import{a as g,c as _}from"../chunk-ZS3IQEZ4.js";import{a as O}from"../chunk-VCBWGHIO.js";import{a as R,b as N,c as B}from"../chunk-7B4KE2DO.js";import{a as w}from"../chunk-KDDXIBYJ.js";import{a as C}from"../chunk-OUZYPRYF.js";import{a as E}from"../chunk-GQCEMYLA.js";import"dotenv/config";import Z from"node:fs";import{z as p}from"zod";import{eq as ee}from"drizzle-orm";import{drizzle as te}from"drizzle-orm/better-sqlite3";import re from"better-sqlite3";import{createPublicClient as oe,fallback as ae,webSocket as ne,http as se}from"viem";import ie from"koa";import le from"@koa/cors";import{createKoaMiddleware as ce}from"trpc-koa-adapter";import{createAppRouter as me}from"@latticexyz/store-sync/trpc-indexer";import{chainState as W,schemaVersion as F,syncToSqlite as ue}from"@latticexyz/store-sync/sqlite";import{asc as H,eq as I}from"drizzle-orm";import{buildTable as K,chainState as A,getTables as j}from"@latticexyz/store-sync/sqlite";import{getAddress as D}from"viem";import{decodeDynamicField as G}from"@latticexyz/protocol-parser/internal";function d(t,{chainId:a,address:r,filters:s=[]}){let i=t.select().from(A).where(I(A.chainId,a)).limit(1).all().find(()=>!0),o=Array.from(new Set(s.map(n=>n.tableId))),y=j(t).filter(n=>r==null||D(r)===D(n.address)).filter(n=>!o.length||o.includes(n.tableId)).map(n=>{let h=K(n),L=t.select().from(h).where(I(h.__isDeleted,!1)).orderBy(H(h.__lastUpdatedBlockNumber)).all(),M=s.length?L.filter(f=>{let l=G("bytes32[]",f.__key);return s.some(u=>u.tableId===n.tableId&&(u.key0==null||u.key0===l[0])&&(u.key1==null||u.key1===l[1]))}):L;return{...n,records:M.map(f=>({key:Object.fromEntries(Object.entries(n.keySchema).map(([l])=>[l,f[l]])),value:Object.fromEntries(Object.entries(n.valueSchema).map(([l])=>[l,f[l]]))}))}});return{blockNumber:i?.lastUpdatedBlockNumber??null,tables:y}}import{tablesWithRecordsToLogs as V}from"@latticexyz/store-sync";async function P(t){return{async getLogs(r){let{blockNumber:s,tables:i}=d(t,r),o=V(i);return{blockNumber:s??0n,logs:o}},async findAll(r){return d(t,r)}}}import{isDefined as de}from"@latticexyz/common/utils";import{combineLatest as pe,filter as fe,first as be}from"rxjs";import x from"@koa/router";import J from"koa-compose";import{input as $}from"@latticexyz/store-sync/indexer-client";import{storeTables as z,tablesWithRecordsToLogs as Y}from"@latticexyz/store-sync";import{createBenchmark as X}from"@latticexyz/common";function Q(t){let a=new x;return a.get("/api/logs",_(),async r=>{let s=X("sqlite:logs"),i;try{i=$.parse(typeof r.query.input=="string"?JSON.parse(r.query.input):{})}catch(o){r.status=400,r.body=JSON.stringify(o),g(o);return}try{i.filters=i.filters.length>0?[...i.filters,{tableId:z.Tables.tableId}]:[],s("parse config");let{blockNumber:o,tables:k}=d(t,i);s("query tables with records");let y=Y(k);s("convert records to logs"),r.body=JSON.stringify({blockNumber:o?.toString()??"-1",logs:y}),r.status=200}catch(o){r.status=500,r.body=JSON.stringify(o),g(o)}}),J([a.routes(),a.allowedMethods()])}var e=B(p.intersection(p.intersection(N,R),p.object({SQLITE_FILENAME:p.string().default("indexer.db"),SENTRY_DSN:p.string().optional()}))),ye=[e.RPC_WS_URL?ne(e.RPC_WS_URL):void 0,e.RPC_HTTP_URL?se(e.RPC_HTTP_URL):void 0].filter(de),S=oe({transport:ae(ye),pollingInterval:e.POLLING_INTERVAL}),he=await S.getChainId(),b=te(new re(e.SQLITE_FILENAME)),U=e.START_BLOCK;async function v(){try{return b.select().from(W).where(ee(W.chainId,he)).all()[0]}catch{}}async function q(){return(await v())?.lastUpdatedBlockNumber??void 0}async function ge(){let[t,a]=await Promise.all([q(),S.getBlock({blockTag:e.FOLLOW_BLOCK_TAG})]);return a.number-(t??-1n)}var m=await v();m&&(m.schemaVersion!=F?(console.log("schema version changed from",m.schemaVersion,"to",F,"recreating database"),Z.truncateSync(e.SQLITE_FILENAME)):m.lastUpdatedBlockNumber!=null&&(console.log("resuming from block number",m.lastUpdatedBlockNumber+1n),U=m.lastUpdatedBlockNumber+1n));var{latestBlockNumber$:Se,storedBlockLogs$:Te}=await ue({database:b,publicClient:S,followBlockTag:e.FOLLOW_BLOCK_TAG,startBlock:U,maxBlockRange:e.MAX_BLOCK_RANGE,address:e.STORE_ADDRESS}),T=!1;pe([Se,Te]).pipe(fe(([t,{blockNumber:a}])=>t===a),be()).subscribe(()=>{T=!0,console.log("all caught up")});var c=new ie;e.SENTRY_DSN&&c.use(O(e.SENTRY_DSN));c.use(le());c.use(w({isReady:()=>T}));c.use(E({isHealthy:()=>!0,isReady:()=>T,getLatestStoredBlockNumber:q,getDistanceFromFollowBlock:ge,followBlockTag:e.FOLLOW_BLOCK_TAG}));c.use(C());c.use(Q(b));c.use(ce({prefix:"/trpc",router:me(),createContext:async()=>({queryAdapter:await P(b)})}));c.listen({host:e.HOST,port:e.PORT});console.log(`sqlite indexer frontend listening on http://${e.HOST}:${e.PORT}`);
3
3
  //# sourceMappingURL=sqlite-indexer.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../bin/sqlite-indexer.ts","../../src/sqlite/getTablesWithRecords.ts","../../src/sqlite/createQueryAdapter.ts","../../src/sqlite/apiRoutes.ts"],"sourcesContent":["#!/usr/bin/env node\nimport \"dotenv/config\";\nimport fs from \"node:fs\";\nimport { z } from \"zod\";\nimport { eq } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/better-sqlite3\";\nimport Database from \"better-sqlite3\";\nimport { createPublicClient, fallback, webSocket, http, Transport } from \"viem\";\nimport Koa from \"koa\";\nimport cors from \"@koa/cors\";\nimport { createKoaMiddleware } from \"trpc-koa-adapter\";\nimport { createAppRouter } from \"@latticexyz/store-sync/trpc-indexer\";\nimport { chainState, schemaVersion, syncToSqlite } from \"@latticexyz/store-sync/sqlite\";\nimport { createQueryAdapter } from \"../src/sqlite/createQueryAdapter\";\nimport { isDefined } from \"@latticexyz/common/utils\";\nimport { combineLatest, filter, first } from \"rxjs\";\nimport { frontendEnvSchema, indexerEnvSchema, parseEnv } from \"./parseEnv\";\nimport { healthcheck } from \"../src/koa-middleware/healthcheck\";\nimport { helloWorld } from \"../src/koa-middleware/helloWorld\";\nimport { apiRoutes } from \"../src/sqlite/apiRoutes\";\nimport { sentry } from \"../src/koa-middleware/sentry\";\nimport { metrics } from \"../src/koa-middleware/metrics\";\n\nconst env = parseEnv(\n z.intersection(\n z.intersection(indexerEnvSchema, frontendEnvSchema),\n z.object({\n SQLITE_FILENAME: z.string().default(\"indexer.db\"),\n SENTRY_DSN: z.string().optional(),\n }),\n ),\n);\n\nconst transports: Transport[] = [\n // prefer WS when specified\n env.RPC_WS_URL ? webSocket(env.RPC_WS_URL) : undefined,\n // otherwise use or fallback to HTTP\n env.RPC_HTTP_URL ? http(env.RPC_HTTP_URL) : undefined,\n].filter(isDefined);\n\nconst publicClient = createPublicClient({\n transport: fallback(transports),\n pollingInterval: env.POLLING_INTERVAL,\n});\n\nconst chainId = await publicClient.getChainId();\nconst database = drizzle(new Database(env.SQLITE_FILENAME));\n\nlet startBlock = env.START_BLOCK;\n\nasync function getCurrentChainState(): Promise<\n | {\n schemaVersion: number;\n chainId: number;\n lastUpdatedBlockNumber: bigint | null;\n lastError: string | null;\n }\n | undefined\n> {\n // This will throw if the DB doesn't exist yet, so we wrap in a try/catch and ignore the error.\n try {\n const currentChainStates = database.select().from(chainState).where(eq(chainState.chainId, chainId)).all();\n // TODO: replace this type workaround with `noUncheckedIndexedAccess: true` when we can fix all the issues related (https://github.com/latticexyz/mud/issues/1212)\n const currentChainState: (typeof currentChainStates)[number] | undefined = currentChainStates[0];\n return currentChainState;\n } catch (error) {\n // ignore errors, this is optional\n }\n}\n\nasync function getLatestStoredBlockNumber(): Promise<bigint | undefined> {\n const currentChainState = await getCurrentChainState();\n return currentChainState?.lastUpdatedBlockNumber ?? undefined;\n}\n\nasync function getDistanceFromFollowBlock(): Promise<bigint> {\n const [latestStoredBlockNumber, latestFollowBlock] = await Promise.all([\n getLatestStoredBlockNumber(),\n publicClient.getBlock({ blockTag: env.FOLLOW_BLOCK_TAG }),\n ]);\n return (latestStoredBlockNumber ?? -1n) - latestFollowBlock.number;\n}\n\nconst currentChainState = await getCurrentChainState();\nif (currentChainState) {\n // Reset the db if the version changed\n if (currentChainState.schemaVersion != schemaVersion) {\n console.log(\n \"schema version changed from\",\n currentChainState.schemaVersion,\n \"to\",\n schemaVersion,\n \"recreating database\",\n );\n fs.truncateSync(env.SQLITE_FILENAME);\n } else if (currentChainState.lastUpdatedBlockNumber != null) {\n // Resume from latest block stored in DB. This will throw if the DB doesn't exist yet, so we wrap in a try/catch and ignore the error.\n console.log(\"resuming from block number\", currentChainState.lastUpdatedBlockNumber + 1n);\n startBlock = currentChainState.lastUpdatedBlockNumber + 1n;\n }\n}\n\nconst { latestBlockNumber$, storedBlockLogs$ } = await syncToSqlite({\n database,\n publicClient,\n followBlockTag: env.FOLLOW_BLOCK_TAG,\n startBlock,\n maxBlockRange: env.MAX_BLOCK_RANGE,\n address: env.STORE_ADDRESS,\n});\n\nlet isCaughtUp = false;\ncombineLatest([latestBlockNumber$, storedBlockLogs$])\n .pipe(\n filter(\n ([latestBlockNumber, { blockNumber: lastBlockNumberProcessed }]) =>\n latestBlockNumber === lastBlockNumberProcessed,\n ),\n first(),\n )\n .subscribe(() => {\n isCaughtUp = true;\n console.log(\"all caught up\");\n });\n\nconst server = new Koa();\n\nif (env.SENTRY_DSN) {\n server.use(sentry(env.SENTRY_DSN));\n}\n\nserver.use(cors());\nserver.use(\n healthcheck({\n isReady: () => isCaughtUp,\n }),\n);\nserver.use(\n metrics({\n isHealthy: () => true,\n isReady: () => isCaughtUp,\n getLatestStoredBlockNumber,\n getDistanceFromFollowBlock,\n followBlockTag: env.FOLLOW_BLOCK_TAG,\n }),\n);\nserver.use(helloWorld());\nserver.use(apiRoutes(database));\n\nserver.use(\n createKoaMiddleware({\n prefix: \"/trpc\",\n router: createAppRouter(),\n createContext: async () => ({\n queryAdapter: await createQueryAdapter(database),\n }),\n }),\n);\n\nserver.listen({ host: env.HOST, port: env.PORT });\nconsole.log(`sqlite indexer frontend listening on http://${env.HOST}:${env.PORT}`);\n","import { asc, eq } from \"drizzle-orm\";\nimport { BaseSQLiteDatabase } from \"drizzle-orm/sqlite-core\";\nimport { buildTable, chainState, getTables } from \"@latticexyz/store-sync/sqlite\";\nimport { Hex, getAddress } from \"viem\";\nimport { decodeDynamicField } from \"@latticexyz/protocol-parser/internal\";\nimport { SyncFilter, TableWithRecords } from \"@latticexyz/store-sync\";\n\n// TODO: refactor sqlite and replace this with getLogs to match postgres (https://github.com/latticexyz/mud/issues/1970)\n\n/**\n * @deprecated\n * */\nexport function getTablesWithRecords(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n database: BaseSQLiteDatabase<\"sync\", any>,\n {\n chainId,\n address,\n filters = [],\n }: {\n readonly chainId: number;\n readonly address?: Hex;\n readonly filters?: readonly SyncFilter[];\n },\n): { blockNumber: bigint | null; tables: readonly TableWithRecords[] } {\n const metadata = database\n .select()\n .from(chainState)\n .where(eq(chainState.chainId, chainId))\n .limit(1)\n .all()\n .find(() => true);\n\n // If _any_ filter has a table ID, this will filter down all data to just those tables. Which mean we can't yet mix table filters with key-only filters.\n // TODO: improve this so we can express this in the query (need to be able to query data across tables more easily)\n const tableIds = Array.from(new Set(filters.map((filter) => filter.tableId)));\n const tables = getTables(database)\n .filter((table) => address == null || getAddress(address) === getAddress(table.address))\n .filter((table) => !tableIds.length || tableIds.includes(table.tableId));\n\n const tablesWithRecords = tables.map((table) => {\n const sqliteTable = buildTable(table);\n const records = database\n .select()\n .from(sqliteTable)\n .where(eq(sqliteTable.__isDeleted, false))\n .orderBy(\n asc(sqliteTable.__lastUpdatedBlockNumber),\n // TODO: add logIndex (https://github.com/latticexyz/mud/issues/1979)\n )\n .all();\n const filteredRecords = !filters.length\n ? records\n : records.filter((record) => {\n const keyTuple = decodeDynamicField(\"bytes32[]\", record.__key);\n return filters.some(\n (filter) =>\n filter.tableId === table.tableId &&\n (filter.key0 == null || filter.key0 === keyTuple[0]) &&\n (filter.key1 == null || filter.key1 === keyTuple[1]),\n );\n });\n return {\n ...table,\n records: filteredRecords.map((record) => ({\n key: Object.fromEntries(Object.entries(table.keySchema).map(([name]) => [name, record[name]])),\n value: Object.fromEntries(Object.entries(table.valueSchema).map(([name]) => [name, record[name]])),\n })),\n };\n });\n\n return {\n blockNumber: metadata?.lastUpdatedBlockNumber ?? null,\n tables: tablesWithRecords,\n };\n}\n","import { BaseSQLiteDatabase } from \"drizzle-orm/sqlite-core\";\nimport { QueryAdapter } from \"@latticexyz/store-sync/trpc-indexer\";\nimport { getTablesWithRecords } from \"./getTablesWithRecords\";\nimport { tablesWithRecordsToLogs } from \"@latticexyz/store-sync\";\n\n/**\n * Creates a storage adapter for the tRPC server/client to query data from SQLite.\n *\n * @param {BaseSQLiteDatabase<\"sync\", any>} database SQLite database object from Drizzle\n * @returns {Promise<QueryAdapter>} A set of methods used by tRPC endpoints.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport async function createQueryAdapter(database: BaseSQLiteDatabase<\"sync\", any>): Promise<QueryAdapter> {\n const adapter: QueryAdapter = {\n async getLogs(opts) {\n const { blockNumber, tables } = getTablesWithRecords(database, opts);\n const logs = tablesWithRecordsToLogs(tables);\n return { blockNumber: blockNumber ?? 0n, logs };\n },\n async findAll(opts) {\n return getTablesWithRecords(database, opts);\n },\n };\n return adapter;\n}\n","import { Middleware } from \"koa\";\nimport Router from \"@koa/router\";\nimport compose from \"koa-compose\";\nimport { input } from \"@latticexyz/store-sync/indexer-client\";\nimport { storeTables, tablesWithRecordsToLogs } from \"@latticexyz/store-sync\";\nimport { debug } from \"../debug\";\nimport { createBenchmark } from \"@latticexyz/common\";\nimport { compress } from \"../koa-middleware/compress\";\nimport { getTablesWithRecords } from \"./getTablesWithRecords\";\nimport { BaseSQLiteDatabase } from \"drizzle-orm/sqlite-core\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function apiRoutes(database: BaseSQLiteDatabase<\"sync\", any>): Middleware {\n const router = new Router();\n\n router.get(\"/api/logs\", compress(), async (ctx) => {\n const benchmark = createBenchmark(\"sqlite:logs\");\n\n let options: ReturnType<typeof input.parse>;\n\n try {\n options = input.parse(typeof ctx.query.input === \"string\" ? JSON.parse(ctx.query.input) : {});\n } catch (error) {\n ctx.status = 400;\n ctx.body = JSON.stringify(error);\n debug(error);\n return;\n }\n\n try {\n options.filters = options.filters.length > 0 ? [...options.filters, { tableId: storeTables.Tables.tableId }] : [];\n benchmark(\"parse config\");\n const { blockNumber, tables } = getTablesWithRecords(database, options);\n benchmark(\"query tables with records\");\n const logs = tablesWithRecordsToLogs(tables);\n benchmark(\"convert records to logs\");\n\n ctx.body = JSON.stringify({ blockNumber: blockNumber?.toString() ?? \"-1\", logs });\n ctx.status = 200;\n } catch (error) {\n ctx.status = 500;\n ctx.body = JSON.stringify(error);\n debug(error);\n }\n });\n\n return compose([router.routes(), router.allowedMethods()]) as Middleware;\n}\n"],"mappings":"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","names":["fs","z","eq","drizzle","Database","createPublicClient","fallback","webSocket","http","Koa","cors","createKoaMiddleware","createAppRouter","chainState","schemaVersion","syncToSqlite","asc","eq","buildTable","chainState","getTables","getAddress","decodeDynamicField","getTablesWithRecords","database","chainId","address","filters","metadata","tableIds","filter","tablesWithRecords","table","sqliteTable","records","filteredRecords","record","keyTuple","name","tablesWithRecordsToLogs","createQueryAdapter","database","opts","blockNumber","tables","getTablesWithRecords","logs","isDefined","combineLatest","filter","first","Router","compose","input","storeTables","tablesWithRecordsToLogs","createBenchmark","apiRoutes","database","router","Router","compress","ctx","benchmark","createBenchmark","options","input","error","debug","storeTables","blockNumber","tables","getTablesWithRecords","logs","tablesWithRecordsToLogs","compose","env","parseEnv","z","indexerEnvSchema","frontendEnvSchema","transports","webSocket","http","isDefined","publicClient","createPublicClient","fallback","chainId","database","drizzle","Database","startBlock","getCurrentChainState","chainState","eq","getLatestStoredBlockNumber","getDistanceFromFollowBlock","latestStoredBlockNumber","latestFollowBlock","currentChainState","schemaVersion","fs","latestBlockNumber$","storedBlockLogs$","syncToSqlite","isCaughtUp","combineLatest","filter","latestBlockNumber","lastBlockNumberProcessed","first","server","Koa","sentry","cors","healthcheck","metrics","helloWorld","apiRoutes","createKoaMiddleware","createAppRouter","createQueryAdapter"]}
1
+ {"version":3,"sources":["../../bin/sqlite-indexer.ts","../../src/sqlite/getTablesWithRecords.ts","../../src/sqlite/createQueryAdapter.ts","../../src/sqlite/apiRoutes.ts"],"sourcesContent":["#!/usr/bin/env node\nimport \"dotenv/config\";\nimport fs from \"node:fs\";\nimport { z } from \"zod\";\nimport { eq } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/better-sqlite3\";\nimport Database from \"better-sqlite3\";\nimport { createPublicClient, fallback, webSocket, http, Transport } from \"viem\";\nimport Koa from \"koa\";\nimport cors from \"@koa/cors\";\nimport { createKoaMiddleware } from \"trpc-koa-adapter\";\nimport { createAppRouter } from \"@latticexyz/store-sync/trpc-indexer\";\nimport { chainState, schemaVersion, syncToSqlite } from \"@latticexyz/store-sync/sqlite\";\nimport { createQueryAdapter } from \"../src/sqlite/createQueryAdapter\";\nimport { isDefined } from \"@latticexyz/common/utils\";\nimport { combineLatest, filter, first } from \"rxjs\";\nimport { frontendEnvSchema, indexerEnvSchema, parseEnv } from \"./parseEnv\";\nimport { healthcheck } from \"../src/koa-middleware/healthcheck\";\nimport { helloWorld } from \"../src/koa-middleware/helloWorld\";\nimport { apiRoutes } from \"../src/sqlite/apiRoutes\";\nimport { sentry } from \"../src/koa-middleware/sentry\";\nimport { metrics } from \"../src/koa-middleware/metrics\";\n\nconst env = parseEnv(\n z.intersection(\n z.intersection(indexerEnvSchema, frontendEnvSchema),\n z.object({\n SQLITE_FILENAME: z.string().default(\"indexer.db\"),\n SENTRY_DSN: z.string().optional(),\n }),\n ),\n);\n\nconst transports: Transport[] = [\n // prefer WS when specified\n env.RPC_WS_URL ? webSocket(env.RPC_WS_URL) : undefined,\n // otherwise use or fallback to HTTP\n env.RPC_HTTP_URL ? http(env.RPC_HTTP_URL) : undefined,\n].filter(isDefined);\n\nconst publicClient = createPublicClient({\n transport: fallback(transports),\n pollingInterval: env.POLLING_INTERVAL,\n});\n\nconst chainId = await publicClient.getChainId();\nconst database = drizzle(new Database(env.SQLITE_FILENAME));\n\nlet startBlock = env.START_BLOCK;\n\nasync function getCurrentChainState(): Promise<\n | {\n schemaVersion: number;\n chainId: number;\n lastUpdatedBlockNumber: bigint | null;\n lastError: string | null;\n }\n | undefined\n> {\n // This will throw if the DB doesn't exist yet, so we wrap in a try/catch and ignore the error.\n try {\n const currentChainStates = database.select().from(chainState).where(eq(chainState.chainId, chainId)).all();\n // TODO: replace this type workaround with `noUncheckedIndexedAccess: true` when we can fix all the issues related (https://github.com/latticexyz/mud/issues/1212)\n const currentChainState: (typeof currentChainStates)[number] | undefined = currentChainStates[0];\n return currentChainState;\n } catch (error) {\n // ignore errors, this is optional\n }\n}\n\nasync function getLatestStoredBlockNumber(): Promise<bigint | undefined> {\n const currentChainState = await getCurrentChainState();\n return currentChainState?.lastUpdatedBlockNumber ?? undefined;\n}\n\nasync function getDistanceFromFollowBlock(): Promise<bigint> {\n const [latestStoredBlockNumber, latestFollowBlock] = await Promise.all([\n getLatestStoredBlockNumber(),\n publicClient.getBlock({ blockTag: env.FOLLOW_BLOCK_TAG }),\n ]);\n return latestFollowBlock.number - (latestStoredBlockNumber ?? -1n);\n}\n\nconst currentChainState = await getCurrentChainState();\nif (currentChainState) {\n // Reset the db if the version changed\n if (currentChainState.schemaVersion != schemaVersion) {\n console.log(\n \"schema version changed from\",\n currentChainState.schemaVersion,\n \"to\",\n schemaVersion,\n \"recreating database\",\n );\n fs.truncateSync(env.SQLITE_FILENAME);\n } else if (currentChainState.lastUpdatedBlockNumber != null) {\n // Resume from latest block stored in DB. This will throw if the DB doesn't exist yet, so we wrap in a try/catch and ignore the error.\n console.log(\"resuming from block number\", currentChainState.lastUpdatedBlockNumber + 1n);\n startBlock = currentChainState.lastUpdatedBlockNumber + 1n;\n }\n}\n\nconst { latestBlockNumber$, storedBlockLogs$ } = await syncToSqlite({\n database,\n publicClient,\n followBlockTag: env.FOLLOW_BLOCK_TAG,\n startBlock,\n maxBlockRange: env.MAX_BLOCK_RANGE,\n address: env.STORE_ADDRESS,\n});\n\nlet isCaughtUp = false;\ncombineLatest([latestBlockNumber$, storedBlockLogs$])\n .pipe(\n filter(\n ([latestBlockNumber, { blockNumber: lastBlockNumberProcessed }]) =>\n latestBlockNumber === lastBlockNumberProcessed,\n ),\n first(),\n )\n .subscribe(() => {\n isCaughtUp = true;\n console.log(\"all caught up\");\n });\n\nconst server = new Koa();\n\nif (env.SENTRY_DSN) {\n server.use(sentry(env.SENTRY_DSN));\n}\n\nserver.use(cors());\nserver.use(\n healthcheck({\n isReady: () => isCaughtUp,\n }),\n);\nserver.use(\n metrics({\n isHealthy: () => true,\n isReady: () => isCaughtUp,\n getLatestStoredBlockNumber,\n getDistanceFromFollowBlock,\n followBlockTag: env.FOLLOW_BLOCK_TAG,\n }),\n);\nserver.use(helloWorld());\nserver.use(apiRoutes(database));\n\nserver.use(\n createKoaMiddleware({\n prefix: \"/trpc\",\n router: createAppRouter(),\n createContext: async () => ({\n queryAdapter: await createQueryAdapter(database),\n }),\n }),\n);\n\nserver.listen({ host: env.HOST, port: env.PORT });\nconsole.log(`sqlite indexer frontend listening on http://${env.HOST}:${env.PORT}`);\n","import { asc, eq } from \"drizzle-orm\";\nimport { BaseSQLiteDatabase } from \"drizzle-orm/sqlite-core\";\nimport { buildTable, chainState, getTables } from \"@latticexyz/store-sync/sqlite\";\nimport { Hex, getAddress } from \"viem\";\nimport { decodeDynamicField } from \"@latticexyz/protocol-parser/internal\";\nimport { SyncFilter, TableWithRecords } from \"@latticexyz/store-sync\";\n\n// TODO: refactor sqlite and replace this with getLogs to match postgres (https://github.com/latticexyz/mud/issues/1970)\n\n/**\n * @deprecated\n * */\nexport function getTablesWithRecords(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n database: BaseSQLiteDatabase<\"sync\", any>,\n {\n chainId,\n address,\n filters = [],\n }: {\n readonly chainId: number;\n readonly address?: Hex;\n readonly filters?: readonly SyncFilter[];\n },\n): { blockNumber: bigint | null; tables: readonly TableWithRecords[] } {\n const metadata = database\n .select()\n .from(chainState)\n .where(eq(chainState.chainId, chainId))\n .limit(1)\n .all()\n .find(() => true);\n\n // If _any_ filter has a table ID, this will filter down all data to just those tables. Which mean we can't yet mix table filters with key-only filters.\n // TODO: improve this so we can express this in the query (need to be able to query data across tables more easily)\n const tableIds = Array.from(new Set(filters.map((filter) => filter.tableId)));\n const tables = getTables(database)\n .filter((table) => address == null || getAddress(address) === getAddress(table.address))\n .filter((table) => !tableIds.length || tableIds.includes(table.tableId));\n\n const tablesWithRecords = tables.map((table) => {\n const sqliteTable = buildTable(table);\n const records = database\n .select()\n .from(sqliteTable)\n .where(eq(sqliteTable.__isDeleted, false))\n .orderBy(\n asc(sqliteTable.__lastUpdatedBlockNumber),\n // TODO: add logIndex (https://github.com/latticexyz/mud/issues/1979)\n )\n .all();\n const filteredRecords = !filters.length\n ? records\n : records.filter((record) => {\n const keyTuple = decodeDynamicField(\"bytes32[]\", record.__key);\n return filters.some(\n (filter) =>\n filter.tableId === table.tableId &&\n (filter.key0 == null || filter.key0 === keyTuple[0]) &&\n (filter.key1 == null || filter.key1 === keyTuple[1]),\n );\n });\n return {\n ...table,\n records: filteredRecords.map((record) => ({\n key: Object.fromEntries(Object.entries(table.keySchema).map(([name]) => [name, record[name]])),\n value: Object.fromEntries(Object.entries(table.valueSchema).map(([name]) => [name, record[name]])),\n })),\n };\n });\n\n return {\n blockNumber: metadata?.lastUpdatedBlockNumber ?? null,\n tables: tablesWithRecords,\n };\n}\n","import { BaseSQLiteDatabase } from \"drizzle-orm/sqlite-core\";\nimport { QueryAdapter } from \"@latticexyz/store-sync/trpc-indexer\";\nimport { getTablesWithRecords } from \"./getTablesWithRecords\";\nimport { tablesWithRecordsToLogs } from \"@latticexyz/store-sync\";\n\n/**\n * Creates a storage adapter for the tRPC server/client to query data from SQLite.\n *\n * @param {BaseSQLiteDatabase<\"sync\", any>} database SQLite database object from Drizzle\n * @returns {Promise<QueryAdapter>} A set of methods used by tRPC endpoints.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport async function createQueryAdapter(database: BaseSQLiteDatabase<\"sync\", any>): Promise<QueryAdapter> {\n const adapter: QueryAdapter = {\n async getLogs(opts) {\n const { blockNumber, tables } = getTablesWithRecords(database, opts);\n const logs = tablesWithRecordsToLogs(tables);\n return { blockNumber: blockNumber ?? 0n, logs };\n },\n async findAll(opts) {\n return getTablesWithRecords(database, opts);\n },\n };\n return adapter;\n}\n","import { Middleware } from \"koa\";\nimport Router from \"@koa/router\";\nimport compose from \"koa-compose\";\nimport { input } from \"@latticexyz/store-sync/indexer-client\";\nimport { storeTables, tablesWithRecordsToLogs } from \"@latticexyz/store-sync\";\nimport { debug } from \"../debug\";\nimport { createBenchmark } from \"@latticexyz/common\";\nimport { compress } from \"../koa-middleware/compress\";\nimport { getTablesWithRecords } from \"./getTablesWithRecords\";\nimport { BaseSQLiteDatabase } from \"drizzle-orm/sqlite-core\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function apiRoutes(database: BaseSQLiteDatabase<\"sync\", any>): Middleware {\n const router = new Router();\n\n router.get(\"/api/logs\", compress(), async (ctx) => {\n const benchmark = createBenchmark(\"sqlite:logs\");\n\n let options: ReturnType<typeof input.parse>;\n\n try {\n options = input.parse(typeof ctx.query.input === \"string\" ? JSON.parse(ctx.query.input) : {});\n } catch (error) {\n ctx.status = 400;\n ctx.body = JSON.stringify(error);\n debug(error);\n return;\n }\n\n try {\n options.filters = options.filters.length > 0 ? [...options.filters, { tableId: storeTables.Tables.tableId }] : [];\n benchmark(\"parse config\");\n const { blockNumber, tables } = getTablesWithRecords(database, options);\n benchmark(\"query tables with records\");\n const logs = tablesWithRecordsToLogs(tables);\n benchmark(\"convert records to logs\");\n\n ctx.body = JSON.stringify({ blockNumber: blockNumber?.toString() ?? \"-1\", logs });\n ctx.status = 200;\n } catch (error) {\n ctx.status = 500;\n ctx.body = JSON.stringify(error);\n debug(error);\n }\n });\n\n return compose([router.routes(), router.allowedMethods()]) as Middleware;\n}\n"],"mappings":"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","names":["fs","z","eq","drizzle","Database","createPublicClient","fallback","webSocket","http","Koa","cors","createKoaMiddleware","createAppRouter","chainState","schemaVersion","syncToSqlite","asc","eq","buildTable","chainState","getTables","getAddress","decodeDynamicField","getTablesWithRecords","database","chainId","address","filters","metadata","tableIds","filter","tablesWithRecords","table","sqliteTable","records","filteredRecords","record","keyTuple","name","tablesWithRecordsToLogs","createQueryAdapter","database","opts","blockNumber","tables","getTablesWithRecords","logs","isDefined","combineLatest","filter","first","Router","compose","input","storeTables","tablesWithRecordsToLogs","createBenchmark","apiRoutes","database","router","Router","compress","ctx","benchmark","createBenchmark","options","input","error","debug","storeTables","blockNumber","tables","getTablesWithRecords","logs","tablesWithRecordsToLogs","compose","env","parseEnv","z","indexerEnvSchema","frontendEnvSchema","transports","webSocket","http","isDefined","publicClient","createPublicClient","fallback","chainId","database","drizzle","Database","startBlock","getCurrentChainState","chainState","eq","getLatestStoredBlockNumber","getDistanceFromFollowBlock","latestStoredBlockNumber","latestFollowBlock","currentChainState","schemaVersion","fs","latestBlockNumber$","storedBlockLogs$","syncToSqlite","isCaughtUp","combineLatest","filter","latestBlockNumber","lastBlockNumberProcessed","first","server","Koa","sentry","cors","healthcheck","metrics","helloWorld","apiRoutes","createKoaMiddleware","createAppRouter","createQueryAdapter"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@latticexyz/store-indexer",
3
- "version": "2.0.10-main-51b137d3",
3
+ "version": "2.0.10-main-0ae9189c",
4
4
  "description": "Minimal Typescript indexer for Store",
5
5
  "repository": {
6
6
  "type": "git",
@@ -41,11 +41,11 @@
41
41
  "trpc-koa-adapter": "^1.1.3",
42
42
  "viem": "2.9.20",
43
43
  "zod": "^3.21.4",
44
- "@latticexyz/block-logs-stream": "2.0.10-main-51b137d3",
45
- "@latticexyz/common": "2.0.10-main-51b137d3",
46
- "@latticexyz/protocol-parser": "2.0.10-main-51b137d3",
47
- "@latticexyz/store": "2.0.10-main-51b137d3",
48
- "@latticexyz/store-sync": "2.0.10-main-51b137d3"
44
+ "@latticexyz/block-logs-stream": "2.0.10-main-0ae9189c",
45
+ "@latticexyz/common": "2.0.10-main-0ae9189c",
46
+ "@latticexyz/protocol-parser": "2.0.10-main-0ae9189c",
47
+ "@latticexyz/store": "2.0.10-main-0ae9189c",
48
+ "@latticexyz/store-sync": "2.0.10-main-0ae9189c"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/accepts": "^1.3.7",