@hugomrdias/foxer 0.0.1

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.
Files changed (311) hide show
  1. package/README.md +56 -0
  2. package/dist/src/api/index.d.ts +2 -0
  3. package/dist/src/api/index.d.ts.map +1 -0
  4. package/dist/src/api/index.js +2 -0
  5. package/dist/src/api/index.js.map +1 -0
  6. package/dist/src/api/runner.d.ts +12 -0
  7. package/dist/src/api/runner.d.ts.map +1 -0
  8. package/dist/src/api/runner.js +29 -0
  9. package/dist/src/api/runner.js.map +1 -0
  10. package/dist/src/api/server.d.ts +18 -0
  11. package/dist/src/api/server.d.ts.map +1 -0
  12. package/dist/src/api/server.js +21 -0
  13. package/dist/src/api/server.js.map +1 -0
  14. package/dist/src/api/sql-middleware.d.ts +7 -0
  15. package/dist/src/api/sql-middleware.d.ts.map +1 -0
  16. package/dist/src/api/sql-middleware.js +95 -0
  17. package/dist/src/api/sql-middleware.js.map +1 -0
  18. package/dist/src/api/sql.d.ts +14 -0
  19. package/dist/src/api/sql.d.ts.map +1 -0
  20. package/dist/src/api/sql.js +108 -0
  21. package/dist/src/api/sql.js.map +1 -0
  22. package/dist/src/api/sse.d.ts +3 -0
  23. package/dist/src/api/sse.d.ts.map +1 -0
  24. package/dist/src/api/sse.js +11 -0
  25. package/dist/src/api/sse.js.map +1 -0
  26. package/dist/src/bin/dev.d.ts +3 -0
  27. package/dist/src/bin/dev.d.ts.map +1 -0
  28. package/dist/src/bin/dev.js +76 -0
  29. package/dist/src/bin/dev.js.map +1 -0
  30. package/dist/src/bin/flags.d.ts +32 -0
  31. package/dist/src/bin/flags.d.ts.map +1 -0
  32. package/dist/src/bin/flags.js +55 -0
  33. package/dist/src/bin/flags.js.map +1 -0
  34. package/dist/src/bin/index.d.ts +3 -0
  35. package/dist/src/bin/index.d.ts.map +1 -0
  36. package/dist/src/bin/index.js +22 -0
  37. package/dist/src/bin/index.js.map +1 -0
  38. package/dist/src/bin/utils.d.ts +4 -0
  39. package/dist/src/bin/utils.d.ts.map +1 -0
  40. package/dist/src/bin/utils.js +52 -0
  41. package/dist/src/bin/utils.js.map +1 -0
  42. package/dist/src/client/index.d.ts +18 -0
  43. package/dist/src/client/index.d.ts.map +1 -0
  44. package/dist/src/client/index.js +150 -0
  45. package/dist/src/client/index.js.map +1 -0
  46. package/dist/src/config/config.d.ts +157 -0
  47. package/dist/src/config/config.d.ts.map +1 -0
  48. package/dist/src/config/config.js +65 -0
  49. package/dist/src/config/config.js.map +1 -0
  50. package/dist/src/config/env.d.ts +25 -0
  51. package/dist/src/config/env.d.ts.map +1 -0
  52. package/dist/src/config/env.js +21 -0
  53. package/dist/src/config/env.js.map +1 -0
  54. package/dist/src/contants.d.ts +4 -0
  55. package/dist/src/contants.d.ts.map +1 -0
  56. package/dist/src/contants.js +4 -0
  57. package/dist/src/contants.js.map +1 -0
  58. package/dist/src/db/actions/blocks.d.ts +44 -0
  59. package/dist/src/db/actions/blocks.d.ts.map +1 -0
  60. package/dist/src/db/actions/blocks.js +152 -0
  61. package/dist/src/db/actions/blocks.js.map +1 -0
  62. package/dist/src/db/actions/index.d.ts +2 -0
  63. package/dist/src/db/actions/index.d.ts.map +1 -0
  64. package/dist/src/db/actions/index.js +2 -0
  65. package/dist/src/db/actions/index.js.map +1 -0
  66. package/dist/src/db/actions/transactions.d.ts +11 -0
  67. package/dist/src/db/actions/transactions.d.ts.map +1 -0
  68. package/dist/src/db/actions/transactions.js +22 -0
  69. package/dist/src/db/actions/transactions.js.map +1 -0
  70. package/dist/src/db/client.d.ts +329 -0
  71. package/dist/src/db/client.d.ts.map +1 -0
  72. package/dist/src/db/client.js +108 -0
  73. package/dist/src/db/client.js.map +1 -0
  74. package/dist/src/db/column-types.d.ts +132 -0
  75. package/dist/src/db/column-types.d.ts.map +1 -0
  76. package/dist/src/db/column-types.js +86 -0
  77. package/dist/src/db/column-types.js.map +1 -0
  78. package/dist/src/db/encode.d.ts +10 -0
  79. package/dist/src/db/encode.d.ts.map +1 -0
  80. package/dist/src/db/encode.js +79 -0
  81. package/dist/src/db/encode.js.map +1 -0
  82. package/dist/src/db/migrate.d.ts +31 -0
  83. package/dist/src/db/migrate.d.ts.map +1 -0
  84. package/dist/src/db/migrate.js +147 -0
  85. package/dist/src/db/migrate.js.map +1 -0
  86. package/dist/src/db/schema/blocks.d.ts +369 -0
  87. package/dist/src/db/schema/blocks.d.ts.map +1 -0
  88. package/dist/src/db/schema/blocks.js +24 -0
  89. package/dist/src/db/schema/blocks.js.map +1 -0
  90. package/dist/src/db/schema/index.d.ts +1415 -0
  91. package/dist/src/db/schema/index.d.ts.map +1 -0
  92. package/dist/src/db/schema/index.js +18 -0
  93. package/dist/src/db/schema/index.js.map +1 -0
  94. package/dist/src/db/schema/transactions.d.ts +336 -0
  95. package/dist/src/db/schema/transactions.d.ts.map +1 -0
  96. package/dist/src/db/schema/transactions.js +33 -0
  97. package/dist/src/db/schema/transactions.js.map +1 -0
  98. package/dist/src/db/transaction.d.ts +7 -0
  99. package/dist/src/db/transaction.d.ts.map +1 -0
  100. package/dist/src/db/transaction.js +8 -0
  101. package/dist/src/db/transaction.js.map +1 -0
  102. package/dist/src/hooks/default-hooks.d.ts +2 -0
  103. package/dist/src/hooks/default-hooks.d.ts.map +1 -0
  104. package/dist/src/hooks/default-hooks.js +107 -0
  105. package/dist/src/hooks/default-hooks.js.map +1 -0
  106. package/dist/src/hooks/registry.d.ts +51 -0
  107. package/dist/src/hooks/registry.d.ts.map +1 -0
  108. package/dist/src/hooks/registry.js +32 -0
  109. package/dist/src/hooks/registry.js.map +1 -0
  110. package/dist/src/index.d.ts +10 -0
  111. package/dist/src/index.d.ts.map +1 -0
  112. package/dist/src/index.js +4 -0
  113. package/dist/src/index.js.map +1 -0
  114. package/dist/src/indexer/backfill.d.ts +15 -0
  115. package/dist/src/indexer/backfill.d.ts.map +1 -0
  116. package/dist/src/indexer/backfill.js +95 -0
  117. package/dist/src/indexer/backfill.js.map +1 -0
  118. package/dist/src/indexer/live.d.ts +20 -0
  119. package/dist/src/indexer/live.d.ts.map +1 -0
  120. package/dist/src/indexer/live.js +51 -0
  121. package/dist/src/indexer/live.js.map +1 -0
  122. package/dist/src/indexer/process-block.d.ts +29 -0
  123. package/dist/src/indexer/process-block.d.ts.map +1 -0
  124. package/dist/src/indexer/process-block.js +91 -0
  125. package/dist/src/indexer/process-block.js.map +1 -0
  126. package/dist/src/indexer/queue-block.d.ts +18 -0
  127. package/dist/src/indexer/queue-block.d.ts.map +1 -0
  128. package/dist/src/indexer/queue-block.js +38 -0
  129. package/dist/src/indexer/queue-block.js.map +1 -0
  130. package/dist/src/indexer/reorg.d.ts +24 -0
  131. package/dist/src/indexer/reorg.d.ts.map +1 -0
  132. package/dist/src/indexer/reorg.js +83 -0
  133. package/dist/src/indexer/reorg.js.map +1 -0
  134. package/dist/src/indexer/runner.d.ts +14 -0
  135. package/dist/src/indexer/runner.d.ts.map +1 -0
  136. package/dist/src/indexer/runner.js +22 -0
  137. package/dist/src/indexer/runner.js.map +1 -0
  138. package/dist/src/rpc/client.d.ts +11 -0
  139. package/dist/src/rpc/client.d.ts.map +1 -0
  140. package/dist/src/rpc/client.js +18 -0
  141. package/dist/src/rpc/client.js.map +1 -0
  142. package/dist/src/rpc/get-block.d.ts +16 -0
  143. package/dist/src/rpc/get-block.d.ts.map +1 -0
  144. package/dist/src/rpc/get-block.js +77 -0
  145. package/dist/src/rpc/get-block.js.map +1 -0
  146. package/dist/src/rpc/get-logs.d.ts +11 -0
  147. package/dist/src/rpc/get-logs.d.ts.map +1 -0
  148. package/dist/src/rpc/get-logs.js +23 -0
  149. package/dist/src/rpc/get-logs.js.map +1 -0
  150. package/dist/src/schema.d.ts +3 -0
  151. package/dist/src/schema.d.ts.map +1 -0
  152. package/dist/src/schema.js +9 -0
  153. package/dist/src/schema.js.map +1 -0
  154. package/dist/src/types.d.ts +22 -0
  155. package/dist/src/types.d.ts.map +1 -0
  156. package/dist/src/types.js +1 -0
  157. package/dist/src/types.js.map +1 -0
  158. package/dist/src/utils/bloom.d.ts +6 -0
  159. package/dist/src/utils/bloom.d.ts.map +1 -0
  160. package/dist/src/utils/bloom.js +30 -0
  161. package/dist/src/utils/bloom.js.map +1 -0
  162. package/dist/src/utils/build-conflict-columns.d.ts +4 -0
  163. package/dist/src/utils/build-conflict-columns.d.ts.map +1 -0
  164. package/dist/src/utils/build-conflict-columns.js +14 -0
  165. package/dist/src/utils/build-conflict-columns.js.map +1 -0
  166. package/dist/src/utils/common.d.ts +2 -0
  167. package/dist/src/utils/common.d.ts.map +1 -0
  168. package/dist/src/utils/common.js +4 -0
  169. package/dist/src/utils/common.js.map +1 -0
  170. package/dist/src/utils/cursor.d.ts +5 -0
  171. package/dist/src/utils/cursor.d.ts.map +1 -0
  172. package/dist/src/utils/cursor.js +8 -0
  173. package/dist/src/utils/cursor.js.map +1 -0
  174. package/dist/src/utils/format.d.ts +2 -0
  175. package/dist/src/utils/format.d.ts.map +1 -0
  176. package/dist/src/utils/format.js +16 -0
  177. package/dist/src/utils/format.js.map +1 -0
  178. package/dist/src/utils/hash.d.ts +9 -0
  179. package/dist/src/utils/hash.d.ts.map +1 -0
  180. package/dist/src/utils/hash.js +15 -0
  181. package/dist/src/utils/hash.js.map +1 -0
  182. package/dist/src/utils/json.d.ts +5 -0
  183. package/dist/src/utils/json.d.ts.map +1 -0
  184. package/dist/src/utils/json.js +11 -0
  185. package/dist/src/utils/json.js.map +1 -0
  186. package/dist/src/utils/logger.d.ts +11 -0
  187. package/dist/src/utils/logger.d.ts.map +1 -0
  188. package/dist/src/utils/logger.js +111 -0
  189. package/dist/src/utils/logger.js.map +1 -0
  190. package/dist/src/utils/shutdown.d.ts +9 -0
  191. package/dist/src/utils/shutdown.d.ts.map +1 -0
  192. package/dist/src/utils/shutdown.js +24 -0
  193. package/dist/src/utils/shutdown.js.map +1 -0
  194. package/dist/src/utils/timer.d.ts +6 -0
  195. package/dist/src/utils/timer.d.ts.map +1 -0
  196. package/dist/src/utils/timer.js +9 -0
  197. package/dist/src/utils/timer.js.map +1 -0
  198. package/dist/src/utils/types.d.ts +39 -0
  199. package/dist/src/utils/types.d.ts.map +1 -0
  200. package/dist/src/utils/types.js +1 -0
  201. package/dist/src/utils/types.js.map +1 -0
  202. package/dist/tsconfig.tsbuildinfo +1 -0
  203. package/hello/apps/foc-api/README.md +69 -0
  204. package/hello/apps/foc-api/biome.json +8 -0
  205. package/hello/apps/foc-api/index.html +13 -0
  206. package/hello/apps/foc-api/package.json +39 -0
  207. package/hello/apps/foc-api/public/vite.svg +1 -0
  208. package/hello/apps/foc-api/src/app.css +45 -0
  209. package/hello/apps/foc-api/src/app.tsx +43 -0
  210. package/hello/apps/foc-api/src/assets/Cloudflare_Logo.svg +51 -0
  211. package/hello/apps/foc-api/src/assets/react.svg +1 -0
  212. package/hello/apps/foc-api/src/client.ts +41 -0
  213. package/hello/apps/foc-api/src/components/account.tsx +100 -0
  214. package/hello/apps/foc-api/src/components/wallet-options.tsx +43 -0
  215. package/hello/apps/foc-api/src/index.css +68 -0
  216. package/hello/apps/foc-api/src/main.tsx +38 -0
  217. package/hello/apps/foc-api/src/vite-env.d.ts +1 -0
  218. package/hello/apps/foc-api/tsconfig.app.json +44 -0
  219. package/hello/apps/foc-api/tsconfig.json +17 -0
  220. package/hello/apps/foc-api/tsconfig.node.json +25 -0
  221. package/hello/apps/foc-api/tsconfig.worker.json +8 -0
  222. package/hello/apps/foc-api/vite.config.ts +8 -0
  223. package/hello/apps/foc-api/worker/capabilities.ts +25 -0
  224. package/hello/apps/foc-api/worker/index.ts +64 -0
  225. package/hello/apps/foc-api/worker/router.ts +35 -0
  226. package/hello/apps/foc-api/worker-configuration.d.ts +7357 -0
  227. package/hello/apps/foc-api/wrangler.jsonc +50 -0
  228. package/hello/apps/foc-app/README.md +69 -0
  229. package/hello/apps/foc-app/biome.json +8 -0
  230. package/hello/apps/foc-app/index.html +13 -0
  231. package/hello/apps/foc-app/package.json +39 -0
  232. package/hello/apps/foc-app/public/vite.svg +1 -0
  233. package/hello/apps/foc-app/src/app.css +45 -0
  234. package/hello/apps/foc-app/src/app.tsx +43 -0
  235. package/hello/apps/foc-app/src/assets/Cloudflare_Logo.svg +51 -0
  236. package/hello/apps/foc-app/src/assets/react.svg +1 -0
  237. package/hello/apps/foc-app/src/client.ts +41 -0
  238. package/hello/apps/foc-app/src/components/account.tsx +100 -0
  239. package/hello/apps/foc-app/src/components/wallet-options.tsx +43 -0
  240. package/hello/apps/foc-app/src/index.css +68 -0
  241. package/hello/apps/foc-app/src/main.tsx +38 -0
  242. package/hello/apps/foc-app/src/vite-env.d.ts +1 -0
  243. package/hello/apps/foc-app/tsconfig.app.json +44 -0
  244. package/hello/apps/foc-app/tsconfig.json +17 -0
  245. package/hello/apps/foc-app/tsconfig.node.json +25 -0
  246. package/hello/apps/foc-app/tsconfig.worker.json +8 -0
  247. package/hello/apps/foc-app/vite.config.ts +8 -0
  248. package/hello/apps/foc-app/worker/capabilities.ts +25 -0
  249. package/hello/apps/foc-app/worker/index.ts +64 -0
  250. package/hello/apps/foc-app/worker/router.ts +35 -0
  251. package/hello/apps/foc-app/worker-configuration.d.ts +7357 -0
  252. package/hello/apps/foc-app/wrangler.jsonc +50 -0
  253. package/hello/biome.json +50 -0
  254. package/hello/package.json +22 -0
  255. package/hello/pnpm-workspace.yaml +3 -0
  256. package/hello/tsconfig.json +37 -0
  257. package/package.json +78 -0
  258. package/src/api/index.ts +1 -0
  259. package/src/api/runner.ts +43 -0
  260. package/src/api/server.ts +38 -0
  261. package/src/api/sql-middleware.ts +131 -0
  262. package/src/api/sql.ts +149 -0
  263. package/src/api/sse.ts +12 -0
  264. package/src/bin/create.ts +199 -0
  265. package/src/bin/dev.ts +91 -0
  266. package/src/bin/flags.ts +65 -0
  267. package/src/bin/index.ts +28 -0
  268. package/src/bin/utils.ts +55 -0
  269. package/src/config/config.ts +221 -0
  270. package/src/config/env.ts +28 -0
  271. package/src/contants.ts +3 -0
  272. package/src/db/actions/blocks.ts +209 -0
  273. package/src/db/actions/index.ts +1 -0
  274. package/src/db/actions/transactions.ts +32 -0
  275. package/src/db/client.ts +186 -0
  276. package/src/db/column-types.ts +105 -0
  277. package/src/db/encode.ts +99 -0
  278. package/src/db/migrate.ts +222 -0
  279. package/src/db/schema/blocks.ts +24 -0
  280. package/src/db/schema/index.ts +21 -0
  281. package/src/db/schema/transactions.ts +39 -0
  282. package/src/db/transaction.ts +20 -0
  283. package/src/hooks/registry.ts +107 -0
  284. package/src/index.ts +9 -0
  285. package/src/indexer/backfill.ts +133 -0
  286. package/src/indexer/live.ts +76 -0
  287. package/src/indexer/process-block.ts +142 -0
  288. package/src/indexer/queue-block.ts +74 -0
  289. package/src/indexer/reorg.ts +120 -0
  290. package/src/indexer/runner.ts +35 -0
  291. package/src/rpc/client.ts +27 -0
  292. package/src/rpc/get-block.ts +100 -0
  293. package/src/rpc/get-logs.ts +38 -0
  294. package/src/schema.ts +10 -0
  295. package/src/types.ts +32 -0
  296. package/src/utils/bloom.ts +41 -0
  297. package/src/utils/build-conflict-columns.ts +26 -0
  298. package/src/utils/common.ts +3 -0
  299. package/src/utils/cursor.ts +7 -0
  300. package/src/utils/format.ts +18 -0
  301. package/src/utils/hash.ts +17 -0
  302. package/src/utils/json.ts +11 -0
  303. package/src/utils/logger.ts +149 -0
  304. package/src/utils/shutdown.ts +36 -0
  305. package/src/utils/timer.ts +8 -0
  306. package/src/utils/types.ts +87 -0
  307. package/template/biome.json +50 -0
  308. package/template/package.json +22 -0
  309. package/template/pnpm-workspace.yaml +3 -0
  310. package/template/tsconfig.json +37 -0
  311. package/tsconfig.json +8 -0
@@ -0,0 +1,209 @@
1
+ /** biome-ignore-all lint/style/noNonNullAssertion: its ok */
2
+
3
+ import { gte } from 'drizzle-orm'
4
+ import {
5
+ getTableConfig,
6
+ type PgAsyncTransaction,
7
+ type PgColumn,
8
+ type PgQueryResultHKT,
9
+ type PgTable,
10
+ } from 'drizzle-orm/pg-core'
11
+ import type { PublicClient } from 'viem'
12
+ import type { FilteredContracts } from '../../config/config.ts'
13
+ import { MAX_QUERY_PARAMS } from '../../contants.ts'
14
+ import { safeGetBlock } from '../../rpc/get-block.ts'
15
+ import type {
16
+ EncodedBlockWithTransactions,
17
+ EncodedTransaction,
18
+ } from '../../types.ts'
19
+ import type { Logger } from '../../utils/logger.ts'
20
+ import { startClock } from '../../utils/timer.ts'
21
+ import type { Database } from '../client.ts'
22
+ import { type relations, schema } from '../schema/index.ts'
23
+ import { insertTransactionsInChunks } from './transactions.ts'
24
+
25
+ /**
26
+ * Deletes canonical block rows from a specific block onward.
27
+ */
28
+ export async function deleteBlocksFrom(
29
+ db: Database,
30
+ fromBlock: bigint
31
+ ): Promise<void> {
32
+ const deleteTargets = getTablesWithBlockNumberColumn(
33
+ db._.fullSchema as Record<string, unknown>
34
+ )
35
+
36
+ await db.transaction(async (tx) => {
37
+ for (const target of deleteTargets) {
38
+ await tx
39
+ .delete(target.table)
40
+ .where(gte(target.blockNumberColumn, fromBlock))
41
+ }
42
+
43
+ // blocks uses `number` instead of blockNumber
44
+ await tx.delete(schema.blocks).where(gte(schema.blocks.number, fromBlock))
45
+ })
46
+ }
47
+
48
+ function getTablesWithBlockNumberColumn(fullSchema: Record<string, unknown>) {
49
+ const targets: Array<{ table: PgTable; blockNumberColumn: PgColumn }> = []
50
+
51
+ for (const table of Object.values(fullSchema)) {
52
+ const pgTable = table as PgTable
53
+ const config = getTableConfig(pgTable)
54
+ const blockNumberColumn = config.columns.find((column) =>
55
+ ['blockNumber', 'block_number'].includes(column.name)
56
+ )
57
+ if (!blockNumberColumn) continue
58
+
59
+ targets.push({ table: pgTable, blockNumberColumn })
60
+ }
61
+
62
+ return targets
63
+ }
64
+
65
+ /**
66
+ * Caches a block and its transactions in the database.
67
+ *
68
+ * @param args - The arguments for the function
69
+ * @param args.db - The database instance
70
+ * @param args.block - The block to cache
71
+ * @returns The cached block and transactions
72
+ */
73
+ export async function cacheBlockAndTransactions(args: {
74
+ db: Database<typeof schema, typeof relations>
75
+ block: EncodedBlockWithTransactions
76
+ logger: Logger
77
+ }): Promise<void> {
78
+ const { db, block } = args
79
+
80
+ await db.transaction(async (tx) => {
81
+ await insertBlocksInChunks({
82
+ db: tx,
83
+ blocks: [block],
84
+ })
85
+ await insertTransactionsInChunks({
86
+ db: tx,
87
+ transactions: block.transactions,
88
+ })
89
+ })
90
+ }
91
+
92
+ /**
93
+ * Gets blocks and their transactions from the database in a range and fetches missing blocks from the RPC.
94
+ *
95
+ * @param args - The arguments for the function
96
+ * @param args.logger - The logger instance
97
+ * @param args.db - The database instance
98
+ * @param args.blockNumbers - The block numbers to get
99
+ * @param args.client - The client instance
100
+ * @returns The blocks and their transactions
101
+ */
102
+ export async function getBlocksInRange(
103
+ logger: Logger,
104
+ db: Database<typeof schema, typeof relations>,
105
+ blockNumbers: bigint[],
106
+ client: PublicClient,
107
+ contracts: FilteredContracts
108
+ ): Promise<Map<bigint, EncodedBlockWithTransactions>> {
109
+ const endClock = startClock()
110
+ const firstBlockNumber = blockNumbers[0]!
111
+ const lastBlockNumber = blockNumbers[blockNumbers.length - 1]!
112
+
113
+ // const r = await db.$prepared.getBlocksInRange.execute({
114
+ // firstBlockNumber,
115
+ // lastBlockNumber,
116
+ // contractAddresses: contracts.addresses,
117
+ // })
118
+
119
+ const r = await db.query.blocks.findMany({
120
+ with: {
121
+ transactions: {
122
+ where: {
123
+ AND: [
124
+ { blockNumber: { gte: firstBlockNumber } },
125
+ { blockNumber: { lte: lastBlockNumber } },
126
+ {
127
+ to: {
128
+ in: contracts.addresses,
129
+ },
130
+ },
131
+ ],
132
+ },
133
+ },
134
+ },
135
+ where: {
136
+ AND: [
137
+ { number: { gte: firstBlockNumber } },
138
+ { number: { lte: lastBlockNumber } },
139
+ ],
140
+ },
141
+ })
142
+
143
+ const blocksByNumber = new Map<bigint, EncodedBlockWithTransactions>()
144
+ const missing = new Set(blockNumbers)
145
+
146
+ for (const block of r) {
147
+ blocksByNumber.set(block.number, block)
148
+ missing.delete(block.number)
149
+ }
150
+
151
+ const missingBlockNumbers = [...missing]
152
+ const newBlocks: EncodedBlockWithTransactions[] = []
153
+ const newTransactions: EncodedTransaction[] = []
154
+
155
+ await Promise.all(
156
+ missingBlockNumbers.map(async (blockNumber) => {
157
+ const block = await safeGetBlock({ client, blockNumber, db })
158
+ const transactions = block.transactions
159
+ blocksByNumber.set(blockNumber, block)
160
+ newBlocks.push(block)
161
+ if (transactions.length > 0) {
162
+ newTransactions.push(...transactions)
163
+ }
164
+ })
165
+ )
166
+
167
+ await db.transaction(async (tx) => {
168
+ await insertBlocksInChunks({
169
+ db: tx,
170
+ blocks: newBlocks,
171
+ })
172
+ await insertTransactionsInChunks({
173
+ db: tx,
174
+ transactions: newTransactions,
175
+ })
176
+ })
177
+
178
+ logger.info(
179
+ {
180
+ blocks: blocksByNumber.size,
181
+ missing: missingBlockNumbers.length,
182
+ duration: endClock(),
183
+ },
184
+ 'get blocks'
185
+ )
186
+ return blocksByNumber
187
+ }
188
+
189
+ /**
190
+ * Inserts blocks in chunks to avoid query parameter limit.
191
+ */
192
+ export async function insertBlocksInChunks(args: {
193
+ db: PgAsyncTransaction<PgQueryResultHKT, typeof schema>
194
+ blocks: EncodedBlockWithTransactions[]
195
+ }): Promise<void> {
196
+ const { db, blocks } = args
197
+ if (blocks.length === 0) return
198
+
199
+ const batchSize = Math.floor(MAX_QUERY_PARAMS / Object.keys(blocks[0]).length)
200
+ for (let i = 0; i < blocks.length; i += batchSize) {
201
+ const chunk = blocks.slice(i, i + batchSize)
202
+ await db
203
+ .insert(schema.blocks)
204
+ .values(chunk)
205
+ .onConflictDoNothing({
206
+ target: [schema.blocks.number],
207
+ })
208
+ }
209
+ }
@@ -0,0 +1 @@
1
+ export * as blocks from './blocks.ts'
@@ -0,0 +1,32 @@
1
+ import type { PgAsyncTransaction, PgQueryResultHKT } from 'drizzle-orm/pg-core'
2
+ import { MAX_QUERY_PARAMS } from '../../contants.ts'
3
+ import type { EncodedTransaction } from '../../types.ts'
4
+ import { schema } from '../schema/index.ts'
5
+
6
+ /**
7
+ * Inserts transactions in chunks to avoid query parameter limit.
8
+ */
9
+ export async function insertTransactionsInChunks(args: {
10
+ db: PgAsyncTransaction<PgQueryResultHKT, typeof schema>
11
+ transactions: EncodedTransaction[]
12
+ }): Promise<void> {
13
+ const { db, transactions } = args
14
+ if (transactions.length === 0) {
15
+ return
16
+ }
17
+
18
+ const batchSize = Math.floor(
19
+ MAX_QUERY_PARAMS / Object.keys(transactions[0]).length
20
+ )
21
+
22
+ for (let i = 0; i < transactions.length; i += batchSize) {
23
+ const chunk = transactions.slice(i, i + batchSize)
24
+
25
+ await db
26
+ .insert(schema.transactions)
27
+ .values(chunk)
28
+ .onConflictDoNothing({
29
+ target: [schema.transactions.hash],
30
+ })
31
+ }
32
+ }
@@ -0,0 +1,186 @@
1
+ import { PGlite } from '@electric-sql/pglite'
2
+ import { desc, eq, sql } from 'drizzle-orm'
3
+ import {
4
+ drizzle as drizzleNodePostgres,
5
+ type NodePgDatabase,
6
+ } from 'drizzle-orm/node-postgres'
7
+ import {
8
+ drizzle as drizzlePglite,
9
+ type PgliteDatabase,
10
+ } from 'drizzle-orm/pglite'
11
+ import type { AnyRelations, EmptyRelations } from 'drizzle-orm/relations'
12
+ import { Pool, type PoolConfig } from 'pg'
13
+ import type { DatabaseConfig } from '../config/config.ts'
14
+ import type { Env } from '../config/env.ts'
15
+ import { type relations, schema } from './schema/index.ts'
16
+
17
+ export type Database<
18
+ TSchema extends Record<string, unknown> = Record<string, unknown>,
19
+ TRelations extends AnyRelations = EmptyRelations,
20
+ > =
21
+ | (PgliteDatabase<TSchema, TRelations> & {
22
+ $client: PGlite
23
+ $prepared: ReturnType<typeof generatePrepared>
24
+ })
25
+ | (NodePgDatabase<TSchema, TRelations> & {
26
+ $client: Pool
27
+ $prepared: ReturnType<typeof generatePrepared>
28
+ })
29
+
30
+ export type DbDriver = 'pglite' | 'postgres'
31
+
32
+ export type DatabaseContext<
33
+ TSchema extends Record<string, unknown> = Record<string, unknown>,
34
+ TRelations extends AnyRelations = EmptyRelations,
35
+ > =
36
+ | {
37
+ db: NodePgDatabase<TSchema, TRelations> & {
38
+ $client: Pool
39
+ $prepared: ReturnType<typeof generatePrepared>
40
+ }
41
+ driver: 'postgres'
42
+ close: () => Promise<void>
43
+ }
44
+ | {
45
+ db: PgliteDatabase<TSchema, TRelations> & {
46
+ $client: PGlite
47
+ $prepared: ReturnType<typeof generatePrepared>
48
+ }
49
+ driver: 'pglite'
50
+ close: () => Promise<void>
51
+ }
52
+
53
+ /**
54
+ * Creates a typed Drizzle database context for either Postgres or PGlite.
55
+ */
56
+ export function createDatabase<
57
+ TSchema extends Record<string, unknown> = Record<string, unknown>,
58
+ TRelations extends AnyRelations = EmptyRelations,
59
+ >({
60
+ env,
61
+ config,
62
+ schema,
63
+ relations,
64
+ }: {
65
+ env: Env
66
+ config?: DatabaseConfig
67
+ schema: TSchema
68
+ relations: TRelations
69
+ }): DatabaseContext<TSchema, TRelations> {
70
+ let driver: string = 'pglite'
71
+ let url: string | undefined
72
+ let options: PoolConfig | undefined
73
+
74
+ if (env.DATABASE_URL && typeof env.DATABASE_URL === 'string') {
75
+ driver = 'postgres'
76
+ url = env.DATABASE_URL
77
+ } else if (config?.driver === 'postgres') {
78
+ driver = config.driver
79
+ url = config.url
80
+ options = config.options
81
+ }
82
+
83
+ if (driver === 'postgres' && url) {
84
+ const pool = new Pool({
85
+ ...options,
86
+ connectionString: url,
87
+ })
88
+ const db = drizzleNodePostgres({
89
+ client: pool,
90
+ relations: relations,
91
+ schema: schema,
92
+ casing: 'snake_case',
93
+ }) as NodePgDatabase<TSchema, TRelations> & {
94
+ $client: Pool
95
+ $prepared: ReturnType<typeof generatePrepared>
96
+ }
97
+
98
+ // @ts-expect-error - TODO: fix this
99
+ db.$prepared = generatePrepared(db)
100
+
101
+ return {
102
+ db,
103
+ driver: 'postgres',
104
+ close: async () => {
105
+ await pool.end()
106
+ },
107
+ }
108
+ }
109
+
110
+ const client = new PGlite(
111
+ config?.driver === 'pglite' && config.directory
112
+ ? config.directory
113
+ : '.pglite'
114
+ )
115
+ const db = drizzlePglite({
116
+ client: client,
117
+ relations: relations,
118
+ schema: schema,
119
+ casing: 'snake_case',
120
+ }) as PgliteDatabase<TSchema, TRelations> & {
121
+ $client: PGlite
122
+ $prepared: ReturnType<typeof generatePrepared>
123
+ }
124
+
125
+ // @ts-expect-error - TODO: fix this
126
+ db.$prepared = generatePrepared(db)
127
+
128
+ return {
129
+ db,
130
+ driver: 'pglite',
131
+ close: async () => {
132
+ await client.close()
133
+ },
134
+ }
135
+ }
136
+
137
+ function generatePrepared(
138
+ db: Omit<Database<typeof schema, typeof relations>, '$prepared'>
139
+ ) {
140
+ const getLatestBlock = db
141
+ .select({
142
+ number: schema.blocks.number,
143
+ hash: schema.blocks.hash,
144
+ parentHash: schema.blocks.parentHash,
145
+ })
146
+ .from(schema.blocks)
147
+ .orderBy(desc(schema.blocks.number))
148
+ .limit(1)
149
+ .prepare('get_latest_block')
150
+
151
+ const getBlockById = db
152
+ .select({
153
+ number: schema.blocks.number,
154
+ hash: schema.blocks.hash,
155
+ parentHash: schema.blocks.parentHash,
156
+ })
157
+ .from(schema.blocks)
158
+ .where(eq(schema.blocks.number, sql.placeholder('blockNumber')))
159
+ .prepare('get_block_by_id')
160
+
161
+ const getBlocksInRange = db.query.blocks
162
+ .findMany({
163
+ with: {
164
+ transactions: {
165
+ where: {
166
+ to: {
167
+ in: sql.placeholder('contractAddresses'),
168
+ },
169
+ },
170
+ },
171
+ },
172
+ where: {
173
+ AND: [
174
+ { number: { gte: sql.placeholder('firstBlockNumber') } },
175
+ { number: { lte: sql.placeholder('lastBlockNumber') } },
176
+ ],
177
+ },
178
+ })
179
+ .prepare('get_blocks_in_range')
180
+
181
+ return {
182
+ getLatestBlock,
183
+ getBlockById,
184
+ getBlocksInRange,
185
+ }
186
+ }
@@ -0,0 +1,105 @@
1
+ import { customType } from 'drizzle-orm/pg-core'
2
+ import { type Address, type Hash, type Hex, stringify } from 'viem'
3
+
4
+ export const numeric78 = customType<{ data: bigint; driverData: string }>({
5
+ dataType() {
6
+ return 'numeric(78,0)'
7
+ },
8
+ fromDriver(value: string) {
9
+ return BigInt(value)
10
+ },
11
+ })
12
+
13
+ export const hex = customType<{
14
+ data: Hex
15
+ driverData: string
16
+ config?: { length: number | undefined }
17
+ }>({
18
+ dataType(config) {
19
+ if (config?.length) {
20
+ return `varchar(${config.length})`
21
+ }
22
+ return 'varchar'
23
+ },
24
+ fromDriver(value: string) {
25
+ return value as Hex
26
+ },
27
+ })
28
+
29
+ /**
30
+ * Hash column type
31
+ * @param config - Configuration for the column
32
+ * @param config.length - Length of the column (default: 66)
33
+ * @returns Hash column type
34
+ */
35
+ export const hash = customType<{
36
+ data: Hash
37
+ driverData: string
38
+ config?: { length: number | undefined }
39
+ }>({
40
+ dataType(config) {
41
+ if (config?.length) {
42
+ return `varchar(${config.length})`
43
+ }
44
+ return 'varchar(66)'
45
+ },
46
+ fromDriver(value: string) {
47
+ return value as Hash
48
+ },
49
+ })
50
+
51
+ export const address = customType<{
52
+ data: Address
53
+ driverData: string
54
+ config?: { length: number | undefined }
55
+ }>({
56
+ dataType(config) {
57
+ if (config?.length) {
58
+ return `varchar(${config.length})`
59
+ }
60
+ return 'varchar(42)'
61
+ },
62
+ fromDriver(value: string) {
63
+ return value as Address
64
+ },
65
+ })
66
+
67
+ /**
68
+ * Bigint column type (8 bytes)
69
+ * For bigger number see {@link numeric78}, {@link uint256} and {@link int256}
70
+ * @returns Bigint column type
71
+ */
72
+ export const bigint = customType<{ data: bigint; driverData: string }>({
73
+ dataType() {
74
+ return 'bigint'
75
+ },
76
+ fromDriver(value: string) {
77
+ return BigInt(value)
78
+ },
79
+ })
80
+
81
+ export const uint256 = numeric78
82
+ export const int256 = numeric78
83
+
84
+ export const jsonb = customType<{ data: unknown; driverData: string }>({
85
+ dataType() {
86
+ return 'jsonb'
87
+ },
88
+ toDriver(value: unknown): string {
89
+ return stringify(value)
90
+ },
91
+ })
92
+
93
+ export const bytea = customType<{ data: Hex; driverData: Buffer }>({
94
+ dataType() {
95
+ return 'bytea'
96
+ },
97
+ toDriver(value: string): Buffer {
98
+ return Buffer.from(value.slice(2), 'hex')
99
+ },
100
+ fromDriver(value: Buffer): Hex {
101
+ const hex = value.toString('hex')
102
+ const _value = hex.startsWith('\\x') ? hex.slice(2) : hex
103
+ return `0x${_value}` as Hex
104
+ },
105
+ })
@@ -0,0 +1,99 @@
1
+ import type { Hash } from 'viem'
2
+ import type {
3
+ ChainBlock,
4
+ ChainTransaction,
5
+ EncodedBlock,
6
+ EncodedBlockWithTransactions,
7
+ EncodedTransaction,
8
+ } from '../types'
9
+
10
+ export function encodeTransaction(tx: ChainTransaction): EncodedTransaction {
11
+ return {
12
+ hash: tx.hash,
13
+ blockNumber: tx.blockNumber,
14
+ transactionIndex: tx.transactionIndex,
15
+ blockHash: tx.blockHash,
16
+ from: tx.from,
17
+ to: tx.to ?? null,
18
+ input: tx.input,
19
+ value: tx.value,
20
+ nonce: tx.nonce,
21
+ r: tx.r,
22
+ s: tx.s,
23
+ v: tx.v,
24
+ type: tx.type,
25
+ gas: tx.gas ?? null,
26
+ gasPrice: tx.gasPrice ?? null,
27
+ maxFeePerGas: tx.maxFeePerGas ?? null,
28
+ maxPriorityFeePerGas: tx.maxPriorityFeePerGas ?? null,
29
+ accessList: tx.accessList ?? null,
30
+ }
31
+ }
32
+
33
+ export function encodeBlock(block: ChainBlock): EncodedBlock {
34
+ return {
35
+ number: block.number,
36
+ timestamp: block.timestamp,
37
+ hash: block.hash,
38
+ parentHash: block.parentHash,
39
+ logsBloom: block.logsBloom,
40
+ miner: block.miner,
41
+ gasUsed: block.gasUsed,
42
+ gasLimit: block.gasLimit,
43
+ baseFeePerGas: block.baseFeePerGas ?? null,
44
+ nonce: block.nonce,
45
+ mixHash: block.mixHash,
46
+ stateRoot: block.stateRoot,
47
+ receiptsRoot: block.receiptsRoot,
48
+ transactionsRoot: block.transactionsRoot,
49
+ sha3Uncles: block.sha3Uncles,
50
+ size: block.size,
51
+ difficulty: block.difficulty,
52
+ totalDifficulty: block.totalDifficulty ?? null,
53
+ extraData: block.extraData,
54
+ }
55
+ }
56
+
57
+ export function encodeBlockWithTransactions(
58
+ block: ChainBlock
59
+ ): EncodedBlockWithTransactions {
60
+ return {
61
+ ...encodeBlock(block),
62
+ transactions: block.transactions.map(encodeTransaction),
63
+ }
64
+ }
65
+
66
+ const EMPTY_TRIE_HASH =
67
+ '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421'
68
+ const EMPTY_LOGS_BLOOM =
69
+ '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
70
+ export function encodeNullRoundBlock(options: {
71
+ number: bigint
72
+ hash: Hash
73
+ }): EncodedBlockWithTransactions {
74
+ return {
75
+ number: options.number,
76
+ // TODO: probably should be previous block timestamp plus 30 seconds
77
+ timestamp: BigInt(Math.floor(Date.now() / 1000)),
78
+ hash: options.hash,
79
+ parentHash: options.hash,
80
+ logsBloom: EMPTY_LOGS_BLOOM,
81
+ miner: '0x0000000000000000000000000000000000000000',
82
+ gasUsed: 0n,
83
+ gasLimit: 30_000_000n,
84
+ baseFeePerGas: 1_000_000_000n,
85
+ nonce: '0x0000000000000000',
86
+ mixHash:
87
+ '0x0000000000000000000000000000000000000000000000000000000000000000',
88
+ stateRoot: EMPTY_TRIE_HASH,
89
+ receiptsRoot: EMPTY_TRIE_HASH,
90
+ transactionsRoot: EMPTY_TRIE_HASH,
91
+ sha3Uncles:
92
+ '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347',
93
+ size: 0n,
94
+ difficulty: 0n,
95
+ totalDifficulty: 0n,
96
+ extraData: '0x',
97
+ transactions: [],
98
+ }
99
+ }