@ar.io/wayfinder-core 2.0.0 → 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
|
@@ -715,6 +715,14 @@ const wayfinder = createWayfinderClient({
|
|
|
715
715
|
});
|
|
716
716
|
```
|
|
717
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
|
+
|
|
718
726
|
## Request Flow
|
|
719
727
|
|
|
720
728
|
```mermaid
|
|
@@ -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,
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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,
|
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 = "v2.0.
|
|
17
|
+
export declare const WAYFINDER_CORE_VERSION = "v2.0.1";
|
|
18
18
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED