async-lube 2.0.0 → 2.1.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/CHANGELOG.md +34 -0
- package/README.md +1 -1
- package/docs/http.md +1 -1
- package/docs/reference.md +4 -2
- package/docs/tasks.md +19 -2
- package/package.json +4 -5
- package/public.d.ts +112 -0
- package/src/attempt.js +17 -7
- package/src/durable/context.js +107 -64
- package/src/durable/error.js +39 -15
- package/src/durable/index.js +18 -10
- package/src/durable/journal.js +29 -14
- package/src/durable/json.js +26 -9
- package/src/durable/lease.js +15 -5
- package/src/durable/persist.js +60 -22
- package/src/durable/run.js +103 -41
- package/src/durable/send.js +30 -13
- package/src/durable/serve.js +30 -24
- package/src/durable/state.js +28 -13
- package/src/durable/store.js +19 -10
- package/src/durable/watch.js +31 -11
- package/src/errors.js +9 -3
- package/src/flow/attempts.js +49 -29
- package/src/flow/builder.js +260 -161
- package/src/flow/compile.js +86 -66
- package/src/flow/graph.js +108 -42
- package/src/flow/index.js +13 -7
- package/src/flow/items.js +79 -46
- package/src/flow/nodes.js +267 -117
- package/src/flow/refs.js +27 -13
- package/src/flow/resources.js +9 -3
- package/src/flow/run.js +287 -114
- package/src/flow/streams.js +44 -19
- package/src/flow/subflows.js +93 -53
- package/src/http/config.js +11 -5
- package/src/http/index.js +25 -14
- package/src/http/refresh.js +29 -12
- package/src/http/request.js +246 -151
- package/src/http/response.js +105 -50
- package/src/http/retry.js +52 -27
- package/src/http/socket.js +220 -172
- package/src/http/sse.js +113 -74
- package/src/http/throttle.js +9 -4
- package/src/http/url.js +58 -35
- package/src/http/xhr.js +29 -24
- package/src/index.js +1 -0
- package/src/key.js +3 -5
- package/src/limiter.js +146 -65
- package/src/offload.js +116 -85
- package/src/offload_module.js +594 -0
- package/src/options.js +8 -4
- package/src/redial.js +129 -0
- package/src/signal.js +14 -4
- package/src/stream.js +353 -198
- package/types/attempt.d.ts.map +1 -1
- package/types/durable/index.d.ts.map +1 -1
- package/types/durable/state.d.ts.map +1 -1
- package/types/durable/store.d.ts.map +1 -1
- package/types/errors.d.ts.map +1 -1
- package/types/http/index.d.ts.map +1 -1
- package/types/index.d.ts +1 -0
- package/types/index.d.ts.map +1 -1
- package/types/limiter.d.ts +5 -1
- package/types/limiter.d.ts.map +1 -1
- package/types/offload.d.ts +2 -16
- package/types/offload.d.ts.map +1 -1
- package/types/redial.d.ts +28 -0
- package/types/redial.d.ts.map +1 -0
- package/types/stream.d.ts +2 -0
- package/types/stream.d.ts.map +1 -1
package/src/http/response.js
CHANGED
|
@@ -42,17 +42,15 @@ function decode_bytes(bytes, charset) {
|
|
|
42
42
|
* @returns {string | undefined}
|
|
43
43
|
*/
|
|
44
44
|
function decode_korean(bytes) {
|
|
45
|
-
if (!get_cp949_regex().test(bytes))
|
|
45
|
+
if (!get_cp949_regex().test(bytes)) {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
46
48
|
let korean
|
|
47
49
|
try {
|
|
48
50
|
korean = decode_bytes(bytes, "euc-kr")
|
|
49
51
|
} catch {
|
|
50
52
|
return
|
|
51
53
|
}
|
|
52
|
-
for (let i = 0; i < korean.length; i++) {
|
|
53
|
-
const code = korean.charCodeAt(i)
|
|
54
|
-
if (code >= 0x80 && code < 0xa0) return
|
|
55
|
-
}
|
|
56
54
|
return reads_as_latin1(bytes) ? void 0 : korean
|
|
57
55
|
}
|
|
58
56
|
/**
|
|
@@ -75,11 +73,11 @@ function decode_plain_name(name) {
|
|
|
75
73
|
}
|
|
76
74
|
return decoded
|
|
77
75
|
}
|
|
78
|
-
/**
|
|
79
|
-
* @returns {RegExp}
|
|
80
|
-
*/
|
|
76
|
+
/** @returns {RegExp} */
|
|
81
77
|
function get_cp949_regex() {
|
|
82
|
-
if (cp949_regex)
|
|
78
|
+
if (cp949_regex) {
|
|
79
|
+
return cp949_regex
|
|
80
|
+
}
|
|
83
81
|
let uhc = false
|
|
84
82
|
try {
|
|
85
83
|
uhc = decode_bytes("\x8c\x63", "euc-kr") == "똠"
|
|
@@ -105,20 +103,25 @@ export function get_file_name(response, url) {
|
|
|
105
103
|
hex => String.fromCharCode(parseInt(hex.slice(1), 16))
|
|
106
104
|
)
|
|
107
105
|
try {
|
|
108
|
-
const decoded =
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
const decoded = decode_bytes(bytes, extended?.[1] || "utf-8")
|
|
107
|
+
if (decoded) {
|
|
108
|
+
return decoded
|
|
109
|
+
}
|
|
112
110
|
} catch {}
|
|
113
111
|
}
|
|
114
112
|
const plain = plain_file_name_regex.exec(disposition)
|
|
115
113
|
const name = plain?.[1]?.replace(/\\(.)/g, "$1") ?? plain?.[2]?.trim()
|
|
116
|
-
if (name)
|
|
114
|
+
if (name) {
|
|
115
|
+
return decode_plain_name(name)
|
|
116
|
+
}
|
|
117
117
|
const segment = new URL(url, "http://localhost").pathname.split("/").pop()
|
|
118
|
+
if (!segment) {
|
|
119
|
+
return "download"
|
|
120
|
+
}
|
|
118
121
|
try {
|
|
119
|
-
return
|
|
122
|
+
return decodeURIComponent(segment)
|
|
120
123
|
} catch {
|
|
121
|
-
return segment
|
|
124
|
+
return segment
|
|
122
125
|
}
|
|
123
126
|
}
|
|
124
127
|
/**
|
|
@@ -188,10 +191,16 @@ export async function read_body(response, as, method, url) {
|
|
|
188
191
|
case "text":
|
|
189
192
|
return response.text()
|
|
190
193
|
}
|
|
191
|
-
if (method == "HEAD" || null_body_statuses.has(response.status))
|
|
194
|
+
if (method == "HEAD" || null_body_statuses.has(response.status)) {
|
|
195
|
+
return
|
|
196
|
+
}
|
|
192
197
|
const type = response.headers.get("Content-Type") ?? ""
|
|
193
|
-
if (json_type_regex.test(type))
|
|
194
|
-
|
|
198
|
+
if (json_type_regex.test(type)) {
|
|
199
|
+
return parse_json(await response.text())
|
|
200
|
+
}
|
|
201
|
+
if (type && !text_type_regex.test(type)) {
|
|
202
|
+
return response.blob()
|
|
203
|
+
}
|
|
195
204
|
const text = await response.text()
|
|
196
205
|
return type || text ? text : void 0
|
|
197
206
|
}
|
|
@@ -200,12 +209,18 @@ export async function read_body(response, as, method, url) {
|
|
|
200
209
|
* @returns {Promise<unknown>}
|
|
201
210
|
*/
|
|
202
211
|
export async function read_error_data(response) {
|
|
203
|
-
if (null_body_statuses.has(response.status))
|
|
212
|
+
if (null_body_statuses.has(response.status)) {
|
|
213
|
+
return
|
|
214
|
+
}
|
|
204
215
|
const type = response.headers.get("Content-Type") ?? ""
|
|
205
216
|
try {
|
|
206
|
-
if (type && !json_type_regex.test(type) && !text_type_regex.test(type))
|
|
217
|
+
if (type && !json_type_regex.test(type) && !text_type_regex.test(type)) {
|
|
218
|
+
return await response.blob()
|
|
219
|
+
}
|
|
207
220
|
const text = await response.text()
|
|
208
|
-
if (!json_type_regex.test(type))
|
|
221
|
+
if (!json_type_regex.test(type)) {
|
|
222
|
+
return text || void 0
|
|
223
|
+
}
|
|
209
224
|
try {
|
|
210
225
|
return parse_json(text)
|
|
211
226
|
} catch {
|
|
@@ -220,7 +235,9 @@ export async function read_error_data(response) {
|
|
|
220
235
|
* @returns {AsyncGenerator<StreamRecord, void, undefined>}
|
|
221
236
|
*/
|
|
222
237
|
async function* read_events(response) {
|
|
223
|
-
if (!response.body)
|
|
238
|
+
if (!response.body) {
|
|
239
|
+
return
|
|
240
|
+
}
|
|
224
241
|
const reader = response.body.pipeThrough(new TextDecoderStream())
|
|
225
242
|
.getReader()
|
|
226
243
|
let buffer = ""
|
|
@@ -235,10 +252,14 @@ async function* read_events(response) {
|
|
|
235
252
|
try {
|
|
236
253
|
for (;;) {
|
|
237
254
|
const { done, value } = await reader.read()
|
|
238
|
-
if (done)
|
|
255
|
+
if (done) {
|
|
256
|
+
return
|
|
257
|
+
}
|
|
239
258
|
const text = pending_cr && value.startsWith("\n") ? value.slice(1) : value
|
|
240
259
|
pending_cr = false
|
|
241
|
-
if (!text)
|
|
260
|
+
if (!text) {
|
|
261
|
+
continue
|
|
262
|
+
}
|
|
242
263
|
buffer += text
|
|
243
264
|
pending_cr = buffer.endsWith("\r")
|
|
244
265
|
const lines = buffer.split(line_break_regex)
|
|
@@ -265,7 +286,9 @@ async function* read_events(response) {
|
|
|
265
286
|
retry = void 0
|
|
266
287
|
continue
|
|
267
288
|
}
|
|
268
|
-
if (line.startsWith(":"))
|
|
289
|
+
if (line.startsWith(":")) {
|
|
290
|
+
continue
|
|
291
|
+
}
|
|
269
292
|
const colon = line.indexOf(":")
|
|
270
293
|
const field = colon < 0 ? line : line.slice(0, colon)
|
|
271
294
|
const field_value = colon < 0
|
|
@@ -273,10 +296,13 @@ async function* read_events(response) {
|
|
|
273
296
|
: line.slice(
|
|
274
297
|
line[colon + 1] == " " ? colon + 2 : colon + 1
|
|
275
298
|
)
|
|
276
|
-
if (field == "data")
|
|
277
|
-
|
|
278
|
-
else if (field == "
|
|
279
|
-
|
|
299
|
+
if (field == "data") {
|
|
300
|
+
data.push(field_value)
|
|
301
|
+
} else if (field == "event") {
|
|
302
|
+
event = field_value
|
|
303
|
+
} else if (field == "id" && !field_value.includes("\0")) {
|
|
304
|
+
id = field_value
|
|
305
|
+
} else if (field == "retry" && /^\d+$/.test(field_value)) {
|
|
280
306
|
retry = +field_value
|
|
281
307
|
yield { retry, type: "retry" }
|
|
282
308
|
}
|
|
@@ -292,22 +318,30 @@ async function* read_events(response) {
|
|
|
292
318
|
* @returns {AsyncGenerator<unknown, void, undefined>}
|
|
293
319
|
*/
|
|
294
320
|
async function* read_lines(response) {
|
|
295
|
-
if (!response.body)
|
|
321
|
+
if (!response.body) {
|
|
322
|
+
return
|
|
323
|
+
}
|
|
296
324
|
const reader = response.body.pipeThrough(new TextDecoderStream())
|
|
297
325
|
.getReader()
|
|
298
326
|
let buffer = ""
|
|
299
327
|
try {
|
|
300
328
|
for (;;) {
|
|
301
329
|
const { done, value } = await reader.read()
|
|
302
|
-
if (done)
|
|
330
|
+
if (done) {
|
|
331
|
+
break
|
|
332
|
+
}
|
|
303
333
|
buffer += value
|
|
304
334
|
const lines = buffer.split("\n")
|
|
305
335
|
buffer = /** @type {string} */(lines.pop())/**/
|
|
306
336
|
for (const line of lines) {
|
|
307
|
-
if (line.trim())
|
|
337
|
+
if (line.trim()) {
|
|
338
|
+
yield JSON.parse(line)
|
|
339
|
+
}
|
|
308
340
|
}
|
|
309
341
|
}
|
|
310
|
-
if (buffer.trim())
|
|
342
|
+
if (buffer.trim()) {
|
|
343
|
+
yield JSON.parse(buffer)
|
|
344
|
+
}
|
|
311
345
|
} finally {
|
|
312
346
|
reader.cancel()
|
|
313
347
|
.catch(noop)
|
|
@@ -321,20 +355,34 @@ function reads_as_latin1(bytes) {
|
|
|
321
355
|
let in_word = false
|
|
322
356
|
for (let i = 0; i < bytes.length; i++) {
|
|
323
357
|
const first = bytes.charCodeAt(i)
|
|
324
|
-
if (first < 0x80)
|
|
325
|
-
|
|
358
|
+
if (first < 0x80) {
|
|
359
|
+
continue
|
|
360
|
+
}
|
|
361
|
+
if (first < 0xa0) {
|
|
362
|
+
return false
|
|
363
|
+
}
|
|
326
364
|
const second = bytes.charCodeAt(i + 1)
|
|
327
365
|
if (second < 0x80 || i + 1 == bytes.length) {
|
|
328
366
|
in_word = true
|
|
329
367
|
continue
|
|
330
368
|
}
|
|
331
|
-
if (bytes.charCodeAt(i + 2) >= 0x80 || !is_latin1_letter(first) || !is_latin1_letter(second))
|
|
369
|
+
if (bytes.charCodeAt(i + 2) >= 0x80 || !is_latin1_letter(first) || !is_latin1_letter(second)) {
|
|
370
|
+
return false
|
|
371
|
+
}
|
|
332
372
|
if (is_latin1_lower(first)) {
|
|
333
|
-
if (!is_latin1_lower(second))
|
|
334
|
-
|
|
373
|
+
if (!is_latin1_lower(second)) {
|
|
374
|
+
return false
|
|
375
|
+
}
|
|
376
|
+
if (is_ascii_letter(bytes.charCodeAt(i - 1)) || is_ascii_letter(bytes.charCodeAt(i + 2))) {
|
|
377
|
+
in_word = true
|
|
378
|
+
}
|
|
335
379
|
} else {
|
|
336
|
-
if (is_latin1_lower(second) && second != 0xdf)
|
|
337
|
-
|
|
380
|
+
if (is_latin1_lower(second) && second != 0xdf) {
|
|
381
|
+
return false
|
|
382
|
+
}
|
|
383
|
+
if (is_ascii_upper(bytes.charCodeAt(i - 1)) && is_ascii_upper(bytes.charCodeAt(i + 2))) {
|
|
384
|
+
in_word = true
|
|
385
|
+
}
|
|
338
386
|
}
|
|
339
387
|
i++
|
|
340
388
|
}
|
|
@@ -358,7 +406,9 @@ export function to_progress(loaded, total) {
|
|
|
358
406
|
* @returns {Response}
|
|
359
407
|
*/
|
|
360
408
|
export function track_progress(response, on_progress) {
|
|
361
|
-
if (!on_progress || response.status < 200 || response.status > 599)
|
|
409
|
+
if (!on_progress || response.status < 200 || response.status > 599) {
|
|
410
|
+
return response
|
|
411
|
+
}
|
|
362
412
|
const notify = on_progress
|
|
363
413
|
if (!response.body) {
|
|
364
414
|
call_progress(
|
|
@@ -375,19 +425,16 @@ export function track_progress(response, on_progress) {
|
|
|
375
425
|
: void 0
|
|
376
426
|
let loaded = 0
|
|
377
427
|
let complete = false
|
|
378
|
-
/** @param {import("../../public.js").Progress} progress */
|
|
379
|
-
function report(progress) {
|
|
380
|
-
complete = progress.ratio == 1
|
|
381
|
-
call_progress(notify, progress)
|
|
382
|
-
}
|
|
383
428
|
report(to_progress(0, total))
|
|
384
429
|
const body = response.body.pipeThrough(
|
|
385
430
|
new TransformStream(
|
|
386
431
|
{
|
|
387
432
|
flush() {
|
|
388
|
-
if (!complete)
|
|
389
|
-
|
|
390
|
-
|
|
433
|
+
if (!complete) {
|
|
434
|
+
report(
|
|
435
|
+
{ loaded, ratio: 1, total: loaded }
|
|
436
|
+
)
|
|
437
|
+
}
|
|
391
438
|
},
|
|
392
439
|
transform(chunk, controller) {
|
|
393
440
|
loaded += chunk.byteLength
|
|
@@ -405,4 +452,12 @@ export function track_progress(response, on_progress) {
|
|
|
405
452
|
statusText: response.statusText
|
|
406
453
|
}
|
|
407
454
|
)
|
|
455
|
+
/**
|
|
456
|
+
* @param {import("../../public.js").Progress} progress
|
|
457
|
+
* @returns {void}
|
|
458
|
+
*/
|
|
459
|
+
function report(progress) {
|
|
460
|
+
complete = progress.ratio == 1
|
|
461
|
+
call_progress(notify, progress)
|
|
462
|
+
}
|
|
408
463
|
}
|
package/src/http/retry.js
CHANGED
|
@@ -25,7 +25,9 @@ const retry_statuses = new Set(
|
|
|
25
25
|
* @returns {void}
|
|
26
26
|
*/
|
|
27
27
|
function check_delay(delay, name) {
|
|
28
|
-
if (delay == null || typeof delay == "function" || typeof delay == "number" && delay >= 0)
|
|
28
|
+
if (delay == null || typeof delay == "function" || typeof delay == "number" && delay >= 0) {
|
|
29
|
+
return
|
|
30
|
+
}
|
|
29
31
|
throw TypeError(
|
|
30
32
|
`${name} must be non-negative milliseconds or a function`
|
|
31
33
|
)
|
|
@@ -36,20 +38,27 @@ function check_delay(delay, name) {
|
|
|
36
38
|
* @returns {void}
|
|
37
39
|
*/
|
|
38
40
|
export function check_reconnect(reconnect, kind) {
|
|
39
|
-
if (reconnect == null || typeof reconnect == "boolean")
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
)
|
|
41
|
+
if (reconnect == null || typeof reconnect == "boolean") {
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
if (typeof reconnect != "object") {
|
|
45
|
+
throw TypeError(
|
|
46
|
+
`The reconnect of ${kind}() must be a boolean or { count, delay }`
|
|
47
|
+
)
|
|
48
|
+
}
|
|
43
49
|
check_options(
|
|
44
50
|
reconnect,
|
|
45
51
|
[ "count", "delay" ],
|
|
46
52
|
kind,
|
|
47
53
|
"reconnect."
|
|
48
54
|
)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
/** @type {import("../../public.js").ReconnectOptions} */
|
|
56
|
+
const { count, delay } = reconnect
|
|
57
|
+
if (count != null && !is_count(count)) {
|
|
58
|
+
throw TypeError(
|
|
59
|
+
`The reconnect count of ${kind}() must be a non-negative integer or Infinity`
|
|
60
|
+
)
|
|
61
|
+
}
|
|
53
62
|
check_delay(
|
|
54
63
|
delay,
|
|
55
64
|
`The reconnect delay of ${kind}()`
|
|
@@ -61,16 +70,22 @@ export function check_reconnect(reconnect, kind) {
|
|
|
61
70
|
* @returns {void}
|
|
62
71
|
*/
|
|
63
72
|
export function check_retry(retry, kind) {
|
|
64
|
-
if (retry == null)
|
|
73
|
+
if (retry == null) {
|
|
74
|
+
return
|
|
75
|
+
}
|
|
65
76
|
if (typeof retry == "number") {
|
|
66
|
-
if (!is_count(retry))
|
|
67
|
-
|
|
68
|
-
|
|
77
|
+
if (!is_count(retry)) {
|
|
78
|
+
throw TypeError(
|
|
79
|
+
`The retry count of ${kind}() must be a non-negative integer or Infinity`
|
|
80
|
+
)
|
|
81
|
+
}
|
|
69
82
|
return
|
|
70
83
|
}
|
|
71
|
-
if (typeof retry != "object")
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
if (typeof retry != "object") {
|
|
85
|
+
throw TypeError(
|
|
86
|
+
`The retry of ${kind}() must be a non-negative integer or { count, delay, when }`
|
|
87
|
+
)
|
|
88
|
+
}
|
|
74
89
|
check_options(
|
|
75
90
|
retry,
|
|
76
91
|
[ "count", "delay", "when" ],
|
|
@@ -78,16 +93,20 @@ export function check_retry(retry, kind) {
|
|
|
78
93
|
"retry."
|
|
79
94
|
)
|
|
80
95
|
const { count, delay, when } = /** @type {import("../../public.js").RetryOptions} */(retry)/**/
|
|
81
|
-
if (!is_count(count))
|
|
82
|
-
|
|
83
|
-
|
|
96
|
+
if (!is_count(count)) {
|
|
97
|
+
throw TypeError(
|
|
98
|
+
`The retry count of ${kind}() must be a non-negative integer or Infinity`
|
|
99
|
+
)
|
|
100
|
+
}
|
|
84
101
|
check_delay(
|
|
85
102
|
delay,
|
|
86
103
|
`The retry delay of ${kind}()`
|
|
87
104
|
)
|
|
88
|
-
if (when != null && typeof when != "function")
|
|
89
|
-
|
|
90
|
-
|
|
105
|
+
if (when != null && typeof when != "function") {
|
|
106
|
+
throw TypeError(
|
|
107
|
+
`The retry when of ${kind}() must be a function`
|
|
108
|
+
)
|
|
109
|
+
}
|
|
91
110
|
}
|
|
92
111
|
/**
|
|
93
112
|
* @param {number} attempt
|
|
@@ -131,7 +150,9 @@ export function get_reconnect_wait(reconnect, failures, error) {
|
|
|
131
150
|
*/
|
|
132
151
|
export function get_retry_delay(error, attempt) {
|
|
133
152
|
const ms = read_retry_after(error)
|
|
134
|
-
if (ms == null)
|
|
153
|
+
if (ms == null) {
|
|
154
|
+
return get_backoff(attempt)
|
|
155
|
+
}
|
|
135
156
|
return ms <= max_retry_after ? ms : void 0
|
|
136
157
|
}
|
|
137
158
|
/**
|
|
@@ -141,7 +162,9 @@ export function get_retry_delay(error, attempt) {
|
|
|
141
162
|
* @returns {number | undefined}
|
|
142
163
|
*/
|
|
143
164
|
export function get_retry_wait(retry, attempt, error) {
|
|
144
|
-
if (typeof retry.delay == "number")
|
|
165
|
+
if (typeof retry.delay == "number") {
|
|
166
|
+
return retry.delay
|
|
167
|
+
}
|
|
145
168
|
const fallback = get_retry_delay(error, attempt)
|
|
146
169
|
return retry.delay ? retry.delay(attempt, error, fallback) : fallback
|
|
147
170
|
}
|
|
@@ -165,8 +188,8 @@ export function is_idempotent(method) {
|
|
|
165
188
|
*/
|
|
166
189
|
export function is_retryable(error) {
|
|
167
190
|
return error instanceof NetworkError
|
|
168
|
-
|
|
169
|
-
|
|
191
|
+
|| error instanceof TimeoutError
|
|
192
|
+
|| error instanceof HttpError && retry_statuses.has(error.status)
|
|
170
193
|
}
|
|
171
194
|
/**
|
|
172
195
|
* @param {unknown} error
|
|
@@ -174,7 +197,9 @@ export function is_retryable(error) {
|
|
|
174
197
|
*/
|
|
175
198
|
export function read_retry_after(error) {
|
|
176
199
|
const retry_after = error instanceof HttpError ? error.headers.get("Retry-After") : null
|
|
177
|
-
if (!retry_after)
|
|
200
|
+
if (!retry_after) {
|
|
201
|
+
return
|
|
202
|
+
}
|
|
178
203
|
const ms = /^\s*\d+\s*$/.test(retry_after)
|
|
179
204
|
? Number(retry_after) * 1000
|
|
180
205
|
: Date.parse(retry_after) - Date.now()
|