@ar.io/wayfinder-core 1.9.2 → 2.0.1

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
@@ -38,11 +38,18 @@ import {
38
38
  NetworkGatewaysProvider,
39
39
  } from '@ar.io/wayfinder-core';
40
40
  import { ARIO } from '@ar.io/sdk';
41
+ import { createSolanaRpc } from '@solana/kit';
42
+
43
+ // AR.IO Solana mainnet — program-id overrides are not needed on mainnet
44
+ // (the SDK ships defaults). For devnet, pass explicit program IDs.
45
+ const ario = ARIO.init({
46
+ rpc: createSolanaRpc('https://api.mainnet-beta.solana.com'),
47
+ });
41
48
 
42
49
  const wayfinder = createWayfinderClient({
43
50
  routingStrategy: new FastestPingRoutingStrategy({
44
51
  gatewaysProvider: new NetworkGatewaysProvider({
45
- ario: ARIO.mainnet(),
52
+ ario,
46
53
  sortBy: 'operatorStake',
47
54
  sortOrder: 'desc',
48
55
  limit: 10,
@@ -176,14 +183,19 @@ Gateway providers supply the list of gateways for routing. **By default, `create
176
183
 
177
184
  #### NetworkGatewaysProvider
178
185
 
179
- Returns a list of gateways from the ARIO Network based on on-chain [Gateway Address Registry](https://docs.ar.io/learn/gateways/gateway-registry). You can specify on-chain metrics for gateways to prioritize the highest quality gateways. This requires installing the `@ar.io/sdk` package and importing the `ARIO` object.
186
+ Returns a list of gateways from the ARIO Network based on on-chain [Gateway Address Registry](https://docs.ar.io/learn/gateways/gateway-registry). You can specify on-chain metrics for gateways to prioritize the highest quality gateways. Requires `@ar.io/sdk` and `@solana/kit`.
180
187
 
181
188
  ```javascript
182
189
  import { NetworkGatewaysProvider } from '@ar.io/wayfinder-core';
183
190
  import { ARIO } from '@ar.io/sdk';
191
+ import { createSolanaRpc } from '@solana/kit';
192
+
193
+ const ario = ARIO.init({
194
+ rpc: createSolanaRpc('https://api.mainnet-beta.solana.com'),
195
+ });
184
196
 
185
197
  const gatewayProvider = new NetworkGatewaysProvider({
186
- ario: ARIO.mainnet(),
198
+ ario,
187
199
  sortBy: 'operatorStake',
188
200
  sortOrder: 'desc',
189
201
  limit: 10,
@@ -222,13 +234,18 @@ import {
222
234
  TrustedPeersGatewaysProvider,
223
235
  } from '@ar.io/wayfinder-core';
224
236
  import { ARIO } from '@ar.io/sdk';
237
+ import { createSolanaRpc } from '@solana/kit';
238
+
239
+ const ario = ARIO.init({
240
+ rpc: createSolanaRpc('https://api.mainnet-beta.solana.com'),
241
+ });
225
242
 
226
243
  // Example: Network-first with static fallback
227
244
  const gatewayProvider = new CompositeGatewaysProvider({
228
245
  providers: [
229
246
  // Try fetching from AR.IO network first
230
247
  new NetworkGatewaysProvider({
231
- ario: ARIO.mainnet(),
248
+ ario,
232
249
  sortBy: 'operatorStake',
233
250
  limit: 10,
234
251
  }),
@@ -325,6 +342,11 @@ import {
325
342
  NetworkGatewaysProvider,
326
343
  } from '@ar.io/wayfinder-core';
327
344
  import { ARIO } from '@ar.io/sdk';
345
+ import { createSolanaRpc } from '@solana/kit';
346
+
347
+ const ario = ARIO.init({
348
+ rpc: createSolanaRpc('https://api.mainnet-beta.solana.com'),
349
+ });
328
350
 
329
351
  // Example 1: Performance-first with resilience fallback
330
352
  const performanceWayfinder = createWayfinderClient({
@@ -334,7 +356,7 @@ const performanceWayfinder = createWayfinderClient({
334
356
  new FastestPingRoutingStrategy({
335
357
  timeoutMs: 500,
336
358
  gatewaysProvider: new NetworkGatewaysProvider({
337
- ario: ARIO.mainnet(),
359
+ ario,
338
360
  sortBy: 'operatorStake',
339
361
  limit: 10,
340
362
  }),
@@ -342,7 +364,7 @@ const performanceWayfinder = createWayfinderClient({
342
364
  // Fallback to random selection (guaranteed to work if gateways exist)
343
365
  new RandomRoutingStrategy({
344
366
  gatewaysProvider: new NetworkGatewaysProvider({
345
- ario: ARIO.mainnet(),
367
+ ario,
346
368
  sortBy: 'operatorStake',
347
369
  limit: 20, // Use more gateways for fallback
348
370
  }),
@@ -363,7 +385,7 @@ const preferredWayfinder = createWayfinderClient({
363
385
  new FastestPingRoutingStrategy({
364
386
  timeoutMs: 1000,
365
387
  gatewaysProvider: new NetworkGatewaysProvider({
366
- ario: ARIO.mainnet(),
388
+ ario,
367
389
  sortBy: 'operatorStake',
368
390
  limit: 5, // Only top 5 gateways
369
391
  }),
@@ -371,7 +393,7 @@ const preferredWayfinder = createWayfinderClient({
371
393
  // Final fallback: any random gateway from a larger pool
372
394
  new RandomRoutingStrategy({
373
395
  gatewaysProvider: new NetworkGatewaysProvider({
374
- ario: ARIO.mainnet(),
396
+ ario,
375
397
  limit: 50, // Larger pool for maximum availability
376
398
  }),
377
399
  }),
@@ -693,6 +715,14 @@ const wayfinder = createWayfinderClient({
693
715
  });
694
716
  ```
695
717
 
718
+ ## Resiliency
719
+
720
+ Wayfinder includes built-in resiliency features:
721
+
722
+ - **Gateway retry**: If a gateway returns a 5xx error or a network failure occurs, Wayfinder automatically re-selects a different gateway and retries (up to 3 attempts). Client errors (4xx) are returned immediately without retry.
723
+ - **Fetch timeouts**: All outbound requests include configurable timeouts — 10s for metadata (HEAD, peer list), 30s for data retrieval — to prevent indefinite hangs on slow or dead gateways.
724
+ - **Smart pagination**: `NetworkGatewaysProvider` stops fetching from the on-chain registry once enough gateways pass the filter, avoiding unnecessary RPC calls.
725
+
696
726
  ## Request Flow
697
727
 
698
728
  ```mermaid
@@ -39,6 +39,9 @@ export declare const arioHeaderNames: {
39
39
  chunkTxId: string;
40
40
  chunkTxStartOffset: string;
41
41
  rootTransactionId: string;
42
+ rootPath: string;
43
+ rootItemOffset: string;
44
+ rootItemSize: string;
42
45
  dataItemDataOffset: string;
43
46
  dataItemRootParentOffset: string;
44
47
  dataItemOffset: string;
@@ -51,9 +54,17 @@ export declare const arioHeaderNames: {
51
54
  arnsRecord: string;
52
55
  arnsResolvedId: string;
53
56
  dataId: string;
54
- arnsProcessId: string;
57
+ arnsAntProgramId: string;
58
+ arnsAntId: string;
55
59
  arnsResolvedAt: string;
56
60
  arnsLimit: string;
57
61
  arnsIndex: string;
62
+ signatureInput: string;
63
+ signature: string;
64
+ arweaveOwner: string;
65
+ arweaveOwnerAddress: string;
66
+ arweaveSignature: string;
67
+ arweaveSignatureType: string;
68
+ arweaveTagCount: string;
58
69
  };
59
70
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,eAAO,MAAM,SAAS,QAA2C,CAAC;AAClE,eAAO,MAAM,SAAS,QAAwB,CAAC;AAG/C,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuC3B,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,eAAO,MAAM,SAAS,QAA2C,CAAC;AAClE,eAAO,MAAM,SAAS,QAAwB,CAAC;AAG/C,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoD3B,CAAC"}
package/dist/constants.js CHANGED
@@ -41,6 +41,9 @@ export const arioHeaderNames = {
41
41
  chunkTxId: 'X-Arweave-Chunk-Tx-Id',
42
42
  chunkTxStartOffset: 'X-Arweave-Chunk-Tx-Start-Offset',
43
43
  rootTransactionId: 'X-AR-IO-Root-Transaction-Id',
44
+ rootPath: 'X-AR-IO-Root-Path',
45
+ rootItemOffset: 'X-AR-IO-Root-Item-Offset',
46
+ rootItemSize: 'X-AR-IO-Root-Item-Size',
44
47
  dataItemDataOffset: 'X-AR-IO-Data-Item-Data-Offset',
45
48
  dataItemRootParentOffset: 'X-AR-IO-Data-Item-Root-Parent-Offset',
46
49
  dataItemOffset: 'X-AR-IO-Data-Item-Offset',
@@ -53,8 +56,18 @@ export const arioHeaderNames = {
53
56
  arnsRecord: 'X-ArNS-Record',
54
57
  arnsResolvedId: 'X-ArNS-Resolved-Id',
55
58
  dataId: 'X-AR-IO-Data-Id',
56
- arnsProcessId: 'X-ArNS-Process-Id',
59
+ arnsAntProgramId: 'X-ArNS-Ant-Program-Id',
60
+ arnsAntId: 'X-ArNS-Ant-Id',
57
61
  arnsResolvedAt: 'X-ArNS-Resolved-At',
58
62
  arnsLimit: 'X-ArNS-Undername-Limit',
59
63
  arnsIndex: 'X-ArNS-Record-Index',
64
+ // HTTPSIG headers (RFC 9421)
65
+ signatureInput: 'Signature-Input',
66
+ signature: 'Signature',
67
+ // Arweave transaction metadata
68
+ arweaveOwner: 'X-Arweave-Owner',
69
+ arweaveOwnerAddress: 'X-Arweave-Owner-Address',
70
+ arweaveSignature: 'X-Arweave-Signature',
71
+ arweaveSignatureType: 'X-Arweave-Signature-Type',
72
+ arweaveTagCount: 'X-Arweave-Tag-Count',
60
73
  };
@@ -1 +1 @@
1
- {"version":3,"file":"wayfinder-fetch.d.ts","sourceRoot":"","sources":["../../src/fetch/wayfinder-fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH,OAAO,KAAK,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAS/E;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,oBAAoB,GAAI,mHAYlC,qBAAqB,KAAG,CAAC,CAC1B,KAAK,EAAE,GAAG,GAAG,WAAW,EACxB,IAAI,CAAC,EAAE,oBAAoB,KACxB,OAAO,CAAC,QAAQ,CAAC,CA4MrB,CAAC"}
1
+ {"version":3,"file":"wayfinder-fetch.d.ts","sourceRoot":"","sources":["../../src/fetch/wayfinder-fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH,OAAO,KAAK,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAS/E;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,oBAAoB,GAAI,mHAYlC,qBAAqB,KAAG,CAAC,CAC1B,KAAK,EAAE,GAAG,GAAG,WAAW,EACxB,IAAI,CAAC,EAAE,oBAAoB,KACxB,OAAO,CAAC,QAAQ,CAAC,CA0PrB,CAAC"}
@@ -94,43 +94,78 @@ export const createWayfinderFetch = ({ logger = defaultLogger, strict = false, f
94
94
  subdomain,
95
95
  path,
96
96
  });
97
- // Select gateway using routing strategy
98
- const selectedGateway = await routingStrategy.selectGateway({
99
- path,
100
- subdomain,
101
- });
102
- // it's just a non data specific request, construct the gateway URL and fetch directly
103
- const redirectUrl = constructGatewayUrl({
104
- selectedGateway,
105
- subdomain,
106
- path,
107
- });
108
- // Emit routing succeeded event
109
- requestEmitter.emit('routing-succeeded', {
110
- originalUrl: requestUri,
111
- selectedGateway: selectedGateway.toString(),
112
- redirectUrl: redirectUrl.toString(),
113
- });
114
- // if its a txId or arnsName use the dataRetrievalStrategy to fetch the data; otherwise just call internal fetch
115
- if (!txId && !arnsName) {
116
- logger.debug('No transaction ID or ARNS name found, performing direct fetch', {
117
- uri: requestUri,
118
- });
119
- return fetch(redirectUrl.toString(), init);
97
+ // Attempt data retrieval with gateway retry. If the selected gateway
98
+ // fails (network error, 5xx, timeout), re-select and try again up to
99
+ // maxAttempts times. 4xx responses are NOT retried (client error).
100
+ const maxAttempts = 3;
101
+ let dataResponse;
102
+ let selectedGateway;
103
+ let redirectUrl;
104
+ let lastError;
105
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
106
+ try {
107
+ selectedGateway = await routingStrategy.selectGateway({
108
+ path,
109
+ subdomain,
110
+ });
111
+ redirectUrl = constructGatewayUrl({
112
+ selectedGateway,
113
+ subdomain,
114
+ path,
115
+ });
116
+ // Emit routing succeeded event
117
+ requestEmitter.emit('routing-succeeded', {
118
+ originalUrl: requestUri,
119
+ selectedGateway: selectedGateway.toString(),
120
+ redirectUrl: redirectUrl.toString(),
121
+ });
122
+ // Non-data requests (e.g. /ar-io/info) don't need retry
123
+ if (!txId && !arnsName) {
124
+ logger.debug('No transaction ID or ARNS name found, performing direct fetch', { uri: requestUri });
125
+ return fetch(redirectUrl.toString(), init);
126
+ }
127
+ const requestHeaders = {
128
+ ...Object.fromEntries(new Headers(init?.headers || {})),
129
+ ...createWayfinderRequestHeaders({
130
+ traceId: requestSpan?.spanContext().traceId,
131
+ }),
132
+ };
133
+ dataResponse = await dataRetrievalStrategy.getData({
134
+ gateway: selectedGateway,
135
+ requestUrl: redirectUrl,
136
+ headers: requestHeaders,
137
+ });
138
+ // 4xx = client error, don't retry; 2xx/3xx = success
139
+ if (dataResponse.ok ||
140
+ (dataResponse.status >= 400 && dataResponse.status < 500)) {
141
+ break;
142
+ }
143
+ // 5xx = server error, retry with different gateway
144
+ logger.warn('Gateway returned server error, retrying with different gateway', {
145
+ uri: requestUri,
146
+ gateway: selectedGateway.toString(),
147
+ status: dataResponse.status,
148
+ attempt: attempt + 1,
149
+ maxAttempts,
150
+ });
151
+ }
152
+ catch (error) {
153
+ lastError = error;
154
+ if (attempt < maxAttempts - 1) {
155
+ logger.warn('Gateway request failed, retrying with different gateway', {
156
+ uri: requestUri,
157
+ gateway: selectedGateway?.toString(),
158
+ error: error.message,
159
+ attempt: attempt + 1,
160
+ maxAttempts,
161
+ });
162
+ }
163
+ }
164
+ }
165
+ // All attempts exhausted
166
+ if (!dataResponse) {
167
+ throw lastError ?? new Error('All gateway attempts failed');
120
168
  }
121
- const requestHeaders = {
122
- ...Object.fromEntries(new Headers(init?.headers || {})),
123
- ...createWayfinderRequestHeaders({
124
- traceId: requestSpan?.spanContext().traceId,
125
- }),
126
- };
127
- // Use data retrieval strategy to fetch the actual data
128
- const dataResponse = await dataRetrievalStrategy.getData({
129
- gateway: selectedGateway,
130
- requestUrl: redirectUrl,
131
- headers: requestHeaders,
132
- });
133
- // If the response is not successful (e.g., 404, 500), return it directly
134
169
  if (!dataResponse.ok) {
135
170
  logger.debug('Gateway returned error response', {
136
171
  uri: requestUri,
@@ -14,7 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- import type { AoARIORead } from '@ar.io/sdk';
17
+ import type { ARIORead } from '@ar.io/sdk';
18
18
  import type { GatewaysProvider, Logger, SortBy, SortOrder } from '../types.js';
19
19
  export declare class NetworkGatewaysProvider implements GatewaysProvider {
20
20
  private ario;
@@ -24,7 +24,7 @@ export declare class NetworkGatewaysProvider implements GatewaysProvider {
24
24
  private filter;
25
25
  private logger;
26
26
  constructor({ ario, sortBy, sortOrder, limit, filter, logger, }: {
27
- ario: AoARIORead;
27
+ ario: ARIORead;
28
28
  sortBy?: SortBy;
29
29
  sortOrder?: SortOrder;
30
30
  limit?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"network.d.ts","sourceRoot":"","sources":["../../src/gateways/network.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE/E,qBAAa,uBAAwB,YAAW,gBAAgB;IAC9D,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,MAAM,CAAS;gBAEX,EACV,IAAI,EACJ,MAAwB,EACxB,SAAkB,EAClB,KAAY,EACZ,MAAqC,EACrC,MAAsB,GACvB,EAAE;QACD,IAAI,EAAE,UAAU,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,SAAS,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QACrB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC;QACnC,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB;IASK,WAAW,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;CA2DpC"}
1
+ {"version":3,"file":"network.d.ts","sourceRoot":"","sources":["../../src/gateways/network.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE/E,qBAAa,uBAAwB,YAAW,gBAAgB;IAC9D,OAAO,CAAC,IAAI,CAAW;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,MAAM,CAAS;gBAEX,EACV,IAAI,EACJ,MAAwB,EACxB,SAAkB,EAClB,KAAY,EACZ,MAAqC,EACrC,MAAsB,GACvB,EAAE;QACD,IAAI,EAAE,QAAQ,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,SAAS,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QACrB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC;QACnC,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB;IASK,WAAW,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;CAwEpC"}
@@ -23,24 +23,36 @@ export class NetworkGatewaysProvider {
23
23
  sortOrder: this.sortOrder,
24
24
  limit: this.limit,
25
25
  });
26
+ // Fetch enough gateways to satisfy the limit after filtering.
27
+ // Request up to limit per page (capped at 1000 by the SDK) and
28
+ // stop paginating once we have enough filtered results.
29
+ const pageSize = Math.min(this.limit, 1000);
26
30
  do {
27
31
  try {
28
32
  this.logger.debug('Fetching gateways batch', { cursor, attempts });
29
33
  const { items: newGateways = [], nextCursor } = await this.ario.getGateways({
30
- limit: 1000,
34
+ limit: pageSize,
31
35
  cursor,
32
36
  sortBy: this.sortBy,
33
37
  sortOrder: this.sortOrder,
34
- // TODO: support filters on gateways
35
38
  });
36
39
  gateways.push(...newGateways);
37
40
  cursor = nextCursor;
38
- attempts = 0; // reset attempts if we get a new cursor
41
+ attempts = 0;
39
42
  this.logger.debug('Fetched gateways batch', {
40
43
  batchSize: newGateways.length,
41
44
  totalFetched: gateways.length,
42
45
  nextCursor: cursor,
43
46
  });
47
+ // Stop early if we already have enough gateways that pass the filter
48
+ const filteredSoFar = gateways.filter(this.filter);
49
+ if (filteredSoFar.length >= this.limit) {
50
+ this.logger.debug('Reached gateway limit, stopping pagination', {
51
+ filteredCount: filteredSoFar.length,
52
+ limit: this.limit,
53
+ });
54
+ break;
55
+ }
44
56
  }
45
57
  catch (error) {
46
58
  this.logger.error('Error fetching gateways', {
@@ -52,7 +64,6 @@ export class NetworkGatewaysProvider {
52
64
  attempts++;
53
65
  }
54
66
  } while (cursor !== undefined && attempts < 3);
55
- // filter out any gateways that are not joined
56
67
  const filteredGateways = gateways.filter(this.filter).slice(0, this.limit);
57
68
  this.logger.debug('Finished fetching gateways', {
58
69
  totalFetched: gateways.length,
@@ -23,9 +23,11 @@ import type { GatewaysProvider, Logger } from '../types.js';
23
23
  export declare class TrustedPeersGatewaysProvider implements GatewaysProvider {
24
24
  private trustedGateway;
25
25
  private logger;
26
- constructor({ trustedGateway, logger, }: {
26
+ private timeoutMs;
27
+ constructor({ trustedGateway, logger, timeoutMs, }: {
27
28
  trustedGateway: string | URL;
28
29
  logger?: Logger;
30
+ timeoutMs?: number;
29
31
  });
30
32
  getGateways(): Promise<URL[]>;
31
33
  }
@@ -1 +1 @@
1
- {"version":3,"file":"trusted-peers.d.ts","sourceRoot":"","sources":["../../src/gateways/trusted-peers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH;;;;GAIG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE5D,qBAAa,4BAA6B,YAAW,gBAAgB;IACnE,OAAO,CAAC,cAAc,CAAM;IAC5B,OAAO,CAAC,MAAM,CAAS;gBAEX,EACV,cAAc,EACd,MAAsB,GACvB,EAAE;QAAE,cAAc,EAAE,MAAM,GAAG,GAAG,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;IAK9C,WAAW,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;CAoCpC"}
1
+ {"version":3,"file":"trusted-peers.d.ts","sourceRoot":"","sources":["../../src/gateways/trusted-peers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH;;;;GAIG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE5D,qBAAa,4BAA6B,YAAW,gBAAgB;IACnE,OAAO,CAAC,cAAc,CAAM;IAC5B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAS;gBAEd,EACV,cAAc,EACd,MAAsB,EACtB,SAAkB,GACnB,EAAE;QAAE,cAAc,EAAE,MAAM,GAAG,GAAG,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;IAMlE,WAAW,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;CAuCpC"}
@@ -23,14 +23,19 @@ import { defaultLogger } from '../logger.js';
23
23
  export class TrustedPeersGatewaysProvider {
24
24
  trustedGateway;
25
25
  logger;
26
- constructor({ trustedGateway, logger = defaultLogger, }) {
26
+ timeoutMs;
27
+ constructor({ trustedGateway, logger = defaultLogger, timeoutMs = 10_000, }) {
27
28
  this.trustedGateway = new URL(trustedGateway.toString());
28
29
  this.logger = logger;
30
+ this.timeoutMs = timeoutMs;
29
31
  }
30
32
  async getGateways() {
31
33
  const endpoint = new URL('/ar-io/peers', this.trustedGateway).toString();
32
34
  this.logger.debug('Fetching trusted peer list from', { endpoint });
33
- const response = await fetch(endpoint, { method: 'GET' });
35
+ const response = await fetch(endpoint, {
36
+ method: 'GET',
37
+ signal: AbortSignal.timeout(this.timeoutMs),
38
+ });
34
39
  if (!response.ok) {
35
40
  throw new Error(`Failed to fetch trusted peer list: ${response.status} ${response.statusText} ${await response.text()}`);
36
41
  }
@@ -23,9 +23,13 @@ import type { DataRetrievalStrategy, Logger } from '../types.js';
23
23
  export declare class ChunkDataRetrievalStrategy implements DataRetrievalStrategy {
24
24
  private logger;
25
25
  private fetch;
26
- constructor({ logger, fetch, }?: {
26
+ private metadataTimeoutMs;
27
+ private chunkTimeoutMs;
28
+ constructor({ logger, fetch, metadataTimeoutMs, chunkTimeoutMs, }?: {
27
29
  logger?: Logger;
28
30
  fetch?: typeof globalThis.fetch;
31
+ metadataTimeoutMs?: number;
32
+ chunkTimeoutMs?: number;
29
33
  });
30
34
  getData({ gateway, requestUrl, headers, }: {
31
35
  gateway: URL;
@@ -1 +1 @@
1
- {"version":3,"file":"chunk.d.ts","sourceRoot":"","sources":["../../src/retrieval/chunk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAEjE;;;;GAIG;AACH,qBAAa,0BAA2B,YAAW,qBAAqB;IACtE,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAA0B;gBAE3B,EACV,MAAsB,EACtB,KAAwB,GACzB,GAAE;QACD,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;KAC5B;IAKA,OAAO,CAAC,EACZ,OAAO,EACP,UAAU,EACV,OAAO,GACR,EAAE;QACD,OAAO,EAAE,GAAG,CAAC;QACb,UAAU,EAAE,GAAG,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAClC,GAAG,OAAO,CAAC,QAAQ,CAAC;CAwOtB"}
1
+ {"version":3,"file":"chunk.d.ts","sourceRoot":"","sources":["../../src/retrieval/chunk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAEjE;;;;GAIG;AACH,qBAAa,0BAA2B,YAAW,qBAAqB;IACtE,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,cAAc,CAAS;gBAEnB,EACV,MAAsB,EACtB,KAAwB,EACxB,iBAA0B,EAC1B,cAAuB,GACxB,GAAE;QACD,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;QAChC,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;KACpB;IAOA,OAAO,CAAC,EACZ,OAAO,EACP,UAAU,EACV,OAAO,GACR,EAAE;QACD,OAAO,EAAE,GAAG,CAAC;QACb,UAAU,EAAE,GAAG,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAClC,GAAG,OAAO,CAAC,QAAQ,CAAC;CA4OtB"}
@@ -24,9 +24,13 @@ import { defaultLogger } from '../logger.js';
24
24
  export class ChunkDataRetrievalStrategy {
25
25
  logger;
26
26
  fetch;
27
- constructor({ logger = defaultLogger, fetch = globalThis.fetch, } = {}) {
27
+ metadataTimeoutMs;
28
+ chunkTimeoutMs;
29
+ constructor({ logger = defaultLogger, fetch = globalThis.fetch, metadataTimeoutMs = 10_000, chunkTimeoutMs = 30_000, } = {}) {
28
30
  this.logger = logger;
29
31
  this.fetch = fetch;
32
+ this.metadataTimeoutMs = metadataTimeoutMs;
33
+ this.chunkTimeoutMs = chunkTimeoutMs;
30
34
  }
31
35
  async getData({ gateway, requestUrl, headers, }) {
32
36
  this.logger.debug('Fetching data via ChunkDataRetrievalStrategy from gateway', {
@@ -35,6 +39,7 @@ export class ChunkDataRetrievalStrategy {
35
39
  });
36
40
  const headResponse = await this.fetch(requestUrl.toString(), {
37
41
  method: 'HEAD',
42
+ signal: AbortSignal.timeout(this.metadataTimeoutMs),
38
43
  headers,
39
44
  });
40
45
  if (!headResponse.ok) {
@@ -56,6 +61,7 @@ export class ChunkDataRetrievalStrategy {
56
61
  const offsetResponse = await this.fetch(offsetForRootTransactionIdUrl.toString(), {
57
62
  method: 'GET',
58
63
  redirect: 'follow',
64
+ signal: AbortSignal.timeout(this.metadataTimeoutMs),
59
65
  headers,
60
66
  });
61
67
  if (!offsetResponse.ok) {
@@ -86,6 +92,7 @@ export class ChunkDataRetrievalStrategy {
86
92
  const logger = this.logger;
87
93
  const fetchFn = this.fetch;
88
94
  const chunkGateway = gateway;
95
+ const chunkTimeoutMs = this.chunkTimeoutMs;
89
96
  // Create a readable stream that fetches chunks on demand
90
97
  const stream = new ReadableStream({
91
98
  async start(controller) {
@@ -103,6 +110,7 @@ export class ChunkDataRetrievalStrategy {
103
110
  const chunkResponse = await fetchFn(chunkUrl.toString(), {
104
111
  method: 'GET',
105
112
  redirect: 'follow',
113
+ signal: AbortSignal.timeout(chunkTimeoutMs),
106
114
  headers,
107
115
  });
108
116
  if (!chunkResponse.ok) {
@@ -22,9 +22,11 @@ import type { DataRetrievalStrategy, Logger } from '../types.js';
22
22
  export declare class ContiguousDataRetrievalStrategy implements DataRetrievalStrategy {
23
23
  private logger;
24
24
  private fetch;
25
- constructor({ logger, fetch, }?: {
25
+ private timeoutMs;
26
+ constructor({ logger, fetch, timeoutMs, }?: {
26
27
  logger?: Logger;
27
28
  fetch?: typeof globalThis.fetch;
29
+ timeoutMs?: number;
28
30
  });
29
31
  getData({ requestUrl, headers, }: {
30
32
  gateway: URL;
@@ -1 +1 @@
1
- {"version":3,"file":"contiguous.d.ts","sourceRoot":"","sources":["../../src/retrieval/contiguous.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAEjE;;;GAGG;AACH,qBAAa,+BAAgC,YAAW,qBAAqB;IAC3E,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAA0B;gBAE3B,EACV,MAAsB,EACtB,KAAwB,GACzB,GAAE;QACD,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;KAC5B;IAKA,OAAO,CAAC,EACZ,UAAU,EACV,OAAO,GACR,EAAE;QACD,OAAO,EAAE,GAAG,CAAC;QACb,UAAU,EAAE,GAAG,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAClC,GAAG,OAAO,CAAC,QAAQ,CAAC;CAatB"}
1
+ {"version":3,"file":"contiguous.d.ts","sourceRoot":"","sources":["../../src/retrieval/contiguous.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAEjE;;;GAGG;AACH,qBAAa,+BAAgC,YAAW,qBAAqB;IAC3E,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,SAAS,CAAS;gBAEd,EACV,MAAsB,EACtB,KAAwB,EACxB,SAAkB,GACnB,GAAE;QACD,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;QAChC,SAAS,CAAC,EAAE,MAAM,CAAC;KACf;IAMA,OAAO,CAAC,EACZ,UAAU,EACV,OAAO,GACR,EAAE;QACD,OAAO,EAAE,GAAG,CAAC;QACb,UAAU,EAAE,GAAG,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAClC,GAAG,OAAO,CAAC,QAAQ,CAAC;CActB"}
@@ -22,9 +22,11 @@ import { defaultLogger } from '../logger.js';
22
22
  export class ContiguousDataRetrievalStrategy {
23
23
  logger;
24
24
  fetch;
25
- constructor({ logger = defaultLogger, fetch = globalThis.fetch, } = {}) {
25
+ timeoutMs;
26
+ constructor({ logger = defaultLogger, fetch = globalThis.fetch, timeoutMs = 30_000, } = {}) {
26
27
  this.logger = logger;
27
28
  this.fetch = fetch;
29
+ this.timeoutMs = timeoutMs;
28
30
  }
29
31
  async getData({ requestUrl, headers, }) {
30
32
  this.logger.debug('Fetching contiguous transaction data', {
@@ -32,6 +34,7 @@ export class ContiguousDataRetrievalStrategy {
32
34
  });
33
35
  return this.fetch(requestUrl.toString(), {
34
36
  method: 'GET',
37
+ signal: AbortSignal.timeout(this.timeoutMs),
35
38
  headers: {
36
39
  ...headers,
37
40
  'x-wayfinder-data-retrieval-strategy': 'contiguous',
package/dist/version.d.ts CHANGED
@@ -14,5 +14,5 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- export declare const WAYFINDER_CORE_VERSION = "v1.9.2";
17
+ export declare const WAYFINDER_CORE_VERSION = "v2.0.1";
18
18
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -14,4 +14,4 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- export const WAYFINDER_CORE_VERSION = 'v1.9.2';
17
+ export const WAYFINDER_CORE_VERSION = 'v2.0.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ar.io/wayfinder-core",
3
- "version": "1.9.2",
3
+ "version": "2.0.1",
4
4
  "description": "WayFinder core library for intelligently routing to optimal AR.IO gateways",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -44,7 +44,8 @@
44
44
  "clean": "rimraf dist",
45
45
  "update-version": "node scripts/update-version.mjs",
46
46
  "test": "npm run test:unit",
47
- "test:unit": "c8 tsx --test 'src/**/*.test.ts'",
47
+ "test:unit": "c8 tsx --test 'src/client.test.ts' 'src/routing/*.test.ts' 'src/gateways/*.test.ts' 'src/retrieval/*.test.ts' 'src/verification/*.test.ts' 'src/utils/*.test.ts'",
48
+ "test:integration": "c8 tsx --test 'src/wayfinder.test.ts'",
48
49
  "lint:fix": "biome check --write --unsafe --config-path=../../biome.json",
49
50
  "lint:check": "biome check --unsafe --config-path=../../biome.json",
50
51
  "format:fix": "biome format --write --config-path=../../biome.json",
@@ -65,7 +66,7 @@
65
66
  "zone.js": "^0.15.1"
66
67
  },
67
68
  "peerDependencies": {
68
- "@ar.io/sdk": ">=3.12.0"
69
+ "@ar.io/sdk": ">=4.0.0"
69
70
  },
70
71
  "peerDependenciesMeta": {
71
72
  "@ar.io/sdk": {
@@ -73,7 +74,7 @@
73
74
  }
74
75
  },
75
76
  "devDependencies": {
76
- "@ar.io/sdk": "^3.13.0",
77
+ "@ar.io/sdk": "^4.0.2",
77
78
  "@types/node": "^24.0.0",
78
79
  "tsx": "^4.20.3"
79
80
  }