ponder 0.9.2 → 0.9.3

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 (48) hide show
  1. package/dist/bin/ponder.js +1933 -1606
  2. package/dist/bin/ponder.js.map +1 -1
  3. package/dist/{chunk-IFTUFVCL.js → chunk-LHCA5XFV.js} +2 -5
  4. package/dist/chunk-LHCA5XFV.js.map +1 -0
  5. package/dist/index.d.ts +5 -2
  6. package/dist/index.js +1 -1
  7. package/dist/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/bin/commands/codegen.ts +8 -10
  10. package/src/bin/commands/dev.ts +30 -42
  11. package/src/bin/commands/list.ts +9 -14
  12. package/src/bin/commands/serve.ts +26 -39
  13. package/src/bin/commands/start.ts +29 -42
  14. package/src/bin/utils/{shutdown.ts → exit.ts} +23 -37
  15. package/src/bin/utils/run.ts +275 -175
  16. package/src/bin/utils/runServer.ts +1 -5
  17. package/src/build/index.ts +3 -8
  18. package/src/build/pre.ts +3 -0
  19. package/src/config/index.ts +5 -2
  20. package/src/database/index.ts +72 -72
  21. package/src/drizzle/kit/index.ts +3 -3
  22. package/src/indexing/index.ts +0 -4
  23. package/src/indexing/service.ts +31 -93
  24. package/src/indexing-store/historical.ts +2 -4
  25. package/src/internal/common.ts +2 -0
  26. package/src/internal/errors.ts +9 -9
  27. package/src/internal/logger.ts +1 -1
  28. package/src/internal/metrics.ts +75 -103
  29. package/src/internal/shutdown.ts +25 -0
  30. package/src/internal/telemetry.ts +16 -18
  31. package/src/internal/types.ts +9 -1
  32. package/src/server/index.ts +3 -5
  33. package/src/sync/events.ts +4 -4
  34. package/src/sync/filter.ts +1 -0
  35. package/src/sync/index.ts +1046 -805
  36. package/src/sync-historical/index.ts +0 -37
  37. package/src/sync-realtime/index.ts +48 -48
  38. package/src/sync-store/encoding.ts +5 -5
  39. package/src/sync-store/index.ts +5 -23
  40. package/src/ui/index.ts +2 -11
  41. package/src/utils/checkpoint.ts +17 -3
  42. package/src/utils/chunk.ts +7 -0
  43. package/src/utils/generators.ts +66 -0
  44. package/src/utils/mutex.ts +34 -0
  45. package/src/utils/partition.ts +41 -0
  46. package/src/utils/requestQueue.ts +19 -10
  47. package/src/utils/zipper.ts +80 -0
  48. package/dist/chunk-IFTUFVCL.js.map +0 -1
@@ -1,34 +1,26 @@
1
1
  import os from "node:os";
2
2
  import readline from "node:readline";
3
3
  import type { Common } from "@/internal/common.js";
4
- import { IgnorableError } from "@/internal/errors.js";
4
+ import { ShutdownError } from "@/internal/errors.js";
5
5
 
6
6
  const SHUTDOWN_GRACE_PERIOD_MS = 5_000;
7
7
 
8
- /**
9
- * Sets up shutdown handlers for the process. Accepts additional cleanup logic to run.
10
- */
11
- export function setupShutdown({
8
+ /** Sets up shutdown handlers for the process. Accepts additional cleanup logic to run. */
9
+ export const createExit = ({
12
10
  common,
13
- cleanup,
14
11
  }: {
15
- common: Common;
16
- cleanup: () => Promise<void>;
17
- }) {
12
+ common: Pick<Common, "logger" | "telemetry" | "shutdown">;
13
+ }) => {
18
14
  let isShuttingDown = false;
19
15
 
20
- const shutdown = async ({
21
- reason,
22
- code,
23
- }: { reason: string; code: 0 | 1 }) => {
16
+ const exit = async ({ reason, code }: { reason: string; code: 0 | 1 }) => {
24
17
  if (isShuttingDown) return;
25
18
  isShuttingDown = true;
26
- setTimeout(async () => {
19
+ const timeout = setTimeout(async () => {
27
20
  common.logger.fatal({
28
21
  service: "process",
29
22
  msg: "Failed to shutdown within 5 seconds, terminating (exit code 1)",
30
23
  });
31
- await common.logger.kill();
32
24
  process.exit(1);
33
25
  }, SHUTDOWN_GRACE_PERIOD_MS);
34
26
 
@@ -43,15 +35,14 @@ export function setupShutdown({
43
35
  properties: { duration_seconds: process.uptime() },
44
36
  });
45
37
 
46
- await cleanup();
38
+ await common.shutdown.kill();
39
+ clearTimeout(timeout);
47
40
 
48
- const level = code === 0 ? "info" : "fatal";
49
- common.logger[level]({
50
- service: "process",
51
- msg: `Finished shutdown sequence, terminating (exit code ${code})`,
52
- });
41
+ if (process.stdin.isTTY) {
42
+ process.stdin.setRawMode(false);
43
+ process.stdin.pause();
44
+ }
53
45
 
54
- await common.logger.kill();
55
46
  process.exit(code);
56
47
  };
57
48
 
@@ -61,36 +52,31 @@ export function setupShutdown({
61
52
  output: process.stdout,
62
53
  });
63
54
  readlineInterface.on("SIGINT", () =>
64
- shutdown({ reason: "Received SIGINT", code: 0 }),
55
+ exit({ reason: "Received SIGINT", code: 0 }),
65
56
  );
66
57
  }
67
58
 
68
- process.on("SIGINT", () => shutdown({ reason: "Received SIGINT", code: 0 }));
69
- process.on("SIGTERM", () =>
70
- shutdown({ reason: "Received SIGTERM", code: 0 }),
71
- );
72
- process.on("SIGQUIT", () =>
73
- shutdown({ reason: "Received SIGQUIT", code: 0 }),
74
- );
75
-
59
+ process.on("SIGINT", () => exit({ reason: "Received SIGINT", code: 0 }));
60
+ process.on("SIGTERM", () => exit({ reason: "Received SIGTERM", code: 0 }));
61
+ process.on("SIGQUIT", () => exit({ reason: "Received SIGQUIT", code: 0 }));
76
62
  process.on("uncaughtException", (error: Error) => {
77
- if (error instanceof IgnorableError) return;
63
+ if (error instanceof ShutdownError) return;
78
64
  common.logger.error({
79
65
  service: "process",
80
66
  msg: "Caught uncaughtException event",
81
67
  error,
82
68
  });
83
- shutdown({ reason: "Received uncaughtException", code: 1 });
69
+ exit({ reason: "Received uncaughtException", code: 1 });
84
70
  });
85
71
  process.on("unhandledRejection", (error: Error) => {
86
- if (error instanceof IgnorableError) return;
72
+ if (error instanceof ShutdownError) return;
87
73
  common.logger.error({
88
74
  service: "process",
89
75
  msg: "Caught unhandledRejection event",
90
76
  error,
91
77
  });
92
- shutdown({ reason: "Received unhandledRejection", code: 1 });
78
+ exit({ reason: "Received unhandledRejection", code: 1 });
93
79
  });
94
80
 
95
- return shutdown;
96
- }
81
+ return exit;
82
+ };
@@ -6,22 +6,24 @@ import { createRealtimeIndexingStore } from "@/indexing-store/realtime.js";
6
6
  import { createIndexingService } from "@/indexing/index.js";
7
7
  import type { Common } from "@/internal/common.js";
8
8
  import { getAppProgress } from "@/internal/metrics.js";
9
- import type { Event, IndexingBuild, SchemaBuild } from "@/internal/types.js";
9
+ import type { IndexingBuild, PreBuild, SchemaBuild } from "@/internal/types.js";
10
10
  import { createSyncStore } from "@/sync-store/index.js";
11
11
  import { decodeEvents } from "@/sync/events.js";
12
12
  import { type RealtimeEvent, createSync, splitEvents } from "@/sync/index.js";
13
13
  import {
14
+ ZERO_CHECKPOINT_STRING,
14
15
  decodeCheckpoint,
15
- encodeCheckpoint,
16
- zeroCheckpoint,
17
16
  } from "@/utils/checkpoint.js";
17
+ import { chunk } from "@/utils/chunk.js";
18
18
  import { formatEta, formatPercentage } from "@/utils/format.js";
19
+ import { createMutex } from "@/utils/mutex.js";
19
20
  import { never } from "@/utils/never.js";
20
- import { createQueue } from "@ponder/common";
21
+ import { createRequestQueue } from "@/utils/requestQueue.js";
21
22
 
22
23
  /** Starts the sync and indexing services for the specified build. */
23
24
  export async function run({
24
25
  common,
26
+ preBuild,
25
27
  schemaBuild,
26
28
  indexingBuild,
27
29
  database,
@@ -29,146 +31,178 @@ export async function run({
29
31
  onReloadableError,
30
32
  }: {
31
33
  common: Common;
34
+ preBuild: PreBuild;
32
35
  schemaBuild: SchemaBuild;
33
36
  indexingBuild: IndexingBuild;
34
37
  database: Database;
35
38
  onFatalError: (error: Error) => void;
36
39
  onReloadableError: (error: Error) => void;
37
40
  }) {
38
- let isKilled = false;
39
-
40
41
  const initialCheckpoint = await database.recoverCheckpoint();
42
+ await database.migrateSync();
43
+
44
+ const requestQueues = indexingBuild.networks.map((network) =>
45
+ createRequestQueue({ network, common }),
46
+ );
47
+
41
48
  const syncStore = createSyncStore({ common, database });
42
49
  const metadataStore = getMetadataStore({ database });
43
50
 
44
- // This can be a long-running operation, so it's best to do it after
45
- // starting the server so the app can become responsive more quickly.
46
- await database.migrateSync();
47
-
48
- runCodegen({ common });
51
+ const realtimeMutex = createMutex();
49
52
 
50
- // Note: can throw
51
53
  const sync = await createSync({
52
54
  common,
53
55
  indexingBuild,
56
+ requestQueues,
54
57
  syncStore,
55
- // Note: this is not great because it references the
56
- // `realtimeQueue` which isn't defined yet
57
58
  onRealtimeEvent: (realtimeEvent) => {
58
- return realtimeQueue.add(realtimeEvent);
59
+ if (realtimeEvent.type === "reorg") {
60
+ realtimeMutex.clear();
61
+ }
62
+
63
+ return onRealtimeEvent(realtimeEvent);
59
64
  },
60
65
  onFatalError,
61
66
  initialCheckpoint,
62
- });
63
-
64
- const handleEvents = async (events: Event[], checkpoint: string) => {
65
- if (events.length === 0) return { status: "success" } as const;
66
-
67
- indexingService.updateTotalSeconds(decodeCheckpoint(checkpoint));
68
-
69
- return await indexingService.processEvents({ events });
70
- };
71
-
72
- const realtimeQueue = createQueue({
73
- initialStart: true,
74
- browser: false,
75
- concurrency: 1,
76
- worker: async (event: RealtimeEvent) => {
77
- switch (event.type) {
78
- case "block": {
79
- // Events must be run block-by-block, so that `database.complete` can accurately
80
- // update the temporary `checkpoint` value set in the trigger.
81
- for (const { checkpoint, events } of splitEvents(event.events)) {
82
- const result = await handleEvents(
83
- decodeEvents(common, indexingBuild.sources, events),
84
- event.checkpoint,
85
- );
86
-
87
- if (result.status === "error") onReloadableError(result.error);
88
-
89
- // Set reorg table `checkpoint` column for newly inserted rows.
90
- await database.complete({ checkpoint });
91
- }
92
-
93
- await metadataStore.setStatus(event.status);
94
-
95
- break;
96
- }
97
- case "reorg":
98
- await database.removeTriggers();
99
- await database.revert({ checkpoint: event.checkpoint });
100
- await database.createTriggers();
101
-
102
- break;
103
-
104
- case "finalize":
105
- await database.finalize({ checkpoint: event.checkpoint });
106
- break;
107
-
108
- default:
109
- never(event);
110
- }
111
- },
67
+ ordering: preBuild.ordering,
112
68
  });
113
69
 
114
70
  const indexingService = createIndexingService({
115
71
  common,
116
72
  indexingBuild,
117
- sync,
73
+ requestQueues,
74
+ syncStore,
118
75
  });
119
76
 
120
77
  const historicalIndexingStore = createHistoricalIndexingStore({
121
78
  common,
122
79
  schemaBuild,
123
80
  database,
124
- initialCheckpoint,
81
+ isDatabaseEmpty: initialCheckpoint === ZERO_CHECKPOINT_STRING,
125
82
  });
126
83
 
127
84
  indexingService.setIndexingStore(historicalIndexingStore);
128
85
 
86
+ runCodegen({ common });
87
+
129
88
  await metadataStore.setStatus(sync.getStatus());
130
89
 
131
- const start = async () => {
132
- // If the initial checkpoint is zero, we need to run setup events.
133
- if (encodeCheckpoint(zeroCheckpoint) === initialCheckpoint) {
134
- const result = await indexingService.processSetupEvents({
135
- sources: indexingBuild.sources,
136
- networks: indexingBuild.networks,
137
- });
138
- if (result.status === "killed") {
139
- return;
140
- } else if (result.status === "error") {
141
- onReloadableError(result.error);
142
- return;
143
- }
144
- }
90
+ for (const network of indexingBuild.networks) {
91
+ const label = { network: network.name };
92
+ common.metrics.ponder_historical_total_indexing_seconds.set(
93
+ label,
94
+ Math.max(
95
+ sync.seconds[network.name]!.end - sync.seconds[network.name]!.start,
96
+ 0,
97
+ ),
98
+ );
99
+ common.metrics.ponder_historical_cached_indexing_seconds.set(
100
+ label,
101
+ Math.max(
102
+ sync.seconds[network.name]!.cached - sync.seconds[network.name]!.start,
103
+ 0,
104
+ ),
105
+ );
106
+ common.metrics.ponder_historical_completed_indexing_seconds.set(label, 0);
107
+ common.metrics.ponder_indexing_timestamp.set(
108
+ label,
109
+ Math.max(
110
+ sync.seconds[network.name]!.cached,
111
+ sync.seconds[network.name]!.start,
112
+ ),
113
+ );
114
+ }
145
115
 
146
- // Track the last processed checkpoint, used to set metrics
147
- let end: string | undefined;
148
- let lastFlush = Date.now();
116
+ // Reset the start timestamp so the eta estimate doesn't include
117
+ // the startup time.
118
+ common.metrics.start_timestamp = Date.now();
149
119
 
150
- // Run historical indexing until complete.
151
- for await (const { events, checkpoint } of sync.getEvents()) {
152
- end = checkpoint;
120
+ // If the initial checkpoint is zero, we need to run setup events.
121
+ if (initialCheckpoint === ZERO_CHECKPOINT_STRING) {
122
+ const result = await indexingService.processSetupEvents({
123
+ sources: indexingBuild.sources,
124
+ networks: indexingBuild.networks,
125
+ });
153
126
 
127
+ if (result.status === "error") {
128
+ onReloadableError(result.error);
129
+ return;
130
+ }
131
+ }
132
+
133
+ let lastFlush = Date.now();
134
+
135
+ // Run historical indexing until complete.
136
+ for await (const events of sync.getEvents()) {
137
+ if (events.length > 0) {
154
138
  const decodedEvents = decodeEvents(common, indexingBuild.sources, events);
155
- const result = await handleEvents(decodedEvents, checkpoint);
139
+ const eventChunks = chunk(decodedEvents, 93);
140
+ common.logger.debug({
141
+ service: "app",
142
+ msg: `Decoded ${decodedEvents.length} events`,
143
+ });
144
+ for (const eventChunk of eventChunks) {
145
+ const result = await indexingService.processEvents({
146
+ events: eventChunk,
147
+ });
148
+
149
+ if (result.status === "error") {
150
+ onReloadableError(result.error);
151
+ return;
152
+ }
153
+
154
+ const checkpoint = decodeCheckpoint(
155
+ eventChunk[eventChunk.length - 1]!.checkpoint,
156
+ );
157
+
158
+ if (preBuild.ordering === "multichain") {
159
+ const network = indexingBuild.networks.find(
160
+ (network) => network.chainId === Number(checkpoint.chainId),
161
+ )!;
162
+ common.metrics.ponder_historical_completed_indexing_seconds.set(
163
+ { network: network.name },
164
+ Math.max(
165
+ checkpoint.blockTimestamp - sync.seconds[network.name]!.start,
166
+ 0,
167
+ ),
168
+ );
169
+ common.metrics.ponder_indexing_timestamp.set(
170
+ { network: network.name },
171
+ checkpoint.blockTimestamp,
172
+ );
173
+ } else {
174
+ for (const network of indexingBuild.networks) {
175
+ common.metrics.ponder_historical_completed_indexing_seconds.set(
176
+ { network: network.name },
177
+ Math.max(
178
+ checkpoint.blockTimestamp - sync.seconds[network.name]!.start,
179
+ 0,
180
+ ),
181
+ );
182
+ common.metrics.ponder_indexing_timestamp.set(
183
+ { network: network.name },
184
+ checkpoint.blockTimestamp,
185
+ );
186
+ }
187
+ }
188
+
189
+ // Note: allows for terminal and logs to be updated
190
+ await new Promise(setImmediate);
191
+ }
156
192
 
157
193
  // underlying metrics collection is actually synchronous
158
194
  // https://github.com/siimon/prom-client/blob/master/lib/histogram.js#L102-L125
159
195
  const { eta, progress } = await getAppProgress(common.metrics);
160
- if (events.length > 0) {
161
- if (eta === undefined || progress === undefined) {
162
- common.logger.info({
163
- service: "app",
164
- msg: `Indexed ${events.length} events`,
165
- });
166
- } else {
167
- common.logger.info({
168
- service: "app",
169
- msg: `Indexed ${events.length} events with ${formatPercentage(progress)} complete and ${formatEta(eta)} remaining`,
170
- });
171
- }
196
+ if (eta === undefined || progress === undefined) {
197
+ common.logger.info({
198
+ service: "app",
199
+ msg: `Indexed ${decodedEvents.length} events`,
200
+ });
201
+ } else {
202
+ common.logger.info({
203
+ service: "app",
204
+ msg: `Indexed ${decodedEvents.length} events with ${formatPercentage(progress)} complete and ${formatEta(eta * 1_000)} remaining`,
205
+ });
172
206
  }
173
207
 
174
208
  // Persist the indexing store to the db if it is too full. The `finalized`
@@ -193,13 +227,9 @@ export async function run({
193
227
  });
194
228
  }
195
229
 
196
- await database.finalize({
197
- checkpoint: encodeCheckpoint(zeroCheckpoint),
198
- });
230
+ await database.finalize({ checkpoint: ZERO_CHECKPOINT_STRING });
199
231
  await historicalIndexingStore.flush();
200
- await database.complete({
201
- checkpoint: encodeCheckpoint(zeroCheckpoint),
202
- });
232
+ await database.complete({ checkpoint: ZERO_CHECKPOINT_STRING });
203
233
  await database.finalize({
204
234
  checkpoint: events[events.length - 1]!.checkpoint,
205
235
  });
@@ -210,86 +240,156 @@ export async function run({
210
240
  msg: "Completed flush",
211
241
  });
212
242
  }
213
-
214
- await metadataStore.setStatus(sync.getStatus());
215
- if (result.status === "killed") {
216
- return;
217
- } else if (result.status === "error") {
218
- onReloadableError(result.error);
219
- return;
220
- }
221
243
  }
222
244
 
223
- if (isKilled) return;
245
+ await metadataStore.setStatus(sync.getStatus());
246
+ }
224
247
 
225
- // Persist the indexing store to the db. The `finalized`
226
- // checkpoint is used as a mutex. Any rows in the reorg table that may
227
- // have been written because of raw sql access are deleted. Also must truncate
228
- // the reorg tables that may have been written because of raw sql access.
248
+ // Persist the indexing store to the db. The `finalized`
249
+ // checkpoint is used as a mutex. Any rows in the reorg table that may
250
+ // have been written because of raw sql access are deleted. Also must truncate
251
+ // the reorg tables that may have been written because of raw sql access.
229
252
 
230
- common.logger.debug({
231
- service: "indexing",
232
- msg: "Completed all historical events, starting final flush",
233
- });
253
+ common.logger.debug({
254
+ service: "indexing",
255
+ msg: "Completed all historical events, starting final flush",
256
+ });
234
257
 
235
- await database.finalize({ checkpoint: encodeCheckpoint(zeroCheckpoint) });
236
- await historicalIndexingStore.flush();
237
- await database.complete({ checkpoint: encodeCheckpoint(zeroCheckpoint) });
238
- await database.finalize({ checkpoint: sync.getFinalizedCheckpoint() });
239
-
240
- // Manually update metrics to fix a UI bug that occurs when the end
241
- // checkpoint is between the last processed event and the finalized
242
- // checkpoint.
243
- const start = sync.getStartCheckpoint();
244
- common.metrics.ponder_indexing_completed_seconds.set(
245
- decodeCheckpoint(end ?? start).blockTimestamp -
246
- decodeCheckpoint(start).blockTimestamp,
258
+ await database.finalize({ checkpoint: ZERO_CHECKPOINT_STRING });
259
+ await historicalIndexingStore.flush();
260
+ await database.complete({ checkpoint: ZERO_CHECKPOINT_STRING });
261
+ await database.finalize({ checkpoint: sync.getFinalizedCheckpoint() });
262
+
263
+ // Manually update metrics to fix a UI bug that occurs when the end
264
+ // checkpoint is between the last processed event and the finalized
265
+ // checkpoint.
266
+
267
+ for (const network of indexingBuild.networks) {
268
+ const label = { network: network.name };
269
+ common.metrics.ponder_historical_completed_indexing_seconds.set(
270
+ label,
271
+ Math.max(
272
+ sync.seconds[network.name]!.end - sync.seconds[network.name]!.start,
273
+ 0,
274
+ ),
247
275
  );
248
- common.metrics.ponder_indexing_total_seconds.set(
249
- decodeCheckpoint(end ?? start).blockTimestamp -
250
- decodeCheckpoint(start).blockTimestamp,
251
- );
252
- common.metrics.ponder_indexing_completed_timestamp.set(
253
- decodeCheckpoint(end ?? start).blockTimestamp,
276
+ common.metrics.ponder_indexing_timestamp.set(
277
+ { network: network.name },
278
+ sync.seconds[network.name]!.end,
254
279
  );
280
+ }
255
281
 
256
- // Become healthy
257
- common.logger.info({
258
- service: "indexing",
259
- msg: "Completed historical indexing",
260
- });
282
+ // Become healthy
283
+ common.logger.info({
284
+ service: "indexing",
285
+ msg: "Completed historical indexing",
286
+ });
261
287
 
262
- await database.createIndexes();
263
- await database.createTriggers();
288
+ const onRealtimeEvent = realtimeMutex(async (event: RealtimeEvent) => {
289
+ switch (event.type) {
290
+ case "block": {
291
+ if (event.events.length > 0) {
292
+ // Events must be run block-by-block, so that `database.complete` can accurately
293
+ // update the temporary `checkpoint` value set in the trigger.
264
294
 
265
- indexingService.setIndexingStore(
266
- createRealtimeIndexingStore({
267
- common,
268
- schemaBuild,
269
- database,
270
- }),
271
- );
295
+ const perBlockEvents = splitEvents(event.events);
272
296
 
273
- await sync.startRealtime();
297
+ common.logger.debug({
298
+ service: "app",
299
+ msg: `Partitioned events into ${perBlockEvents.length} blocks`,
300
+ });
274
301
 
275
- await metadataStore.setStatus(sync.getStatus());
302
+ for (const { checkpoint, events } of perBlockEvents) {
303
+ const network = indexingBuild.networks.find(
304
+ (network) =>
305
+ network.chainId ===
306
+ Number(decodeCheckpoint(checkpoint).chainId),
307
+ )!;
308
+
309
+ const decodedEvents = decodeEvents(
310
+ common,
311
+ indexingBuild.sources,
312
+ events,
313
+ );
276
314
 
277
- common.logger.info({
278
- service: "server",
279
- msg: "Started returning 200 responses from /ready endpoint",
280
- });
281
- };
282
-
283
- const startPromise = start();
284
-
285
- return async () => {
286
- isKilled = true;
287
- indexingService.kill();
288
- await sync.kill();
289
- realtimeQueue.pause();
290
- realtimeQueue.clear();
291
- await realtimeQueue.onIdle();
292
- await startPromise;
293
- await database.unlock();
294
- };
315
+ common.logger.debug({
316
+ service: "app",
317
+ msg: `Decoded ${decodedEvents.length} '${network.name}' events for block ${Number(decodeCheckpoint(checkpoint).blockNumber)}`,
318
+ });
319
+
320
+ const result = await indexingService.processEvents({
321
+ events: decodedEvents,
322
+ });
323
+
324
+ common.logger.info({
325
+ service: "app",
326
+ msg: `Indexed ${decodedEvents.length} '${network.name}' events for block ${Number(decodeCheckpoint(checkpoint).blockNumber)}`,
327
+ });
328
+
329
+ if (result.status === "error") onReloadableError(result.error);
330
+
331
+ // Set reorg table `checkpoint` column for newly inserted rows.
332
+ await database.complete({ checkpoint });
333
+
334
+ if (preBuild.ordering === "multichain") {
335
+ const network = indexingBuild.networks.find(
336
+ (network) =>
337
+ network.chainId ===
338
+ Number(decodeCheckpoint(checkpoint).chainId),
339
+ )!;
340
+
341
+ common.metrics.ponder_indexing_timestamp.set(
342
+ { network: network.name },
343
+ decodeCheckpoint(checkpoint).blockTimestamp,
344
+ );
345
+ } else {
346
+ for (const network of indexingBuild.networks) {
347
+ common.metrics.ponder_indexing_timestamp.set(
348
+ { network: network.name },
349
+ decodeCheckpoint(checkpoint).blockTimestamp,
350
+ );
351
+ }
352
+ }
353
+ }
354
+ }
355
+
356
+ await metadataStore.setStatus(event.status);
357
+
358
+ break;
359
+ }
360
+ case "reorg":
361
+ await database.removeTriggers();
362
+ await database.revert({ checkpoint: event.checkpoint });
363
+ await database.createTriggers();
364
+
365
+ break;
366
+
367
+ case "finalize":
368
+ await database.finalize({ checkpoint: event.checkpoint });
369
+ break;
370
+
371
+ default:
372
+ never(event);
373
+ }
374
+ });
375
+
376
+ await database.createIndexes();
377
+ await database.createTriggers();
378
+
379
+ indexingService.setIndexingStore(
380
+ createRealtimeIndexingStore({
381
+ common,
382
+ schemaBuild,
383
+ database,
384
+ }),
385
+ );
386
+
387
+ await sync.startRealtime();
388
+
389
+ await metadataStore.setStatus(sync.getStatus());
390
+
391
+ common.logger.info({
392
+ service: "server",
393
+ msg: "Started returning 200 responses from /ready endpoint",
394
+ });
295
395
  }
@@ -11,9 +11,5 @@ export async function runServer(params: {
11
11
  apiBuild: ApiBuild;
12
12
  database: Database;
13
13
  }) {
14
- const server = await createServer(params);
15
-
16
- return async () => {
17
- await server.kill();
18
- };
14
+ await createServer(params);
19
15
  }