@hiero-ledger/sdk 2.74.0 → 2.75.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.
@@ -49,8 +49,15 @@ import {
49
49
  * description: string,
50
50
  * stake: number
51
51
  * }>} nodes
52
+ * @property {?{next: ?string}} links - Links object containing pagination information
52
53
  */
53
54
 
55
+ /**
56
+ * Default page size limit for optimal pagination performance
57
+ * @constant {number}
58
+ */
59
+ const DEFAULT_PAGE_SIZE = 25;
60
+
54
61
  /**
55
62
  * Web-compatible query to get a list of Hedera network node addresses from a mirror node.
56
63
  * Uses fetch API instead of gRPC for web environments.
@@ -65,7 +72,7 @@ export default class AddressBookQueryWeb extends Query {
65
72
  /**
66
73
  * @param {object} props
67
74
  * @param {FileId | string} [props.fileId]
68
- * @param {number} [props.limit]
75
+ * @param {number} [props.limit] - Page size limit (defaults to 25 for optimal performance)
69
76
  */
70
77
  constructor(props = {}) {
71
78
  super();
@@ -232,111 +239,135 @@ export default class AddressBookQueryWeb extends Query {
232
239
  baseUrl = `${baseUrl}:${port}`;
233
240
  }
234
241
 
235
- const url = new URL(`${baseUrl}/api/v1/network/nodes`);
242
+ // Initialize aggregated results
243
+ this._addresses = [];
244
+ let nextUrl = null;
245
+ let isLastPage = false;
236
246
 
247
+ // Build initial URL
248
+ const initialUrl = new URL(`${baseUrl}/api/v1/network/nodes`);
237
249
  if (this._fileId != null) {
238
- url.searchParams.append("file.id", this._fileId.toString());
239
- }
240
- if (this._limit != null) {
241
- url.searchParams.append("limit", this._limit.toString());
250
+ initialUrl.searchParams.append("file.id", this._fileId.toString());
242
251
  }
243
252
 
244
- for (let attempt = 0; attempt <= this._maxAttempts; attempt++) {
245
- try {
246
- // eslint-disable-next-line n/no-unsupported-features/node-builtins
247
- const response = await fetch(url.toString(), {
248
- method: "GET",
249
- headers: {
250
- Accept: "application/json",
251
- },
252
- signal: requestTimeout
253
- ? AbortSignal.timeout(requestTimeout)
254
- : undefined,
255
- });
253
+ // Use the specified limit, or default to DEFAULT_PAGE_SIZE for optimal pagination performance
254
+ const effectiveLimit =
255
+ this._limit != null ? this._limit : DEFAULT_PAGE_SIZE;
256
+ initialUrl.searchParams.append("limit", effectiveLimit.toString());
257
+
258
+ // Fetch all pages
259
+ while (!isLastPage) {
260
+ const currentUrl = nextUrl ? new URL(nextUrl, baseUrl) : initialUrl;
261
+
262
+ for (let attempt = 0; attempt <= this._maxAttempts; attempt++) {
263
+ try {
264
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
265
+ const response = await fetch(currentUrl.toString(), {
266
+ method: "GET",
267
+ headers: {
268
+ Accept: "application/json",
269
+ },
270
+ signal: requestTimeout
271
+ ? AbortSignal.timeout(requestTimeout)
272
+ : undefined,
273
+ });
274
+
275
+ if (!response.ok) {
276
+ throw new Error(
277
+ `HTTP error! status: ${response.status}`,
278
+ );
279
+ }
256
280
 
257
- if (!response.ok) {
258
- throw new Error(`HTTP error! status: ${response.status}`);
259
- }
281
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
282
+ const data = /** @type {AddressBookQueryWebResponse} */ (
283
+ await response.json()
284
+ );
260
285
 
261
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
262
- const data = /** @type {AddressBookQueryWebResponse} */ (
263
- await response.json()
264
- );
265
-
266
- const nodes = data.nodes || [];
267
-
268
- // eslint-disable-next-line ie11/no-loop-func
269
- this._addresses = nodes.map((node) =>
270
- NodeAddress.fromJSON({
271
- nodeId: node.node_id.toString(),
272
- accountId: node.node_account_id,
273
- addresses: this._handleAddressesFromGrpcProxyEndpoint(
274
- node,
275
- client,
276
- ),
277
- certHash: node.node_cert_hash,
278
- publicKey: node.public_key,
279
- description: node.description,
280
- stake: node.stake.toString(),
281
- }),
282
- );
283
-
284
- const addressBook = new NodeAddressBook({
285
- nodeAddresses: this._addresses,
286
- });
287
-
288
- resolve(addressBook);
289
- return;
290
- } catch (error) {
291
- console.error("Error in _makeFetchRequest:", error);
292
- const message =
293
- error instanceof Error ? error.message : String(error);
294
-
295
- // Check if we should retry
296
- if (
297
- attempt < this._maxAttempts &&
298
- !client.isClientShutDown &&
299
- this._retryHandler(
300
- /** @type {MirrorError | Error | null} */ (error),
301
- )
302
- ) {
303
- const delay = Math.min(
304
- 250 * 2 ** attempt,
305
- this._maxBackoff,
286
+ const nodes = data.nodes || [];
287
+
288
+ // Aggregate nodes from this page
289
+ const pageNodes = nodes.map((node) =>
290
+ NodeAddress.fromJSON({
291
+ nodeId: node.node_id.toString(),
292
+ accountId: node.node_account_id,
293
+ addresses:
294
+ this._handleAddressesFromGrpcProxyEndpoint(
295
+ node,
296
+ client,
297
+ ),
298
+ certHash: node.node_cert_hash,
299
+ publicKey: node.public_key,
300
+ description: node.description,
301
+ stake: node.stake.toString(),
302
+ }),
306
303
  );
307
304
 
308
- if (this._logger) {
309
- this._logger.debug(
310
- `Error getting nodes from mirror for file ${
311
- this._fileId != null
312
- ? this._fileId.toString()
313
- : "UNKNOWN"
314
- } during attempt ${
315
- attempt + 1
316
- }. Waiting ${delay} ms before next attempt: ${message}`,
305
+ this._addresses.push(...pageNodes);
306
+ nextUrl = data.links?.next || null;
307
+
308
+ // If no more pages, set flag to exit loop
309
+ if (!nextUrl) {
310
+ isLastPage = true;
311
+ }
312
+
313
+ // Move to next page
314
+ break;
315
+ } catch (error) {
316
+ console.error("Error in _makeFetchRequest:", error);
317
+ const message =
318
+ error instanceof Error ? error.message : String(error);
319
+
320
+ // Check if we should retry
321
+ if (
322
+ attempt < this._maxAttempts &&
323
+ !client.isClientShutDown &&
324
+ this._retryHandler(
325
+ /** @type {MirrorError | Error | null} */ (error),
326
+ )
327
+ ) {
328
+ const delay = Math.min(
329
+ 250 * 2 ** attempt,
330
+ this._maxBackoff,
317
331
  );
332
+
333
+ if (this._logger) {
334
+ this._logger.debug(
335
+ `Error getting nodes from mirror for file ${
336
+ this._fileId != null
337
+ ? this._fileId.toString()
338
+ : "UNKNOWN"
339
+ } during attempt ${
340
+ attempt + 1
341
+ }. Waiting ${delay} ms before next attempt: ${message}`,
342
+ );
343
+ }
344
+
345
+ // Wait before next attempt
346
+ // eslint-disable-next-line ie11/no-loop-func
347
+ await new Promise((resolve) =>
348
+ setTimeout(resolve, delay),
349
+ );
350
+ continue;
318
351
  }
319
352
 
320
- // Wait before next attempt
321
- // eslint-disable-next-line ie11/no-loop-func
322
- await new Promise((resolve) => setTimeout(resolve, delay));
323
- continue;
353
+ // If we shouldn't retry or have exhausted attempts, reject
354
+ const maxAttemptsReached = attempt >= this._maxAttempts;
355
+ const errorMessage = maxAttemptsReached
356
+ ? `Failed to query address book after ${
357
+ this._maxAttempts + 1
358
+ } attempts. Last error: ${message}`
359
+ : `Failed to query address book: ${message}`;
360
+ reject(new Error(errorMessage));
361
+ return;
324
362
  }
325
-
326
- // If we shouldn't retry or have exhausted attempts, reject
327
- const maxAttemptsReached = attempt >= this._maxAttempts;
328
- const errorMessage = maxAttemptsReached
329
- ? `Failed to query address book after ${
330
- this._maxAttempts + 1
331
- } attempts. Last error: ${message}`
332
- : `Failed to query address book: ${message}`;
333
- reject(new Error(errorMessage));
334
- return;
335
363
  }
336
364
  }
337
365
 
338
- // This should never be reached, but just in case
339
- reject(new Error("failed to query address book"));
366
+ // Return the aggregated results
367
+ const addressBook = new NodeAddressBook({
368
+ nodeAddresses: this._addresses,
369
+ });
370
+ resolve(addressBook);
340
371
  }
341
372
 
342
373
  /**