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/redial.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { check_options } from "./options.js"
|
|
2
|
+
import { clamp_delay, on_abort } from "./signal.js"
|
|
3
|
+
/**
|
|
4
|
+
* Keeps one connection up: calls `connect` at once and again after each close, waiting `base` milliseconds
|
|
5
|
+
* after a connection that opened and doubling the wait up to `most` after each one that closed without opening,
|
|
6
|
+
* until the `signal` aborts. The connector tells it when its connection opened and closed, so a WebSocket or a
|
|
7
|
+
* server stream reconnects. The result resolves once the signal has aborted and the connection in hand has closed.
|
|
8
|
+
* `connect` gets `{ attempt, signal, open, close }`: `attempt` counts every call from 1, so it can rotate addresses;
|
|
9
|
+
* `signal` of the connection aborts when the outer signal does, so close the connection on it; `open()` marks the
|
|
10
|
+
* connection up and resets the wait; `close()` ends it, and only its first call counts. A `connect` that throws or
|
|
11
|
+
* returns a promise that rejects counts as a close.
|
|
12
|
+
* @example
|
|
13
|
+
* const stop = new AbortController()
|
|
14
|
+
* const stopped = redial(
|
|
15
|
+
* ({ attempt, signal, open, close }) => {
|
|
16
|
+
* const socket = new WebSocket(urls[(attempt - 1) % urls.length])
|
|
17
|
+
* socket.onopen = open
|
|
18
|
+
* socket.onclose = close
|
|
19
|
+
* signal.addEventListener("abort", () => socket.close(), { once: true })
|
|
20
|
+
* },
|
|
21
|
+
* { signal: stop.signal, base: 1000, most: 30000 }
|
|
22
|
+
* )
|
|
23
|
+
* stop.abort()
|
|
24
|
+
* await stopped
|
|
25
|
+
* @param {(context: import("../public.js").RedialContext) => unknown} connect
|
|
26
|
+
* @param {import("../public.js").RedialOptions} options
|
|
27
|
+
* @returns {Promise<void>}
|
|
28
|
+
*/
|
|
29
|
+
export function redial(connect, options) {
|
|
30
|
+
check_options(
|
|
31
|
+
options,
|
|
32
|
+
[ "base", "most", "signal" ],
|
|
33
|
+
"redial"
|
|
34
|
+
)
|
|
35
|
+
const signal = options?.signal
|
|
36
|
+
if (!(signal instanceof AbortSignal)) {
|
|
37
|
+
throw TypeError(
|
|
38
|
+
"The signal of redial() must be an AbortSignal"
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
const base = options.base ?? 1000
|
|
42
|
+
const most = options.most ?? 30000
|
|
43
|
+
if (!(typeof base == "number" && base > 0)) {
|
|
44
|
+
throw TypeError(
|
|
45
|
+
"The base of redial() must be a positive number of milliseconds"
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
if (!(typeof most == "number" && most >= base)) {
|
|
49
|
+
throw TypeError(
|
|
50
|
+
"The most of redial() must be a number of milliseconds that is not less than base"
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
if (signal.aborted) {
|
|
54
|
+
return Promise.resolve()
|
|
55
|
+
}
|
|
56
|
+
return new Promise(
|
|
57
|
+
resolve => {
|
|
58
|
+
let count = 0
|
|
59
|
+
let failures = 0
|
|
60
|
+
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
61
|
+
let timer
|
|
62
|
+
/** @type {AbortController | undefined} */
|
|
63
|
+
let current
|
|
64
|
+
const unlink = on_abort(
|
|
65
|
+
signal,
|
|
66
|
+
() => {
|
|
67
|
+
clearTimeout(timer)
|
|
68
|
+
if (current) {
|
|
69
|
+
current.abort(signal.reason)
|
|
70
|
+
} else {
|
|
71
|
+
resolve()
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
start()
|
|
76
|
+
/** @returns {void} */
|
|
77
|
+
function start() {
|
|
78
|
+
const controller = new AbortController()
|
|
79
|
+
let opened = false
|
|
80
|
+
let closed = false
|
|
81
|
+
current = controller
|
|
82
|
+
count++
|
|
83
|
+
try {
|
|
84
|
+
Promise.resolve(
|
|
85
|
+
connect(
|
|
86
|
+
{
|
|
87
|
+
attempt: count,
|
|
88
|
+
close,
|
|
89
|
+
open,
|
|
90
|
+
signal: controller.signal
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
).catch(close)
|
|
94
|
+
} catch {
|
|
95
|
+
close()
|
|
96
|
+
}
|
|
97
|
+
/** @returns {void} */
|
|
98
|
+
function close() {
|
|
99
|
+
if (closed) {
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
closed = true
|
|
103
|
+
current = void 0
|
|
104
|
+
if (signal.aborted) {
|
|
105
|
+
unlink()
|
|
106
|
+
resolve()
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
failures = opened ? 0 : failures + 1
|
|
110
|
+
timer = setTimeout(
|
|
111
|
+
start,
|
|
112
|
+
clamp_delay(
|
|
113
|
+
Math.min(
|
|
114
|
+
base * 2 ** Math.max(failures - 1, 0),
|
|
115
|
+
most
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
/** @returns {void} */
|
|
121
|
+
function open() {
|
|
122
|
+
if (!closed) {
|
|
123
|
+
opened = true
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
}
|
package/src/signal.js
CHANGED
|
@@ -14,7 +14,9 @@ export function clamp_delay(ms) {
|
|
|
14
14
|
* @returns {() => void}
|
|
15
15
|
*/
|
|
16
16
|
export function link_signal(controller, signal) {
|
|
17
|
-
if (!signal)
|
|
17
|
+
if (!signal) {
|
|
18
|
+
return noop
|
|
19
|
+
}
|
|
18
20
|
if (signal.aborted) {
|
|
19
21
|
controller.abort(signal.reason)
|
|
20
22
|
return noop
|
|
@@ -24,6 +26,7 @@ export function link_signal(controller, signal) {
|
|
|
24
26
|
() => controller.abort(signal.reason)
|
|
25
27
|
)
|
|
26
28
|
}
|
|
29
|
+
/** @returns {void} */
|
|
27
30
|
export function noop() {}
|
|
28
31
|
/**
|
|
29
32
|
* @param {AbortSignal} signal
|
|
@@ -36,9 +39,12 @@ export function on_abort(signal, callback) {
|
|
|
36
39
|
if (!entry) {
|
|
37
40
|
/** @type {Set<() => void>} */
|
|
38
41
|
const callbacks = new Set()
|
|
42
|
+
/** @returns {void} */
|
|
39
43
|
function listener() {
|
|
40
44
|
abort_entries.delete(signal)
|
|
41
|
-
for (const call of [ ...callbacks ])
|
|
45
|
+
for (const call of [ ...callbacks ]) {
|
|
46
|
+
call()
|
|
47
|
+
}
|
|
42
48
|
}
|
|
43
49
|
entry = { callbacks, listener }
|
|
44
50
|
abort_entries.set(signal, entry)
|
|
@@ -47,7 +53,9 @@ export function on_abort(signal, callback) {
|
|
|
47
53
|
const current = entry
|
|
48
54
|
current.callbacks.add(callback)
|
|
49
55
|
return () => {
|
|
50
|
-
if (!current.callbacks.delete(callback) || current.callbacks.size || abort_entries.get(signal) != current)
|
|
56
|
+
if (!current.callbacks.delete(callback) || current.callbacks.size || abort_entries.get(signal) != current) {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
51
59
|
abort_entries.delete(signal)
|
|
52
60
|
signal.removeEventListener("abort", current.listener)
|
|
53
61
|
}
|
|
@@ -113,7 +121,9 @@ export function sleep(ms, signal) {
|
|
|
113
121
|
*/
|
|
114
122
|
export function sleep_until(deadline, signal) {
|
|
115
123
|
const left = deadline - Date.now()
|
|
116
|
-
if (left <= max_delay)
|
|
124
|
+
if (left <= max_delay) {
|
|
125
|
+
return sleep(left, signal)
|
|
126
|
+
}
|
|
117
127
|
return sleep(max_delay, signal)
|
|
118
128
|
.then(
|
|
119
129
|
() => sleep_until(deadline, signal)
|