univo 0.2.17 → 0.2.19

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/dist/cli.mjs CHANGED
@@ -4,8 +4,8 @@ import open from 'open';
4
4
  import { config } from 'dotenv';
5
5
  import { WebSocket } from 'partysocket';
6
6
  import { defineCommand, runMain } from 'citty';
7
- import { h as http } from './shared/univo.DgQqsZEe.mjs';
8
- import { c as createLogger, v as version, b as name } from './shared/univo.BMmU53c6.mjs';
7
+ import { h as http } from './shared/univo.D1x0MkrS.mjs';
8
+ import { c as createLogger, v as version, b as name } from './shared/univo.bwpFiVa7.mjs';
9
9
 
10
10
  const log = createLogger({ quiet: false });
11
11
  const dev = defineCommand({
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- export { B as Block, E as Event, a as Filter, H as Head, I as Indexer, M as Metadata, R as Result, i as indexer } from './shared/univo.DdHfpTWq.mjs';
2
1
  import '@storagesdk/core';
2
+ export { B as Block, E as Event, F as Filter, H as Head, I as Indexer, M as Metadata, R as Result, i as indexer } from './rpc.mjs';
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { B as Block, E as Event, a as Filter, H as Head, I as Indexer, M as Metadata, R as Result, i as indexer } from './shared/univo.DdHfpTWq.js';
2
1
  import '@storagesdk/core';
2
+ export { B as Block, E as Event, F as Filter, H as Head, I as Indexer, M as Metadata, R as Result, i as indexer } from './rpc.js';
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { StorageError } from '@storagesdk/core';
2
- import { g as getException, c as createException, a as catchException, l as local } from './shared/univo.DgQqsZEe.mjs';
3
- import { c as createLogger, d as decompress, e as decoder, r as retry, v as version, h as hexToNumber, a as isHexEqual, f as normalizeHex, g as compress } from './shared/univo.BMmU53c6.mjs';
2
+ import { g as getException, c as createException, a as catchException, l as local } from './shared/univo.D1x0MkrS.mjs';
3
+ import { c as createLogger, d as decompress, e as decoder, r as retry, v as version, h as hexToNumber, a as isHexEqual, n as numberToHex, f as normalizeHex, g as compress } from './shared/univo.bwpFiVa7.mjs';
4
4
  import 'ws';
5
5
  import 'partysocket';
6
6
 
@@ -136,36 +136,31 @@ function indexer(opts) {
136
136
  const log = createLogger({ quiet: opts.quiet ?? false, prefix: "[indexer]" });
137
137
  const all_events = [];
138
138
  const events_grouped_by_storage_map = /* @__PURE__ */ new Map();
139
+ const all_actions = [];
139
140
  const metadata = {
141
+ // The blocks store essentially operates as our write-ahead-log (WAL) for unfinalized blocks. Before we
142
+ // commit any events to storage we first record the block input in our metadata layer. This allows our
143
+ // finalization handler to always have access to the input data in the case we need to remove events
144
+ // because of chain reorganisations
140
145
  blocks: {
141
- async getNextUnfinalizedBlock(chain) {
142
- const prefix = `blocks/v1/${normalizeHex(chain)}`;
143
- const keys = await opts.metadataStorage.list({ prefix, limit: 1 });
144
- const [key] = keys.items;
145
- if (key === void 0) {
146
- return null;
146
+ async list(chain, number) {
147
+ let prefix = `blocks/v1/${normalizeHex(chain)}`;
148
+ if (typeof number === "string") {
149
+ prefix += `/${normalizeHex(number, 16)}`;
147
150
  }
148
- const [_, __, ___, number, hash] = key.path.split("/");
149
- return { chain, number, hash };
150
- },
151
- async getBlocksByNumber(chain, number) {
152
- const prefix = `blocks/v1/${normalizeHex(chain)}/${normalizeHex(number, 16)}`;
153
151
  const keys = await opts.metadataStorage.list({ prefix });
154
- if (keys.items.length === 0) {
155
- return [];
156
- }
157
- const blocks = await Promise.all(
158
- keys.items.map(async (item) => {
159
- const blob = await opts.metadataStorage.download(item.path, { as: "blob" });
160
- const block = await decompress(blob);
161
- const parsed = JSON.parse(block);
162
- return parsed;
163
- })
164
- );
165
- return blocks;
152
+ const mapped = keys.items.map((key) => {
153
+ const [_, __, ___, number2, hash, parent_hash] = key.path.split("/");
154
+ return { chain, number: number2, hash, parent_hash };
155
+ });
156
+ return mapped;
166
157
  },
167
- async getBlockByNumberAndHash(chain, number, hash) {
168
- const prefix = `blocks/v1/${normalizeHex(chain)}/${normalizeHex(number, 16)}/${normalizeHex(hash)}`;
158
+ async get(head) {
159
+ const chain = normalizeHex(head.chain);
160
+ const number = normalizeHex(head.number, 16);
161
+ const hash = normalizeHex(head.hash);
162
+ const parentHash = normalizeHex(head.parent_hash);
163
+ const prefix = `blocks/v1/${chain}/${number}/${hash}/${parentHash}`;
169
164
  const blob = await opts.metadataStorage.download(prefix, { as: "blob" }).catch((error) => {
170
165
  if (error instanceof StorageError) {
171
166
  if (error.code === "NotFound") {
@@ -188,9 +183,10 @@ function indexer(opts) {
188
183
  await Promise.all(
189
184
  blocks.map(async (block) => {
190
185
  const chain = normalizeHex(block.eth_chainId);
191
- const hash = normalizeHex(block.eth_getBlockByNumber.hash);
192
186
  const number = normalizeHex(block.eth_getBlockByNumber.number, 16);
193
- const prefix = `blocks/v1/${chain}/${number}/${hash}`;
187
+ const hash = normalizeHex(block.eth_getBlockByNumber.hash);
188
+ const parentHash = normalizeHex(block.eth_getBlockByNumber.parentHash);
189
+ const prefix = `blocks/v1/${chain}/${number}/${hash}/${parentHash}`;
194
190
  const compressed = await compress(JSON.stringify(block));
195
191
  await opts.metadataStorage.upload(prefix, compressed);
196
192
  })
@@ -202,14 +198,75 @@ function indexer(opts) {
202
198
  }
203
199
  await Promise.all(
204
200
  blocks.map(async (block) => {
205
- const chain = normalizeHex(block.eth_chainId);
206
- const hash = normalizeHex(block.eth_getBlockByNumber.hash);
207
- const number = normalizeHex(block.eth_getBlockByNumber.number, 16);
208
- const prefix = `blocks/v1/${chain}/${number}/${hash}`;
201
+ const chain = normalizeHex(block.chain);
202
+ const number = normalizeHex(block.number, 16);
203
+ const hash = normalizeHex(block.hash);
204
+ const parentHash = normalizeHex(block.parent_hash);
205
+ const prefix = `blocks/v1/${chain}/${number}/${hash}/${parentHash}`;
206
+ await opts.metadataStorage.delete(prefix);
207
+ })
208
+ );
209
+ }
210
+ },
211
+ // When unfinalized events are upserted we commit a given block number and block hash to indicate
212
+ // that those events were successfully recorded. This enables an optimisation in our finalized
213
+ // handler that allows it to skip processing blocks that were successfully processed
214
+ commits: {
215
+ async list(chain, number) {
216
+ let prefix = `commits/v1/${normalizeHex(chain)}`;
217
+ if (typeof number === "string") {
218
+ prefix += `/${normalizeHex(number, 16)}`;
219
+ }
220
+ const keys = await opts.metadataStorage.list({ prefix });
221
+ const mapped = keys.items.map((key) => {
222
+ const [_, __, ___, number2, hash, parent_hash] = key.path.split("/");
223
+ return { chain, number: number2, hash, parent_hash };
224
+ });
225
+ return mapped;
226
+ },
227
+ async upsert(head) {
228
+ const chain = normalizeHex(head.chain);
229
+ const number = normalizeHex(head.number, 16);
230
+ const hash = normalizeHex(head.hash);
231
+ const parentHash = normalizeHex(head.parent_hash);
232
+ const prefix = `commits/v1/${chain}/${number}/${hash}/${parentHash}`;
233
+ const body = JSON.stringify({ hello: "world" });
234
+ await opts.metadataStorage.upload(prefix, body);
235
+ },
236
+ async delete(commits) {
237
+ await Promise.all(
238
+ commits.map(async (commit) => {
239
+ const chain = normalizeHex(commit.chain);
240
+ const number = normalizeHex(commit.number, 16);
241
+ const hash = normalizeHex(commit.hash);
242
+ const parentHash = normalizeHex(commit.parent_hash);
243
+ const prefix = `commits/v1/${chain}/${number}/${hash}/${parentHash}`;
209
244
  await opts.metadataStorage.delete(prefix);
210
245
  })
211
246
  );
212
247
  }
248
+ },
249
+ // Our indexer always lags behind the canonical chain. The heights table is used as a durable record
250
+ // to track its own finalized height. As the chain finalizes, we process newly finalized blocks and
251
+ // then update the heights here to reflect to that outside world what height the indexer has currently
252
+ // finalized up to. This information is stored in a separate table so that we can `list` keys and
253
+ // immediately know which blocks were finalized correctly without ever having to load the entire block
254
+ // values themselves reducing memory usage and increasing finalizing throughput
255
+ heights: {
256
+ async list(chain) {
257
+ const prefix = `heights/v1/${normalizeHex(chain)}`;
258
+ const keys = await opts.metadataStorage.list({ prefix });
259
+ const mapped = keys.items.map((key) => {
260
+ const [_, __, ___, number] = key.path.split("/");
261
+ return { chain, number };
262
+ });
263
+ return mapped;
264
+ },
265
+ async upsert(chain, number) {
266
+ const prefix = `heights/v1/${normalizeHex(chain)}/${normalizeHex(number, 16)}`;
267
+ const body = JSON.stringify({ hello: "world" });
268
+ await opts.metadataStorage.upload(prefix, body);
269
+ }
213
270
  }
214
271
  };
215
272
  async function getBlock(head) {
@@ -237,21 +294,18 @@ function indexer(opts) {
237
294
  }
238
295
  const GetBlockError = createException("Failed to load block from the provided `getBlock` function");
239
296
  const public_getFinalizedHeight = async (chain) => {
240
- const [stored, finalizedBlock] = await Promise.all([
241
- metadata.blocks.getNextUnfinalizedBlock(chain),
242
- //
243
- getBlock({ chain, number: "finalized" })
244
- ]);
245
- if (finalizedBlock === null) {
246
- log.debug("Failed to fetch finalized block and unable to determine finalized height, aborting...");
247
- throw new Error(GetBlockError);
248
- }
249
- const finalizedHeight = hexToNumber(finalizedBlock.eth_getBlockByNumber.number);
250
- if (stored === null) {
251
- return finalizedHeight;
297
+ const heights = await metadata.heights.list(chain);
298
+ if (heights.length === 0) {
299
+ const block = await getBlock({ chain, number: "finalized" });
300
+ if (block === null) {
301
+ log.debug("Failed to fetch finalized block and unable to determine finalized height, aborting...");
302
+ throw new Error(GetBlockError);
303
+ }
304
+ await metadata.heights.upsert(chain, block.eth_getBlockByNumber.number);
305
+ return hexToNumber(block.eth_getBlockByNumber.number);
252
306
  }
253
- const indexerHeight = hexToNumber(stored.number) - 1;
254
- return Math.min(indexerHeight, finalizedHeight);
307
+ const height = Math.max(...heights.map((height2) => hexToNumber(height2.number)));
308
+ return height;
255
309
  };
256
310
  const public_writeUnfinalizedHead = async (head) => {
257
311
  log.debug("Received unfinalized head...");
@@ -276,7 +330,7 @@ function indexer(opts) {
276
330
  await metadata.blocks.upsert([block]);
277
331
  log.debug(`Persisted block to metadata in ${Date.now() - metadata_start}ms`);
278
332
  const events_start = Date.now();
279
- const promises = events_grouped_by_storage_map.entries().map(async ([storage, grouped_events]) => {
333
+ const events = events_grouped_by_storage_map.entries().map(async ([storage, grouped_events]) => {
280
334
  const batch = [];
281
335
  for (const event2 of grouped_events) {
282
336
  try {
@@ -284,8 +338,8 @@ function indexer(opts) {
284
338
  log.debug(`Block matches no filters for event ${event2.id}`);
285
339
  continue;
286
340
  }
287
- const events = event2.handler(block);
288
- for (const event3 of events) {
341
+ const events2 = event2.handler(block);
342
+ for (const event3 of events2) {
289
343
  batch.push(event3);
290
344
  }
291
345
  } catch (error) {
@@ -306,94 +360,28 @@ function indexer(opts) {
306
360
  }
307
361
  }
308
362
  });
309
- await Promise.all(promises);
310
- log.debug(`Wrote head in ${Date.now() - events_start}ms`);
311
- };
312
- const public_writeUnfinalizedHeads = async (heads) => {
313
- if (heads.length === 0) {
314
- return log.debug("No heads received, returning...");
315
- }
316
- let chain = void 0;
317
- for (const head of heads) {
318
- if (chain === void 0) {
319
- chain = head.chain;
320
- }
321
- if (!isHexEqual(chain, head.chain)) {
322
- throw new Error("Received heads from separate chains");
323
- }
324
- }
325
- if (chain === void 0) {
326
- throw new Error("Internal error. Expected chain to be defined after checking heads length");
327
- }
328
- log.debug(`Received ${heads.length} unfinalized heads...`);
329
- const blocks_start = Date.now();
330
- const [finalizedBlock, ...blocks_nullable] = await Promise.all([
331
- getBlock({ chain, number: "finalized" }),
332
- ...heads.map(async (head) => {
333
- return await getBlock({ chain, number: head.number, hash: head.hash });
334
- })
335
- ]);
336
- if (finalizedBlock === null) {
337
- log.debug("Failed to determine finalized height when processing unfinalized heads");
338
- throw new Error(GetBlockError);
339
- }
340
- const finalizedHeight = hexToNumber(finalizedBlock.eth_getBlockByNumber.number);
341
- const blocks = blocks_nullable.flatMap((block) => {
342
- if (block === null) {
343
- return [];
344
- }
345
- if (hexToNumber(block.eth_getBlockByNumber.number) <= finalizedHeight) {
363
+ await Promise.all(events);
364
+ log.debug(`Wrote events in ${Date.now() - events_start}ms`);
365
+ await metadata.commits.upsert(head);
366
+ const actions = all_actions.flatMap((action2) => {
367
+ if (typeof action2.handler.latest === "undefined") {
346
368
  return [];
347
369
  }
348
- return block;
349
- });
350
- if (blocks.length === 0) {
351
- return log.debug("All blocks failed to load, aborting early...");
352
- }
353
- log.debug(`Loaded ${blocks.length} block(s) in ${Date.now() - blocks_start}ms`);
354
- await metadata.blocks.upsert(blocks);
355
- log.debug(`Recorded ${blocks.length} block(s) to metadata storage`);
356
- const events_start = Date.now();
357
- const promises = events_grouped_by_storage_map.entries().map(async ([storage, grouped_events]) => {
358
- const batch = [];
359
- for (const block of blocks) {
360
- for (const event2 of grouped_events) {
361
- try {
362
- if (!event2.filters.some((filter) => matchFilter(block, filter))) {
363
- log.debug(`Block matches no filters for event ${event2.id}`);
364
- continue;
365
- }
366
- const events = event2.handler(block);
367
- for (const event3 of events) {
368
- batch.push(event3);
369
- }
370
- } catch (error) {
371
- log.error(`Failed to run your 'handler' for event ${event2.id}`);
372
- throw error;
373
- }
374
- }
375
- }
376
- if (batch.length > 0) {
377
- const start = Date.now();
378
- await retry(() => storage.upsert(batch), 2).catch((error) => {
379
- for (const event2 of grouped_events) {
380
- log.error(`Failed to run your 'upsert' handler for event ${event2.id}`);
381
- }
370
+ return action2.event.handler(block).map(async (event2) => {
371
+ await retry(() => action2.handler.latest(event2), 2).catch((error) => {
372
+ log.error(`Failed to execute 'latest' action ${action2.id}`);
382
373
  throw error;
383
374
  });
384
- for (const event2 of grouped_events) {
385
- log.debug(`Recorded ${batch.length} ${event2.id} in ${Date.now() - start}ms`);
386
- }
387
- }
375
+ });
388
376
  });
389
- await Promise.all(promises);
390
- log.debug(`Wrote ${heads.length} heads in ${Date.now() - events_start}ms`);
377
+ await Promise.allSettled(actions);
391
378
  };
392
379
  const public_deleteReorganisedHead = async (head) => {
393
380
  log.debug(`Received reorganised head ${hexToNumber(head.number)}`);
394
- const [canonical_block, stored_block] = await Promise.all([
395
- getBlock({ chain: head.chain, number: head.number }),
396
- metadata.blocks.getBlockByNumberAndHash(head.chain, head.number, head.hash)
381
+ const [stored_block, canonical_block] = await Promise.all([
382
+ metadata.blocks.get(head),
383
+ //
384
+ getBlock({ chain: head.chain, number: head.number })
397
385
  ]);
398
386
  if (stored_block === null) {
399
387
  return log.debug("Reorganised block never/already processed");
@@ -404,10 +392,7 @@ function indexer(opts) {
404
392
  if (isHexEqual(head.hash, canonical_block.eth_getBlockByNumber.hash)) {
405
393
  throw new Error("Attempted to delete canonical block");
406
394
  }
407
- const promises = all_events.map(async (event2) => {
408
- if (event2.storage.delete === void 0) {
409
- return;
410
- }
395
+ const deletes = all_events.map(async (event2) => {
411
396
  const batch = [];
412
397
  try {
413
398
  const events = event2.handler(stored_block);
@@ -426,11 +411,37 @@ function indexer(opts) {
426
411
  throw error;
427
412
  });
428
413
  });
429
- await Promise.all(promises);
414
+ await Promise.all(deletes);
415
+ const upserts = events_grouped_by_storage_map.entries().map(async ([storage, grouped_events]) => {
416
+ const batch = [];
417
+ for (const event2 of grouped_events) {
418
+ try {
419
+ if (!event2.filters.some((filter) => matchFilter(canonical_block, filter))) {
420
+ continue;
421
+ }
422
+ const events = event2.handler(canonical_block);
423
+ for (const event3 of events) {
424
+ batch.push(event3);
425
+ }
426
+ } catch (error) {
427
+ log.error(`Failed to run your 'handler' for event ${event2.id}`);
428
+ throw error;
429
+ }
430
+ }
431
+ if (batch.length > 0) {
432
+ await retry(() => storage.upsert(batch), 2).catch((error) => {
433
+ for (const event2 of grouped_events) {
434
+ log.error(`Failed to run your 'upsert' handler for event ${event2.id}`);
435
+ }
436
+ throw error;
437
+ });
438
+ }
439
+ });
440
+ await Promise.all(upserts);
430
441
  };
431
442
  const public_writeFinalizedHeads = async (heads) => {
432
443
  if (heads.length === 0) {
433
- return log.debug("No heads received, returning...");
444
+ throw new Error("No heads received, aborting...");
434
445
  }
435
446
  log.debug(`Received ${heads.length} finalized heads...`);
436
447
  let chain = void 0;
@@ -459,8 +470,8 @@ function indexer(opts) {
459
470
  }
460
471
  previousHead = head;
461
472
  }
462
- const [unfinalizedMetadataBlock, finalizedBlock] = await Promise.all([
463
- metadata.blocks.getNextUnfinalizedBlock(chain),
473
+ const [heights, finalizedBlock] = await Promise.all([
474
+ metadata.heights.list(chain),
464
475
  //
465
476
  getBlock({ chain, number: "finalized" })
466
477
  ]);
@@ -468,56 +479,82 @@ function indexer(opts) {
468
479
  log.debug("Failed to load chain finalized block. Request should be retried");
469
480
  throw new Error(GetBlockError);
470
481
  }
471
- const finalizedHeight = hexToNumber(finalizedBlock.eth_getBlockByNumber.number);
472
- for (const head of heads) {
473
- if (hexToNumber(head.number) > finalizedHeight) {
474
- throw new Error("Received head greater than chain finalized height");
475
- }
482
+ if (heights.length === 0) {
483
+ await metadata.heights.upsert(chain, finalizedBlock.eth_getBlockByNumber.number);
484
+ heights.push({ chain, number: finalizedBlock.eth_getBlockByNumber.number });
476
485
  }
477
- if (unfinalizedMetadataBlock === null) {
478
- return log.debug("No heads to finalize, returning...");
486
+ const indexerHeight = Math.max(...heights.map((height) => hexToNumber(height.number)));
487
+ const finalizedHeight = hexToNumber(finalizedBlock.eth_getBlockByNumber.number);
488
+ const lastHead = heads[heads.length - 1];
489
+ if (lastHead === void 0 || hexToNumber(lastHead.number) !== finalizedHeight) {
490
+ throw new Error("Client has stalled. Chain has finalized greater than the heads received");
479
491
  }
480
- const indexerHeight = hexToNumber(unfinalizedMetadataBlock.number) - 1;
481
492
  log.debug(`Indexer height ${indexerHeight}, finalized height ${finalizedHeight}`);
482
493
  if (indexerHeight === finalizedHeight) {
483
- return log.debug("No heads to finalize, returning...");
494
+ log.debug("No heads to finalize, returning...");
495
+ return await cleanupFinalizedHeads(chain, numberToHex(finalizedHeight));
484
496
  }
485
497
  const newHeads = heads.filter((head) => {
486
498
  return hexToNumber(head.number) > indexerHeight;
487
499
  });
488
500
  const [firstNewHead] = newHeads;
489
501
  if (firstNewHead === void 0) {
490
- return log.debug("No new heads received, returning...");
502
+ throw new Error("Internal error. Expected at least one head greater than the indexer height.");
491
503
  }
492
504
  if (hexToNumber(firstNewHead.number) !== indexerHeight + 1) {
493
- throw new Error(
494
- `Client advanced too far (${hexToNumber(firstNewHead.number)}). Heads received are greater than the first new head ${indexerHeight + 1}`
495
- );
505
+ throw new Error("Client advanced too far. Heads received are greater than the first new head");
496
506
  }
497
507
  for (const head of newHeads) {
498
- const processed = await metadata.blocks.getBlocksByNumber(head.chain, head.number);
499
- if (processed.length === 0) {
500
- await writeFinalizedHead(head, null);
501
- continue;
508
+ await Promise.all([
509
+ processEventsForCanonicalHead(head),
510
+ processActionsForCanonicalHead(head)
511
+ //
512
+ ]);
513
+ await metadata.heights.upsert(head.chain, head.number);
514
+ }
515
+ await cleanupFinalizedHeads(chain, numberToHex(finalizedHeight));
516
+ };
517
+ async function processActionsForCanonicalHead(head) {
518
+ if (all_actions.every((action2) => typeof action2.handler.finalized === "undefined")) {
519
+ return;
520
+ }
521
+ const block = await getBlock({ chain: head.chain, number: head.number, hash: head.hash });
522
+ const promises = all_actions.flatMap((action2) => {
523
+ if (typeof action2.handler.finalized === "undefined") {
524
+ return [];
502
525
  }
503
- const reorganised = processed.filter((block2) => {
504
- return !isHexEqual(head.hash, block2.eth_getBlockByNumber.hash);
526
+ return action2.event.handler(block).map(async (event2) => {
527
+ await retry(() => action2.handler.finalized(event2), 2).catch((error) => {
528
+ log.error(`Failed to execute 'finalized' action ${action2.id}`);
529
+ throw error;
530
+ });
505
531
  });
506
- if (reorganised.length > 0) {
507
- await deleteReorganisedBlocks(reorganised);
532
+ });
533
+ await Promise.all(promises);
534
+ }
535
+ async function processEventsForCanonicalHead(head) {
536
+ const [processed, commits] = await Promise.all([
537
+ metadata.blocks.list(head.chain, head.number),
538
+ //
539
+ metadata.commits.list(head.chain, head.number)
540
+ ]);
541
+ if (processed.length === 1) {
542
+ if (commits.some((commit) => isHexEqual(head.hash, commit.hash) && isHexEqual(head.parent_hash, commit.parent_hash))) {
543
+ return;
508
544
  }
509
- const block = processed.find((block2) => {
510
- return isHexEqual(head.hash, block2.eth_getBlockByNumber.hash);
511
- });
512
- await writeFinalizedHead(head, block ?? null);
513
- await metadata.blocks.delete(processed);
514
- }
515
- };
516
- async function writeFinalizedHead(head, block_nullable) {
517
- let block = block_nullable;
518
- if (block === null) {
519
- block = await getBlock({ chain: head.chain, number: head.number, hash: head.hash });
520
545
  }
546
+ const reorganised = processed.filter((block) => {
547
+ return !isHexEqual(head.hash, block.hash);
548
+ });
549
+ await Promise.all(
550
+ reorganised.map(async (block) => {
551
+ await deleteReorganisedHead(block);
552
+ })
553
+ );
554
+ await writeFinalizedHead(head);
555
+ }
556
+ async function writeFinalizedHead(head) {
557
+ const block = await getBlock({ chain: head.chain, number: head.number, hash: head.hash });
521
558
  if (block === null) {
522
559
  throw new Error("Received unknown head that wasn't canonical");
523
560
  }
@@ -553,21 +590,17 @@ function indexer(opts) {
553
590
  });
554
591
  await Promise.all(promises);
555
592
  }
556
- async function deleteReorganisedBlocks(blocks) {
557
- if (blocks.length === 0) {
558
- return;
593
+ async function deleteReorganisedHead(head) {
594
+ const block = await metadata.blocks.get(head);
595
+ if (block === null) {
596
+ throw new Error("Expected reorganised block to exist in metadata");
559
597
  }
560
598
  const promises = all_events.map(async (event2) => {
561
- if (event2.storage.delete === void 0) {
562
- return;
563
- }
564
599
  const batch = [];
565
600
  try {
566
- for (const block of blocks) {
567
- const events = event2.handler(block);
568
- for (const event3 of events) {
569
- batch.push(event3);
570
- }
601
+ const events = event2.handler(block);
602
+ for (const event3 of events) {
603
+ batch.push(event3);
571
604
  }
572
605
  } catch (error) {
573
606
  log.error(`Failed to run your 'handler' for event ${event2.id}`);
@@ -583,6 +616,24 @@ function indexer(opts) {
583
616
  });
584
617
  await Promise.all(promises);
585
618
  }
619
+ async function cleanupFinalizedHeads(chain, finalized) {
620
+ const [_blocks, _commits] = await Promise.all([
621
+ metadata.blocks.list(chain),
622
+ //
623
+ metadata.commits.list(chain)
624
+ ]);
625
+ const blocks = _blocks.filter((block) => {
626
+ return hexToNumber(block.number) <= hexToNumber(finalized);
627
+ });
628
+ const commits = _commits.filter((commit) => {
629
+ return hexToNumber(commit.number) < hexToNumber(finalized);
630
+ });
631
+ await Promise.all([
632
+ metadata.blocks.delete(blocks),
633
+ //
634
+ metadata.commits.delete(commits)
635
+ ]);
636
+ }
586
637
  const private_getMetadata = async () => {
587
638
  return {
588
639
  version,
@@ -855,12 +906,15 @@ function indexer(opts) {
855
906
  events_grouped_by_storage_map.set(event2.storage, group);
856
907
  return event2;
857
908
  };
909
+ const action = (action2) => {
910
+ all_actions.push(action2);
911
+ return action2;
912
+ };
858
913
  const rpc = {
859
914
  request: {
860
915
  public_getFinalizedHeight,
861
916
  public_writeFinalizedHeads,
862
917
  public_writeUnfinalizedHead,
863
- public_writeUnfinalizedHeads,
864
918
  public_deleteReorganisedHead,
865
919
  private_getEvents,
866
920
  private_getMetadata,
@@ -877,6 +931,7 @@ function indexer(opts) {
877
931
  const indexer2 = {
878
932
  ...rpc,
879
933
  event,
934
+ action,
880
935
  fetch: server.http
881
936
  };
882
937
  return indexer2;
@@ -1,6 +1,5 @@
1
1
  import { NodeRpc, IndexerRpc } from './rpc.mjs';
2
2
  import { Transport } from './transport.mjs';
3
- import './shared/univo.DdHfpTWq.mjs';
4
3
  import '@storagesdk/core';
5
4
 
6
5
  type RealtimeOptions = {
@@ -1,6 +1,5 @@
1
1
  import { NodeRpc, IndexerRpc } from './rpc.js';
2
2
  import { Transport } from './transport.js';
3
- import './shared/univo.DdHfpTWq.js';
4
3
  import '@storagesdk/core';
5
4
 
6
5
  type RealtimeOptions = {
package/dist/realtime.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { c as createLogger, i as iife, h as hexToNumber, n as numberToHex, m as mutex, r as retry, a as isHexEqual } from './shared/univo.BMmU53c6.mjs';
1
+ import { c as createLogger, i as iife, h as hexToNumber, n as numberToHex, m as mutex, r as retry, a as isHexEqual } from './shared/univo.bwpFiVa7.mjs';
2
2
 
3
3
  function defineBlockchain(opts) {
4
4
  const log = createLogger({ quiet: opts.quiet });
@@ -165,7 +165,7 @@ function realtime(opts) {
165
165
  });
166
166
  log.debug("Subscribed indexer chain to new heads");
167
167
  let indexerHeight = initialIndexerHeight;
168
- const poll = async () => {
168
+ async function poll() {
169
169
  try {
170
170
  log.debug("Polling for finalized height...");
171
171
  const finalizedBlock = await opts.node.request({
@@ -189,7 +189,11 @@ function realtime(opts) {
189
189
  return log.debug("No new finalized heads to deliver, ignoring...");
190
190
  }
191
191
  log.debug(`Delivering ${newHeads.length} finalized head(s)...`);
192
- await opts.indexer.request({ method: "public_writeFinalizedHeads", params: [newHeads] });
192
+ await opts.indexer.request({
193
+ params: [newHeads],
194
+ method: "public_writeFinalizedHeads",
195
+ signal: AbortSignal.timeout(Number.POSITIVE_INFINITY)
196
+ });
193
197
  log.debug(`Delivered ${newHeads.length} finalized head(s)`);
194
198
  indexerHeight = hexToNumber(finalizedBlock.number);
195
199
  log.debug(`Updated indexer height to ${indexerHeight}`);
@@ -207,11 +211,9 @@ function realtime(opts) {
207
211
  if (error instanceof Error) {
208
212
  log.error(`Failed to reconcile finalized head: ${error.message}`);
209
213
  }
210
- } finally {
211
- setTimeout(poll, POLLING_INTERVAL_MS);
212
214
  }
213
- };
214
- await poll();
215
+ }
216
+ setInterval(mutex(poll), POLLING_INTERVAL_MS);
215
217
  log.debug("Started polling finalized height");
216
218
  });
217
219
  promise.catch((error) => {