soroban-events 2.0.1 → 3.0.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.
package/README.md CHANGED
@@ -1,46 +1,61 @@
1
1
  # soroban-events
2
2
 
3
- Lightweight Soroban RPC event ingestion, pagination, retries, deduplication, and ScVal decoding for Node.js.
3
+ Resilient, windowed event streaming and XDR decoding for Soroban RPC.
4
4
 
5
- Build Soroban dashboards, activity feeds, lightweight indexers, analytics tools, monitoring services, bots, and event-driven backends without implementing RPC pagination and event decoding yourself.
5
+ `soroban-events` provides production-oriented primitives for retrieving, decoding, processing, storing, replaying, and exporting Soroban contract events.
6
6
 
7
7
  ## Features
8
8
 
9
- - Ledger-range windowing
10
- - RPC cursor pagination
11
- - Event deduplication
12
- - Rate-limit and transient retries
13
- - Soroban `ScVal` decoding
14
- - Large integer precision preservation
15
- - Contract event filtering
16
- - Recent-event lookup with `tail()`
17
- - Async event streaming with `stream()`
18
- - AbortSignal support
19
-
20
- ## Install
9
+ - Safe windowed retrieval for ledger ranges larger than the Soroban RPC range limit.
10
+ - Cursor-based pagination.
11
+ - Event-ID deduplication.
12
+ - Unlimited internal pagination with `limit: null`.
13
+ - Reliable ledger checkpointing.
14
+ - Durable file checkpoints.
15
+ - Memory and SQLite event stores.
16
+ - Backfill and historical replay.
17
+ - Backfill/live handoff protection.
18
+ - Reorg detection and recovery.
19
+ - RPC health and ledger-retention inspection.
20
+ - Configurable RPC failover.
21
+ - Circuit-breaker protection.
22
+ - Adaptive rate-limit handling.
23
+ - Exponential retry with jitter.
24
+ - Event filtering and custom predicates.
25
+ - Deterministic event querying.
26
+ - Event processing pipelines.
27
+ - JSONL and CSV export.
28
+ - Streaming export.
29
+ - Optional dead-letter queue primitives.
30
+ - CLI commands for status, backfill, replay, and health inspection.
31
+ - TypeScript declarations.
32
+ - Packed npm-consumer verification.
33
+
34
+ ## Requirements
35
+
36
+ Node.js 22.12.0 or newer.
37
+
38
+ The runtime requirement follows the current Stellar SDK dependency requirements.
39
+
40
+ ## Installation
21
41
 
22
42
  ```bash
23
43
  npm install soroban-events
24
44
  ```
25
45
 
26
- Requires Node.js 20+.
27
-
28
- ## Quick start
46
+ ## Basic usage
29
47
 
30
48
  ```js
31
- import { SorobanEventStreamer } from 'soroban-events';
49
+ import { SorobanEventStreamer } from "soroban-events";
32
50
 
33
51
  const streamer = new SorobanEventStreamer(
34
- 'https://soroban-testnet.stellar.org'
52
+ "https://soroban-testnet.stellar.org"
35
53
  );
36
54
 
37
- const latest = await streamer.getLatestLedger();
38
-
39
55
  const events = await streamer.getEventsWindowed({
40
- startLedger: latest - 100,
41
- endLedger: latest,
42
- filters: [{ type: 'contract' }],
43
- limit: 10
56
+ startLedger: 1000000,
57
+ endLedger: 1010000,
58
+ limit: null
44
59
  });
45
60
 
46
61
  for (const event of events) {
@@ -48,183 +63,455 @@ for (const event of events) {
48
63
  }
49
64
  ```
50
65
 
51
- ## Filter by contract
66
+ ## Latest ledger
52
67
 
53
68
  ```js
54
- const events = await streamer.getEventsWindowed({
55
- startLedger: latest - 1000,
56
- endLedger: latest,
57
- filters: [
58
- {
59
- type: 'contract',
60
- contractIds: ['YOUR_CONTRACT_ID']
61
- }
62
- ],
63
- limit: 100
69
+ const latest = await streamer.getLatestLedger();
70
+
71
+ console.log(latest);
72
+ ```
73
+
74
+ Metadata can be requested when ledger information beyond the sequence is needed:
75
+
76
+ ```js
77
+ const latest = await streamer.getLatestLedger({
78
+ metadata: true
64
79
  });
80
+
81
+ console.log(latest.sequence);
82
+ console.log(latest.hash);
65
83
  ```
66
84
 
67
- ## Get recent events
85
+ ## Large event ranges
86
+
87
+ Soroban RPC limits individual ledger-range queries. `soroban-events` automatically divides large ranges into safe windows.
88
+
89
+ Internal consumers such as backfill and consume use unlimited pagination so events are not silently truncated by a default event limit.
90
+
91
+ Public window retrieval accepts either a finite limit or `null` for unlimited retrieval:
68
92
 
69
93
  ```js
70
- const events = await streamer.tail({
71
- contractId: 'YOUR_CONTRACT_ID',
72
- limit: 20
94
+ const events = await streamer.getEventsWindowed({
95
+ startLedger: 1000000,
96
+ endLedger: 1200000,
97
+ limit: null
73
98
  });
99
+ ```
100
+
101
+ ## Backfill
74
102
 
75
- console.log(events);
103
+ ```js
104
+ import { backfill } from "soroban-events";
105
+
106
+ await backfill(streamer, {
107
+ startLedger: 1000000,
108
+ endLedger: 1100000,
109
+ onEvent: async (event) => {
110
+ console.log(event);
111
+ }
112
+ });
76
113
  ```
77
114
 
78
- ## Stream events
115
+ Backfill supports checkpointing, event stores, pipelines, progress reporting, and concurrent window processing.
116
+
117
+ ## Replay
79
118
 
80
119
  ```js
81
- for await (const event of streamer.stream({
82
- startLedger: 4750000,
83
- filters: [
84
- {
85
- type: 'contract',
86
- contractIds: ['YOUR_CONTRACT_ID']
87
- }
88
- ]
89
- })) {
90
- console.log('New event:', event);
91
- }
120
+ import { EventReplay } from "soroban-events";
121
+
122
+ const replay = new EventReplay(streamer);
123
+
124
+ await replay.run({
125
+ startLedger: 1000000,
126
+ endLedger: 1010000,
127
+ onEvent: async (event) => {
128
+ console.log(event);
129
+ }
130
+ });
92
131
  ```
93
132
 
94
- Stop a stream with `AbortController`:
133
+ Replay supports checkpoint recovery, pipelines, persistence, and abort signals.
134
+
135
+ ## Streaming
95
136
 
96
137
  ```js
97
138
  const controller = new AbortController();
98
139
 
99
- setTimeout(() => controller.abort(), 30_000);
100
-
101
140
  for await (const event of streamer.stream({
102
- startLedger: 4750000,
141
+ startLedger: 1000000,
103
142
  signal: controller.signal
104
143
  })) {
105
144
  console.log(event);
106
145
  }
146
+
147
+ controller.abort();
107
148
  ```
108
149
 
109
- ## Decode ScVal
150
+ Stream checkpointing does not advance past a ledger until all events from that ledger have been successfully consumed.
110
151
 
111
- The decoder can also be used independently:
152
+ ## Checkpointing
153
+
154
+ Checkpoint stores use the raw store contract:
112
155
 
113
156
  ```js
114
- import { unwrapScVal } from 'soroban-events';
157
+ await store.save(key, ledger);
158
+ const ledger = await store.load(key);
159
+ await store.clear(key);
160
+ ```
115
161
 
116
- const value = unwrapScVal(scVal);
117
- console.log(value);
162
+ For monotonic checkpoint management and explicit rewinds, use `CheckpointManager`:
163
+
164
+ ```js
165
+ const manager = new CheckpointManager(store);
166
+
167
+ await manager.save(ledger, "my-consumer");
168
+
169
+ const resumeLedger = await manager.resumeFrom(
170
+ "my-consumer",
171
+ startLedger
172
+ );
173
+
174
+ await manager.rewind(
175
+ rewindLedger,
176
+ "my-consumer"
177
+ );
118
178
  ```
119
179
 
120
- Common Soroban values supported include integers, symbols, strings, booleans, bytes, addresses, vectors, and maps.
180
+ Normal checkpoint saves cannot move backwards. Explicit rewinds are used for recovery workflows.
121
181
 
122
- Large integer values are normalized without relying on JavaScript `Number` precision.
182
+ ## File checkpoints
123
183
 
124
- ## Event format
184
+ ```js
185
+ import {
186
+ FileCheckpointStore,
187
+ CheckpointManager
188
+ } from "soroban-events";
125
189
 
126
- Events are normalized into a consistent object containing fields such as:
190
+ const store = new FileCheckpointStore("./checkpoints");
191
+ const checkpoints = new CheckpointManager(store);
192
+
193
+ await checkpoints.save(
194
+ 1000000,
195
+ "my-consumer"
196
+ );
197
+
198
+ const resumeLedger = await checkpoints.resumeFrom(
199
+ "my-consumer",
200
+ 1
201
+ );
202
+ ```
203
+
204
+ File checkpoints use atomic replacement so a partially written checkpoint does not replace the last valid checkpoint.
205
+
206
+ ## Event filtering
127
207
 
128
208
  ```js
129
- {
130
- id,
131
- type,
132
- ledger,
133
- ledgerClosedAt,
134
- contractId,
135
- transactionIndex,
136
- operationIndex,
137
- txHash,
138
- topics,
139
- value,
140
- inSuccessfulContractCall
141
- }
209
+ import { createEventFilter } from "soroban-events";
210
+
211
+ const filter = createEventFilter({
212
+ contractId: "C...",
213
+ type: "transfer"
214
+ });
215
+
216
+ const matching = events.filter(filter);
142
217
  ```
143
218
 
144
- ## Configuration
219
+ Filters can match:
220
+
221
+ - contract ID
222
+ - event type
223
+ - topics
224
+ - transaction hash
225
+ - exact ledger
226
+ - ledger ranges
227
+ - multiple contracts
228
+ - custom predicates
229
+
230
+ ## Event querying
145
231
 
146
232
  ```js
147
- const streamer = new SorobanEventStreamer(RPC_URL, {
148
- pollInterval: 3000,
149
- windowSize: 9500,
150
- pageSize: 1000,
151
- maxRetries: 3,
152
- retryBaseMs: 500
233
+ import { createEventQuery } from "soroban-events";
234
+
235
+ const query = createEventQuery(store);
236
+
237
+ const results = await query({
238
+ contractId: "C...",
239
+ startLedger: 1000000,
240
+ endLedger: 1010000,
241
+ limit: 100
153
242
  });
154
243
  ```
155
244
 
156
- | Option | Default | Description |
157
- |---|---:|---|
158
- | `pollInterval` | `3000` | Polling interval for `stream()` |
159
- | `windowSize` | `9500` | Maximum ledger window |
160
- | `pageSize` | `1000` | RPC page size |
161
- | `maxRetries` | `3` | Maximum retry attempts |
162
- | `retryBaseMs` | `500` | Base retry delay |
163
-
164
- ## How it works
165
-
166
- ```text
167
- Soroban RPC
168
- |
169
- v
170
- ledger windows
171
- |
172
- v
173
- cursor pagination
174
- |
175
- v
176
- retry handling
177
- |
178
- v
179
- deduplication
180
- |
181
- v
182
- ScVal decoding
183
- |
184
- v
185
- your application
186
- ```
187
-
188
- No database is required.
189
-
190
- No full indexer stack is required.
191
-
192
- Use the normalized events in whatever application or storage layer you need.
245
+ Queries provide deterministic ordering and pagination.
193
246
 
194
- ## Testing
247
+ ## Event storage
248
+
249
+ The package provides memory and SQLite event stores.
250
+
251
+ Memory storage:
252
+
253
+ ```js
254
+ import { MemoryEventStore } from "soroban-events";
255
+
256
+ const store = new MemoryEventStore();
257
+
258
+ await store.put(event);
259
+
260
+ const events = await store.query({
261
+ startLedger: 1000000,
262
+ endLedger: 1010000
263
+ });
264
+ ```
265
+
266
+ SQLite storage:
267
+
268
+ ```js
269
+ import { SqliteEventStore } from "soroban-events";
270
+
271
+ const store = new SqliteEventStore("./events.db");
272
+
273
+ await store.put(event);
274
+ ```
275
+
276
+ SQLite uses Node's built-in SQLite support and therefore requires a supported Node.js runtime.
277
+
278
+ ## Event pipelines
279
+
280
+ Pipelines can filter, transform, observe, batch, and handle event-processing failures.
281
+
282
+ ```js
283
+ import { EventPipeline } from "soroban-events";
284
+
285
+ const pipeline = new EventPipeline()
286
+ .filter((event) => event.type === "transfer")
287
+ .map((event) => ({
288
+ ...event,
289
+ processed: true
290
+ }))
291
+ .tap((event) => {
292
+ console.log("processed", event.id);
293
+ });
294
+ ```
295
+
296
+ ## RPC health
297
+
298
+ ```js
299
+ const health = await streamer.getHealth();
300
+
301
+ console.log(health.status);
302
+ console.log(health.latestLedger);
303
+ console.log(health.oldestLedger);
304
+ console.log(health.ledgerRetentionWindow);
305
+ ```
306
+
307
+ Retention can be checked directly:
195
308
 
196
- Run the unit tests:
309
+ ```js
310
+ const result = await streamer.checkRetention(1000000);
311
+
312
+ console.log(result.retained);
313
+ ```
314
+
315
+ ## RPC failover
316
+
317
+ ```js
318
+ const streamer = new SorobanEventStreamer(
319
+ "https://primary-rpc.example",
320
+ {
321
+ failoverRpcUrls: [
322
+ "https://secondary-rpc.example",
323
+ "https://third-rpc.example"
324
+ ]
325
+ }
326
+ );
327
+ ```
328
+
329
+ The streamer can rotate through configured RPC endpoints when transient failures exhaust retry attempts.
330
+
331
+ ## Circuit breaker
332
+
333
+ Circuit-breaker behavior can be configured:
334
+
335
+ ```js
336
+ const streamer = new SorobanEventStreamer(
337
+ rpcUrl,
338
+ {
339
+ circuitBreakerThreshold: 3,
340
+ circuitBreakerCooldownMs: 30000
341
+ }
342
+ );
343
+ ```
344
+
345
+ The circuit protects the consumer from repeatedly hammering an unavailable RPC endpoint.
346
+
347
+ ## Adaptive retry
348
+
349
+ Retry behavior supports exponential backoff, rate-limit adaptation, maximum delay, and jitter:
350
+
351
+ ```js
352
+ const streamer = new SorobanEventStreamer(
353
+ rpcUrl,
354
+ {
355
+ maxRetries: 3,
356
+ retryBaseMs: 500,
357
+ retryMaxMs: 30000,
358
+ retryJitter: 0.2,
359
+ adaptiveRateLimit: true
360
+ }
361
+ );
362
+ ```
363
+
364
+ ## Reorg recovery
365
+
366
+ The package provides ledger consistency tracking and reorg recovery primitives.
367
+
368
+ Recovery can:
369
+
370
+ - detect ledger hash mismatches;
371
+ - determine an affected rewind point;
372
+ - rewind checkpoints;
373
+ - invalidate affected stored events;
374
+ - restore the backfill/live handoff boundary.
375
+
376
+ ## Event export
377
+
378
+ JSONL:
379
+
380
+ ```js
381
+ import { exportEvents } from "soroban-events";
382
+
383
+ const jsonl = exportEvents(events, {
384
+ format: "jsonl"
385
+ });
386
+ ```
387
+
388
+ CSV:
389
+
390
+ ```js
391
+ const csv = exportEvents(events, {
392
+ format: "csv"
393
+ });
394
+ ```
395
+
396
+ Streaming export is also supported for large event collections.
397
+
398
+ ## CLI
399
+
400
+ Check RPC status:
197
401
 
198
402
  ```bash
199
- npm test
403
+ npx soroban-events status --rpc <RPC_URL>
404
+ ```
405
+
406
+ Backfill a ledger range:
407
+
408
+ ```bash
409
+ npx soroban-events backfill --rpc <RPC_URL> --start <LEDGER> --end <LEDGER>
200
410
  ```
201
411
 
202
- Live Stellar Testnet verification:
412
+ Replay a ledger range:
203
413
 
204
414
  ```bash
205
- npm run test:live
415
+ npx soroban-events replay --rpc <RPC_URL> --start <LEDGER> --end <LEDGER>
206
416
  ```
207
417
 
208
- The project currently includes 22 automated tests covering windowing, pagination, deduplication, limits, retries, abort handling, filtering, ScVal decoding, large integers, and event normalization.
418
+ Health information:
419
+
420
+ ```bash
421
+ npx soroban-events health --rpc <RPC_URL>
422
+ ```
423
+
424
+ The CLI supports JSONL and CSV export options where applicable.
425
+
426
+ ## TypeScript
427
+
428
+ TypeScript declarations are included with the package.
429
+
430
+ ```ts
431
+ import {
432
+ SorobanEventStreamer,
433
+ CheckpointManager,
434
+ MemoryCheckpointStore
435
+ } from "soroban-events";
436
+
437
+ const store = new MemoryCheckpointStore();
438
+ const checkpoints = new CheckpointManager(store);
439
+ ```
440
+
441
+ ## Reliability guarantees
442
+
443
+ The library is designed around several important ingestion properties.
444
+
445
+ ### Pagination safety
446
+
447
+ Large RPC ranges are split into safe ledger windows and paginated using RPC cursors.
448
+
449
+ ### Event deduplication
450
+
451
+ Event IDs are deduplicated across pagination and processing boundaries.
209
452
 
210
- The live test has also been verified against Stellar Testnet RPC.
453
+ ### Checkpoint safety
211
454
 
212
- ## Status
455
+ Consumer checkpoints are advanced only after the relevant ledger has been completely processed.
213
456
 
214
- **v0.1.0**
457
+ ### Restart recovery
215
458
 
216
- The current release focuses on the core RPC ingestion and ScVal decoding layer.
459
+ A failed consumer can resume from its last durable checkpoint and reprocess the uncommitted ledger.
460
+
461
+ ### Backfill/live handoff
462
+
463
+ The handoff layer protects the transition between historical backfill and live consumption from duplicate delivery.
464
+
465
+ ### Reorg handling
466
+
467
+ Ledger consistency checks can detect hash mismatches and trigger controlled checkpoint rewind and storage invalidation.
468
+
469
+ ### RPC resilience
470
+
471
+ Transient failures, rate limits, endpoint failures, and unavailable RPC services can be handled through retry, adaptive backoff, failover, and circuit-breaking mechanisms.
472
+
473
+ ## Testing
474
+
475
+ Run the complete test suite:
476
+
477
+ ```bash
478
+ npm test
479
+ ```
480
+
481
+ Run the packed-consumer test:
482
+
483
+ ```bash
484
+ npm run test:pack
485
+ ```
486
+
487
+ Run the release verification suite:
488
+
489
+ ```bash
490
+ npm run test:release
491
+ ```
217
492
 
218
- This is intentionally a lightweight library, not a database-backed blockchain indexer.
493
+ The release verification includes:
219
494
 
220
- Potential future work includes durable cursor persistence, stronger recovery strategies, webhook delivery, richer filtering helpers, and production indexing integrations.
495
+ - complete test suite;
496
+ - packed npm consumer verification;
497
+ - npm pack dry-run;
498
+ - Git whitespace validation.
221
499
 
222
- ## Contributing
500
+ ## v3.0.0
223
501
 
224
- Bug reports, edge cases, documentation improvements, test cases, and pull requests are welcome.
502
+ Version 3 is a major compatibility boundary for the expanded public API.
225
503
 
226
- When reporting an RPC issue, include the endpoint/network, ledger range, relevant RPC error or response, expected behavior, and actual behavior.
504
+ Before upgrading, review:
227
505
 
228
- ## License
506
+ - checkpoint store method signatures;
507
+ - `CheckpointManager` usage;
508
+ - streamer `limit` behavior;
509
+ - event filtering and querying;
510
+ - event storage APIs;
511
+ - export APIs;
512
+ - pipeline APIs;
513
+ - RPC failover and retry configuration;
514
+ - TypeScript declarations;
515
+ - Node.js 22.12.0 or newer requirement.
229
516
 
230
- MIT
517
+ The v3.0.0 release is currently unreleased.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "soroban-events",
3
- "version": "2.0.1",
3
+ "version": "3.0.0",
4
4
  "description": "Resilient, windowed event streamer and XDR decoder for Soroban RPC",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -22,7 +22,9 @@
22
22
  "cli": "node src/cli.js",
23
23
  "test:unit": "node --test tests/*.test.js",
24
24
  "audit": "node scripts/audit.mjs",
25
- "pack:audit": "node scripts/pack-audit.mjs"
25
+ "pack:audit": "node scripts/pack-audit.mjs",
26
+ "test:pack": "node --test tests/packed-consumer.test.js",
27
+ "test:release": "npm test && npm run test:pack && npm pack --dry-run && git diff --check"
26
28
  },
27
29
  "keywords": [
28
30
  "soroban",
@@ -39,7 +41,7 @@
39
41
  ],
40
42
  "license": "MIT",
41
43
  "engines": {
42
- "node": ">=20"
44
+ "node": ">=22.12.0"
43
45
  },
44
46
  "dependencies": {
45
47
  "@stellar/stellar-sdk": "^17.1.0"
package/src/backfill.js CHANGED
@@ -291,7 +291,7 @@ export class BackfillEngine {
291
291
  startLedger: window.startLedger,
292
292
  endLedger: window.endLedger,
293
293
  filters,
294
- limit: 10000,
294
+ limit: null,
295
295
  signal
296
296
  });
297
297