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.
Files changed (70) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +1 -1
  3. package/docs/http.md +1 -1
  4. package/docs/reference.md +4 -2
  5. package/docs/tasks.md +19 -2
  6. package/package.json +4 -5
  7. package/public.d.ts +112 -0
  8. package/src/attempt.js +17 -7
  9. package/src/durable/context.js +107 -64
  10. package/src/durable/error.js +39 -15
  11. package/src/durable/index.js +18 -10
  12. package/src/durable/journal.js +29 -14
  13. package/src/durable/json.js +26 -9
  14. package/src/durable/lease.js +15 -5
  15. package/src/durable/persist.js +60 -22
  16. package/src/durable/run.js +103 -41
  17. package/src/durable/send.js +30 -13
  18. package/src/durable/serve.js +30 -24
  19. package/src/durable/state.js +28 -13
  20. package/src/durable/store.js +19 -10
  21. package/src/durable/watch.js +31 -11
  22. package/src/errors.js +9 -3
  23. package/src/flow/attempts.js +49 -29
  24. package/src/flow/builder.js +260 -161
  25. package/src/flow/compile.js +86 -66
  26. package/src/flow/graph.js +108 -42
  27. package/src/flow/index.js +13 -7
  28. package/src/flow/items.js +79 -46
  29. package/src/flow/nodes.js +267 -117
  30. package/src/flow/refs.js +27 -13
  31. package/src/flow/resources.js +9 -3
  32. package/src/flow/run.js +287 -114
  33. package/src/flow/streams.js +44 -19
  34. package/src/flow/subflows.js +93 -53
  35. package/src/http/config.js +11 -5
  36. package/src/http/index.js +25 -14
  37. package/src/http/refresh.js +29 -12
  38. package/src/http/request.js +246 -151
  39. package/src/http/response.js +105 -50
  40. package/src/http/retry.js +52 -27
  41. package/src/http/socket.js +220 -172
  42. package/src/http/sse.js +113 -74
  43. package/src/http/throttle.js +9 -4
  44. package/src/http/url.js +58 -35
  45. package/src/http/xhr.js +29 -24
  46. package/src/index.js +1 -0
  47. package/src/key.js +3 -5
  48. package/src/limiter.js +146 -65
  49. package/src/offload.js +116 -85
  50. package/src/offload_module.js +594 -0
  51. package/src/options.js +8 -4
  52. package/src/redial.js +129 -0
  53. package/src/signal.js +14 -4
  54. package/src/stream.js +353 -198
  55. package/types/attempt.d.ts.map +1 -1
  56. package/types/durable/index.d.ts.map +1 -1
  57. package/types/durable/state.d.ts.map +1 -1
  58. package/types/durable/store.d.ts.map +1 -1
  59. package/types/errors.d.ts.map +1 -1
  60. package/types/http/index.d.ts.map +1 -1
  61. package/types/index.d.ts +1 -0
  62. package/types/index.d.ts.map +1 -1
  63. package/types/limiter.d.ts +5 -1
  64. package/types/limiter.d.ts.map +1 -1
  65. package/types/offload.d.ts +2 -16
  66. package/types/offload.d.ts.map +1 -1
  67. package/types/redial.d.ts +28 -0
  68. package/types/redial.d.ts.map +1 -0
  69. package/types/stream.d.ts +2 -0
  70. 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) return noop
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 ]) call()
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) return
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) return sleep(left, signal)
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)