@latticexyz/store-indexer 2.2.22-f6d87edb8513fd0f255ac4389a2e613a508ffab4 → 2.2.22-fbf1be12730c08acd460aa36124a0565f4e73401
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.
@@ -110,6 +110,37 @@ import compose from "koa-compose";
|
|
110
110
|
import { input } from "@latticexyz/store-sync/indexer-client";
|
111
111
|
import { schemasTable, tablesWithRecordsToLogs as tablesWithRecordsToLogs2 } from "@latticexyz/store-sync";
|
112
112
|
import { createBenchmark } from "@latticexyz/common";
|
113
|
+
|
114
|
+
// src/sqlite/formatSqlQuery.ts
|
115
|
+
import sqlParser from "node-sql-parser";
|
116
|
+
function formatSqlQuery(sqlQuery) {
|
117
|
+
const parser = new sqlParser.Parser();
|
118
|
+
const ast = parser.astify(sqlQuery);
|
119
|
+
updateNode(ast);
|
120
|
+
return parser.sqlify(ast, { database: "sqlite" });
|
121
|
+
}
|
122
|
+
function updateNode(node) {
|
123
|
+
if (Array.isArray(node)) {
|
124
|
+
node.forEach((item) => updateNode(item));
|
125
|
+
return;
|
126
|
+
}
|
127
|
+
if (node && typeof node === "object") {
|
128
|
+
const astNode = node;
|
129
|
+
if (astNode.type === "column_ref" && typeof astNode.column === "string" && /^[a-z]+[A-Z][a-z]*$/.test(astNode.column)) {
|
130
|
+
astNode.column = camelToSnakeCase(astNode.column);
|
131
|
+
}
|
132
|
+
for (const [, value] of Object.entries(astNode)) {
|
133
|
+
if (typeof value === "object" && value !== null) {
|
134
|
+
updateNode(value);
|
135
|
+
}
|
136
|
+
}
|
137
|
+
}
|
138
|
+
}
|
139
|
+
function camelToSnakeCase(str) {
|
140
|
+
return str.replace(/([a-z]+)([A-Z][a-z]*)/g, "$1_$2").toLowerCase();
|
141
|
+
}
|
142
|
+
|
143
|
+
// src/sqlite/apiRoutes.ts
|
113
144
|
function apiRoutes({ database: database2, enableUnsafeQueryApi = false }) {
|
114
145
|
const router = new Router();
|
115
146
|
router.get("/api/logs", compress(), async (ctx) => {
|
@@ -153,7 +184,8 @@ function apiRoutes({ database: database2, enableUnsafeQueryApi = false }) {
|
|
153
184
|
}
|
154
185
|
const result = [];
|
155
186
|
for (const { query } of queries) {
|
156
|
-
const
|
187
|
+
const formattedQuery = formatSqlQuery(query);
|
188
|
+
const data = database2.all(sql.raw(formattedQuery));
|
157
189
|
if (!data || !Array.isArray(data)) {
|
158
190
|
throw new Error("Invalid query result");
|
159
191
|
}
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["../../src/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 Koa from \"koa\";\nimport cors from \"@koa/cors\";\nimport bodyParser from \"koa-bodyparser\";\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 \"../sqlite/createQueryAdapter\";\nimport { combineLatest, filter, first } from \"rxjs\";\nimport { frontendEnvSchema, indexerEnvSchema, parseEnv } from \"./parseEnv\";\nimport { healthcheck } from \"../koa-middleware/healthcheck\";\nimport { helloWorld } from \"../koa-middleware/helloWorld\";\nimport { apiRoutes } from \"../sqlite/apiRoutes\";\nimport { sentry } from \"../koa-middleware/sentry\";\nimport { metrics } from \"../koa-middleware/metrics\";\nimport { getClientOptions } from \"./getClientOptions\";\nimport { getRpcClient } from \"@latticexyz/block-logs-stream\";\nimport { getBlock, getChainId } from \"viem/actions\";\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 ENABLE_UNSAFE_QUERY_API: z\n .string()\n .optional()\n .default(\"false\")\n .transform((val) => val === \"true\"),\n }),\n ),\n);\n\nconst clientOptions = await getClientOptions(env);\n\nconst chainId = await getChainId(getRpcClient(clientOptions));\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 getBlock(getRpcClient(clientOptions), { 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 ...clientOptions,\n database,\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(bodyParser());\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, enableUnsafeQueryApi: env.ENABLE_UNSAFE_QUERY_API }));\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\nif (env.ENABLE_UNSAFE_QUERY_API) {\n console.warn(\"\\n\\n⚠️ SECURITY WARNING ⚠️\");\n console.warn(\"=========================\\n\");\n console.warn(\"UNSAFE QUERY API IS ENABLED\");\n console.warn(\"DO NOT USE IN PRODUCTION\");\n console.warn(\"This will expose your database to public access\");\n console.warn(\"\\n=========================\\n\\n\");\n}\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, TableRecord, TableWithRecords } from \"@latticexyz/store-sync\";\nimport { hexToResource } from \"@latticexyz/common\";\nimport { mapObject } from \"@latticexyz/common/utils\";\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 const resource = hexToResource(table.tableId);\n return {\n ...table,\n type: resource.type as never,\n schema: mapObject({ ...table.keySchema, ...table.valueSchema }, (type) => ({ type, internalType: type })),\n key: Object.keys(table.keySchema),\n records: filteredRecords.map((record): TableRecord => {\n const key = Object.fromEntries(Object.entries(table.keySchema).map(([name]) => [name, record[name]]));\n const value = Object.fromEntries(Object.entries(table.valueSchema).map(([name]) => [name, record[name]]));\n return { key, value, fields: { ...key, ...value } };\n }),\n } satisfies TableWithRecords;\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 { BaseSQLiteDatabase } from \"drizzle-orm/sqlite-core\";\nimport { sql } from \"drizzle-orm\";\nimport { Middleware } from \"koa\";\nimport Router from \"@koa/router\";\nimport compose from \"koa-compose\";\nimport { input } from \"@latticexyz/store-sync/indexer-client\";\nimport { schemasTable, tablesWithRecordsToLogs } from \"@latticexyz/store-sync\";\nimport { debug } from \"../debug\";\nimport { createBenchmark } from \"@latticexyz/common\";\nimport { compress } from \"../koa-middleware/compress\";\nimport { getTablesWithRecords } from \"./getTablesWithRecords\";\n\ntype Props = {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n database: BaseSQLiteDatabase<\"sync\", any>;\n enableUnsafeQueryApi?: boolean;\n};\n\nexport function apiRoutes({ database, enableUnsafeQueryApi = false }: Props): 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: schemasTable.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 router.post(\"/q\", async (ctx) => {\n if (!enableUnsafeQueryApi) {\n ctx.status = 404;\n ctx.body = JSON.stringify({ error: \"Query endpoint is not enabled\" });\n return;\n }\n\n try {\n const queries = Array.isArray(ctx.request.body) ? ctx.request.body : [];\n if (queries.length === 0) {\n ctx.status = 400;\n ctx.body = JSON.stringify({ error: \"No queries provided\" });\n return;\n }\n\n const result = [];\n for (const { query } of queries) {\n const data = database.all(sql.raw(query)) as Record<string, unknown>[];\n if (!data || !Array.isArray(data)) {\n throw new Error(\"Invalid query result\");\n }\n\n if (data.length === 0) {\n result.push([]);\n continue;\n }\n\n if (!data[0]) {\n throw new Error(\"Invalid row data\");\n }\n\n const columns = Object.keys(data[0]).map((key) => key.replaceAll(\"_\", \"\").toLowerCase());\n const rows = data.map((row) => Object.values(row).map((value) => value?.toString() ?? \"\"));\n result.push([columns, ...rows]);\n }\n\n ctx.status = 200;\n ctx.body = JSON.stringify({ result });\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"An unknown error occurred\";\n ctx.status = 400;\n ctx.body = JSON.stringify({ error: errorMessage });\n }\n });\n\n return compose([router.routes(), router.allowedMethods()]) as Middleware;\n}\n"],"mappings":"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","names":["eq","chainState","database","chainId","filter","database","tablesWithRecordsToLogs","database","tablesWithRecordsToLogs","chainState","eq","currentChainState"]}
|
1
|
+
{"version":3,"sources":["../../src/bin/sqlite-indexer.ts","../../src/sqlite/getTablesWithRecords.ts","../../src/sqlite/createQueryAdapter.ts","../../src/sqlite/apiRoutes.ts","../../src/sqlite/formatSqlQuery.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 Koa from \"koa\";\nimport cors from \"@koa/cors\";\nimport bodyParser from \"koa-bodyparser\";\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 \"../sqlite/createQueryAdapter\";\nimport { combineLatest, filter, first } from \"rxjs\";\nimport { frontendEnvSchema, indexerEnvSchema, parseEnv } from \"./parseEnv\";\nimport { healthcheck } from \"../koa-middleware/healthcheck\";\nimport { helloWorld } from \"../koa-middleware/helloWorld\";\nimport { apiRoutes } from \"../sqlite/apiRoutes\";\nimport { sentry } from \"../koa-middleware/sentry\";\nimport { metrics } from \"../koa-middleware/metrics\";\nimport { getClientOptions } from \"./getClientOptions\";\nimport { getRpcClient } from \"@latticexyz/block-logs-stream\";\nimport { getBlock, getChainId } from \"viem/actions\";\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 ENABLE_UNSAFE_QUERY_API: z\n .string()\n .optional()\n .default(\"false\")\n .transform((val) => val === \"true\"),\n }),\n ),\n);\n\nconst clientOptions = await getClientOptions(env);\n\nconst chainId = await getChainId(getRpcClient(clientOptions));\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 getBlock(getRpcClient(clientOptions), { 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 ...clientOptions,\n database,\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(bodyParser());\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, enableUnsafeQueryApi: env.ENABLE_UNSAFE_QUERY_API }));\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\nif (env.ENABLE_UNSAFE_QUERY_API) {\n console.warn(\"\\n\\n⚠️ SECURITY WARNING ⚠️\");\n console.warn(\"=========================\\n\");\n console.warn(\"UNSAFE QUERY API IS ENABLED\");\n console.warn(\"DO NOT USE IN PRODUCTION\");\n console.warn(\"This will expose your database to public access\");\n console.warn(\"\\n=========================\\n\\n\");\n}\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, TableRecord, TableWithRecords } from \"@latticexyz/store-sync\";\nimport { hexToResource } from \"@latticexyz/common\";\nimport { mapObject } from \"@latticexyz/common/utils\";\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 const resource = hexToResource(table.tableId);\n return {\n ...table,\n type: resource.type as never,\n schema: mapObject({ ...table.keySchema, ...table.valueSchema }, (type) => ({ type, internalType: type })),\n key: Object.keys(table.keySchema),\n records: filteredRecords.map((record): TableRecord => {\n const key = Object.fromEntries(Object.entries(table.keySchema).map(([name]) => [name, record[name]]));\n const value = Object.fromEntries(Object.entries(table.valueSchema).map(([name]) => [name, record[name]]));\n return { key, value, fields: { ...key, ...value } };\n }),\n } satisfies TableWithRecords;\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 { BaseSQLiteDatabase } from \"drizzle-orm/sqlite-core\";\nimport { sql } from \"drizzle-orm\";\nimport { Middleware } from \"koa\";\nimport Router from \"@koa/router\";\nimport compose from \"koa-compose\";\nimport { input } from \"@latticexyz/store-sync/indexer-client\";\nimport { schemasTable, 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 { formatSqlQuery } from \"./formatSqlQuery\";\n\ntype Props = {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n database: BaseSQLiteDatabase<\"sync\", any>;\n enableUnsafeQueryApi?: boolean;\n};\n\nexport function apiRoutes({ database, enableUnsafeQueryApi = false }: Props): 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: schemasTable.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 router.post(\"/q\", async (ctx) => {\n if (!enableUnsafeQueryApi) {\n ctx.status = 404;\n ctx.body = JSON.stringify({ error: \"Query endpoint is not enabled\" });\n return;\n }\n\n try {\n const queries = Array.isArray(ctx.request.body) ? ctx.request.body : [];\n if (queries.length === 0) {\n ctx.status = 400;\n ctx.body = JSON.stringify({ error: \"No queries provided\" });\n return;\n }\n\n const result = [];\n for (const { query } of queries) {\n const formattedQuery = formatSqlQuery(query);\n const data = database.all(sql.raw(formattedQuery)) as Record<string, unknown>[];\n if (!data || !Array.isArray(data)) {\n throw new Error(\"Invalid query result\");\n }\n\n if (data.length === 0) {\n result.push([]);\n continue;\n }\n\n if (!data[0]) {\n throw new Error(\"Invalid row data\");\n }\n\n const columns = Object.keys(data[0]).map((key) => key.replaceAll(\"_\", \"\").toLowerCase());\n const rows = data.map((row) => Object.values(row).map((value) => value?.toString() ?? \"\"));\n result.push([columns, ...rows]);\n }\n\n ctx.status = 200;\n ctx.body = JSON.stringify({ result });\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"An unknown error occurred\";\n ctx.status = 400;\n ctx.body = JSON.stringify({ error: errorMessage });\n }\n });\n\n return compose([router.routes(), router.allowedMethods()]) as Middleware;\n}\n","import sqlParser from \"node-sql-parser\";\n\ntype AstNode = {\n type?: string;\n [key: string]: unknown;\n};\n\n/**\n * Transforms camelCase identifiers to snake_case in SQL queries\n *\n * @param sqlQuery The SQL query to transform\n * @returns The transformed SQL query\n */\nexport function formatSqlQuery(sqlQuery: string): string {\n const parser = new sqlParser.Parser();\n const ast = parser.astify(sqlQuery);\n\n updateNode(ast);\n\n return parser.sqlify(ast, { database: \"sqlite\" });\n}\n\nfunction updateNode(node: unknown): void {\n if (Array.isArray(node)) {\n node.forEach((item) => updateNode(item));\n return;\n }\n\n if (node && typeof node === \"object\") {\n const astNode = node as AstNode;\n\n if (\n astNode.type === \"column_ref\" &&\n typeof astNode.column === \"string\" &&\n /^[a-z]+[A-Z][a-z]*$/.test(astNode.column)\n ) {\n astNode.column = camelToSnakeCase(astNode.column);\n }\n\n for (const [, value] of Object.entries(astNode)) {\n if (typeof value === \"object\" && value !== null) {\n updateNode(value);\n }\n }\n }\n}\n\nfunction camelToSnakeCase(str: string): string {\n return str.replace(/([a-z]+)([A-Z][a-z]*)/g, \"$1_$2\").toLowerCase();\n}\n"],"mappings":"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","names":["eq","chainState","database","chainId","filter","database","tablesWithRecordsToLogs","database","tablesWithRecordsToLogs","chainState","eq","currentChainState"]}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@latticexyz/store-indexer",
|
3
|
-
"version": "2.2.22-
|
3
|
+
"version": "2.2.22-fbf1be12730c08acd460aa36124a0565f4e73401",
|
4
4
|
"description": "Minimal Typescript indexer for Store",
|
5
5
|
"repository": {
|
6
6
|
"type": "git",
|
@@ -46,17 +46,18 @@
|
|
46
46
|
"koa": "^2.15.4",
|
47
47
|
"koa-bodyparser": "^4.4.1",
|
48
48
|
"koa-compose": "^4.1.0",
|
49
|
+
"node-sql-parser": "^5.3.3",
|
49
50
|
"postgres": "3.3.5",
|
50
51
|
"prom-client": "^15.1.2",
|
51
52
|
"rxjs": "7.5.5",
|
52
53
|
"superjson": "^1.12.4",
|
53
54
|
"trpc-koa-adapter": "^1.1.3",
|
54
55
|
"zod": "3.23.8",
|
55
|
-
"@latticexyz/block-logs-stream": "2.2.22-
|
56
|
-
"@latticexyz/common": "2.2.22-
|
57
|
-
"@latticexyz/protocol-parser": "2.2.22-
|
58
|
-
"@latticexyz/store": "2.2.22-
|
59
|
-
"@latticexyz/store-sync": "2.2.22-
|
56
|
+
"@latticexyz/block-logs-stream": "2.2.22-fbf1be12730c08acd460aa36124a0565f4e73401",
|
57
|
+
"@latticexyz/common": "2.2.22-fbf1be12730c08acd460aa36124a0565f4e73401",
|
58
|
+
"@latticexyz/protocol-parser": "2.2.22-fbf1be12730c08acd460aa36124a0565f4e73401",
|
59
|
+
"@latticexyz/store": "2.2.22-fbf1be12730c08acd460aa36124a0565f4e73401",
|
60
|
+
"@latticexyz/store-sync": "2.2.22-fbf1be12730c08acd460aa36124a0565f4e73401"
|
60
61
|
},
|
61
62
|
"devDependencies": {
|
62
63
|
"@types/accepts": "^1.3.7",
|
@@ -91,7 +92,7 @@
|
|
91
92
|
"start:sqlite": "tsx src/bin/sqlite-indexer",
|
92
93
|
"start:sqlite:local": "SQLITE_FILENAME=anvil.db RPC_HTTP_URL=http://127.0.0.1:8545 pnpm start:sqlite",
|
93
94
|
"start:sqlite:testnet": "SQLITE_FILENAME=testnet.db RPC_HTTP_URL=https://rpc.holesky.redstone.xyz pnpm start:sqlite",
|
94
|
-
"test": "tsc --noEmit",
|
95
|
+
"test": "tsc --noEmit && vitest --run --passWithNoTests",
|
95
96
|
"test:ci": "pnpm run test"
|
96
97
|
}
|
97
98
|
}
|