@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 +38 -8
- package/dist/constants.d.ts +12 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +14 -1
- package/dist/fetch/wayfinder-fetch.d.ts.map +1 -1
- package/dist/fetch/wayfinder-fetch.js +71 -36
- package/dist/gateways/network.d.ts +2 -2
- package/dist/gateways/network.d.ts.map +1 -1
- package/dist/gateways/network.js +15 -4
- package/dist/gateways/trusted-peers.d.ts +3 -1
- package/dist/gateways/trusted-peers.d.ts.map +1 -1
- package/dist/gateways/trusted-peers.js +7 -2
- package/dist/retrieval/chunk.d.ts +5 -1
- package/dist/retrieval/chunk.d.ts.map +1 -1
- package/dist/retrieval/chunk.js +9 -1
- package/dist/retrieval/contiguous.d.ts +3 -1
- package/dist/retrieval/contiguous.d.ts.map +1 -1
- package/dist/retrieval/contiguous.js +4 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -4
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
package/dist/constants.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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,
|
|
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
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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 {
|
|
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:
|
|
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,
|
|
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"}
|
package/dist/gateways/network.js
CHANGED
|
@@ -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:
|
|
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;
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
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, {
|
|
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
|
-
|
|
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;
|
|
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"}
|
package/dist/retrieval/chunk.js
CHANGED
|
@@ -24,9 +24,13 @@ import { defaultLogger } from '../logger.js';
|
|
|
24
24
|
export class ChunkDataRetrievalStrategy {
|
|
25
25
|
logger;
|
|
26
26
|
fetch;
|
|
27
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
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 = "
|
|
17
|
+
export declare const WAYFINDER_CORE_VERSION = "v2.0.1";
|
|
18
18
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ar.io/wayfinder-core",
|
|
3
|
-
"version": "
|
|
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
|
|
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": ">=
|
|
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": "^
|
|
77
|
+
"@ar.io/sdk": "^4.0.2",
|
|
77
78
|
"@types/node": "^24.0.0",
|
|
78
79
|
"tsx": "^4.20.3"
|
|
79
80
|
}
|