@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.
- package/README.md +56 -0
- package/dist/src/api/index.d.ts +2 -0
- package/dist/src/api/index.d.ts.map +1 -0
- package/dist/src/api/index.js +2 -0
- package/dist/src/api/index.js.map +1 -0
- package/dist/src/api/runner.d.ts +12 -0
- package/dist/src/api/runner.d.ts.map +1 -0
- package/dist/src/api/runner.js +29 -0
- package/dist/src/api/runner.js.map +1 -0
- package/dist/src/api/server.d.ts +18 -0
- package/dist/src/api/server.d.ts.map +1 -0
- package/dist/src/api/server.js +21 -0
- package/dist/src/api/server.js.map +1 -0
- package/dist/src/api/sql-middleware.d.ts +7 -0
- package/dist/src/api/sql-middleware.d.ts.map +1 -0
- package/dist/src/api/sql-middleware.js +95 -0
- package/dist/src/api/sql-middleware.js.map +1 -0
- package/dist/src/api/sql.d.ts +14 -0
- package/dist/src/api/sql.d.ts.map +1 -0
- package/dist/src/api/sql.js +108 -0
- package/dist/src/api/sql.js.map +1 -0
- package/dist/src/api/sse.d.ts +3 -0
- package/dist/src/api/sse.d.ts.map +1 -0
- package/dist/src/api/sse.js +11 -0
- package/dist/src/api/sse.js.map +1 -0
- package/dist/src/bin/dev.d.ts +3 -0
- package/dist/src/bin/dev.d.ts.map +1 -0
- package/dist/src/bin/dev.js +76 -0
- package/dist/src/bin/dev.js.map +1 -0
- package/dist/src/bin/flags.d.ts +32 -0
- package/dist/src/bin/flags.d.ts.map +1 -0
- package/dist/src/bin/flags.js +55 -0
- package/dist/src/bin/flags.js.map +1 -0
- package/dist/src/bin/index.d.ts +3 -0
- package/dist/src/bin/index.d.ts.map +1 -0
- package/dist/src/bin/index.js +22 -0
- package/dist/src/bin/index.js.map +1 -0
- package/dist/src/bin/utils.d.ts +4 -0
- package/dist/src/bin/utils.d.ts.map +1 -0
- package/dist/src/bin/utils.js +52 -0
- package/dist/src/bin/utils.js.map +1 -0
- package/dist/src/client/index.d.ts +18 -0
- package/dist/src/client/index.d.ts.map +1 -0
- package/dist/src/client/index.js +150 -0
- package/dist/src/client/index.js.map +1 -0
- package/dist/src/config/config.d.ts +157 -0
- package/dist/src/config/config.d.ts.map +1 -0
- package/dist/src/config/config.js +65 -0
- package/dist/src/config/config.js.map +1 -0
- package/dist/src/config/env.d.ts +25 -0
- package/dist/src/config/env.d.ts.map +1 -0
- package/dist/src/config/env.js +21 -0
- package/dist/src/config/env.js.map +1 -0
- package/dist/src/contants.d.ts +4 -0
- package/dist/src/contants.d.ts.map +1 -0
- package/dist/src/contants.js +4 -0
- package/dist/src/contants.js.map +1 -0
- package/dist/src/db/actions/blocks.d.ts +44 -0
- package/dist/src/db/actions/blocks.d.ts.map +1 -0
- package/dist/src/db/actions/blocks.js +152 -0
- package/dist/src/db/actions/blocks.js.map +1 -0
- package/dist/src/db/actions/index.d.ts +2 -0
- package/dist/src/db/actions/index.d.ts.map +1 -0
- package/dist/src/db/actions/index.js +2 -0
- package/dist/src/db/actions/index.js.map +1 -0
- package/dist/src/db/actions/transactions.d.ts +11 -0
- package/dist/src/db/actions/transactions.d.ts.map +1 -0
- package/dist/src/db/actions/transactions.js +22 -0
- package/dist/src/db/actions/transactions.js.map +1 -0
- package/dist/src/db/client.d.ts +329 -0
- package/dist/src/db/client.d.ts.map +1 -0
- package/dist/src/db/client.js +108 -0
- package/dist/src/db/client.js.map +1 -0
- package/dist/src/db/column-types.d.ts +132 -0
- package/dist/src/db/column-types.d.ts.map +1 -0
- package/dist/src/db/column-types.js +86 -0
- package/dist/src/db/column-types.js.map +1 -0
- package/dist/src/db/encode.d.ts +10 -0
- package/dist/src/db/encode.d.ts.map +1 -0
- package/dist/src/db/encode.js +79 -0
- package/dist/src/db/encode.js.map +1 -0
- package/dist/src/db/migrate.d.ts +31 -0
- package/dist/src/db/migrate.d.ts.map +1 -0
- package/dist/src/db/migrate.js +147 -0
- package/dist/src/db/migrate.js.map +1 -0
- package/dist/src/db/schema/blocks.d.ts +369 -0
- package/dist/src/db/schema/blocks.d.ts.map +1 -0
- package/dist/src/db/schema/blocks.js +24 -0
- package/dist/src/db/schema/blocks.js.map +1 -0
- package/dist/src/db/schema/index.d.ts +1415 -0
- package/dist/src/db/schema/index.d.ts.map +1 -0
- package/dist/src/db/schema/index.js +18 -0
- package/dist/src/db/schema/index.js.map +1 -0
- package/dist/src/db/schema/transactions.d.ts +336 -0
- package/dist/src/db/schema/transactions.d.ts.map +1 -0
- package/dist/src/db/schema/transactions.js +33 -0
- package/dist/src/db/schema/transactions.js.map +1 -0
- package/dist/src/db/transaction.d.ts +7 -0
- package/dist/src/db/transaction.d.ts.map +1 -0
- package/dist/src/db/transaction.js +8 -0
- package/dist/src/db/transaction.js.map +1 -0
- package/dist/src/hooks/default-hooks.d.ts +2 -0
- package/dist/src/hooks/default-hooks.d.ts.map +1 -0
- package/dist/src/hooks/default-hooks.js +107 -0
- package/dist/src/hooks/default-hooks.js.map +1 -0
- package/dist/src/hooks/registry.d.ts +51 -0
- package/dist/src/hooks/registry.d.ts.map +1 -0
- package/dist/src/hooks/registry.js +32 -0
- package/dist/src/hooks/registry.js.map +1 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/indexer/backfill.d.ts +15 -0
- package/dist/src/indexer/backfill.d.ts.map +1 -0
- package/dist/src/indexer/backfill.js +95 -0
- package/dist/src/indexer/backfill.js.map +1 -0
- package/dist/src/indexer/live.d.ts +20 -0
- package/dist/src/indexer/live.d.ts.map +1 -0
- package/dist/src/indexer/live.js +51 -0
- package/dist/src/indexer/live.js.map +1 -0
- package/dist/src/indexer/process-block.d.ts +29 -0
- package/dist/src/indexer/process-block.d.ts.map +1 -0
- package/dist/src/indexer/process-block.js +91 -0
- package/dist/src/indexer/process-block.js.map +1 -0
- package/dist/src/indexer/queue-block.d.ts +18 -0
- package/dist/src/indexer/queue-block.d.ts.map +1 -0
- package/dist/src/indexer/queue-block.js +38 -0
- package/dist/src/indexer/queue-block.js.map +1 -0
- package/dist/src/indexer/reorg.d.ts +24 -0
- package/dist/src/indexer/reorg.d.ts.map +1 -0
- package/dist/src/indexer/reorg.js +83 -0
- package/dist/src/indexer/reorg.js.map +1 -0
- package/dist/src/indexer/runner.d.ts +14 -0
- package/dist/src/indexer/runner.d.ts.map +1 -0
- package/dist/src/indexer/runner.js +22 -0
- package/dist/src/indexer/runner.js.map +1 -0
- package/dist/src/rpc/client.d.ts +11 -0
- package/dist/src/rpc/client.d.ts.map +1 -0
- package/dist/src/rpc/client.js +18 -0
- package/dist/src/rpc/client.js.map +1 -0
- package/dist/src/rpc/get-block.d.ts +16 -0
- package/dist/src/rpc/get-block.d.ts.map +1 -0
- package/dist/src/rpc/get-block.js +77 -0
- package/dist/src/rpc/get-block.js.map +1 -0
- package/dist/src/rpc/get-logs.d.ts +11 -0
- package/dist/src/rpc/get-logs.d.ts.map +1 -0
- package/dist/src/rpc/get-logs.js +23 -0
- package/dist/src/rpc/get-logs.js.map +1 -0
- package/dist/src/schema.d.ts +3 -0
- package/dist/src/schema.d.ts.map +1 -0
- package/dist/src/schema.js +9 -0
- package/dist/src/schema.js.map +1 -0
- package/dist/src/types.d.ts +22 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +1 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils/bloom.d.ts +6 -0
- package/dist/src/utils/bloom.d.ts.map +1 -0
- package/dist/src/utils/bloom.js +30 -0
- package/dist/src/utils/bloom.js.map +1 -0
- package/dist/src/utils/build-conflict-columns.d.ts +4 -0
- package/dist/src/utils/build-conflict-columns.d.ts.map +1 -0
- package/dist/src/utils/build-conflict-columns.js +14 -0
- package/dist/src/utils/build-conflict-columns.js.map +1 -0
- package/dist/src/utils/common.d.ts +2 -0
- package/dist/src/utils/common.d.ts.map +1 -0
- package/dist/src/utils/common.js +4 -0
- package/dist/src/utils/common.js.map +1 -0
- package/dist/src/utils/cursor.d.ts +5 -0
- package/dist/src/utils/cursor.d.ts.map +1 -0
- package/dist/src/utils/cursor.js +8 -0
- package/dist/src/utils/cursor.js.map +1 -0
- package/dist/src/utils/format.d.ts +2 -0
- package/dist/src/utils/format.d.ts.map +1 -0
- package/dist/src/utils/format.js +16 -0
- package/dist/src/utils/format.js.map +1 -0
- package/dist/src/utils/hash.d.ts +9 -0
- package/dist/src/utils/hash.d.ts.map +1 -0
- package/dist/src/utils/hash.js +15 -0
- package/dist/src/utils/hash.js.map +1 -0
- package/dist/src/utils/json.d.ts +5 -0
- package/dist/src/utils/json.d.ts.map +1 -0
- package/dist/src/utils/json.js +11 -0
- package/dist/src/utils/json.js.map +1 -0
- package/dist/src/utils/logger.d.ts +11 -0
- package/dist/src/utils/logger.d.ts.map +1 -0
- package/dist/src/utils/logger.js +111 -0
- package/dist/src/utils/logger.js.map +1 -0
- package/dist/src/utils/shutdown.d.ts +9 -0
- package/dist/src/utils/shutdown.d.ts.map +1 -0
- package/dist/src/utils/shutdown.js +24 -0
- package/dist/src/utils/shutdown.js.map +1 -0
- package/dist/src/utils/timer.d.ts +6 -0
- package/dist/src/utils/timer.d.ts.map +1 -0
- package/dist/src/utils/timer.js +9 -0
- package/dist/src/utils/timer.js.map +1 -0
- package/dist/src/utils/types.d.ts +39 -0
- package/dist/src/utils/types.d.ts.map +1 -0
- package/dist/src/utils/types.js +1 -0
- package/dist/src/utils/types.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/hello/apps/foc-api/README.md +69 -0
- package/hello/apps/foc-api/biome.json +8 -0
- package/hello/apps/foc-api/index.html +13 -0
- package/hello/apps/foc-api/package.json +39 -0
- package/hello/apps/foc-api/public/vite.svg +1 -0
- package/hello/apps/foc-api/src/app.css +45 -0
- package/hello/apps/foc-api/src/app.tsx +43 -0
- package/hello/apps/foc-api/src/assets/Cloudflare_Logo.svg +51 -0
- package/hello/apps/foc-api/src/assets/react.svg +1 -0
- package/hello/apps/foc-api/src/client.ts +41 -0
- package/hello/apps/foc-api/src/components/account.tsx +100 -0
- package/hello/apps/foc-api/src/components/wallet-options.tsx +43 -0
- package/hello/apps/foc-api/src/index.css +68 -0
- package/hello/apps/foc-api/src/main.tsx +38 -0
- package/hello/apps/foc-api/src/vite-env.d.ts +1 -0
- package/hello/apps/foc-api/tsconfig.app.json +44 -0
- package/hello/apps/foc-api/tsconfig.json +17 -0
- package/hello/apps/foc-api/tsconfig.node.json +25 -0
- package/hello/apps/foc-api/tsconfig.worker.json +8 -0
- package/hello/apps/foc-api/vite.config.ts +8 -0
- package/hello/apps/foc-api/worker/capabilities.ts +25 -0
- package/hello/apps/foc-api/worker/index.ts +64 -0
- package/hello/apps/foc-api/worker/router.ts +35 -0
- package/hello/apps/foc-api/worker-configuration.d.ts +7357 -0
- package/hello/apps/foc-api/wrangler.jsonc +50 -0
- package/hello/apps/foc-app/README.md +69 -0
- package/hello/apps/foc-app/biome.json +8 -0
- package/hello/apps/foc-app/index.html +13 -0
- package/hello/apps/foc-app/package.json +39 -0
- package/hello/apps/foc-app/public/vite.svg +1 -0
- package/hello/apps/foc-app/src/app.css +45 -0
- package/hello/apps/foc-app/src/app.tsx +43 -0
- package/hello/apps/foc-app/src/assets/Cloudflare_Logo.svg +51 -0
- package/hello/apps/foc-app/src/assets/react.svg +1 -0
- package/hello/apps/foc-app/src/client.ts +41 -0
- package/hello/apps/foc-app/src/components/account.tsx +100 -0
- package/hello/apps/foc-app/src/components/wallet-options.tsx +43 -0
- package/hello/apps/foc-app/src/index.css +68 -0
- package/hello/apps/foc-app/src/main.tsx +38 -0
- package/hello/apps/foc-app/src/vite-env.d.ts +1 -0
- package/hello/apps/foc-app/tsconfig.app.json +44 -0
- package/hello/apps/foc-app/tsconfig.json +17 -0
- package/hello/apps/foc-app/tsconfig.node.json +25 -0
- package/hello/apps/foc-app/tsconfig.worker.json +8 -0
- package/hello/apps/foc-app/vite.config.ts +8 -0
- package/hello/apps/foc-app/worker/capabilities.ts +25 -0
- package/hello/apps/foc-app/worker/index.ts +64 -0
- package/hello/apps/foc-app/worker/router.ts +35 -0
- package/hello/apps/foc-app/worker-configuration.d.ts +7357 -0
- package/hello/apps/foc-app/wrangler.jsonc +50 -0
- package/hello/biome.json +50 -0
- package/hello/package.json +22 -0
- package/hello/pnpm-workspace.yaml +3 -0
- package/hello/tsconfig.json +37 -0
- package/package.json +78 -0
- package/src/api/index.ts +1 -0
- package/src/api/runner.ts +43 -0
- package/src/api/server.ts +38 -0
- package/src/api/sql-middleware.ts +131 -0
- package/src/api/sql.ts +149 -0
- package/src/api/sse.ts +12 -0
- package/src/bin/create.ts +199 -0
- package/src/bin/dev.ts +91 -0
- package/src/bin/flags.ts +65 -0
- package/src/bin/index.ts +28 -0
- package/src/bin/utils.ts +55 -0
- package/src/config/config.ts +221 -0
- package/src/config/env.ts +28 -0
- package/src/contants.ts +3 -0
- package/src/db/actions/blocks.ts +209 -0
- package/src/db/actions/index.ts +1 -0
- package/src/db/actions/transactions.ts +32 -0
- package/src/db/client.ts +186 -0
- package/src/db/column-types.ts +105 -0
- package/src/db/encode.ts +99 -0
- package/src/db/migrate.ts +222 -0
- package/src/db/schema/blocks.ts +24 -0
- package/src/db/schema/index.ts +21 -0
- package/src/db/schema/transactions.ts +39 -0
- package/src/db/transaction.ts +20 -0
- package/src/hooks/registry.ts +107 -0
- package/src/index.ts +9 -0
- package/src/indexer/backfill.ts +133 -0
- package/src/indexer/live.ts +76 -0
- package/src/indexer/process-block.ts +142 -0
- package/src/indexer/queue-block.ts +74 -0
- package/src/indexer/reorg.ts +120 -0
- package/src/indexer/runner.ts +35 -0
- package/src/rpc/client.ts +27 -0
- package/src/rpc/get-block.ts +100 -0
- package/src/rpc/get-logs.ts +38 -0
- package/src/schema.ts +10 -0
- package/src/types.ts +32 -0
- package/src/utils/bloom.ts +41 -0
- package/src/utils/build-conflict-columns.ts +26 -0
- package/src/utils/common.ts +3 -0
- package/src/utils/cursor.ts +7 -0
- package/src/utils/format.ts +18 -0
- package/src/utils/hash.ts +17 -0
- package/src/utils/json.ts +11 -0
- package/src/utils/logger.ts +149 -0
- package/src/utils/shutdown.ts +36 -0
- package/src/utils/timer.ts +8 -0
- package/src/utils/types.ts +87 -0
- package/template/biome.json +50 -0
- package/template/package.json +22 -0
- package/template/pnpm-workspace.yaml +3 -0
- package/template/tsconfig.json +37 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { migrate as migratePostgresJs } from 'drizzle-orm/node-postgres/migrator'
|
|
2
|
+
import { getTableConfig, type PgTable } from 'drizzle-orm/pg-core'
|
|
3
|
+
import { IndexedColumn } from 'drizzle-orm/pg-core/columns/common'
|
|
4
|
+
import { migrate as migratePglite } from 'drizzle-orm/pglite/migrator'
|
|
5
|
+
import { FOXER_TABLES, PUBLICATION_NAME } from '../contants.ts'
|
|
6
|
+
import type { Logger } from '../utils/logger.ts'
|
|
7
|
+
import { startClock } from '../utils/timer.ts'
|
|
8
|
+
import type { Database, DatabaseContext } from './client.ts'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Applies pending SQL migrations at runtime.
|
|
12
|
+
*/
|
|
13
|
+
export async function runMigrations({
|
|
14
|
+
dbContext,
|
|
15
|
+
folder,
|
|
16
|
+
logger,
|
|
17
|
+
}: {
|
|
18
|
+
folder: string
|
|
19
|
+
dbContext: DatabaseContext
|
|
20
|
+
logger: Logger
|
|
21
|
+
}): Promise<void> {
|
|
22
|
+
const endClock = startClock()
|
|
23
|
+
const { db, driver } = dbContext
|
|
24
|
+
// apply migrations
|
|
25
|
+
if (driver === 'postgres') {
|
|
26
|
+
await migratePostgresJs(db, { migrationsFolder: folder })
|
|
27
|
+
} else {
|
|
28
|
+
await migratePglite(db, { migrationsFolder: folder })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// get tables to migrate
|
|
32
|
+
const tables = Object.keys(dbContext.db._.fullSchema).filter(
|
|
33
|
+
(table) => !FOXER_TABLES.includes(table)
|
|
34
|
+
)
|
|
35
|
+
// assert tables have blockNumber column and index
|
|
36
|
+
assertTablesHaveBlockNumberIndex(dbContext.db._.fullSchema, tables)
|
|
37
|
+
|
|
38
|
+
// check if wal is enabled
|
|
39
|
+
const wal = await isWalEnabled(db)
|
|
40
|
+
if (!wal) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
'WAL is not enabled, set wal_level=logical in your postgresql.conf or pass -c wal_level=logical to the postgres client'
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// create publications
|
|
47
|
+
await createPublications(db, tables)
|
|
48
|
+
|
|
49
|
+
logger.info({ driver, duration: endClock() }, 'migrations applied')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Creates or updates a publication for the provided tables in the database.
|
|
54
|
+
* The resulting publication contains only the provided tables.
|
|
55
|
+
* This is needed for the live sync to work.
|
|
56
|
+
*
|
|
57
|
+
* @param db - The database to create the publication for
|
|
58
|
+
* @param tables - The list of table names to include
|
|
59
|
+
*/
|
|
60
|
+
export async function createPublications(db: Database, tables: string[]) {
|
|
61
|
+
if (tables.length === 0) return
|
|
62
|
+
|
|
63
|
+
const quotedTables = tables.map((table) => `"${table.replaceAll('"', '""')}"`)
|
|
64
|
+
const publication = await db.execute(
|
|
65
|
+
`SELECT puballtables FROM pg_publication WHERE pubname = '${PUBLICATION_NAME}'`
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if (publication.rows.length === 0) {
|
|
69
|
+
await db.execute(
|
|
70
|
+
`CREATE PUBLICATION ${PUBLICATION_NAME} FOR TABLE ${quotedTables.join(', ')}`
|
|
71
|
+
)
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const isForAllTables = Boolean(publication.rows[0].puballtables)
|
|
76
|
+
if (isForAllTables) {
|
|
77
|
+
await db.execute(`DROP PUBLICATION ${PUBLICATION_NAME}`)
|
|
78
|
+
await db.execute(
|
|
79
|
+
`CREATE PUBLICATION ${PUBLICATION_NAME} FOR TABLE ${quotedTables.join(', ')}`
|
|
80
|
+
)
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
await db.execute(
|
|
85
|
+
`ALTER PUBLICATION ${PUBLICATION_NAME} SET TABLE ${quotedTables.join(', ')}`
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Ensures every provided table has a blockNumber column and an index on it.
|
|
91
|
+
*/
|
|
92
|
+
export function assertTablesHaveBlockNumberIndex(
|
|
93
|
+
fullSchema: Record<string, unknown>,
|
|
94
|
+
tableNames: string[]
|
|
95
|
+
) {
|
|
96
|
+
const missingBlockNumberColumn: string[] = []
|
|
97
|
+
const missingBlockNumberIndex: string[] = []
|
|
98
|
+
const tableConfigs = new Map<string, ReturnType<typeof getTableConfig>>()
|
|
99
|
+
const tableHasBlockNumberColumn = new Map<string, boolean>()
|
|
100
|
+
|
|
101
|
+
for (const tableName of tableNames) {
|
|
102
|
+
const table = fullSchema[tableName]
|
|
103
|
+
if (!table) continue
|
|
104
|
+
|
|
105
|
+
const config = getTableConfig(table as PgTable)
|
|
106
|
+
tableConfigs.set(tableName, config)
|
|
107
|
+
tableHasBlockNumberColumn.set(
|
|
108
|
+
tableName,
|
|
109
|
+
config.columns.some((column) =>
|
|
110
|
+
['blockNumber', 'block_number'].includes(column.name)
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for (const tableName of tableNames) {
|
|
116
|
+
const config = tableConfigs.get(tableName)
|
|
117
|
+
if (!config) continue
|
|
118
|
+
const blockNumberColumns = config.columns.filter((column) =>
|
|
119
|
+
['blockNumber', 'block_number'].includes(column.name)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if (blockNumberColumns.length === 0) {
|
|
123
|
+
if (
|
|
124
|
+
hasCascadeForeignKeyToBlockNumberTable(
|
|
125
|
+
config,
|
|
126
|
+
tableHasBlockNumberColumn
|
|
127
|
+
)
|
|
128
|
+
) {
|
|
129
|
+
continue
|
|
130
|
+
}
|
|
131
|
+
missingBlockNumberColumn.push(tableName)
|
|
132
|
+
continue
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const blockNumberColumnNames = new Set(
|
|
136
|
+
blockNumberColumns.map((column) => column.name)
|
|
137
|
+
)
|
|
138
|
+
const hasBlockNumberIndex = config.indexes.some((index) => {
|
|
139
|
+
const indexColumns = index.config?.columns
|
|
140
|
+
|
|
141
|
+
if (!indexColumns || indexColumns.length === 0) return false
|
|
142
|
+
return indexColumns.some((column) => {
|
|
143
|
+
if (!(column instanceof IndexedColumn)) return false
|
|
144
|
+
if (!column.name) return false
|
|
145
|
+
return blockNumberColumnNames.has(column.name)
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
if (!hasBlockNumberIndex) {
|
|
150
|
+
missingBlockNumberIndex.push(tableName)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (
|
|
155
|
+
missingBlockNumberColumn.length > 0 ||
|
|
156
|
+
missingBlockNumberIndex.length > 0
|
|
157
|
+
) {
|
|
158
|
+
const missingColumnTables = missingBlockNumberColumn.sort()
|
|
159
|
+
const missingIndexTables = missingBlockNumberIndex.sort()
|
|
160
|
+
const lines = [
|
|
161
|
+
'Invalid schema for Foxer sync.',
|
|
162
|
+
'',
|
|
163
|
+
'Each published table must have:',
|
|
164
|
+
"1) a 'blockNumber' column (db name can be 'block_number')",
|
|
165
|
+
"2) an index that includes 'blockNumber'",
|
|
166
|
+
"Exception: table can skip both when it has a foreign key with onDelete('cascade') to a table with blockNumber.",
|
|
167
|
+
'',
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
if (missingColumnTables.length > 0) {
|
|
171
|
+
lines.push(
|
|
172
|
+
`Tables missing blockNumber column: ${missingColumnTables.join(', ')}`
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
if (missingIndexTables.length > 0) {
|
|
176
|
+
lines.push(
|
|
177
|
+
`Tables missing blockNumber index: ${missingIndexTables.join(', ')}`
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
lines.push(
|
|
182
|
+
'',
|
|
183
|
+
'Drizzle example:',
|
|
184
|
+
"const myTable = pgTable('my_table', {",
|
|
185
|
+
' // 1) Add the blockNumber column',
|
|
186
|
+
' blockNumber: bigint().notNull(),',
|
|
187
|
+
' // ...other columns',
|
|
188
|
+
'}, (table) => [',
|
|
189
|
+
' // 2) Add an index on blockNumber',
|
|
190
|
+
" index('my_table_block_number_index').on(table.blockNumber),",
|
|
191
|
+
'])'
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
throw new Error(lines.join('\n'))
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function hasCascadeForeignKeyToBlockNumberTable(
|
|
199
|
+
tableConfig: ReturnType<typeof getTableConfig>,
|
|
200
|
+
tableHasBlockNumberColumn: Map<string, boolean>
|
|
201
|
+
) {
|
|
202
|
+
return tableConfig.foreignKeys.some((foreignKey) => {
|
|
203
|
+
if (foreignKey.onDelete !== 'cascade') return false
|
|
204
|
+
|
|
205
|
+
const referencedTable = foreignKey.reference().foreignTable
|
|
206
|
+
const referencedTableName = getTableConfig(referencedTable).name
|
|
207
|
+
return tableHasBlockNumberColumn.get(referencedTableName) === true
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Checks if WAL is enabled.
|
|
213
|
+
*
|
|
214
|
+
* @param db - The database to check
|
|
215
|
+
* @returns True if WAL is enabled, false otherwise
|
|
216
|
+
*/
|
|
217
|
+
export async function isWalEnabled(db: Database) {
|
|
218
|
+
const wal = await db
|
|
219
|
+
.execute('SHOW WAL_LEVEL')
|
|
220
|
+
.then((result) => result.rows[0].wal_level)
|
|
221
|
+
return wal === 'logical'
|
|
222
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { pgTable } from 'drizzle-orm/pg-core'
|
|
2
|
+
import { address, bigint, bytea, hash, numeric78 } from '../column-types.ts'
|
|
3
|
+
|
|
4
|
+
export const blocks = pgTable('blocks', {
|
|
5
|
+
number: bigint().notNull().primaryKey(),
|
|
6
|
+
timestamp: bigint().notNull(),
|
|
7
|
+
hash: hash().notNull(),
|
|
8
|
+
parentHash: hash().notNull(),
|
|
9
|
+
logsBloom: bytea().notNull(),
|
|
10
|
+
miner: address().notNull(),
|
|
11
|
+
gasUsed: numeric78().notNull(),
|
|
12
|
+
gasLimit: numeric78().notNull(),
|
|
13
|
+
baseFeePerGas: numeric78(),
|
|
14
|
+
nonce: bytea().notNull(),
|
|
15
|
+
mixHash: bytea().notNull(),
|
|
16
|
+
stateRoot: bytea().notNull(),
|
|
17
|
+
receiptsRoot: bytea().notNull(),
|
|
18
|
+
transactionsRoot: bytea().notNull(),
|
|
19
|
+
sha3Uncles: bytea().notNull(),
|
|
20
|
+
size: numeric78().notNull(),
|
|
21
|
+
difficulty: numeric78().notNull(),
|
|
22
|
+
totalDifficulty: numeric78(),
|
|
23
|
+
extraData: bytea().notNull(),
|
|
24
|
+
})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { defineRelations } from 'drizzle-orm'
|
|
2
|
+
|
|
3
|
+
import { blocks } from './blocks.ts'
|
|
4
|
+
import { transactions } from './transactions.ts'
|
|
5
|
+
|
|
6
|
+
export const relations = defineRelations({ blocks, transactions }, (r) => {
|
|
7
|
+
return {
|
|
8
|
+
blocks: {
|
|
9
|
+
transactions: r.many.transactions(),
|
|
10
|
+
},
|
|
11
|
+
transactions: {
|
|
12
|
+
block: r.one.blocks({
|
|
13
|
+
from: r.transactions.blockNumber,
|
|
14
|
+
to: r.blocks.number,
|
|
15
|
+
}),
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
export const schema = { blocks, transactions }
|
|
21
|
+
export type Schema = typeof schema
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { index, integer, jsonb, pgEnum, pgTable } from 'drizzle-orm/pg-core'
|
|
2
|
+
import type { AccessList } from 'viem'
|
|
3
|
+
import { address, bigint, bytea, hash, numeric78 } from '../column-types.ts'
|
|
4
|
+
|
|
5
|
+
export const transactionTypeEnum = pgEnum('transaction_type', [
|
|
6
|
+
'legacy',
|
|
7
|
+
'eip1559',
|
|
8
|
+
'eip2930',
|
|
9
|
+
'eip4844',
|
|
10
|
+
'eip7702',
|
|
11
|
+
])
|
|
12
|
+
|
|
13
|
+
export const transactions = pgTable(
|
|
14
|
+
'transactions',
|
|
15
|
+
{
|
|
16
|
+
hash: hash().primaryKey(),
|
|
17
|
+
blockNumber: bigint().notNull(),
|
|
18
|
+
transactionIndex: integer().notNull(),
|
|
19
|
+
blockHash: hash().notNull(),
|
|
20
|
+
from: address().notNull(),
|
|
21
|
+
to: address(),
|
|
22
|
+
input: bytea().notNull(),
|
|
23
|
+
value: numeric78().notNull(),
|
|
24
|
+
nonce: integer().notNull(),
|
|
25
|
+
r: bytea().notNull(),
|
|
26
|
+
s: bytea().notNull(),
|
|
27
|
+
v: numeric78().notNull(),
|
|
28
|
+
type: transactionTypeEnum().notNull(),
|
|
29
|
+
gas: numeric78().notNull(),
|
|
30
|
+
gasPrice: numeric78(),
|
|
31
|
+
maxFeePerGas: numeric78(),
|
|
32
|
+
maxPriorityFeePerGas: numeric78(),
|
|
33
|
+
accessList: jsonb().$type<AccessList>(),
|
|
34
|
+
},
|
|
35
|
+
(table) => [
|
|
36
|
+
index('transactions_block_number_index').on(table.blockNumber),
|
|
37
|
+
index('transactions_to_index').on(table.to),
|
|
38
|
+
]
|
|
39
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Database } from './client'
|
|
2
|
+
import type { relations, schema } from './schema/index.ts'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Runs work in a transaction for either postgres or pglite drivers.
|
|
6
|
+
*/
|
|
7
|
+
export function withTransaction<T>(
|
|
8
|
+
db: Database<typeof schema, typeof relations>,
|
|
9
|
+
run: (tx: Database<typeof schema, typeof relations>) => Promise<T>
|
|
10
|
+
): Promise<T> {
|
|
11
|
+
const executor = db as unknown as Database<
|
|
12
|
+
typeof schema,
|
|
13
|
+
typeof relations
|
|
14
|
+
> & {
|
|
15
|
+
transaction: <R>(
|
|
16
|
+
fn: (tx: Database<typeof schema, typeof relations>) => Promise<R>
|
|
17
|
+
) => Promise<R>
|
|
18
|
+
}
|
|
19
|
+
return executor.transaction(run)
|
|
20
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { AnyRelations, EmptyRelations } from 'drizzle-orm/relations'
|
|
2
|
+
import type { GetEventArgs, Log } from 'viem'
|
|
3
|
+
import type { Database } from '../db/client'
|
|
4
|
+
import type { EncodedBlockWithTransactions, EncodedTransaction } from '../types'
|
|
5
|
+
import type { Logger } from '../utils/logger'
|
|
6
|
+
import type {
|
|
7
|
+
ContractAbiByEventKey,
|
|
8
|
+
ContractAbiEventByEventKey,
|
|
9
|
+
ContractsConfig,
|
|
10
|
+
EventKey,
|
|
11
|
+
EventNameFromEventKey,
|
|
12
|
+
MergedContractEvents,
|
|
13
|
+
} from '../utils/types'
|
|
14
|
+
|
|
15
|
+
export type HookContext<
|
|
16
|
+
TSchema extends Record<string, unknown> = Record<string, unknown>,
|
|
17
|
+
TRelations extends AnyRelations = EmptyRelations,
|
|
18
|
+
> = {
|
|
19
|
+
db: Database<TSchema, TRelations>
|
|
20
|
+
chainId: number
|
|
21
|
+
logger: Logger
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type DecodedEvent<
|
|
25
|
+
C extends ContractsConfig<NonNullable<unknown>>,
|
|
26
|
+
Event extends EventKey,
|
|
27
|
+
> = {
|
|
28
|
+
// Resolve the concrete ABI for the `contract:event` key.
|
|
29
|
+
args: GetEventArgs<
|
|
30
|
+
ContractAbiByEventKey<C, Event>,
|
|
31
|
+
EventNameFromEventKey<Event>,
|
|
32
|
+
{ EnableUnion: false; IndexedOnly: false; Required: true }
|
|
33
|
+
>
|
|
34
|
+
log: Log<bigint, number, false, ContractAbiEventByEventKey<C, Event>>
|
|
35
|
+
block: EncodedBlockWithTransactions
|
|
36
|
+
transaction: EncodedTransaction
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type EventHook<
|
|
40
|
+
C extends ContractsConfig<NonNullable<unknown>>,
|
|
41
|
+
Event extends EventKey = EventKey,
|
|
42
|
+
TSchema extends Record<string, unknown> = Record<string, unknown>,
|
|
43
|
+
TRelations extends AnyRelations = EmptyRelations,
|
|
44
|
+
> = (args: {
|
|
45
|
+
context: HookContext<TSchema, TRelations>
|
|
46
|
+
event: DecodedEvent<C, Event>
|
|
47
|
+
}) => Promise<void> | void
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Registry for strongly typed contract-event hooks.
|
|
51
|
+
*/
|
|
52
|
+
export class HookRegistry<
|
|
53
|
+
C extends ContractsConfig<NonNullable<unknown>> = ContractsConfig<
|
|
54
|
+
NonNullable<unknown>
|
|
55
|
+
>,
|
|
56
|
+
TSchema extends Record<string, unknown> = Record<string, unknown>,
|
|
57
|
+
TRelations extends AnyRelations = EmptyRelations,
|
|
58
|
+
> {
|
|
59
|
+
private readonly hooks = new Map<MergedContractEvents<C>, unknown>()
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Registers a hook for a specific `contract:event` key.
|
|
63
|
+
*/
|
|
64
|
+
on<K extends MergedContractEvents<C>>(
|
|
65
|
+
streamKey: K,
|
|
66
|
+
hook: EventHook<C, K, TSchema, TRelations>
|
|
67
|
+
): void {
|
|
68
|
+
this.hooks.set(streamKey, hook)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Decodes a log using stream ABI metadata and dispatches to the registered hook.
|
|
73
|
+
*/
|
|
74
|
+
async dispatch<K extends MergedContractEvents<C>>(options: {
|
|
75
|
+
key: K
|
|
76
|
+
args: GetEventArgs<
|
|
77
|
+
ContractAbiByEventKey<C, K>,
|
|
78
|
+
EventNameFromEventKey<K>,
|
|
79
|
+
{ EnableUnion: false; IndexedOnly: false; Required: true }
|
|
80
|
+
>
|
|
81
|
+
log: Log<bigint, number, false, ContractAbiEventByEventKey<C, K>>
|
|
82
|
+
block: EncodedBlockWithTransactions
|
|
83
|
+
transaction: EncodedTransaction
|
|
84
|
+
context: HookContext<TSchema, TRelations>
|
|
85
|
+
}): Promise<void> {
|
|
86
|
+
const { key, args, log, block, transaction, context } = options
|
|
87
|
+
const hook = this.hooks.get(key) as unknown as EventHook<
|
|
88
|
+
C,
|
|
89
|
+
K,
|
|
90
|
+
TSchema,
|
|
91
|
+
TRelations
|
|
92
|
+
>
|
|
93
|
+
if (!hook) return
|
|
94
|
+
|
|
95
|
+
const event = {
|
|
96
|
+
args,
|
|
97
|
+
log,
|
|
98
|
+
block,
|
|
99
|
+
transaction,
|
|
100
|
+
} as DecodedEvent<C, K>
|
|
101
|
+
|
|
102
|
+
await hook({
|
|
103
|
+
context,
|
|
104
|
+
event,
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type * from './config/config.ts'
|
|
2
|
+
export { createConfig } from './config/config.ts'
|
|
3
|
+
export type { Database } from './db/client.ts'
|
|
4
|
+
export * from './db/column-types.ts'
|
|
5
|
+
export type { HookRegistry } from './hooks/registry.ts'
|
|
6
|
+
export type * from './rpc/client.ts'
|
|
7
|
+
export { buildConflictUpdateColumns } from './utils/build-conflict-columns.ts'
|
|
8
|
+
export type { Logger } from './utils/logger.ts'
|
|
9
|
+
export type * from './utils/types.ts'
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { filterContracts, type InternalConfig } from '../config/config.ts'
|
|
2
|
+
import { getBlocksInRange } from '../db/actions/blocks.ts'
|
|
3
|
+
import type { Database } from '../db/client.ts'
|
|
4
|
+
import type { relations, schema } from '../db/schema/index.ts'
|
|
5
|
+
import { withTransaction } from '../db/transaction.ts'
|
|
6
|
+
import type { HookRegistry } from '../hooks/registry.ts'
|
|
7
|
+
import { getLogsInRange } from '../rpc/get-logs.ts'
|
|
8
|
+
import { windowEnd } from '../utils/cursor.ts'
|
|
9
|
+
import type { Logger } from '../utils/logger.ts'
|
|
10
|
+
import { startClock } from '../utils/timer.ts'
|
|
11
|
+
import { processBlock } from './process-block.ts'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Executes historical catch-up from the current cursor to the safe head.
|
|
15
|
+
*/
|
|
16
|
+
export async function runBackfill(args: {
|
|
17
|
+
logger: Logger
|
|
18
|
+
config: InternalConfig
|
|
19
|
+
db: Database<typeof schema, typeof relations>
|
|
20
|
+
registry: HookRegistry
|
|
21
|
+
}): Promise<bigint> {
|
|
22
|
+
const endClock = startClock()
|
|
23
|
+
const { db, registry, config, logger } = args
|
|
24
|
+
const client = config.clients.backfill
|
|
25
|
+
const chainHead = await client.getBlockNumber()
|
|
26
|
+
const safeHead =
|
|
27
|
+
chainHead > config.finality ? chainHead - config.finality : 0n
|
|
28
|
+
let cursor = config.startBlockNumber
|
|
29
|
+
|
|
30
|
+
if (cursor > safeHead) {
|
|
31
|
+
logger.debug(
|
|
32
|
+
{
|
|
33
|
+
cursor: cursor.toString(),
|
|
34
|
+
backfillHead: safeHead.toString(),
|
|
35
|
+
head: chainHead.toString(),
|
|
36
|
+
},
|
|
37
|
+
'no historical catch-up needed'
|
|
38
|
+
)
|
|
39
|
+
return cursor
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const batchSize = config.batchSize
|
|
43
|
+
logger.debug(
|
|
44
|
+
{
|
|
45
|
+
fromBlock: cursor.toString(),
|
|
46
|
+
toBlock: safeHead.toString(),
|
|
47
|
+
batchSize: batchSize.toString(),
|
|
48
|
+
},
|
|
49
|
+
'starting backfill'
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
while (cursor <= safeHead) {
|
|
53
|
+
const batchStartMs = Date.now()
|
|
54
|
+
const toBlock = windowEnd(cursor, batchSize, safeHead)
|
|
55
|
+
const windowContracts = filterContracts(config, cursor, toBlock)
|
|
56
|
+
|
|
57
|
+
logger.debug(
|
|
58
|
+
{
|
|
59
|
+
batchFromBlock: cursor.toString(),
|
|
60
|
+
batchToBlock: toBlock.toString(),
|
|
61
|
+
streamCount: windowContracts.addresses.length,
|
|
62
|
+
},
|
|
63
|
+
'processing backfill batch'
|
|
64
|
+
)
|
|
65
|
+
const batchBlockNumbers: bigint[] = []
|
|
66
|
+
let blockNumber = cursor
|
|
67
|
+
while (blockNumber <= toBlock) {
|
|
68
|
+
batchBlockNumbers.push(blockNumber)
|
|
69
|
+
blockNumber += 1n
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const [blocksByNumber, logsByBlock] = await Promise.all([
|
|
73
|
+
getBlocksInRange(logger, db, batchBlockNumbers, client, windowContracts),
|
|
74
|
+
getLogsInRange({
|
|
75
|
+
logger,
|
|
76
|
+
client,
|
|
77
|
+
addresses: windowContracts.addresses,
|
|
78
|
+
events: windowContracts.eventAbis,
|
|
79
|
+
fromBlock: cursor,
|
|
80
|
+
toBlock,
|
|
81
|
+
}),
|
|
82
|
+
])
|
|
83
|
+
|
|
84
|
+
let blockIndex = 0
|
|
85
|
+
|
|
86
|
+
const endClockBatch = startClock()
|
|
87
|
+
await withTransaction(db, async (tx) => {
|
|
88
|
+
while (blockIndex < batchBlockNumbers.length) {
|
|
89
|
+
const blockNumber = batchBlockNumbers[blockIndex]
|
|
90
|
+
const prefetchedBlock = blocksByNumber.get(blockNumber)
|
|
91
|
+
|
|
92
|
+
await processBlock({
|
|
93
|
+
logger,
|
|
94
|
+
config,
|
|
95
|
+
db: tx,
|
|
96
|
+
client,
|
|
97
|
+
registry,
|
|
98
|
+
blockNumber,
|
|
99
|
+
logs: logsByBlock.get(blockNumber) ?? [],
|
|
100
|
+
block: prefetchedBlock,
|
|
101
|
+
type: 'backfill',
|
|
102
|
+
contracts: windowContracts,
|
|
103
|
+
})
|
|
104
|
+
blockIndex += 1
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
logger.info(
|
|
108
|
+
{ duration: endClockBatch() },
|
|
109
|
+
'batch block and events processed'
|
|
110
|
+
)
|
|
111
|
+
const batchElapsedMs = Date.now() - batchStartMs
|
|
112
|
+
const blocksInRange = Number(toBlock - cursor + 1n)
|
|
113
|
+
const blocksPerSecond =
|
|
114
|
+
batchElapsedMs > 0
|
|
115
|
+
? blocksInRange / (batchElapsedMs / 1000)
|
|
116
|
+
: blocksInRange
|
|
117
|
+
logger.info(
|
|
118
|
+
{
|
|
119
|
+
indexedUpTo: toBlock.toString(),
|
|
120
|
+
duration: batchElapsedMs,
|
|
121
|
+
throughput: Number(blocksPerSecond.toFixed(2)),
|
|
122
|
+
},
|
|
123
|
+
'backfill batch completed'
|
|
124
|
+
)
|
|
125
|
+
cursor = toBlock + 1n
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
logger.info(
|
|
129
|
+
{ duration: endClock(), blocks: cursor - config.startBlockNumber },
|
|
130
|
+
'backfill completed'
|
|
131
|
+
)
|
|
132
|
+
return cursor
|
|
133
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import PQueue from 'p-queue'
|
|
2
|
+
import type { PublicClient } from 'viem'
|
|
3
|
+
import type { InternalConfig } from '../config/config.ts'
|
|
4
|
+
import type { Database } from '../db/client.ts'
|
|
5
|
+
import type { relations, schema } from '../db/schema/index.ts'
|
|
6
|
+
import type { HookRegistry } from '../hooks/registry.ts'
|
|
7
|
+
import { noop } from '../utils/common.ts'
|
|
8
|
+
import type { Logger } from '../utils/logger.ts'
|
|
9
|
+
import { queueBlock } from './queue-block.ts'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Starts live head following and sequential block processing.
|
|
13
|
+
*/
|
|
14
|
+
export function startLiveSync(args: {
|
|
15
|
+
logger: Logger
|
|
16
|
+
config: InternalConfig
|
|
17
|
+
db: Database<typeof schema, typeof relations>
|
|
18
|
+
client: PublicClient
|
|
19
|
+
registry: HookRegistry
|
|
20
|
+
initialCursor: bigint
|
|
21
|
+
}): { stop: () => void } {
|
|
22
|
+
const { config, db, client, registry, logger } = args
|
|
23
|
+
|
|
24
|
+
// filter out contracts that have endBlock set
|
|
25
|
+
const contracts = config.contractsForLive
|
|
26
|
+
|
|
27
|
+
if (contracts.length === 0) {
|
|
28
|
+
logger.debug(
|
|
29
|
+
'all configured contracts have endBlock set; live sync disabled'
|
|
30
|
+
)
|
|
31
|
+
return { stop: noop }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const pqueue = new PQueue({ concurrency: 1 })
|
|
35
|
+
pqueue.on('error', (error) => {
|
|
36
|
+
logger.error({ error }, 'live queue error')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
let nextBlockToQueue = args.initialCursor
|
|
40
|
+
|
|
41
|
+
const unwatch = client.watchBlockNumber({
|
|
42
|
+
emitMissed: true,
|
|
43
|
+
emitOnBegin: true,
|
|
44
|
+
onBlockNumber: (head) => {
|
|
45
|
+
while (nextBlockToQueue <= head) {
|
|
46
|
+
const blockNumber = nextBlockToQueue
|
|
47
|
+
pqueue.add(async () => {
|
|
48
|
+
await queueBlock({
|
|
49
|
+
logger,
|
|
50
|
+
blockNumber,
|
|
51
|
+
config,
|
|
52
|
+
db,
|
|
53
|
+
client,
|
|
54
|
+
registry,
|
|
55
|
+
queueSize: pqueue.size,
|
|
56
|
+
onRewind: (rewindTo) => {
|
|
57
|
+
nextBlockToQueue = rewindTo
|
|
58
|
+
pqueue.clear()
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
nextBlockToQueue += 1n
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
onError: (error) => {
|
|
66
|
+
logger.error({ error }, 'watchBlockNumber stream error')
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
logger.debug(
|
|
71
|
+
{ startBlock: nextBlockToQueue.toString() },
|
|
72
|
+
'watching latest chain head'
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
return { stop: unwatch }
|
|
76
|
+
}
|