@friggframework/core 2.0.0--canary.579.4f65577.0 → 2.0.0--canary.579.2d1eba8.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/modules/requester/requester.js +106 -58
- package/package.json +5 -5
|
@@ -87,73 +87,121 @@ class Requester extends Delegate {
|
|
|
87
87
|
? { ...options, signal: controller.signal }
|
|
88
88
|
: options;
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
// Timer must stay active through body consumption. node-fetch v2
|
|
91
|
+
// resolves the fetch() promise when headers arrive, not when the
|
|
92
|
+
// body is fully read — so a server that sends headers and then
|
|
93
|
+
// stalls the body would still hang parsedBody() or
|
|
94
|
+
// FetchError.create()'s response.text() call. We clear the timer
|
|
95
|
+
// only after the body is fully consumed (success path) or
|
|
96
|
+
// deliberately before each recursive retry so the new attempt
|
|
97
|
+
// starts with its own fresh timer.
|
|
98
|
+
let timerCleared = false;
|
|
99
|
+
const clearRequestTimer = () => {
|
|
100
|
+
if (!timerCleared && timeoutHandle) {
|
|
101
|
+
clearTimeout(timeoutHandle);
|
|
102
|
+
timerCleared = true;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
91
106
|
try {
|
|
92
|
-
response
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
107
|
+
let response;
|
|
108
|
+
try {
|
|
109
|
+
response = await this.fetch(encodedUrl, fetchOptions);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
// AbortController fires AbortError (name) / ETIMEDOUT-shaped
|
|
112
|
+
// errors (type on node-fetch) when we hit the timeout. No
|
|
113
|
+
// retry on timeout: a slow endpoint is a downstream problem,
|
|
114
|
+
// and each retry would wait another `timeoutMs` before giving
|
|
115
|
+
// up — amplifying the hang into a per-record multi-minute
|
|
116
|
+
// stall at batch scale.
|
|
117
|
+
const isTimeout =
|
|
118
|
+
e?.name === 'AbortError' || e?.type === 'aborted';
|
|
119
|
+
if (e?.code === 'ECONNRESET' && i < this.backOff.length) {
|
|
120
|
+
clearRequestTimer();
|
|
121
|
+
const delay = this.backOff[i] * 1000;
|
|
122
|
+
await new Promise((resolve) =>
|
|
123
|
+
setTimeout(resolve, delay)
|
|
124
|
+
);
|
|
125
|
+
return this._request(url, options, i + 1);
|
|
126
|
+
}
|
|
127
|
+
const fetchError = await FetchError.create({
|
|
128
|
+
resource: encodedUrl,
|
|
129
|
+
init: options,
|
|
130
|
+
responseBody: isTimeout
|
|
131
|
+
? `Request timed out after ${timeoutMs}ms`
|
|
132
|
+
: e,
|
|
133
|
+
});
|
|
134
|
+
if (isTimeout) {
|
|
135
|
+
// Flag + machine-readable fields so callers can
|
|
136
|
+
// distinguish a timeout from a generic network error
|
|
137
|
+
// without parsing the message (which FetchError
|
|
138
|
+
// sanitizes outside of STAGE=dev).
|
|
139
|
+
fetchError.isTimeout = true;
|
|
140
|
+
fetchError.timeoutMs = timeoutMs;
|
|
141
|
+
}
|
|
142
|
+
throw fetchError;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const { status } = response;
|
|
146
|
+
|
|
147
|
+
// If the status is retriable and there are back off requests left, retry the request
|
|
148
|
+
if ((status === 429 || status >= 500) && i < this.backOff.length) {
|
|
149
|
+
clearRequestTimer();
|
|
103
150
|
const delay = this.backOff[i] * 1000;
|
|
104
151
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
105
152
|
return this._request(url, options, i + 1);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// distinguish a timeout from a generic network error
|
|
117
|
-
// without parsing the message (which FetchError
|
|
118
|
-
// sanitizes outside of STAGE=dev).
|
|
119
|
-
fetchError.isTimeout = true;
|
|
120
|
-
fetchError.timeoutMs = timeoutMs;
|
|
121
|
-
}
|
|
122
|
-
throw fetchError;
|
|
123
|
-
} finally {
|
|
124
|
-
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
125
|
-
}
|
|
126
|
-
const { status } = response;
|
|
127
|
-
|
|
128
|
-
// If the status is retriable and there are back off requests left, retry the request
|
|
129
|
-
if ((status === 429 || status >= 500) && i < this.backOff.length) {
|
|
130
|
-
const delay = this.backOff[i] * 1000;
|
|
131
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
132
|
-
return this._request(url, options, i + 1);
|
|
133
|
-
} else if (status === 401) {
|
|
134
|
-
if (!this.isRefreshable || this.refreshCount > 0) {
|
|
135
|
-
await this.notify(this.DLGT_INVALID_AUTH);
|
|
136
|
-
} else {
|
|
137
|
-
this.refreshCount++;
|
|
138
|
-
const refreshSucceeded = await this.refreshAuth();
|
|
139
|
-
if (refreshSucceeded) {
|
|
140
|
-
return this._request(url, options, i + 1);
|
|
153
|
+
} else if (status === 401) {
|
|
154
|
+
if (!this.isRefreshable || this.refreshCount > 0) {
|
|
155
|
+
await this.notify(this.DLGT_INVALID_AUTH);
|
|
156
|
+
} else {
|
|
157
|
+
this.refreshCount++;
|
|
158
|
+
const refreshSucceeded = await this.refreshAuth();
|
|
159
|
+
if (refreshSucceeded) {
|
|
160
|
+
clearRequestTimer();
|
|
161
|
+
return this._request(url, options, i + 1);
|
|
162
|
+
}
|
|
141
163
|
}
|
|
142
164
|
}
|
|
143
|
-
}
|
|
144
165
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
166
|
+
// If the error wasn't retried, throw. FetchError.create reads
|
|
167
|
+
// the response body (response.text()) — timer must still be
|
|
168
|
+
// alive to catch a stalled body stream.
|
|
169
|
+
if (status >= 400) {
|
|
170
|
+
const fetchError = await FetchError.create({
|
|
171
|
+
resource: encodedUrl,
|
|
172
|
+
init: options,
|
|
173
|
+
response,
|
|
174
|
+
});
|
|
175
|
+
throw this._maybeFlagTimeoutDuringBodyRead(
|
|
176
|
+
fetchError,
|
|
177
|
+
timeoutMs
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// parsedBody consumes the response body stream. If the server
|
|
182
|
+
// stalls mid-stream the timer (still armed) aborts it.
|
|
183
|
+
return options.returnFullRes
|
|
184
|
+
? response
|
|
185
|
+
: await this.parsedBody(response);
|
|
186
|
+
} catch (e) {
|
|
187
|
+
// If the abort fired during body consumption, node-fetch emits
|
|
188
|
+
// the error as an AbortError on the body stream. Surface the
|
|
189
|
+
// same isTimeout flag callers use for header-phase timeouts.
|
|
190
|
+
throw this._maybeFlagTimeoutDuringBodyRead(e, timeoutMs);
|
|
191
|
+
} finally {
|
|
192
|
+
clearRequestTimer();
|
|
152
193
|
}
|
|
194
|
+
}
|
|
153
195
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
196
|
+
_maybeFlagTimeoutDuringBodyRead(err, timeoutMs) {
|
|
197
|
+
if (!err || typeof err !== 'object') return err;
|
|
198
|
+
if (err.isTimeout) return err;
|
|
199
|
+
const isAbort =
|
|
200
|
+
err.name === 'AbortError' || err.type === 'aborted';
|
|
201
|
+
if (!isAbort) return err;
|
|
202
|
+
err.isTimeout = true;
|
|
203
|
+
err.timeoutMs = timeoutMs;
|
|
204
|
+
return err;
|
|
157
205
|
}
|
|
158
206
|
|
|
159
207
|
async _get(options) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/core",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0--canary.579.
|
|
4
|
+
"version": "2.0.0--canary.579.2d1eba8.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.588.0",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@friggframework/eslint-config": "2.0.0--canary.579.
|
|
42
|
-
"@friggframework/prettier-config": "2.0.0--canary.579.
|
|
43
|
-
"@friggframework/test": "2.0.0--canary.579.
|
|
41
|
+
"@friggframework/eslint-config": "2.0.0--canary.579.2d1eba8.0",
|
|
42
|
+
"@friggframework/prettier-config": "2.0.0--canary.579.2d1eba8.0",
|
|
43
|
+
"@friggframework/test": "2.0.0--canary.579.2d1eba8.0",
|
|
44
44
|
"@prisma/client": "^6.17.0",
|
|
45
45
|
"@types/lodash": "4.17.15",
|
|
46
46
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
@@ -80,5 +80,5 @@
|
|
|
80
80
|
"publishConfig": {
|
|
81
81
|
"access": "public"
|
|
82
82
|
},
|
|
83
|
-
"gitHead": "
|
|
83
|
+
"gitHead": "2d1eba87ebd7c2e83b0e95a87a41eb9037417694"
|
|
84
84
|
}
|