ponder 0.14.13 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/esm/bin/commands/createViews.js +28 -11
  3. package/dist/esm/bin/commands/createViews.js.map +1 -1
  4. package/dist/esm/bin/commands/dev.js +42 -22
  5. package/dist/esm/bin/commands/dev.js.map +1 -1
  6. package/dist/esm/bin/commands/prune.js +3 -0
  7. package/dist/esm/bin/commands/prune.js.map +1 -1
  8. package/dist/esm/bin/commands/serve.js +4 -1
  9. package/dist/esm/bin/commands/serve.js.map +1 -1
  10. package/dist/esm/bin/commands/start.js +18 -6
  11. package/dist/esm/bin/commands/start.js.map +1 -1
  12. package/dist/esm/bin/isolatedController.js +200 -0
  13. package/dist/esm/bin/isolatedController.js.map +1 -0
  14. package/dist/esm/bin/isolatedWorker.js +146 -0
  15. package/dist/esm/bin/isolatedWorker.js.map +1 -0
  16. package/dist/esm/build/config.js +322 -402
  17. package/dist/esm/build/config.js.map +1 -1
  18. package/dist/esm/build/index.js +8 -11
  19. package/dist/esm/build/index.js.map +1 -1
  20. package/dist/esm/build/pre.js +1 -4
  21. package/dist/esm/build/pre.js.map +1 -1
  22. package/dist/esm/build/schema.js +25 -3
  23. package/dist/esm/build/schema.js.map +1 -1
  24. package/dist/esm/client/index.js +306 -42
  25. package/dist/esm/client/index.js.map +1 -1
  26. package/dist/esm/database/actions.js +264 -104
  27. package/dist/esm/database/actions.js.map +1 -1
  28. package/dist/esm/database/index.js +39 -33
  29. package/dist/esm/database/index.js.map +1 -1
  30. package/dist/esm/database/queryBuilder.js +1 -0
  31. package/dist/esm/database/queryBuilder.js.map +1 -1
  32. package/dist/esm/drizzle/index.js +11 -7
  33. package/dist/esm/drizzle/index.js.map +1 -1
  34. package/dist/esm/drizzle/onchain.js +18 -0
  35. package/dist/esm/drizzle/onchain.js.map +1 -1
  36. package/dist/esm/indexing/client.js +32 -25
  37. package/dist/esm/indexing/client.js.map +1 -1
  38. package/dist/esm/indexing/index.js +110 -178
  39. package/dist/esm/indexing/index.js.map +1 -1
  40. package/dist/esm/indexing/profile.js +1 -1
  41. package/dist/esm/indexing/profile.js.map +1 -1
  42. package/dist/esm/indexing-store/cache.js +196 -274
  43. package/dist/esm/indexing-store/cache.js.map +1 -1
  44. package/dist/esm/indexing-store/historical.js +17 -13
  45. package/dist/esm/indexing-store/historical.js.map +1 -1
  46. package/dist/esm/indexing-store/index.js +10 -1
  47. package/dist/esm/indexing-store/index.js.map +1 -1
  48. package/dist/esm/indexing-store/profile.js +3 -3
  49. package/dist/esm/indexing-store/profile.js.map +1 -1
  50. package/dist/esm/indexing-store/realtime.js +27 -2
  51. package/dist/esm/indexing-store/realtime.js.map +1 -1
  52. package/dist/esm/internal/errors.js +28 -0
  53. package/dist/esm/internal/errors.js.map +1 -1
  54. package/dist/esm/internal/metrics.js +279 -82
  55. package/dist/esm/internal/metrics.js.map +1 -1
  56. package/dist/esm/internal/options.js +1 -0
  57. package/dist/esm/internal/options.js.map +1 -1
  58. package/dist/esm/internal/telemetry.js +1 -1
  59. package/dist/esm/internal/telemetry.js.map +1 -1
  60. package/dist/esm/rpc/http.js +130 -0
  61. package/dist/esm/rpc/http.js.map +1 -0
  62. package/dist/esm/rpc/index.js +38 -7
  63. package/dist/esm/rpc/index.js.map +1 -1
  64. package/dist/esm/runtime/events.js +179 -212
  65. package/dist/esm/runtime/events.js.map +1 -1
  66. package/dist/esm/runtime/filter.js +71 -0
  67. package/dist/esm/runtime/filter.js.map +1 -1
  68. package/dist/esm/runtime/fragments.js +78 -73
  69. package/dist/esm/runtime/fragments.js.map +1 -1
  70. package/dist/esm/runtime/historical.js +306 -130
  71. package/dist/esm/runtime/historical.js.map +1 -1
  72. package/dist/esm/runtime/index.js +183 -58
  73. package/dist/esm/runtime/index.js.map +1 -1
  74. package/dist/esm/runtime/isolated.js +462 -0
  75. package/dist/esm/runtime/isolated.js.map +1 -0
  76. package/dist/esm/runtime/multichain.js +80 -73
  77. package/dist/esm/runtime/multichain.js.map +1 -1
  78. package/dist/esm/runtime/omnichain.js +82 -75
  79. package/dist/esm/runtime/omnichain.js.map +1 -1
  80. package/dist/esm/runtime/realtime.js +198 -66
  81. package/dist/esm/runtime/realtime.js.map +1 -1
  82. package/dist/esm/sync-historical/index.js +416 -457
  83. package/dist/esm/sync-historical/index.js.map +1 -1
  84. package/dist/esm/sync-realtime/bloom.js +3 -3
  85. package/dist/esm/sync-realtime/bloom.js.map +1 -1
  86. package/dist/esm/sync-realtime/index.js +27 -46
  87. package/dist/esm/sync-realtime/index.js.map +1 -1
  88. package/dist/esm/sync-store/index.js +112 -63
  89. package/dist/esm/sync-store/index.js.map +1 -1
  90. package/dist/esm/utils/abi.js +20 -32
  91. package/dist/esm/utils/abi.js.map +1 -1
  92. package/dist/esm/utils/chunk.js +8 -0
  93. package/dist/esm/utils/chunk.js.map +1 -0
  94. package/dist/esm/utils/promiseAllSettledWithThrow.js +19 -0
  95. package/dist/esm/utils/promiseAllSettledWithThrow.js.map +1 -0
  96. package/dist/esm/{client/parse.js → utils/sql-parse.js} +94 -80
  97. package/dist/esm/utils/sql-parse.js.map +1 -0
  98. package/dist/types/bin/commands/createViews.d.ts.map +1 -1
  99. package/dist/types/bin/commands/dev.d.ts.map +1 -1
  100. package/dist/types/bin/commands/prune.d.ts.map +1 -1
  101. package/dist/types/bin/commands/serve.d.ts.map +1 -1
  102. package/dist/types/bin/commands/start.d.ts.map +1 -1
  103. package/dist/types/bin/isolatedController.d.ts +13 -0
  104. package/dist/types/bin/isolatedController.d.ts.map +1 -0
  105. package/dist/types/bin/isolatedWorker.d.ts +9 -0
  106. package/dist/types/bin/isolatedWorker.d.ts.map +1 -0
  107. package/dist/types/build/config.d.ts +29 -11
  108. package/dist/types/build/config.d.ts.map +1 -1
  109. package/dist/types/build/index.d.ts +3 -2
  110. package/dist/types/build/index.d.ts.map +1 -1
  111. package/dist/types/build/pre.d.ts +1 -1
  112. package/dist/types/build/pre.d.ts.map +1 -1
  113. package/dist/types/build/schema.d.ts +5 -3
  114. package/dist/types/build/schema.d.ts.map +1 -1
  115. package/dist/types/client/index.d.ts +1 -1
  116. package/dist/types/client/index.d.ts.map +1 -1
  117. package/dist/types/config/index.d.ts +3 -3
  118. package/dist/types/config/index.d.ts.map +1 -1
  119. package/dist/types/database/actions.d.ts +53 -7
  120. package/dist/types/database/actions.d.ts.map +1 -1
  121. package/dist/types/database/index.d.ts +21 -21
  122. package/dist/types/database/index.d.ts.map +1 -1
  123. package/dist/types/database/queryBuilder.d.ts.map +1 -1
  124. package/dist/types/drizzle/index.d.ts +4 -5
  125. package/dist/types/drizzle/index.d.ts.map +1 -1
  126. package/dist/types/drizzle/onchain.d.ts +6 -0
  127. package/dist/types/drizzle/onchain.d.ts.map +1 -1
  128. package/dist/types/indexing/client.d.ts.map +1 -1
  129. package/dist/types/indexing/index.d.ts +2 -5
  130. package/dist/types/indexing/index.d.ts.map +1 -1
  131. package/dist/types/indexing-store/cache.d.ts +3 -2
  132. package/dist/types/indexing-store/cache.d.ts.map +1 -1
  133. package/dist/types/indexing-store/historical.d.ts +2 -1
  134. package/dist/types/indexing-store/historical.d.ts.map +1 -1
  135. package/dist/types/indexing-store/index.d.ts +1 -0
  136. package/dist/types/indexing-store/index.d.ts.map +1 -1
  137. package/dist/types/indexing-store/realtime.d.ts +2 -1
  138. package/dist/types/indexing-store/realtime.d.ts.map +1 -1
  139. package/dist/types/internal/errors.d.ts +5 -0
  140. package/dist/types/internal/errors.d.ts.map +1 -1
  141. package/dist/types/internal/metrics.d.ts +21 -0
  142. package/dist/types/internal/metrics.d.ts.map +1 -1
  143. package/dist/types/internal/options.d.ts +2 -0
  144. package/dist/types/internal/options.d.ts.map +1 -1
  145. package/dist/types/internal/types.d.ts +66 -58
  146. package/dist/types/internal/types.d.ts.map +1 -1
  147. package/dist/types/rpc/http.d.ts +17 -0
  148. package/dist/types/rpc/http.d.ts.map +1 -0
  149. package/dist/types/rpc/index.d.ts.map +1 -1
  150. package/dist/types/runtime/events.d.ts +4 -4
  151. package/dist/types/runtime/events.d.ts.map +1 -1
  152. package/dist/types/runtime/filter.d.ts +5 -1
  153. package/dist/types/runtime/filter.d.ts.map +1 -1
  154. package/dist/types/runtime/fragments.d.ts +4 -3
  155. package/dist/types/runtime/fragments.d.ts.map +1 -1
  156. package/dist/types/runtime/historical.d.ts +29 -13
  157. package/dist/types/runtime/historical.d.ts.map +1 -1
  158. package/dist/types/runtime/index.d.ts +49 -6
  159. package/dist/types/runtime/index.d.ts.map +1 -1
  160. package/dist/types/runtime/init.d.ts +5 -5
  161. package/dist/types/runtime/init.d.ts.map +1 -1
  162. package/dist/types/runtime/isolated.d.ts +14 -0
  163. package/dist/types/runtime/isolated.d.ts.map +1 -0
  164. package/dist/types/runtime/multichain.d.ts.map +1 -1
  165. package/dist/types/runtime/omnichain.d.ts.map +1 -1
  166. package/dist/types/runtime/realtime.d.ts +21 -10
  167. package/dist/types/runtime/realtime.d.ts.map +1 -1
  168. package/dist/types/sync-historical/index.d.ts +18 -8
  169. package/dist/types/sync-historical/index.d.ts.map +1 -1
  170. package/dist/types/sync-realtime/bloom.d.ts.map +1 -1
  171. package/dist/types/sync-realtime/index.d.ts +2 -2
  172. package/dist/types/sync-realtime/index.d.ts.map +1 -1
  173. package/dist/types/sync-store/index.d.ts +9 -9
  174. package/dist/types/sync-store/index.d.ts.map +1 -1
  175. package/dist/types/utils/abi.d.ts +3 -34
  176. package/dist/types/utils/abi.d.ts.map +1 -1
  177. package/dist/types/utils/chunk.d.ts +2 -0
  178. package/dist/types/utils/chunk.d.ts.map +1 -0
  179. package/dist/types/utils/promiseAllSettledWithThrow.d.ts +8 -0
  180. package/dist/types/utils/promiseAllSettledWithThrow.d.ts.map +1 -0
  181. package/dist/types/utils/sql-parse.d.ts +21 -0
  182. package/dist/types/utils/sql-parse.d.ts.map +1 -0
  183. package/package.json +2 -2
  184. package/src/bin/commands/createViews.ts +35 -15
  185. package/src/bin/commands/dev.ts +43 -21
  186. package/src/bin/commands/prune.ts +6 -0
  187. package/src/bin/commands/serve.ts +4 -1
  188. package/src/bin/commands/start.ts +20 -5
  189. package/src/bin/isolatedController.ts +300 -0
  190. package/src/bin/isolatedWorker.ts +192 -0
  191. package/src/build/config.ts +570 -632
  192. package/src/build/index.ts +14 -14
  193. package/src/build/pre.ts +1 -4
  194. package/src/build/schema.ts +49 -4
  195. package/src/client/index.ts +386 -48
  196. package/src/config/index.ts +3 -3
  197. package/src/database/actions.ts +469 -120
  198. package/src/database/index.ts +85 -58
  199. package/src/database/queryBuilder.ts +1 -0
  200. package/src/drizzle/index.ts +15 -7
  201. package/src/drizzle/onchain.ts +19 -0
  202. package/src/indexing/client.ts +38 -25
  203. package/src/indexing/index.ts +137 -230
  204. package/src/indexing/profile.ts +1 -1
  205. package/src/indexing-store/cache.ts +285 -414
  206. package/src/indexing-store/historical.ts +20 -10
  207. package/src/indexing-store/index.ts +16 -0
  208. package/src/indexing-store/profile.ts +3 -3
  209. package/src/indexing-store/realtime.ts +28 -0
  210. package/src/internal/errors.ts +26 -0
  211. package/src/internal/metrics.ts +341 -111
  212. package/src/internal/options.ts +4 -0
  213. package/src/internal/telemetry.ts +1 -1
  214. package/src/internal/types.ts +70 -87
  215. package/src/rpc/http.ts +164 -0
  216. package/src/rpc/index.ts +39 -7
  217. package/src/runtime/events.ts +195 -240
  218. package/src/runtime/filter.ts +85 -1
  219. package/src/runtime/fragments.ts +109 -113
  220. package/src/runtime/historical.ts +467 -189
  221. package/src/runtime/index.ts +337 -69
  222. package/src/runtime/init.ts +5 -5
  223. package/src/runtime/isolated.ts +768 -0
  224. package/src/runtime/multichain.ts +137 -102
  225. package/src/runtime/omnichain.ts +138 -106
  226. package/src/runtime/realtime.ts +322 -123
  227. package/src/sync-historical/index.ts +556 -692
  228. package/src/sync-realtime/bloom.ts +7 -3
  229. package/src/sync-realtime/index.ts +31 -46
  230. package/src/sync-store/index.ts +189 -95
  231. package/src/utils/abi.ts +33 -90
  232. package/src/utils/chunk.ts +7 -0
  233. package/src/utils/promiseAllSettledWithThrow.ts +27 -0
  234. package/src/{client/parse.ts → utils/sql-parse.ts} +100 -90
  235. package/dist/esm/client/parse.js.map +0 -1
  236. package/dist/types/client/parse.d.ts +0 -14
  237. package/dist/types/client/parse.d.ts.map +0 -1
@@ -0,0 +1,768 @@
1
+ import {
2
+ commitBlock,
3
+ createLiveQueryTriggers,
4
+ createTriggers,
5
+ dropLiveQueryTriggers,
6
+ dropTriggers,
7
+ finalizeIsolated,
8
+ revertIsolated,
9
+ } from "@/database/actions.js";
10
+ import { type Database, getPonderCheckpointTable } from "@/database/index.js";
11
+ import { createIndexingCache } from "@/indexing-store/cache.js";
12
+ import { createHistoricalIndexingStore } from "@/indexing-store/historical.js";
13
+ import { createRealtimeIndexingStore } from "@/indexing-store/realtime.js";
14
+ import { createCachedViemClient } from "@/indexing/client.js";
15
+ import {
16
+ createColumnAccessPattern,
17
+ createIndexing,
18
+ getEventCount,
19
+ } from "@/indexing/index.js";
20
+ import type { Common } from "@/internal/common.js";
21
+ import {
22
+ InvalidEventAccessError,
23
+ NonRetryableUserError,
24
+ type RetryableError,
25
+ } from "@/internal/errors.js";
26
+ import type {
27
+ CrashRecoveryCheckpoint,
28
+ IndexingBuild,
29
+ IndexingErrorHandler,
30
+ NamespaceBuild,
31
+ PreBuild,
32
+ SchemaBuild,
33
+ Seconds,
34
+ } from "@/internal/types.js";
35
+ import { splitEvents } from "@/runtime/events.js";
36
+ import type { RealtimeSyncEvent } from "@/sync-realtime/index.js";
37
+ import { createSyncStore } from "@/sync-store/index.js";
38
+ import {
39
+ ZERO_CHECKPOINT_STRING,
40
+ decodeCheckpoint,
41
+ min,
42
+ } from "@/utils/checkpoint.js";
43
+ import {
44
+ bufferAsyncGenerator,
45
+ recordAsyncGenerator,
46
+ } from "@/utils/generators.js";
47
+ import { never } from "@/utils/never.js";
48
+ import { startClock } from "@/utils/timer.js";
49
+ import { eq, getTableName, isTable, sql } from "drizzle-orm";
50
+ import {
51
+ getHistoricalEventsIsolated,
52
+ refetchHistoricalEvents,
53
+ } from "./historical.js";
54
+ import { getCachedIntervals, getChildAddresses } from "./index.js";
55
+ import { initSyncProgress } from "./init.js";
56
+ import { getRealtimeEventsIsolated } from "./realtime.js";
57
+
58
+ export async function runIsolated({
59
+ common,
60
+ preBuild,
61
+ namespaceBuild,
62
+ schemaBuild,
63
+ indexingBuild,
64
+ crashRecoveryCheckpoint,
65
+ database,
66
+ onReady,
67
+ }: {
68
+ common: Common;
69
+ preBuild: PreBuild;
70
+ namespaceBuild: NamespaceBuild;
71
+ schemaBuild: SchemaBuild;
72
+ indexingBuild: IndexingBuild;
73
+ crashRecoveryCheckpoint: CrashRecoveryCheckpoint;
74
+ database: Database;
75
+ onReady: () => void;
76
+ }) {
77
+ const chain = indexingBuild.chains[0]!;
78
+
79
+ const columnAccessPattern = createColumnAccessPattern({
80
+ indexingBuild,
81
+ });
82
+ const syncStore = createSyncStore({ common, qb: database.syncQB });
83
+
84
+ const PONDER_CHECKPOINT = getPonderCheckpointTable(namespaceBuild.schema);
85
+
86
+ const eventCount = getEventCount(indexingBuild.indexingFunctions);
87
+
88
+ const cachedViemClient = createCachedViemClient({
89
+ common,
90
+ indexingBuild,
91
+ syncStore,
92
+ eventCount,
93
+ });
94
+
95
+ const indexingErrorHandler: IndexingErrorHandler = {
96
+ getRetryableError: () => {
97
+ return indexingErrorHandler.error;
98
+ },
99
+ setRetryableError: (error: RetryableError) => {
100
+ indexingErrorHandler.error = error;
101
+ },
102
+ clearRetryableError: () => {
103
+ indexingErrorHandler.error = undefined;
104
+ },
105
+ error: undefined as RetryableError | undefined,
106
+ };
107
+
108
+ const indexing = createIndexing({
109
+ common,
110
+ indexingBuild,
111
+ client: cachedViemClient,
112
+ indexingErrorHandler,
113
+ columnAccessPattern,
114
+ });
115
+
116
+ const indexingCache = createIndexingCache({
117
+ common,
118
+ schemaBuild,
119
+ crashRecoveryCheckpoint,
120
+ eventCount,
121
+ chainId: chain.id,
122
+ });
123
+
124
+ const historicalIndexingStore = createHistoricalIndexingStore({
125
+ common,
126
+ schemaBuild,
127
+ indexingCache,
128
+ indexingErrorHandler,
129
+ chainId: chain.id,
130
+ });
131
+
132
+ const seconds: Seconds = {};
133
+
134
+ const eventCallbacks =
135
+ indexingBuild.eventCallbacks[indexingBuild.chains.indexOf(chain)]!;
136
+
137
+ const cachedIntervals = await getCachedIntervals({
138
+ chain,
139
+ filters: eventCallbacks.map(({ filter }) => filter),
140
+ syncStore,
141
+ });
142
+ const syncProgress = await initSyncProgress({
143
+ common,
144
+ filters: eventCallbacks.map(({ filter }) => filter),
145
+ chain,
146
+ rpc: indexingBuild.rpcs[0]!,
147
+ finalizedBlock: indexingBuild.finalizedBlocks[0]!,
148
+ cachedIntervals,
149
+ });
150
+ const childAddresses = await getChildAddresses({
151
+ filters: eventCallbacks.map(({ filter }) => filter),
152
+ syncStore,
153
+ });
154
+ const unfinalizedBlocks: Omit<
155
+ Extract<RealtimeSyncEvent, { type: "block" }>,
156
+ "type"
157
+ >[] = [];
158
+
159
+ const start = Number(
160
+ decodeCheckpoint(syncProgress.getCheckpoint({ tag: "start" }))
161
+ .blockTimestamp,
162
+ );
163
+
164
+ const end = Number(
165
+ decodeCheckpoint(
166
+ min(
167
+ syncProgress.getCheckpoint({ tag: "end" }),
168
+ syncProgress.getCheckpoint({ tag: "finalized" }),
169
+ ),
170
+ ).blockTimestamp,
171
+ );
172
+
173
+ const _crashRecoveryCheckpoint = crashRecoveryCheckpoint?.find(
174
+ ({ chainId }) => chainId === chain.id,
175
+ )?.checkpoint;
176
+
177
+ const cached = Math.min(
178
+ Number(
179
+ decodeCheckpoint(_crashRecoveryCheckpoint ?? ZERO_CHECKPOINT_STRING)
180
+ .blockTimestamp,
181
+ ),
182
+ end,
183
+ );
184
+
185
+ seconds[chain.name] = { start, end, cached };
186
+
187
+ const label = { chain: chain.name };
188
+ common.metrics.ponder_historical_total_indexing_seconds.set(
189
+ label,
190
+ Math.max(seconds[chain.name]!.end - seconds[chain.name]!.start, 0),
191
+ );
192
+ common.metrics.ponder_historical_cached_indexing_seconds.set(
193
+ label,
194
+ Math.max(seconds[chain.name]!.cached - seconds[chain.name]!.start, 0),
195
+ );
196
+ common.metrics.ponder_historical_completed_indexing_seconds.set(label, 0);
197
+ common.metrics.ponder_indexing_timestamp.set(
198
+ label,
199
+ Math.max(seconds[chain.name]!.cached, seconds[chain.name]!.start),
200
+ );
201
+
202
+ const startTimestamp = Math.round(Date.now() / 1000);
203
+ common.metrics.ponder_historical_start_timestamp_seconds.set(
204
+ label,
205
+ startTimestamp,
206
+ );
207
+
208
+ // Reset the start timestamp so the eta estimate doesn't include
209
+ // the startup time.
210
+ common.metrics.start_timestamp = Date.now();
211
+
212
+ // If the initial checkpoint is zero, we need to run setup events.
213
+ if (crashRecoveryCheckpoint === undefined) {
214
+ await database.userQB.transaction(async (tx) => {
215
+ historicalIndexingStore.qb = tx;
216
+ historicalIndexingStore.isProcessingEvents = true;
217
+
218
+ indexingCache.qb = tx;
219
+
220
+ await indexing.processSetupEvents({
221
+ db: historicalIndexingStore,
222
+ });
223
+
224
+ historicalIndexingStore.isProcessingEvents = false;
225
+
226
+ await indexingCache.flush();
227
+
228
+ const initialCheckpoint = min(
229
+ syncProgress.getCheckpoint({ tag: "start" }),
230
+ syncProgress.getCheckpoint({ tag: "finalized" }),
231
+ );
232
+
233
+ await tx.wrap({ label: "update_checkpoints" }, (tx) =>
234
+ tx
235
+ .insert(PONDER_CHECKPOINT)
236
+ .values({
237
+ chainName: chain.name,
238
+ chainId: chain.id,
239
+ latestCheckpoint: initialCheckpoint,
240
+ safeCheckpoint: initialCheckpoint,
241
+ finalizedCheckpoint: initialCheckpoint,
242
+ })
243
+ .onConflictDoUpdate({
244
+ target: PONDER_CHECKPOINT.chainName,
245
+ set: {
246
+ finalizedCheckpoint: sql`excluded.finalized_checkpoint`,
247
+ safeCheckpoint: sql`excluded.safe_checkpoint`,
248
+ latestCheckpoint: sql`excluded.latest_checkpoint`,
249
+ },
250
+ }),
251
+ );
252
+ });
253
+ }
254
+
255
+ const backfillEndClock = startClock();
256
+
257
+ // Run historical indexing until complete.
258
+ for await (let {
259
+ events,
260
+ chainId,
261
+ checkpoint,
262
+ blockRange,
263
+ } of recordAsyncGenerator(
264
+ bufferAsyncGenerator(
265
+ getHistoricalEventsIsolated({
266
+ common,
267
+ chain,
268
+ indexingBuild,
269
+ crashRecoveryCheckpoint,
270
+ syncProgress,
271
+ childAddresses,
272
+ cachedIntervals,
273
+ database,
274
+ }),
275
+ 1,
276
+ ),
277
+ (params) => {
278
+ common.metrics.ponder_historical_concurrency_group_duration.inc(
279
+ { group: "extract" },
280
+ params.await,
281
+ );
282
+ common.metrics.ponder_historical_concurrency_group_duration.inc(
283
+ { group: "transform" },
284
+ params.yield,
285
+ );
286
+ },
287
+ )) {
288
+ const context = {
289
+ logger: common.logger.child({ action: "index_block_range" }),
290
+ };
291
+ const indexStartClock = startClock();
292
+
293
+ indexingCache.qb = database.userQB;
294
+ await Promise.all([
295
+ indexingCache.prefetch({ events }),
296
+ cachedViemClient.prefetch({ events }),
297
+ ]);
298
+ common.metrics.ponder_historical_transform_duration.inc(
299
+ { step: "prefetch" },
300
+ indexStartClock(),
301
+ );
302
+
303
+ let endClock = startClock();
304
+ await database.userQB.transaction(
305
+ async (tx) => {
306
+ const initialCompletedEvents = structuredClone(
307
+ await common.metrics.ponder_indexing_completed_events.get(),
308
+ );
309
+
310
+ try {
311
+ historicalIndexingStore.qb = tx;
312
+ historicalIndexingStore.isProcessingEvents = true;
313
+ indexingCache.qb = tx;
314
+
315
+ common.metrics.ponder_historical_transform_duration.inc(
316
+ { step: "begin" },
317
+ endClock(),
318
+ );
319
+
320
+ endClock = startClock();
321
+
322
+ await indexing.processHistoricalEvents({
323
+ events,
324
+ db: historicalIndexingStore,
325
+ cache: indexingCache,
326
+ updateIndexingSeconds(event, chain) {
327
+ const checkpoint = decodeCheckpoint(event!.checkpoint);
328
+
329
+ common.metrics.ponder_historical_completed_indexing_seconds.set(
330
+ { chain: chain.name },
331
+ Math.max(
332
+ Number(checkpoint.blockTimestamp) -
333
+ Math.max(
334
+ seconds[chain.name]!.cached,
335
+ seconds[chain.name]!.start,
336
+ ),
337
+ 0,
338
+ ),
339
+ );
340
+ common.metrics.ponder_indexing_timestamp.set(
341
+ { chain: chain.name },
342
+ Number(checkpoint.blockTimestamp),
343
+ );
344
+ },
345
+ });
346
+
347
+ historicalIndexingStore.isProcessingEvents = false;
348
+
349
+ common.metrics.ponder_historical_transform_duration.inc(
350
+ { step: "index" },
351
+ endClock(),
352
+ );
353
+
354
+ endClock = startClock();
355
+
356
+ // Note: at this point, the next events can be preloaded, as long as the are not indexed until
357
+ // the "flush" + "finalize" is complete.
358
+
359
+ await indexingCache.flush();
360
+
361
+ common.metrics.ponder_historical_transform_duration.inc(
362
+ { step: "load" },
363
+ endClock(),
364
+ );
365
+
366
+ endClock = startClock();
367
+
368
+ await tx.wrap(
369
+ { label: "update_checkpoints" },
370
+ (tx) =>
371
+ tx
372
+ .insert(PONDER_CHECKPOINT)
373
+ .values({
374
+ chainName: chain.name,
375
+ chainId,
376
+ latestCheckpoint: checkpoint,
377
+ finalizedCheckpoint: checkpoint,
378
+ safeCheckpoint: checkpoint,
379
+ })
380
+ .onConflictDoUpdate({
381
+ target: PONDER_CHECKPOINT.chainName,
382
+ set: {
383
+ safeCheckpoint: sql`excluded.safe_checkpoint`,
384
+ finalizedCheckpoint: sql`excluded.finalized_checkpoint`,
385
+ latestCheckpoint: sql`excluded.latest_checkpoint`,
386
+ },
387
+ }),
388
+ context,
389
+ );
390
+
391
+ common.metrics.ponder_historical_transform_duration.inc(
392
+ { step: "finalize" },
393
+ endClock(),
394
+ );
395
+ endClock = startClock();
396
+ } catch (error) {
397
+ // Note: This can cause a bug with "dev" command, because there are multiple instances
398
+ // updating the same metric.
399
+ for (const value of initialCompletedEvents.values) {
400
+ common.metrics.ponder_indexing_completed_events.set(
401
+ value.labels,
402
+ value.value,
403
+ );
404
+ }
405
+
406
+ indexingCache.invalidate();
407
+ indexingCache.clear();
408
+
409
+ if (error instanceof InvalidEventAccessError) {
410
+ common.logger.debug({
411
+ msg: "Failed to index block range",
412
+ chain: chain.name,
413
+ chain_id: chain.id,
414
+ block_range: JSON.stringify(blockRange),
415
+ duration: indexStartClock(),
416
+ error,
417
+ });
418
+ events = await refetchHistoricalEvents({
419
+ common,
420
+ indexingBuild,
421
+ perChainSync: new Map([[chain, { childAddresses }]]),
422
+ syncStore,
423
+ events,
424
+ });
425
+ } else if (error instanceof NonRetryableUserError === false) {
426
+ common.logger.warn({
427
+ msg: "Failed to index block range",
428
+ chain: chain.name,
429
+ chain_id: chain.id,
430
+ block_range: JSON.stringify(blockRange),
431
+ duration: indexStartClock(),
432
+ error: error as Error,
433
+ });
434
+ }
435
+
436
+ throw error;
437
+ }
438
+ },
439
+ undefined,
440
+ context,
441
+ );
442
+
443
+ cachedViemClient.clear();
444
+ common.metrics.ponder_historical_transform_duration.inc(
445
+ { step: "commit" },
446
+ endClock(),
447
+ );
448
+
449
+ await new Promise(setImmediate);
450
+
451
+ common.logger.info({
452
+ msg: "Indexed block range",
453
+ chain: chain.name,
454
+ chain_id: chain.id,
455
+ event_count: events.length,
456
+ block_range: JSON.stringify(blockRange),
457
+ duration: indexStartClock(),
458
+ });
459
+ }
460
+
461
+ indexingCache.clear();
462
+
463
+ // Manually update metrics to fix a UI bug that occurs when the end
464
+ // checkpoint is between the last processed event and the finalized
465
+ // checkpoint.
466
+
467
+ common.metrics.ponder_historical_completed_indexing_seconds.set(
468
+ label,
469
+ Math.max(
470
+ seconds[chain.name]!.end -
471
+ Math.max(seconds[chain.name]!.cached, seconds[chain.name]!.start),
472
+ 0,
473
+ ),
474
+ );
475
+ common.metrics.ponder_indexing_timestamp.set(
476
+ { chain: chain.name },
477
+ seconds[chain.name]!.end,
478
+ );
479
+
480
+ const endTimestamp = Math.round(Date.now() / 1000);
481
+ common.metrics.ponder_historical_end_timestamp_seconds.set(
482
+ { chain: chain.name },
483
+ endTimestamp,
484
+ );
485
+
486
+ common.logger.info({
487
+ msg: "Completed backfill indexing",
488
+ chain: chain.name,
489
+ chain_id: chain.id,
490
+ duration: backfillEndClock(),
491
+ });
492
+
493
+ const tables = Object.values(schemaBuild.schema).filter(isTable);
494
+
495
+ const endClock = startClock();
496
+
497
+ await createTriggers(database.adminQB, { tables, chainId: chain.id });
498
+ await createLiveQueryTriggers(database.adminQB, {
499
+ namespaceBuild,
500
+ tables,
501
+ chainId: chain.id,
502
+ });
503
+
504
+ common.logger.debug({
505
+ msg: "Created database triggers",
506
+ chain: chain.name,
507
+ chain_id: chain.id,
508
+ count: tables.length,
509
+ duration: endClock(),
510
+ });
511
+
512
+ onReady();
513
+
514
+ const realtimeIndexingStore = createRealtimeIndexingStore({
515
+ common,
516
+ schemaBuild,
517
+ indexingErrorHandler,
518
+ chainId: chain.id,
519
+ });
520
+
521
+ const bufferCallback = (bufferSize: number) => {
522
+ // Note: Only log when the buffer size is greater than 1 because
523
+ // a buffer size of 1 is not backpressure.
524
+ if (bufferSize === 1) return;
525
+ common.logger.trace({
526
+ msg: "Detected live indexing backpressure",
527
+ buffer_size: bufferSize,
528
+ indexing_step: "index block",
529
+ });
530
+ };
531
+
532
+ for await (const event of bufferAsyncGenerator(
533
+ getRealtimeEventsIsolated({
534
+ common,
535
+ indexingBuild,
536
+ chain,
537
+ syncProgress,
538
+ childAddresses,
539
+ unfinalizedBlocks,
540
+ database,
541
+ }),
542
+ 100,
543
+ bufferCallback,
544
+ )) {
545
+ switch (event.type) {
546
+ case "block": {
547
+ const context = {
548
+ logger: common.logger.child({ action: "index_block" }),
549
+ };
550
+ const endClock = startClock();
551
+
552
+ await database.userQB.transaction(
553
+ async (tx) => {
554
+ if (database.userQB.$dialect === "postgres") {
555
+ await tx.wrap(
556
+ (tx) =>
557
+ tx.execute(
558
+ "CREATE TEMP TABLE live_query_tables (table_name TEXT PRIMARY KEY) ON COMMIT DROP",
559
+ ),
560
+ context,
561
+ );
562
+ } else {
563
+ await tx.wrap(
564
+ (tx) =>
565
+ tx.execute(
566
+ "CREATE TEMP TABLE IF NOT EXISTS live_query_tables (table_name TEXT PRIMARY KEY)",
567
+ ),
568
+ context,
569
+ );
570
+ }
571
+
572
+ // Events must be run block-by-block, so that `database.commitBlock` can accurately
573
+ // update the temporary `checkpoint` value set in the trigger.
574
+ for (const { checkpoint, events } of splitEvents(event.events)) {
575
+ try {
576
+ realtimeIndexingStore.qb = tx;
577
+ realtimeIndexingStore.isProcessingEvents = true;
578
+
579
+ common.logger.trace({
580
+ msg: "Processing block events",
581
+ chain: chain.name,
582
+ chain_id: chain.id,
583
+ number: Number(decodeCheckpoint(checkpoint).blockNumber),
584
+ event_count: events.length,
585
+ });
586
+
587
+ await indexing.processRealtimeEvents({
588
+ events,
589
+ db: realtimeIndexingStore,
590
+ });
591
+
592
+ common.logger.trace({
593
+ msg: "Processed block events",
594
+ chain: chain.name,
595
+ chain_id: chain.id,
596
+ number: Number(decodeCheckpoint(checkpoint).blockNumber),
597
+ event_count: events.length,
598
+ });
599
+
600
+ realtimeIndexingStore.isProcessingEvents = false;
601
+
602
+ await Promise.all(
603
+ tables.map((table) =>
604
+ commitBlock(tx, { table, checkpoint, preBuild }, context),
605
+ ),
606
+ );
607
+
608
+ common.logger.trace({
609
+ msg: "Committed reorg data for block",
610
+ chain: chain.name,
611
+ chain_id: chain.id,
612
+ number: Number(decodeCheckpoint(checkpoint).blockNumber),
613
+ event_count: events.length,
614
+ checkpoint,
615
+ });
616
+
617
+ common.metrics.ponder_indexing_timestamp.set(
618
+ { chain: chain.name },
619
+ Number(decodeCheckpoint(checkpoint).blockTimestamp),
620
+ );
621
+ } catch (error) {
622
+ if (error instanceof NonRetryableUserError === false) {
623
+ common.logger.warn({
624
+ msg: "Failed to index block",
625
+ chain: chain.name,
626
+ chain_id: chain.id,
627
+ number: Number(decodeCheckpoint(checkpoint).blockNumber),
628
+ error: error,
629
+ });
630
+ }
631
+
632
+ throw error;
633
+ }
634
+ }
635
+
636
+ await tx.wrap(
637
+ { label: "update_checkpoints" },
638
+ (db) =>
639
+ db
640
+ .update(PONDER_CHECKPOINT)
641
+ .set({ latestCheckpoint: event.checkpoint })
642
+ .where(eq(PONDER_CHECKPOINT.chainName, event.chain.name)),
643
+ context,
644
+ );
645
+
646
+ if (
647
+ event.events.length > 0 &&
648
+ database.userQB.$dialect === "pglite"
649
+ ) {
650
+ await tx.wrap(
651
+ (tx) => tx.execute("TRUNCATE TABLE live_query_tables"),
652
+ context,
653
+ );
654
+ }
655
+ },
656
+ undefined,
657
+ context,
658
+ );
659
+
660
+ event.blockCallback?.(true);
661
+
662
+ common.logger.info({
663
+ msg: "Indexed block",
664
+ chain: event.chain.name,
665
+ chain_id: event.chain.id,
666
+ number: Number(decodeCheckpoint(event.checkpoint).blockNumber),
667
+ event_count: event.events.length,
668
+ duration: endClock(),
669
+ });
670
+
671
+ break;
672
+ }
673
+ case "reorg": {
674
+ const context = {
675
+ logger: common.logger.child({ action: "reorg_block" }),
676
+ };
677
+ const endClock = startClock();
678
+
679
+ // Note: `_ponder_checkpoint` is not called here, instead it is called
680
+ // in the `block` case.
681
+
682
+ await database.userQB.transaction(
683
+ async (tx) => {
684
+ await dropTriggers(tx, { tables, chainId: chain.id }, context);
685
+ await dropLiveQueryTriggers(
686
+ tx,
687
+ { namespaceBuild, tables, chainId: chain.id },
688
+ context,
689
+ );
690
+
691
+ const counts = await revertIsolated(
692
+ tx,
693
+ {
694
+ checkpoint: event.checkpoint,
695
+ tables,
696
+ },
697
+ context,
698
+ );
699
+
700
+ for (const [index, table] of tables.entries()) {
701
+ common.logger.debug({
702
+ msg: "Reverted reorged database rows",
703
+ chain: chain.name,
704
+ chain_id: chain.id,
705
+ table: getTableName(table),
706
+ row_count: counts[index],
707
+ });
708
+ }
709
+
710
+ await createTriggers(tx, { tables, chainId: chain.id }, context);
711
+ await createLiveQueryTriggers(
712
+ tx,
713
+ { namespaceBuild, tables, chainId: chain.id },
714
+ context,
715
+ );
716
+ },
717
+ undefined,
718
+ context,
719
+ );
720
+
721
+ common.logger.info({
722
+ msg: "Reorged block",
723
+ chain: event.chain.name,
724
+ chain_id: event.chain.id,
725
+ number: Number(decodeCheckpoint(event.checkpoint).blockNumber),
726
+ duration: endClock(),
727
+ });
728
+
729
+ break;
730
+ }
731
+ case "finalize": {
732
+ const context = {
733
+ logger: common.logger.child({ action: "finalize_block" }),
734
+ };
735
+ const endClock = startClock();
736
+
737
+ await finalizeIsolated(
738
+ database.userQB,
739
+ {
740
+ checkpoint: event.checkpoint,
741
+ tables,
742
+ namespaceBuild,
743
+ },
744
+ context,
745
+ );
746
+
747
+ common.logger.info({
748
+ msg: "Finalized block",
749
+ chain: event.chain.name,
750
+ chain_id: event.chain.id,
751
+ number: Number(decodeCheckpoint(event.checkpoint).blockNumber),
752
+ duration: endClock(),
753
+ });
754
+
755
+ break;
756
+ }
757
+ default:
758
+ never(event);
759
+ }
760
+ }
761
+
762
+ common.logger.info({
763
+ msg: "Completed indexing",
764
+ chain: chain.name,
765
+ chain_id: chain.id,
766
+ duration: backfillEndClock(),
767
+ });
768
+ }