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
@@ -33,14 +33,25 @@ const error_types = new Map(
33
33
  export function error_from(info) {
34
34
  const error = /** @type {Error & Record<string, unknown>} */(Error(info.message))/**/
35
35
  const type = error_types.get(info.name)
36
- if (type) Object.setPrototypeOf(error, type.prototype)
37
- if (error.name != info.name) error.name = info.name
36
+ if (type) {
37
+ Object.setPrototypeOf(error, type.prototype)
38
+ }
39
+ if (error.name != info.name) {
40
+ error.name = info.name
41
+ }
38
42
  for (const key of Object.keys(info)) {
39
- if (key != "cause" && key != "message" && key != "name") error[key] = copy(info[key])
43
+ if (key != "cause" && key != "message" && key != "name") {
44
+ error[key] = copy(info[key])
45
+ }
46
+ }
47
+ if (info.cause) {
48
+ error.cause = error_from(info.cause)
49
+ } else if (info.name == "CancelError") {
50
+ error.cause = info.message
51
+ }
52
+ if (info.name != "HttpError") {
53
+ return error
40
54
  }
41
- if (info.cause) error.cause = error_from(info.cause)
42
- else if (info.name == "CancelError") error.cause = info.message
43
- if (info.name != "HttpError") return error
44
55
  const { headers: pairs, status, statusText } = info
45
56
  const headers = new Headers(
46
57
  /** @type {[string, string][]} */(pairs ?? [])/**/
@@ -66,7 +77,9 @@ export function error_from(info) {
66
77
  * @returns {unknown}
67
78
  */
68
79
  export function error_of(saved) {
69
- if (saved.status == "cancelled" && !saved.error) return new CancelError("Cancelled")
80
+ if (saved.status == "cancelled" && !saved.error) {
81
+ return new CancelError("Cancelled")
82
+ }
70
83
  return error_from(
71
84
  saved.error ?? {
72
85
  message: "The run failed",
@@ -80,9 +93,11 @@ export function error_of(saved) {
80
93
  * @returns {SavedError}
81
94
  */
82
95
  export function saved_error(error, seen = []) {
83
- if (!(error instanceof Error)) return {
84
- message: String(error),
85
- name: "Error"
96
+ if (!(error instanceof Error)) {
97
+ return {
98
+ message: String(error),
99
+ name: "Error"
100
+ }
86
101
  }
87
102
  seen.push(error)
88
103
  /** @type {SavedError} */
@@ -93,12 +108,19 @@ export function saved_error(error, seen = []) {
93
108
  const fields = /** @type {Error & Record<string, unknown>} */(error)/**/
94
109
  for (const key of Object.keys(error)) {
95
110
  const value = fields[key]
96
- if (key == "cause" || key == "message" || key == "name" || key == "stack" || value === void 0) continue
97
- if (value instanceof Headers) saved[key] = [ ...value ]
98
- else if (!json_problem(value)) saved[key] = copy(value)
111
+ if (key == "cause" || key == "message" || key == "name" || key == "stack" || value === void 0) {
112
+ continue
113
+ }
114
+ if (value instanceof Headers) {
115
+ saved[key] = [ ...value ]
116
+ } else if (!json_problem(value)) {
117
+ saved[key] = copy(value)
118
+ }
99
119
  }
100
120
  const { cause } = error
101
- if (cause instanceof Error && !seen.includes(cause)) saved.cause = saved_error(cause, seen)
121
+ if (cause instanceof Error && !seen.includes(cause)) {
122
+ saved.cause = saved_error(cause, seen)
123
+ }
102
124
  return saved
103
125
  }
104
126
  /**
@@ -120,6 +142,8 @@ export function store_error(error) {
120
142
  export function timeout_error(entry, wait_options) {
121
143
  const { timeout, until } = wait_options
122
144
  const error = new TimeoutError(timeout ?? 0)
123
- if (until != null && entry.until == until) error.message = `Timed out at ${new Date(until).toISOString()}`
145
+ if (until != null && entry.until == until) {
146
+ error.message = `Timed out at ${new Date(until).toISOString()}`
147
+ }
124
148
  return error
125
149
  }
@@ -47,15 +47,21 @@ export function durable(store, fn, options = {}) {
47
47
  const idle_ms = options.idle ?? 1000
48
48
  const lease_ms = options.lease ?? 30000
49
49
  const poll_ms = options.poll ?? 500
50
- if (!(typeof idle_ms == "number" && idle_ms >= 0)) throw TypeError(
51
- "The idle of durable() must be a non-negative number of milliseconds"
52
- )
53
- if (!(Number.isFinite(lease_ms) && lease_ms > 0)) throw TypeError(
54
- "The lease of durable() must be a positive number of milliseconds"
55
- )
56
- if (!(Number.isFinite(poll_ms) && poll_ms > 0)) throw TypeError(
57
- "The poll of durable() must be a positive number of milliseconds"
58
- )
50
+ if (!(typeof idle_ms == "number" && idle_ms >= 0)) {
51
+ throw TypeError(
52
+ "The idle of durable() must be a non-negative number of milliseconds"
53
+ )
54
+ }
55
+ if (!(Number.isFinite(lease_ms) && lease_ms > 0)) {
56
+ throw TypeError(
57
+ "The lease of durable() must be a positive number of milliseconds"
58
+ )
59
+ }
60
+ if (!(Number.isFinite(poll_ms) && poll_ms > 0)) {
61
+ throw TypeError(
62
+ "The poll of durable() must be a positive number of milliseconds"
63
+ )
64
+ }
59
65
  /** @type {DurableWorker} */
60
66
  const d = {
61
67
  fn: /** @type {DurableWorker["fn"]} */(fn)/**/,
@@ -101,7 +107,9 @@ export function durable(store, fn, options = {}) {
101
107
  ) => enter(d, key, input, "start"),
102
108
  stop() {
103
109
  d.halted = true
104
- for (const stop of [ ...d.stoppers ]) stop()
110
+ for (const stop of [ ...d.stoppers ]) {
111
+ stop()
112
+ }
105
113
  for (const key of [ ...d.watchers.keys() ]) {
106
114
  settle(
107
115
  d,
@@ -11,9 +11,11 @@ export function entry_of(execution, name, type) {
11
11
  const { journal } = execution.saved
12
12
  const entry = journal[index]
13
13
  if (entry) {
14
- if (entry.name != name || entry.type != type) throw Error(
15
- `The run "${execution.key}" changed: step ${index + 1} was "${entry.name}", now "${name}"`
16
- )
14
+ if (entry.name != name || entry.type != type) {
15
+ throw Error(
16
+ `The run "${execution.key}" changed: step ${index + 1} was "${entry.name}", now "${name}"`
17
+ )
18
+ }
17
19
  return entry
18
20
  }
19
21
  /** @type {JournalEntry} */
@@ -58,18 +60,25 @@ export function rewind(execution, error) {
58
60
  seen.add(cause)
59
61
  const failure = execution.failures.get(cause)
60
62
  if (failure) {
61
- if (execution.cursor > failure.reached) return
63
+ if (execution.cursor > failure.reached) {
64
+ return
65
+ }
62
66
  const { saved } = execution
63
- const index = saved.journal.indexOf(failure.entry)
64
- if (index < 0) return
65
- for (const later of saved.journal.splice(index).reverse()) {
66
- if (later.type != "wait" || !later.done || later.timed_out) continue
67
+ for (const later of saved.journal.splice(
68
+ saved.journal.indexOf(failure.entry)
69
+ ).reverse()) {
70
+ if (later.type != "wait" || !later.done || later.timed_out) {
71
+ continue
72
+ }
67
73
  const events = saved.events ??= {}
68
- ;(events[later.name] ??= []).unshift({ at: 0, value: later.value })
74
+ const queue = events[later.name] ??= []
75
+ queue.unshift({ at: 0, value: later.value })
69
76
  }
70
77
  return
71
78
  }
72
- if (!(cause instanceof Error)) return
79
+ if (!(cause instanceof Error)) {
80
+ return
81
+ }
73
82
  cause = cause.cause
74
83
  }
75
84
  }
@@ -81,7 +90,9 @@ export function rewind(execution, error) {
81
90
  */
82
91
  export function take_waiter(execution, name, at) {
83
92
  const list = execution.waiters.get(name)
84
- if (!list) return
93
+ if (!list) {
94
+ return
95
+ }
85
96
  const index = list.findIndex(
86
97
  waiter => waiter.entry.until == null || at <= waiter.entry.until
87
98
  )
@@ -93,8 +104,12 @@ export function take_waiter(execution, name, at) {
93
104
  * @returns {void}
94
105
  */
95
106
  export function unblock(execution, waiter) {
96
- if (!waiter.block) return
97
- const index = execution.blocked.indexOf(waiter.block)
98
- if (index >= 0) execution.blocked.splice(index, 1)
107
+ if (!waiter.block) {
108
+ return
109
+ }
110
+ execution.blocked.splice(
111
+ execution.blocked.indexOf(waiter.block),
112
+ 1
113
+ )
99
114
  waiter.block = void 0
100
115
  }
@@ -13,13 +13,19 @@ export function copy(value) {
13
13
  * @returns {string | undefined}
14
14
  */
15
15
  export function json_problem(value) {
16
- if (value === void 0) return
16
+ if (value === void 0) {
17
+ return
18
+ }
17
19
  /** @type {(number | string)[]} */
18
20
  const path = []
19
21
  const problem = json_problem_at(value, path, [])
20
- if (!problem) return
22
+ if (!problem) {
23
+ return
24
+ }
21
25
  let at = ""
22
- for (const part of path) at += typeof part == "number" ? `[${part}]` : `.${part}`
26
+ for (const part of path) {
27
+ at += typeof part == "number" ? `[${part}]` : `.${part}`
28
+ }
23
29
  return at ? `${problem} at ${at}` : problem
24
30
  }
25
31
  /**
@@ -38,20 +44,27 @@ function json_problem_at(value, path, parents) {
38
44
  case "number": return Number.isFinite(value) ? void 0 : String(value)
39
45
  case "undefined": return "undefined"
40
46
  }
41
- if (value === null) return
42
- if (parents.includes(value)) return "a circular reference"
47
+ if (value === null) {
48
+ return
49
+ }
50
+ if (parents.includes(value)) {
51
+ return "a circular reference"
52
+ }
43
53
  if (Array.isArray(value)) {
44
54
  parents.push(value)
45
55
  for (let index = 0; index < value.length; index++) {
46
56
  path.push(index)
47
57
  const problem = json_problem_at(value[index], path, parents)
48
- if (problem) return problem
58
+ if (problem) {
59
+ return problem
60
+ }
49
61
  path.pop()
50
62
  }
51
63
  parents.pop()
52
64
  return
53
65
  }
54
- const prototype = /** @type {{ constructor?: unknown } | null} */(Object.getPrototypeOf(value))/**/
66
+ /** @type {{ constructor?: unknown } | null} */
67
+ const prototype = Object.getPrototypeOf(value)
55
68
  if (prototype && Object.getPrototypeOf(prototype)) {
56
69
  const { constructor } = prototype
57
70
  return `an instance of ${typeof constructor == "function" && constructor.name || "a class"}`
@@ -60,10 +73,14 @@ function json_problem_at(value, path, parents) {
60
73
  const record = /** @type {Record<string, unknown>} */(value)/**/
61
74
  for (const key of Object.keys(record)) {
62
75
  const item = record[key]
63
- if (item === void 0) continue
76
+ if (item === void 0) {
77
+ continue
78
+ }
64
79
  path.push(key)
65
80
  const problem = json_problem_at(item, path, parents)
66
- if (problem) return problem
81
+ if (problem) {
82
+ return problem
83
+ }
67
84
  path.pop()
68
85
  }
69
86
  parents.pop()
@@ -12,7 +12,9 @@ export const SUSPENDED = Symbol("suspended")
12
12
  * @returns {void}
13
13
  */
14
14
  export function check_halted(d) {
15
- if (d.halted) throw new CancelError("The worker stopped")
15
+ if (d.halted) {
16
+ throw new CancelError("The worker stopped")
17
+ }
16
18
  }
17
19
  /**
18
20
  * @param {DurableWorker} d
@@ -37,7 +39,9 @@ export async function claim(d, key, saved, input) {
37
39
  }
38
40
  next.status = "failed"
39
41
  } else {
40
- if (next.status == "pending") next.input = copy(input)
42
+ if (next.status == "pending") {
43
+ next.input = copy(input)
44
+ }
41
45
  next.lease = Date.now() + d.lease_ms
42
46
  next.owner = d.owner
43
47
  next.revision = d.revision
@@ -59,7 +63,9 @@ export function expiring(d, execution) {
59
63
  * @returns {boolean}
60
64
  */
61
65
  export function is_due(d, saved) {
62
- if (saved.status == "running") return is_free(d, saved)
66
+ if (saved.status == "running") {
67
+ return is_free(d, saved)
68
+ }
63
69
  return (saved.status == "sleeping" || saved.status == "waiting") && saved.wake != null && saved.wake <= Date.now()
64
70
  }
65
71
  /**
@@ -76,7 +82,9 @@ function is_free(d, saved) {
76
82
  * @returns {void}
77
83
  */
78
84
  export function lose(execution, reason) {
79
- if (execution.lost) return
85
+ if (execution.lost) {
86
+ return
87
+ }
80
88
  execution.lost = true
81
89
  execution.controller.abort(reason)
82
90
  execution.interrupt(LOST)
@@ -86,7 +94,9 @@ export function lose(execution, reason) {
86
94
  * @returns {Promise<never> | undefined}
87
95
  */
88
96
  export function refuse(execution) {
89
- if (!execution.lost && !execution.finished) return
97
+ if (!execution.lost && !execution.finished) {
98
+ return
99
+ }
90
100
  const { signal } = execution.controller
91
101
  return Promise.reject(
92
102
  signal.aborted ? signal.reason : new CancelError("The run left this worker")
@@ -29,7 +29,9 @@ function deliver(execution, item) {
29
29
  0,
30
30
  { at: item.at, value: item.value }
31
31
  )
32
- if (execution.finished) wake_if_sent(saved)
32
+ if (execution.finished) {
33
+ wake_if_sent(saved)
34
+ }
33
35
  }
34
36
  /**
35
37
  * @param {DurableExecution} execution
@@ -37,12 +39,16 @@ function deliver(execution, item) {
37
39
  */
38
40
  function drain(execution) {
39
41
  const { saved } = execution
40
- if (!saved.events || execution.finished) return
42
+ if (!saved.events || execution.finished) {
43
+ return
44
+ }
41
45
  for (const name of Object.keys(saved.events)) {
42
46
  for (;;) {
43
47
  const event = saved.events?.[name]?.[0]
44
48
  const waiter = event && !execution.unsaved.has(event) && take_waiter(execution, name, event.at)
45
- if (!event || !waiter) break
49
+ if (!event || !waiter) {
50
+ break
51
+ }
46
52
  take_event(saved, name)
47
53
  give(execution, waiter, event.value)
48
54
  waiter.resolve(copy(event.value))
@@ -55,10 +61,14 @@ function drain(execution) {
55
61
  * @returns {Promise<boolean>}
56
62
  */
57
63
  export async function inbox_read(d, execution) {
58
- if (execution.writing) await execution.writing
64
+ if (execution.writing) {
65
+ await execution.writing
66
+ }
59
67
  try {
60
68
  const latest = await load(d.store, execution.key)
61
- if (execution.lost || execution.finished) return false
69
+ if (execution.lost || execution.finished) {
70
+ return false
71
+ }
62
72
  return execution.fault === void 0 && (!latest?.inbox?.length || latest.version == execution.saved.version) || await persist(d, execution)
63
73
  } catch (error) {
64
74
  execution.fault = error
@@ -71,7 +81,9 @@ export async function inbox_read(d, execution) {
71
81
  * @returns {Promise<boolean>}
72
82
  */
73
83
  export function persist(d, execution) {
74
- if (execution.lost) return Promise.resolve(false)
84
+ if (execution.lost) {
85
+ return Promise.resolve(false)
86
+ }
75
87
  if (execution.writing) {
76
88
  execution.dirty = true
77
89
  return execution.writing
@@ -83,9 +95,13 @@ export function persist(d, execution) {
83
95
  const current = execution.saved
84
96
  const next = snapshot(current)
85
97
  const included = execution.unsaved
86
- if (included.size) execution.unsaved = new Set()
98
+ if (included.size) {
99
+ execution.unsaved = new Set()
100
+ }
87
101
  const attempt = ++execution.taken
88
- if (next.owner) next.lease = Date.now() + d.lease_ms
102
+ if (next.owner) {
103
+ next.lease = Date.now() + d.lease_ms
104
+ }
89
105
  /** @type {SavedRun | undefined} */
90
106
  let latest
91
107
  let putting = true
@@ -105,16 +121,26 @@ export function persist(d, execution) {
105
121
  continue
106
122
  }
107
123
  putting = false
108
- for (const event of included) execution.unsaved.add(event)
124
+ for (const event of included) {
125
+ execution.unsaved.add(event)
126
+ }
109
127
  latest = await load(d.store, execution.key)
110
128
  } catch (error) {
111
- for (const event of included) execution.unsaved.add(event)
112
- if (putting) execution.tried.push(next)
129
+ for (const event of included) {
130
+ execution.unsaved.add(event)
131
+ }
132
+ if (putting) {
133
+ execution.tried.push(next)
134
+ }
113
135
  execution.fault = error
114
- if (expiring(d, execution)) lose(execution, store_error(error))
136
+ if (expiring(d, execution)) {
137
+ lose(execution, store_error(error))
138
+ }
139
+ return false
140
+ }
141
+ if (execution.lost) {
115
142
  return false
116
143
  }
117
- if (execution.lost) return false
118
144
  const committed = latest && execution.tried.findLast(
119
145
  tried => same_state(
120
146
  /** @type {SavedRun} */(latest)/**/,
@@ -137,10 +163,12 @@ export function persist(d, execution) {
137
163
  execution.tried = []
138
164
  current.version = latest.version
139
165
  const inbox = latest.inbox ?? []
140
- for (let index = execution.absorbed; index < inbox.length; index++) deliver(
141
- execution,
142
- /** @type {NonNullable<SavedRun["inbox"]>[number]} */(inbox[index])/**/
143
- )
166
+ for (let index = execution.absorbed; index < inbox.length; index++) {
167
+ deliver(
168
+ execution,
169
+ /** @type {NonNullable<SavedRun["inbox"]>[number]} */(inbox[index])/**/
170
+ )
171
+ }
144
172
  execution.absorbed = inbox.length
145
173
  execution.dirty = true
146
174
  } while (execution.dirty && !execution.lost)
@@ -158,7 +186,9 @@ export function persist(d, execution) {
158
186
  */
159
187
  export async function save(d, execution) {
160
188
  while (!await persist(d, execution)) {
161
- if (execution.lost) return false
189
+ if (execution.lost) {
190
+ return false
191
+ }
162
192
  await sleep(
163
193
  d.renew_ms,
164
194
  execution.controller.signal
@@ -174,13 +204,21 @@ export async function save(d, execution) {
174
204
  export function watch_inbox(d, execution) {
175
205
  execution.inbox_timer ??= setInterval(
176
206
  () => {
177
- if (execution.finished || execution.lost || execution.writing) return
207
+ if (execution.finished || execution.lost || execution.writing) {
208
+ return
209
+ }
178
210
  let waiting = false
179
- for (const list of execution.waiters.values()) waiting ||= list.length > 0
180
- if (!waiting) return
211
+ for (const list of execution.waiters.values()) {
212
+ waiting ||= list.length > 0
213
+ }
214
+ if (!waiting) {
215
+ return
216
+ }
181
217
  void load(d.store, execution.key).then(
182
218
  latest => {
183
- if (latest?.inbox?.length && latest.version != execution.saved.version) void persist(d, execution)
219
+ if (latest?.inbox?.length && latest.version != execution.saved.version) {
220
+ void persist(d, execution)
221
+ }
184
222
  },
185
223
  noop
186
224
  )