@xyo-network/event-kit-statement-graph 0.1.5 → 0.1.8

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.
@@ -217,9 +217,9 @@ var EventKitClaimedObjectSchemas = Object.freeze([
217
217
  var EventKitClaimedObjectSchemaSet = new Set(EventKitClaimedObjectSchemas);
218
218
 
219
219
  // src/finalizedReplayController.ts
220
- import { HashZod as HashZod3 } from "@ariestools/sdk";
220
+ import { HashZod as HashZod4 } from "@ariestools/sdk";
221
221
  import {
222
- ChainIdZod as ChainIdZod2,
222
+ ChainIdZod as ChainIdZod3,
223
223
  normalizeAddress as normalizeAddress3,
224
224
  NormalizedAddressZod as NormalizedAddressZod3,
225
225
  WakeSubscriptionAcceptanceSchema as WakeSubscriptionAcceptanceSchema2,
@@ -231,13 +231,70 @@ import {
231
231
  scanFinalizedStatements
232
232
  } from "@xyo-network/xl1-statement-graph-sdk";
233
233
 
234
- // src/replayCheckpoint.ts
234
+ // src/finalizedReplayChain.ts
235
235
  import { HashZod as HashZod2 } from "@ariestools/sdk";
236
+ import { ChainIdZod } from "@xyo-network/event-kit-schemas";
237
+ import { PayloadBuilder } from "@xyo-network/sdk";
238
+ import { isBlockBoundWitness } from "@xyo-network/xl1-protocol";
239
+ async function requireBlockIdentity(chain, raw, chainId, requestedBlock) {
240
+ if (!Array.isArray(raw) || raw.length !== 2 || !Array.isArray(raw[1])) {
241
+ throw new TypeError(requestedBlock === void 0 ? "finalized block is unavailable" : `finalized block ${requestedBlock} is unavailable`);
242
+ }
243
+ const header = raw[0];
244
+ if (!isBlockBoundWitness(header) || !Number.isSafeInteger(header.block) || header.block < 0) {
245
+ throw new TypeError("finalized block has an invalid header");
246
+ }
247
+ const block = header.block;
248
+ const expectedChainId = chain.chainId === void 0 ? chainId : ChainIdZod.parse(String(await chain.chainId(block)).toLowerCase());
249
+ if (requestedBlock !== void 0 && block !== requestedBlock || header.chain !== expectedChainId) {
250
+ throw new TypeError(`finalized block ${block} does not match the configured chain`);
251
+ }
252
+ return header;
253
+ }
254
+ async function exactFinalizedBlockHash(chain, block, chainId) {
255
+ const raw = await chain.block.blockByNumber(block);
256
+ const header = await requireBlockIdentity(chain, raw, chainId, block);
257
+ const hash = await PayloadBuilder.hash(PayloadBuilder.omitStorageMeta(header));
258
+ const storageHash = header._hash;
259
+ if (typeof storageHash === "string" && storageHash !== hash) {
260
+ throw new TypeError(`finalized block ${block} has invalid storage hash metadata`);
261
+ }
262
+ return HashZod2.parse(hash);
263
+ }
264
+ function lineageValidatingScanChain(chain, chainId) {
265
+ return {
266
+ block: {
267
+ async blockByNumber(block) {
268
+ const raw = await chain.block.blockByNumber(block);
269
+ await requireBlockIdentity(chain, raw, chainId, block);
270
+ return raw;
271
+ },
272
+ ...chain.block.blocksByNumber === void 0 ? {} : {
273
+ async blocksByNumber(high, count) {
274
+ const values = await chain.block.blocksByNumber?.(high, count) ?? [];
275
+ for (const value of values) await requireBlockIdentity(chain, value, chainId);
276
+ return values;
277
+ }
278
+ }
279
+ },
280
+ finalization: chain.finalization
281
+ };
282
+ }
283
+ async function requireCurrentReplayChain(chain, finalizedHead, chainId) {
284
+ if (chain.chainId === void 0) return;
285
+ const headChainId = ChainIdZod.parse(String(await chain.chainId(finalizedHead)).toLowerCase());
286
+ if (headChainId !== chainId) {
287
+ throw new TypeError(`finalized head ${finalizedHead} does not match the configured current chain`);
288
+ }
289
+ }
290
+
291
+ // src/replayCheckpoint.ts
292
+ import { HashZod as HashZod3 } from "@ariestools/sdk";
236
293
  import {
237
- ChainIdZod,
294
+ ChainIdZod as ChainIdZod2,
238
295
  NormalizedAddressZod as NormalizedAddressZod2
239
296
  } from "@xyo-network/event-kit-schemas";
240
- import { PayloadBuilder } from "@xyo-network/sdk";
297
+ import { PayloadBuilder as PayloadBuilder2 } from "@xyo-network/sdk";
241
298
  import {
242
299
  AppliedEventRecordZod,
243
300
  BlockNumberZod,
@@ -249,10 +306,10 @@ import {
249
306
  StatementScanCheckpointZod
250
307
  } from "@xyo-network/xl1-statement-graph-sdk";
251
308
  import { z } from "zod";
252
- var EVENT_KIT_REPLAY_CHECKPOINT_VERSION = 2;
309
+ var EVENT_KIT_REPLAY_CHECKPOINT_VERSION = 3;
253
310
  var ResolvedReplayBodyZod = z.strictObject({
254
311
  body: z.record(z.string(), z.unknown()),
255
- hash: HashZod2
312
+ hash: HashZod3
256
313
  });
257
314
  var ReplayCorruptBodyZod = z.strictObject({
258
315
  claimedHash: z.string().optional(),
@@ -261,18 +318,19 @@ var ReplayCorruptBodyZod = z.strictObject({
261
318
  var ReplayBlockedStateZod = z.strictObject({
262
319
  atBlock: BlockNumberZod,
263
320
  corrupt: z.array(ReplayCorruptBodyZod),
264
- objectHashes: z.array(HashZod2),
321
+ objectHashes: z.array(HashZod3),
265
322
  unresolvedEvents: z.array(UnresolvedEventReferenceZod)
266
323
  });
267
324
  var EventKitReplayCheckpointZod = z.strictObject({
268
325
  applied: z.array(AppliedEventRecordZod),
269
326
  blocked: ReplayBlockedStateZod.optional(),
270
327
  bodies: z.array(ResolvedReplayBodyZod),
271
- chainId: ChainIdZod,
328
+ chainId: ChainIdZod2,
272
329
  finalizedHead: BlockNumberZod,
273
330
  floorBlock: BlockNumberZod,
274
331
  foldVersion: z.literal(STATEMENT_FOLD_VERSION),
275
332
  indexedThrough: z.union([BlockNumberZod, z.null()]),
333
+ indexedThroughHash: z.union([HashZod3, z.null()]),
276
334
  network: z.string().min(1),
277
335
  objectSchemas: z.tuple(EventKitClaimedObjectSchemas.map((schema) => z.literal(schema))),
278
336
  publisher: NormalizedAddressZod2,
@@ -280,6 +338,22 @@ var EventKitReplayCheckpointZod = z.strictObject({
280
338
  sourceId: z.string().min(1),
281
339
  version: z.literal(EVENT_KIT_REPLAY_CHECKPOINT_VERSION)
282
340
  });
341
+ var LegacyEventKitReplayCheckpointIdentityZod = z.looseObject({
342
+ chainId: ChainIdZod2,
343
+ floorBlock: BlockNumberZod,
344
+ network: z.string().min(1),
345
+ publisher: NormalizedAddressZod2,
346
+ sourceId: z.string().min(1),
347
+ version: z.literal(2)
348
+ });
349
+ function checkpointIdentityError(checkpoint, identity) {
350
+ if (checkpoint.chainId !== identity.chainId) return `chainId ${checkpoint.chainId} != ${identity.chainId}`;
351
+ if (checkpoint.floorBlock !== identity.floorBlock) return `floorBlock ${checkpoint.floorBlock} != ${identity.floorBlock}`;
352
+ if (checkpoint.network !== identity.network) return `network ${checkpoint.network} != ${identity.network}`;
353
+ if (checkpoint.sourceId !== identity.sourceId) return `sourceId ${checkpoint.sourceId} != ${identity.sourceId}`;
354
+ if (checkpoint.publisher !== identity.publisher) return `publisher ${checkpoint.publisher} != ${identity.publisher}`;
355
+ return void 0;
356
+ }
283
357
  function appliedKey(record) {
284
358
  return `${record.txHash}:${record.eventHash}:${record.hashIndex}`;
285
359
  }
@@ -328,7 +402,7 @@ function sameClaimRecords(left, right) {
328
402
  });
329
403
  }
330
404
  function progressIntegrityError(checkpoint) {
331
- const { indexedThrough } = checkpoint;
405
+ const { indexedThrough, indexedThroughHash } = checkpoint;
332
406
  if (indexedThrough !== null && indexedThrough > checkpoint.finalizedHead) {
333
407
  return `indexedThrough ${indexedThrough} exceeds finalizedHead ${checkpoint.finalizedHead}`;
334
408
  }
@@ -341,6 +415,9 @@ function progressIntegrityError(checkpoint) {
341
415
  if (indexedThrough !== null && checkpoint.scan === null) {
342
416
  return "scan checkpoint is missing for indexed state";
343
417
  }
418
+ if (indexedThrough === null !== (indexedThroughHash === null)) {
419
+ return "indexedThrough and indexedThroughHash must be present together";
420
+ }
344
421
  if (indexedThrough === null && checkpoint.applied.length > 0) {
345
422
  return "applied events exist before floorBlock has been indexed";
346
423
  }
@@ -500,7 +577,7 @@ async function persistedBodyIntegrityError(checkpoint) {
500
577
  for (const entry of checkpoint.bodies) {
501
578
  let computedHash;
502
579
  try {
503
- computedHash = await PayloadBuilder.dataHash(entry.body);
580
+ computedHash = await PayloadBuilder2.dataHash(entry.body);
504
581
  } catch {
505
582
  return `persisted body ${entry.hash} cannot be hashed`;
506
583
  }
@@ -512,26 +589,21 @@ async function persistedBodyIntegrityError(checkpoint) {
512
589
  }
513
590
  async function loadEventKitReplayCheckpoint(value, identity) {
514
591
  if (value === void 0 || value === null) return { status: "missing" };
592
+ if (typeof value === "object" && value !== null && value.version === 2) {
593
+ const legacy = LegacyEventKitReplayCheckpointIdentityZod.safeParse(value);
594
+ if (!legacy.success) {
595
+ return { detail: "recognized Event Kit replay checkpoint v2 has invalid identity", status: "corrupt" };
596
+ }
597
+ const mismatch2 = checkpointIdentityError(legacy.data, identity);
598
+ return mismatch2 === void 0 ? { status: "missing" } : { detail: mismatch2, status: "identity-mismatch" };
599
+ }
515
600
  const parsed = EventKitReplayCheckpointZod.safeParse(value);
516
601
  if (!parsed.success) {
517
602
  return { detail: "recognized Event Kit replay checkpoint has invalid content", status: "corrupt" };
518
603
  }
519
604
  const checkpoint = parsed.data;
520
- if (checkpoint.chainId !== identity.chainId) {
521
- return { detail: `chainId ${checkpoint.chainId} != ${identity.chainId}`, status: "identity-mismatch" };
522
- }
523
- if (checkpoint.floorBlock !== identity.floorBlock) {
524
- return { detail: `floorBlock ${checkpoint.floorBlock} != ${identity.floorBlock}`, status: "identity-mismatch" };
525
- }
526
- if (checkpoint.network !== identity.network) {
527
- return { detail: `network ${checkpoint.network} != ${identity.network}`, status: "identity-mismatch" };
528
- }
529
- if (checkpoint.sourceId !== identity.sourceId) {
530
- return { detail: `sourceId ${checkpoint.sourceId} != ${identity.sourceId}`, status: "identity-mismatch" };
531
- }
532
- if (checkpoint.publisher !== identity.publisher) {
533
- return { detail: `publisher ${checkpoint.publisher} != ${identity.publisher}`, status: "identity-mismatch" };
534
- }
605
+ const mismatch = checkpointIdentityError(checkpoint, identity);
606
+ if (mismatch !== void 0) return { detail: mismatch, status: "identity-mismatch" };
535
607
  const bodyIntegrityError = await persistedBodyIntegrityError(checkpoint);
536
608
  if (bodyIntegrityError !== void 0) return { detail: bodyIntegrityError, status: "corrupt" };
537
609
  const integrityError = checkpointIntegrityError(checkpoint);
@@ -540,6 +612,8 @@ async function loadEventKitReplayCheckpoint(value, identity) {
540
612
  }
541
613
 
542
614
  // src/finalizedReplayController.ts
615
+ var DEFAULT_CHECKPOINT_BATCH_SIZE = 4096;
616
+ var DEFAULT_SCAN_BATCH_SIZE = 16;
543
617
  function requireHead(value) {
544
618
  const head = Number(value);
545
619
  if (!Number.isSafeInteger(head) || head < 0) {
@@ -553,6 +627,19 @@ function requireFloor(value) {
553
627
  }
554
628
  return value;
555
629
  }
630
+ function requirePositiveInteger(value, fallback, name) {
631
+ const selected = value ?? fallback;
632
+ if (!Number.isSafeInteger(selected) || selected < 1) {
633
+ throw new TypeError(`${name} must be a positive safe integer, received ${String(selected)}`);
634
+ }
635
+ return selected;
636
+ }
637
+ function requireMaxBlocks(value) {
638
+ if (value !== void 0 && (!Number.isSafeInteger(value) || value < 0)) {
639
+ throw new TypeError(`maxBlocks must be a nonnegative safe integer, received ${String(value)}`);
640
+ }
641
+ return value;
642
+ }
556
643
  function replayContract(options, throughBlock) {
557
644
  return {
558
645
  eventBodyCorpus: { sourceId: options.sourceId },
@@ -572,6 +659,7 @@ function initialCheckpoint(options) {
572
659
  floorBlock: options.floorBlock,
573
660
  foldVersion: STATEMENT_FOLD_VERSION2,
574
661
  indexedThrough: null,
662
+ indexedThroughHash: null,
575
663
  network: options.network,
576
664
  objectSchemas: [...EventKitClaimedObjectSchemas],
577
665
  publisher: options.publisher,
@@ -613,23 +701,9 @@ function mergeBodies(existing, additions) {
613
701
  }
614
702
  merged.set(hash, body);
615
703
  }
616
- return [...merged].map(([hash, body]) => ({ body, hash: HashZod3.parse(hash) })).toSorted((left, right) => left.hash.localeCompare(right.hash));
704
+ return [...merged].map(([hash, body]) => ({ body, hash: HashZod4.parse(hash) })).toSorted((left, right) => left.hash.localeCompare(right.hash));
617
705
  }
618
- async function stageBlock(options, prior, block) {
619
- const result = await scanFinalizedStatements(
620
- options.chain,
621
- options.bodies,
622
- replayContract(options, block),
623
- {
624
- batchSize: 1,
625
- ...prior.scan === null ? {} : { resumeFrom: prior.scan },
626
- strict: { rejectAmbiguousSchemaDeclarations: true },
627
- through: block
628
- }
629
- );
630
- const applied = result.fold.applied.filter(
631
- (record) => normalizeAddress3(record.subject) === options.publisher
632
- );
706
+ async function stageAppliedBlock(options, applied) {
633
707
  const appliedObjectHashes = uniqueStrings(applied.map((record) => record.objectHash));
634
708
  const objects = await resolveVerifiedBodies(options.bodies, appliedObjectHashes);
635
709
  const missingObjects = appliedObjectHashes.filter((hash) => !objects.bodies.has(hash));
@@ -654,20 +728,73 @@ async function stageBlock(options, prior, block) {
654
728
  ...missingObjects,
655
729
  ...schemaMismatchedObjects,
656
730
  ...missingAttachments
657
- ]).map((hash) => HashZod3.parse(hash));
658
- const isBlocked = result.unresolved.length > 0 || objectHashes.length > 0 || corrupt.length > 0;
731
+ ]).map((hash) => HashZod4.parse(hash));
659
732
  return {
660
- applied,
661
733
  bodies: new Map([...objects.bodies, ...attachments.bodies]),
734
+ corrupt,
735
+ objectHashes
736
+ };
737
+ }
738
+ async function stageRange(options, prior, fromBlock, throughBlock, signal) {
739
+ signal?.throwIfAborted();
740
+ const beforeHash = await exactFinalizedBlockHash(options.chain, throughBlock, options.chainId);
741
+ signal?.throwIfAborted();
742
+ const result = await scanFinalizedStatements(
743
+ lineageValidatingScanChain(options.chain, options.chainId),
744
+ options.bodies,
745
+ replayContract(options, throughBlock),
746
+ {
747
+ batchSize: requirePositiveInteger(options.scanBatchSize, DEFAULT_SCAN_BATCH_SIZE, "scanBatchSize"),
748
+ ...prior.scan === null ? {} : { resumeFrom: prior.scan },
749
+ ...signal === void 0 ? {} : { signal },
750
+ strict: { rejectAmbiguousSchemaDeclarations: true },
751
+ through: throughBlock
752
+ }
753
+ );
754
+ signal?.throwIfAborted();
755
+ const throughHash = await exactFinalizedBlockHash(options.chain, throughBlock, options.chainId);
756
+ signal?.throwIfAborted();
757
+ if (beforeHash !== throughHash) {
758
+ throw new Error(`finalized block root changed while staging through ${throughBlock}`);
759
+ }
760
+ if (result.head !== throughBlock || result.checkpoint.contract.throughBlock !== throughBlock) {
761
+ throw new RangeError(
762
+ `finalized range scan stopped at ${result.head}/${result.checkpoint.contract.throughBlock}; requested ${throughBlock}`
763
+ );
764
+ }
765
+ const applied = result.fold.applied.filter((record) => record.block >= fromBlock && record.block <= throughBlock && normalizeAddress3(record.subject) === options.publisher);
766
+ const appliedByBlock = Map.groupBy(applied, (record) => record.block);
767
+ const unresolvedByBlock = Map.groupBy(
768
+ result.unresolved.filter((event) => event.block >= fromBlock && event.block <= throughBlock),
769
+ (event) => event.block
770
+ );
771
+ const relevantBlocks = [.../* @__PURE__ */ new Set([...appliedByBlock.keys(), ...unresolvedByBlock.keys()])].toSorted((left, right) => left - right);
772
+ const bodies = /* @__PURE__ */ new Map();
773
+ for (const block of relevantBlocks) {
774
+ signal?.throwIfAborted();
775
+ const staged = await stageAppliedBlock(options, appliedByBlock.get(block) ?? []);
776
+ for (const [hash, body] of staged.bodies) bodies.set(hash, body);
777
+ const unresolvedEvents = unresolvedByBlock.get(block) ?? [];
778
+ if (unresolvedEvents.length > 0 || staged.objectHashes.length > 0 || staged.corrupt.length > 0) {
779
+ return {
780
+ applied,
781
+ blocked: {
782
+ atBlock: block,
783
+ corrupt: staged.corrupt,
784
+ objectHashes: staged.objectHashes,
785
+ unresolvedEvents
786
+ },
787
+ bodies,
788
+ scan: result.checkpoint,
789
+ throughHash
790
+ };
791
+ }
792
+ }
793
+ return {
794
+ applied,
795
+ bodies,
662
796
  scan: result.checkpoint,
663
- ...isBlocked ? {
664
- blocked: {
665
- atBlock: block,
666
- corrupt,
667
- objectHashes,
668
- unresolvedEvents: result.unresolved
669
- }
670
- } : {}
797
+ throughHash
671
798
  };
672
799
  }
673
800
  var DefaultFinalizedEventKitReplayController = class {
@@ -678,6 +805,9 @@ var DefaultFinalizedEventKitReplayController = class {
678
805
  this.#options = options;
679
806
  this.#checkpoint = checkpoint;
680
807
  }
808
+ anchor() {
809
+ return this.#checkpoint.indexedThrough === null || this.#checkpoint.indexedThroughHash === null ? null : Object.freeze({ block: this.#checkpoint.indexedThrough, hash: this.#checkpoint.indexedThroughHash });
810
+ }
681
811
  body(hash) {
682
812
  return this.#checkpoint.bodies.find((entry) => entry.hash === hash)?.body;
683
813
  }
@@ -691,8 +821,8 @@ var DefaultFinalizedEventKitReplayController = class {
691
821
  indexedThrough: this.#checkpoint.indexedThrough
692
822
  };
693
823
  }
694
- sync() {
695
- const result = this.#syncTail.then(async () => await this.#syncOnce());
824
+ sync(options) {
825
+ const result = this.#syncTail.then(async () => await this.#syncOnce(options));
696
826
  this.#syncTail = result.then(
697
827
  () => true,
698
828
  () => false
@@ -702,8 +832,56 @@ var DefaultFinalizedEventKitReplayController = class {
702
832
  viewer() {
703
833
  return createCheckpointStatementGraphViewer(this.#checkpoint);
704
834
  }
705
- async #syncOnce() {
835
+ async #commitRange(fromBlock, throughBlock, finalizedHead, signal) {
836
+ const staged = await stageRange(this.#options, this.#checkpoint, fromBlock, throughBlock, signal);
837
+ signal?.throwIfAborted();
838
+ if (staged.blocked !== void 0) {
839
+ const blockedAt = staged.blocked.atBlock;
840
+ if (blockedAt > fromBlock) {
841
+ const prefixBlocked = await this.#commitRange(fromBlock, blockedAt - 1, finalizedHead, signal);
842
+ if (prefixBlocked) return true;
843
+ }
844
+ const exact = await stageRange(this.#options, this.#checkpoint, blockedAt, blockedAt, signal);
845
+ signal?.throwIfAborted();
846
+ if (exact.blocked === void 0) {
847
+ await this.#commitStaged(exact, blockedAt, finalizedHead, signal);
848
+ return blockedAt === throughBlock ? false : await this.#commitRange(blockedAt + 1, throughBlock, finalizedHead, signal);
849
+ }
850
+ const blocked = EventKitReplayCheckpointZod.parse({
851
+ ...this.#checkpoint,
852
+ blocked: exact.blocked,
853
+ finalizedHead
854
+ });
855
+ signal?.throwIfAborted();
856
+ await this.#options.checkpointStore.save(blocked);
857
+ this.#checkpoint = blocked;
858
+ return true;
859
+ }
860
+ await this.#commitStaged(staged, throughBlock, finalizedHead, signal);
861
+ return false;
862
+ }
863
+ async #commitStaged(staged, throughBlock, finalizedHead, signal) {
864
+ const committed = EventKitReplayCheckpointZod.parse({
865
+ ...this.#checkpoint,
866
+ applied: mergeApplied(this.#checkpoint.applied, staged.applied),
867
+ bodies: mergeBodies(this.#checkpoint.bodies, staged.bodies),
868
+ blocked: void 0,
869
+ finalizedHead,
870
+ indexedThrough: throughBlock,
871
+ indexedThroughHash: staged.throughHash,
872
+ scan: staged.scan
873
+ });
874
+ signal?.throwIfAborted();
875
+ await this.#options.checkpointStore.save(committed);
876
+ this.#checkpoint = committed;
877
+ }
878
+ async #syncOnce(rawOptions) {
879
+ const maxBlocks = requireMaxBlocks(rawOptions?.maxBlocks);
880
+ const signal = rawOptions?.signal;
881
+ signal?.throwIfAborted();
706
882
  const finalizedHead = requireHead(await this.#options.chain.finalization.headNumber());
883
+ signal?.throwIfAborted();
884
+ await requireCurrentReplayChain(this.#options.chain, finalizedHead, this.#options.chainId);
707
885
  if (finalizedHead < this.#checkpoint.finalizedHead) {
708
886
  throw new RangeError(
709
887
  `finalized head regressed from ${this.#checkpoint.finalizedHead} to ${finalizedHead}; refusing to roll back`
@@ -714,39 +892,26 @@ var DefaultFinalizedEventKitReplayController = class {
714
892
  throw new RangeError(`finalized head ${finalizedHead} is behind indexedThrough ${indexedThrough}`);
715
893
  }
716
894
  let next = indexedThrough === null ? this.#options.floorBlock : indexedThrough + 1;
717
- let changed = finalizedHead !== this.#checkpoint.finalizedHead;
718
- while (next <= finalizedHead) {
719
- const staged = await stageBlock(this.#options, this.#checkpoint, next);
720
- if (staged.blocked !== void 0) {
721
- const blocked = EventKitReplayCheckpointZod.parse({
722
- ...this.#checkpoint,
723
- blocked: staged.blocked,
724
- finalizedHead
725
- });
726
- await this.#options.checkpointStore.save(blocked);
727
- this.#checkpoint = blocked;
728
- return this.status();
729
- }
730
- const committed = EventKitReplayCheckpointZod.parse({
731
- ...this.#checkpoint,
732
- applied: mergeApplied(this.#checkpoint.applied, staged.applied),
733
- bodies: mergeBodies(this.#checkpoint.bodies, staged.bodies),
734
- blocked: void 0,
735
- finalizedHead,
736
- indexedThrough: next,
737
- scan: staged.scan
738
- });
739
- await this.#options.checkpointStore.save(committed);
740
- this.#checkpoint = committed;
741
- changed = false;
742
- next += 1;
895
+ const remaining = next <= finalizedHead ? finalizedHead - next + 1 : 0;
896
+ const budget = maxBlocks === void 0 ? remaining : Math.min(maxBlocks, remaining);
897
+ const target = budget === 0 ? next - 1 : next + budget - 1;
898
+ const checkpointBatchSize = requirePositiveInteger(
899
+ this.#options.checkpointBatchSize,
900
+ DEFAULT_CHECKPOINT_BATCH_SIZE,
901
+ "checkpointBatchSize"
902
+ );
903
+ while (next <= target) {
904
+ signal?.throwIfAborted();
905
+ const throughBlock = Math.min(target, next + Math.min(checkpointBatchSize, target - next + 1) - 1);
906
+ if (await this.#commitRange(next, throughBlock, finalizedHead, signal)) return this.status();
907
+ next = throughBlock + 1;
743
908
  }
744
- if (changed || this.#checkpoint.blocked !== void 0) {
909
+ if (this.#checkpoint.finalizedHead !== finalizedHead) {
745
910
  const observed = EventKitReplayCheckpointZod.parse({
746
911
  ...this.#checkpoint,
747
- blocked: void 0,
748
912
  finalizedHead
749
913
  });
914
+ signal?.throwIfAborted();
750
915
  await this.#options.checkpointStore.save(observed);
751
916
  this.#checkpoint = observed;
752
917
  }
@@ -756,10 +921,16 @@ var DefaultFinalizedEventKitReplayController = class {
756
921
  async function createFinalizedEventKitReplayController(rawOptions) {
757
922
  const options = {
758
923
  ...rawOptions,
759
- chainId: ChainIdZod2.parse(rawOptions.chainId),
924
+ chainId: ChainIdZod3.parse(rawOptions.chainId),
925
+ checkpointBatchSize: requirePositiveInteger(
926
+ rawOptions.checkpointBatchSize,
927
+ DEFAULT_CHECKPOINT_BATCH_SIZE,
928
+ "checkpointBatchSize"
929
+ ),
760
930
  floorBlock: requireFloor(rawOptions.floorBlock),
761
931
  network: rawOptions.network.trim(),
762
932
  publisher: NormalizedAddressZod3.parse(rawOptions.publisher),
933
+ scanBatchSize: requirePositiveInteger(rawOptions.scanBatchSize, DEFAULT_SCAN_BATCH_SIZE, "scanBatchSize"),
763
934
  sourceId: rawOptions.sourceId.trim()
764
935
  };
765
936
  if (options.network.length === 0) throw new TypeError("network must not be empty");
@@ -787,7 +958,7 @@ function createMemoryEventKitReplayCheckpointStore(initial) {
787
958
  }
788
959
 
789
960
  // src/grantReadModel.ts
790
- import { HashZod as HashZod4 } from "@ariestools/sdk";
961
+ import { HashZod as HashZod5 } from "@ariestools/sdk";
791
962
  import { XyoAddressZod } from "@xyo-network/sdk-protocol/address";
792
963
  function createStatementGraphGrantReadModel(options) {
793
964
  const { viewer } = options;
@@ -800,7 +971,7 @@ function createStatementGraphGrantReadModel(options) {
800
971
  return Object.freeze(records.map(toGrantClaim));
801
972
  },
802
973
  async object(objectHash) {
803
- const record = await viewer.object(HashZod4.parse(objectHash));
974
+ const record = await viewer.object(HashZod5.parse(objectHash));
804
975
  return record === void 0 ? void 0 : toGrantObject(record);
805
976
  }
806
977
  });
@@ -809,7 +980,7 @@ function toClaimQuery(query) {
809
980
  if (query === void 0) return void 0;
810
981
  return {
811
982
  ...query.declaredObjectSchemaPrefix === void 0 ? {} : { declaredObjectSchemaPrefix: query.declaredObjectSchemaPrefix },
812
- ...query.objectHash === void 0 ? {} : { objectHash: HashZod4.parse(query.objectHash) },
983
+ ...query.objectHash === void 0 ? {} : { objectHash: HashZod5.parse(query.objectHash) },
813
984
  ...query.subject === void 0 ? {} : { subject: XyoAddressZod.parse(query.subject) }
814
985
  };
815
986
  }
@@ -862,7 +1033,7 @@ import {
862
1033
  selectPublisherService
863
1034
  } from "@xyo-network/event-kit-protocol";
864
1035
  import {
865
- ChainIdZod as ChainIdZod3,
1036
+ ChainIdZod as ChainIdZod4,
866
1037
  NormalizedAddressZod as NormalizedAddressZod4,
867
1038
  PublisherEncryptionKeyRevocationSchema as PublisherEncryptionKeyRevocationSchema2,
868
1039
  PublisherEncryptionKeyRevocationZod,
@@ -874,7 +1045,7 @@ import {
874
1045
  PublisherServiceZod,
875
1046
  SOURCE_WAKE_EVENT_KIND as SOURCE_WAKE_EVENT_KIND2
876
1047
  } from "@xyo-network/event-kit-schemas";
877
- import { PayloadBuilder as PayloadBuilder2 } from "@xyo-network/sdk";
1048
+ import { PayloadBuilder as PayloadBuilder3 } from "@xyo-network/sdk";
878
1049
  import { XyoAddressZod as XyoAddressZod2 } from "@xyo-network/sdk-protocol/address";
879
1050
  function handshakeEvidence(record) {
880
1051
  return {
@@ -914,12 +1085,12 @@ async function verifiedHandshakeObjectBody(viewer, record) {
914
1085
  const object = await viewer.object(record.objectHash);
915
1086
  if (object?.assurance !== "resolved" || object.declaredSchema !== record.objectSchema || object.params === null) return void 0;
916
1087
  const body = { schema: object.declaredSchema, ...object.params };
917
- return await PayloadBuilder2.dataHash(body) === record.objectHash ? body : void 0;
1088
+ return await PayloadBuilder3.dataHash(body) === record.objectHash ? body : void 0;
918
1089
  }
919
1090
  async function verifiedHandshakeAttachmentBody(resolver, hash) {
920
1091
  const body = resolver?.(hash);
921
1092
  if (body === void 0) return void 0;
922
- return await PayloadBuilder2.dataHash(body) === hash ? { ...body } : void 0;
1093
+ return await PayloadBuilder3.dataHash(body) === hash ? { ...body } : void 0;
923
1094
  }
924
1095
  function firstHandshakeRevocation(revocations, target) {
925
1096
  const blocks = revocations.filter((item) => item.target === target).map((item) => item.block);
@@ -1070,7 +1241,7 @@ function hasPendingPublisherService(catalogs, query) {
1070
1241
  async function discoverHandshakeService(options, rawQuery) {
1071
1242
  const query = {
1072
1243
  ...rawQuery,
1073
- chainId: ChainIdZod3.parse(rawQuery.chainId),
1244
+ chainId: ChainIdZod4.parse(rawQuery.chainId),
1074
1245
  eventKind: rawQuery.eventKind ?? SOURCE_WAKE_EVENT_KIND2,
1075
1246
  publisher: NormalizedAddressZod4.parse(rawQuery.publisher)
1076
1247
  };