soroban-events 0.1.0 → 1.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/package.json +1 -1
- package/src/decoder.js +10 -2
- package/src/streamer.js +86 -11
package/package.json
CHANGED
package/src/decoder.js
CHANGED
|
@@ -2,6 +2,13 @@ import { scValToNative } from '@stellar/stellar-sdk';
|
|
|
2
2
|
|
|
3
3
|
const DEFAULT_MAX_DEPTH = 50;
|
|
4
4
|
|
|
5
|
+
class ScValDepthError extends Error {
|
|
6
|
+
constructor(maxDepth) {
|
|
7
|
+
super(`ScVal nesting exceeds maximum depth of ${maxDepth}`);
|
|
8
|
+
this.name = "ScValDepthError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
5
12
|
export function unwrapScVal(scVal, options = {}) {
|
|
6
13
|
if (scVal == null) return null;
|
|
7
14
|
|
|
@@ -9,14 +16,15 @@ export function unwrapScVal(scVal, options = {}) {
|
|
|
9
16
|
|
|
10
17
|
try {
|
|
11
18
|
return normalizeNative(scValToNative(scVal), 0, maxDepth);
|
|
12
|
-
} catch {
|
|
19
|
+
} catch (error) {
|
|
20
|
+
if (error instanceof ScValDepthError) throw error;
|
|
13
21
|
return parseScValDirect(scVal);
|
|
14
22
|
}
|
|
15
23
|
}
|
|
16
24
|
|
|
17
25
|
function normalizeNative(value, depth, maxDepth) {
|
|
18
26
|
if (depth > maxDepth) {
|
|
19
|
-
throw new
|
|
27
|
+
throw new ScValDepthError(maxDepth);
|
|
20
28
|
}
|
|
21
29
|
|
|
22
30
|
if (value === null || value === undefined) return null;
|
package/src/streamer.js
CHANGED
|
@@ -45,15 +45,20 @@ export class SorobanEventStreamer {
|
|
|
45
45
|
);
|
|
46
46
|
|
|
47
47
|
this.pollInterval = options.pollInterval ?? 3000;
|
|
48
|
-
this.windowSize = Math.min(
|
|
49
|
-
options.windowSize ?? MAX_SAFE_LEDGER_SPAN,
|
|
50
|
-
MAX_SAFE_LEDGER_SPAN
|
|
51
|
-
);
|
|
52
48
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
49
|
+
const windowSize = options.windowSize ?? MAX_SAFE_LEDGER_SPAN;
|
|
50
|
+
if (!Number.isInteger(windowSize) || windowSize < 1) {
|
|
51
|
+
throw new TypeError("windowSize must be a positive integer");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.windowSize = Math.min(windowSize, MAX_SAFE_LEDGER_SPAN);
|
|
55
|
+
|
|
56
|
+
const pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
57
|
+
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 10000) {
|
|
58
|
+
throw new TypeError("pageSize must be an integer between 1 and 10000");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.pageSize = pageSize;
|
|
57
62
|
|
|
58
63
|
this.maxRetries = options.maxRetries ?? 3;
|
|
59
64
|
this.retryBaseMs = options.retryBaseMs ?? 500;
|
|
@@ -75,6 +80,10 @@ export class SorobanEventStreamer {
|
|
|
75
80
|
throw new TypeError('startLedger must be a positive integer');
|
|
76
81
|
}
|
|
77
82
|
|
|
83
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
84
|
+
throw new TypeError('limit must be a positive integer');
|
|
85
|
+
}
|
|
86
|
+
|
|
78
87
|
if (endLedger != null && (!Number.isInteger(endLedger) || endLedger < startLedger)) {
|
|
79
88
|
throw new TypeError('endLedger must be >= startLedger');
|
|
80
89
|
}
|
|
@@ -182,6 +191,70 @@ export class SorobanEventStreamer {
|
|
|
182
191
|
return rawEvents.slice(0, limit);
|
|
183
192
|
}
|
|
184
193
|
|
|
194
|
+
async fetchWindowNewest(
|
|
195
|
+
startLedger,
|
|
196
|
+
endLedgerExclusive,
|
|
197
|
+
filters,
|
|
198
|
+
limit,
|
|
199
|
+
signal
|
|
200
|
+
) {
|
|
201
|
+
const newest = [];
|
|
202
|
+
let cursor;
|
|
203
|
+
|
|
204
|
+
while (true) {
|
|
205
|
+
if (signal?.aborted) {
|
|
206
|
+
throw new DOMException("Operation aborted", "AbortError");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const pagination = {
|
|
210
|
+
limit: this.pageSize
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
if (cursor) {
|
|
214
|
+
pagination.cursor = cursor;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const params = {
|
|
218
|
+
filters,
|
|
219
|
+
pagination
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
if (!cursor) {
|
|
223
|
+
params.startLedger = startLedger;
|
|
224
|
+
params.endLedger = endLedgerExclusive;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const response = await this.requestWithRetry(params, signal);
|
|
228
|
+
const events = response?.events ?? [];
|
|
229
|
+
|
|
230
|
+
for (const event of events) {
|
|
231
|
+
if (!event?.id) continue;
|
|
232
|
+
|
|
233
|
+
newest.push(event);
|
|
234
|
+
|
|
235
|
+
if (newest.length > limit) {
|
|
236
|
+
newest.shift();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const nextCursor = response?.cursor;
|
|
241
|
+
|
|
242
|
+
if (!nextCursor || events.length === 0) {
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (nextCursor === cursor) {
|
|
247
|
+
throw new Error(
|
|
248
|
+
`RPC pagination cursor did not advance for ledger range ${startLedger}-${endLedgerExclusive}`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
cursor = nextCursor;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return newest;
|
|
256
|
+
}
|
|
257
|
+
|
|
185
258
|
async requestWithRetry(params, signal) {
|
|
186
259
|
let attempt = 0;
|
|
187
260
|
|
|
@@ -217,7 +290,8 @@ export class SorobanEventStreamer {
|
|
|
217
290
|
contractId,
|
|
218
291
|
limit = 10,
|
|
219
292
|
maxLookbackLedgers = 50000,
|
|
220
|
-
filters
|
|
293
|
+
filters,
|
|
294
|
+
signal
|
|
221
295
|
} = {}) {
|
|
222
296
|
if (limit <= 0) return [];
|
|
223
297
|
|
|
@@ -247,11 +321,12 @@ export class SorobanEventStreamer {
|
|
|
247
321
|
endExclusive - this.windowSize
|
|
248
322
|
);
|
|
249
323
|
|
|
250
|
-
const events = await this.
|
|
324
|
+
const events = await this.fetchWindowNewest(
|
|
251
325
|
start,
|
|
252
326
|
endExclusive,
|
|
253
327
|
effectiveFilters,
|
|
254
|
-
|
|
328
|
+
limit,
|
|
329
|
+
signal
|
|
255
330
|
);
|
|
256
331
|
|
|
257
332
|
for (let i = events.length - 1; i >= 0; i--) {
|