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/limiter.js
CHANGED
|
@@ -3,48 +3,49 @@ import { CancelError } from "./errors.js"
|
|
|
3
3
|
import { check_options } from "./options.js"
|
|
4
4
|
import { clamp_delay, noop, on_abort } from "./signal.js"
|
|
5
5
|
/**
|
|
6
|
-
* @param {{ concurrency?: number | undefined, rate?: { count: number, per: number } | undefined }} options
|
|
6
|
+
* @param {{ concurrency?: number | undefined, rate?: { count: number, per: number, window?: "fixed" | "sliding" | undefined } | undefined }} options
|
|
7
7
|
* @param {() => void=} on_idle
|
|
8
8
|
* @returns {LimiterState}
|
|
9
9
|
*/
|
|
10
10
|
function create_state(options, on_idle) {
|
|
11
11
|
const { concurrency = Infinity, rate } = options
|
|
12
|
+
const fixed = rate?.window == "fixed"
|
|
12
13
|
/** @type {LimiterJob[]} */
|
|
13
14
|
const queue = []
|
|
14
15
|
/** @type {number[]} */
|
|
15
16
|
const starts = []
|
|
17
|
+
let window_start = 0
|
|
18
|
+
let used = 0
|
|
16
19
|
/** @type {LimiterState} */
|
|
17
|
-
const state = {
|
|
20
|
+
const state = {
|
|
21
|
+
queue,
|
|
22
|
+
running: 0,
|
|
23
|
+
schedule: pump,
|
|
24
|
+
take
|
|
25
|
+
}
|
|
18
26
|
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
19
27
|
let timer
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
*/
|
|
28
|
+
return state
|
|
29
|
+
/** @returns {void} */
|
|
23
30
|
function done() {
|
|
24
31
|
state.running--
|
|
25
32
|
pump()
|
|
26
33
|
}
|
|
27
|
-
/**
|
|
28
|
-
* @returns {void}
|
|
29
|
-
*/
|
|
34
|
+
/** @returns {void} */
|
|
30
35
|
function pump() {
|
|
31
36
|
while (queue.length && state.running < concurrency) {
|
|
32
37
|
if (rate) {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
if (starts.length >= rate.count) {
|
|
38
|
+
const wait = reserve(Date.now())
|
|
39
|
+
if (wait > 0) {
|
|
36
40
|
timer ??= setTimeout(
|
|
37
41
|
() => {
|
|
38
42
|
timer = void 0
|
|
39
43
|
pump()
|
|
40
44
|
},
|
|
41
|
-
clamp_delay(
|
|
42
|
-
/** @type {number} */(starts[0])/**/ + rate.per - now
|
|
43
|
-
)
|
|
45
|
+
clamp_delay(wait)
|
|
44
46
|
)
|
|
45
47
|
return
|
|
46
48
|
}
|
|
47
|
-
starts.push(now)
|
|
48
49
|
}
|
|
49
50
|
const job = /** @type {LimiterJob} */(queue.shift())/**/
|
|
50
51
|
job.unlink()
|
|
@@ -61,17 +62,27 @@ function create_state(options, on_idle) {
|
|
|
61
62
|
job.resolve(
|
|
62
63
|
{
|
|
63
64
|
release: () => {
|
|
64
|
-
if (released)
|
|
65
|
+
if (released) {
|
|
66
|
+
return
|
|
67
|
+
}
|
|
65
68
|
released = true
|
|
66
69
|
done()
|
|
67
70
|
}
|
|
68
71
|
}
|
|
69
72
|
)
|
|
70
73
|
}
|
|
71
|
-
if (queue.length || state.running || timer || !on_idle)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
if (queue.length || state.running || timer || !on_idle) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
const left = !rate
|
|
78
|
+
? 0
|
|
79
|
+
: fixed
|
|
80
|
+
? used
|
|
81
|
+
? window_start + rate.per - Date.now()
|
|
82
|
+
: 0
|
|
83
|
+
: starts.length
|
|
84
|
+
? /** @type {number} */(starts.at(-1))/**/ + rate.per - Date.now()
|
|
85
|
+
: 0
|
|
75
86
|
if (left > 0) {
|
|
76
87
|
timer = setTimeout(
|
|
77
88
|
() => {
|
|
@@ -84,16 +95,50 @@ function create_state(options, on_idle) {
|
|
|
84
95
|
}
|
|
85
96
|
on_idle()
|
|
86
97
|
}
|
|
87
|
-
|
|
98
|
+
/**
|
|
99
|
+
* @param {number} now
|
|
100
|
+
* @returns {number}
|
|
101
|
+
*/
|
|
102
|
+
function reserve(now) {
|
|
103
|
+
const { count, per } = /** @type {{ count: number, per: number }} */(rate)/**/
|
|
104
|
+
if (fixed) {
|
|
105
|
+
if (used && now - window_start >= per) {
|
|
106
|
+
used = 0
|
|
107
|
+
}
|
|
108
|
+
if (used >= count) {
|
|
109
|
+
return window_start + per - now
|
|
110
|
+
}
|
|
111
|
+
if (!used) {
|
|
112
|
+
window_start = now
|
|
113
|
+
}
|
|
114
|
+
used++
|
|
115
|
+
return 0
|
|
116
|
+
}
|
|
117
|
+
while (starts.length && now - /** @type {number} */(starts[0])/**/ >= per) {
|
|
118
|
+
starts.shift()
|
|
119
|
+
}
|
|
120
|
+
if (starts.length >= count) {
|
|
121
|
+
return /** @type {number} */(starts[0])/**/ + per - now
|
|
122
|
+
}
|
|
123
|
+
starts.push(now)
|
|
124
|
+
return 0
|
|
125
|
+
}
|
|
126
|
+
/** @returns {number} */
|
|
127
|
+
function take() {
|
|
128
|
+
return reserve(Date.now())
|
|
129
|
+
}
|
|
88
130
|
}
|
|
89
131
|
/**
|
|
90
132
|
* Limits the calls that run at the same time, and the calls that start per interval, across everything that shares it:
|
|
91
133
|
* a database pool, a file system, an API quota. Use it inside flow nodes to limit them across runs.
|
|
134
|
+
* With a `rate`, `take()` answers at once whether a start is free, e.g. for a packet limit per socket.
|
|
92
135
|
* @example
|
|
93
136
|
* const db = limiter({ concurrency: 10 })
|
|
94
137
|
* const openai = limiter({ rate: { count: 60, per: 60000 } })
|
|
95
138
|
* const user = await db(() => pool.query("select ..."))
|
|
96
|
-
*
|
|
139
|
+
* const packets = limiter({ rate: { count: 20, per: 1000, window: "fixed" } })
|
|
140
|
+
* const retry_after = packets.take(socket.id)
|
|
141
|
+
* @param {{ concurrency?: number | undefined, rate?: { count: number, per: number, window?: "fixed" | "sliding" | undefined } | undefined }} options
|
|
97
142
|
* @returns {import("../public.js").Limiter}
|
|
98
143
|
*/
|
|
99
144
|
export function limiter(options) {
|
|
@@ -104,46 +149,78 @@ export function limiter(options) {
|
|
|
104
149
|
)
|
|
105
150
|
check_options(
|
|
106
151
|
options.rate,
|
|
107
|
-
[ "count", "per" ],
|
|
152
|
+
[ "count", "per", "window" ],
|
|
108
153
|
"limiter",
|
|
109
154
|
"rate."
|
|
110
155
|
)
|
|
111
156
|
const { concurrency = Infinity, rate } = options
|
|
112
|
-
if (!(concurrency === Infinity || Number.isInteger(concurrency) && concurrency >= 1))
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
)
|
|
157
|
+
if (!(concurrency === Infinity || Number.isInteger(concurrency) && concurrency >= 1)) {
|
|
158
|
+
throw TypeError(
|
|
159
|
+
"The concurrency of limiter() must be a positive integer or Infinity"
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
if (rate && !(Number.isInteger(rate.count) && rate.count >= 1 && typeof rate.per == "number" && rate.per > 0)) {
|
|
163
|
+
throw TypeError(
|
|
164
|
+
"The rate of limiter() must be { count, per } with a positive integer count and positive milliseconds"
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
if (rate?.window !== void 0 && rate.window != "sliding" && rate.window != "fixed") {
|
|
168
|
+
throw TypeError(
|
|
169
|
+
"The rate.window of limiter() must be \"sliding\" or \"fixed\""
|
|
170
|
+
)
|
|
171
|
+
}
|
|
118
172
|
/** @type {Map<unknown, LimiterState>} */
|
|
119
173
|
const keyed = new Map()
|
|
120
174
|
const root = create_state(options)
|
|
175
|
+
return to_limiter(
|
|
176
|
+
() => root,
|
|
177
|
+
() => root,
|
|
178
|
+
key,
|
|
179
|
+
name => take(() => root, name)
|
|
180
|
+
)
|
|
121
181
|
/**
|
|
122
182
|
* @param {unknown} name
|
|
123
183
|
* @returns {import("../public.js").Limiter}
|
|
124
184
|
*/
|
|
125
185
|
function key(name) {
|
|
126
|
-
/**
|
|
127
|
-
* @returns {LimiterState}
|
|
128
|
-
*/
|
|
129
|
-
function current() {
|
|
130
|
-
let state = keyed.get(name)
|
|
131
|
-
if (!state) {
|
|
132
|
-
state = create_state(
|
|
133
|
-
options,
|
|
134
|
-
() => keyed.delete(name)
|
|
135
|
-
)
|
|
136
|
-
keyed.set(name, state)
|
|
137
|
-
}
|
|
138
|
-
return state
|
|
139
|
-
}
|
|
140
186
|
return to_limiter(
|
|
141
|
-
|
|
187
|
+
() => state_of(name),
|
|
142
188
|
() => keyed.get(name),
|
|
143
|
-
key
|
|
189
|
+
key,
|
|
190
|
+
other => take(() => state_of(name), other)
|
|
144
191
|
)
|
|
145
192
|
}
|
|
146
|
-
|
|
193
|
+
/**
|
|
194
|
+
* @param {unknown} name
|
|
195
|
+
* @returns {LimiterState}
|
|
196
|
+
*/
|
|
197
|
+
function state_of(name) {
|
|
198
|
+
let state = keyed.get(name)
|
|
199
|
+
if (!state) {
|
|
200
|
+
state = create_state(
|
|
201
|
+
options,
|
|
202
|
+
() => keyed.delete(name)
|
|
203
|
+
)
|
|
204
|
+
keyed.set(name, state)
|
|
205
|
+
}
|
|
206
|
+
return state
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* @param {() => LimiterState} own
|
|
210
|
+
* @param {unknown} name
|
|
211
|
+
* @returns {number}
|
|
212
|
+
*/
|
|
213
|
+
function take(own, name) {
|
|
214
|
+
if (!rate) {
|
|
215
|
+
throw TypeError(
|
|
216
|
+
"take() of limiter() needs a rate"
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
const state = name === void 0 ? own() : state_of(name)
|
|
220
|
+
const wait = state.take()
|
|
221
|
+
state.schedule()
|
|
222
|
+
return wait
|
|
223
|
+
}
|
|
147
224
|
}
|
|
148
225
|
/**
|
|
149
226
|
* @param {unknown} reason
|
|
@@ -156,9 +233,28 @@ function to_cancel_error(reason) {
|
|
|
156
233
|
* @param {() => LimiterState} current
|
|
157
234
|
* @param {() => LimiterState | undefined} peek
|
|
158
235
|
* @param {(key: unknown) => import("../public.js").Limiter} key
|
|
236
|
+
* @param {(key: unknown) => number} take
|
|
159
237
|
* @returns {import("../public.js").Limiter}
|
|
160
238
|
*/
|
|
161
|
-
function to_limiter(current, peek, key) {
|
|
239
|
+
function to_limiter(current, peek, key, take) {
|
|
240
|
+
return /** @type {import("../public.js").Limiter} */(Object.defineProperties(
|
|
241
|
+
limit,
|
|
242
|
+
{
|
|
243
|
+
acquire: {
|
|
244
|
+
value: (
|
|
245
|
+
/** @type {AbortSignal=} */ signal
|
|
246
|
+
) => enqueue(void 0, signal)
|
|
247
|
+
},
|
|
248
|
+
key: { value: key },
|
|
249
|
+
pending: {
|
|
250
|
+
get: () => peek()?.queue.length ?? 0
|
|
251
|
+
},
|
|
252
|
+
running: {
|
|
253
|
+
get: () => peek()?.running ?? 0
|
|
254
|
+
},
|
|
255
|
+
take: { value: take }
|
|
256
|
+
}
|
|
257
|
+
))/**/
|
|
162
258
|
/**
|
|
163
259
|
* @param {(() => unknown) | undefined} fn
|
|
164
260
|
* @param {AbortSignal | undefined} signal
|
|
@@ -179,7 +275,9 @@ function to_limiter(current, peek, key) {
|
|
|
179
275
|
signal,
|
|
180
276
|
() => {
|
|
181
277
|
const index = state.queue.indexOf(job)
|
|
182
|
-
if (index < 0)
|
|
278
|
+
if (index < 0) {
|
|
279
|
+
return
|
|
280
|
+
}
|
|
183
281
|
state.queue.splice(index, 1)
|
|
184
282
|
reject(to_cancel_error(signal.reason))
|
|
185
283
|
state.schedule()
|
|
@@ -199,21 +297,4 @@ function to_limiter(current, peek, key) {
|
|
|
199
297
|
function limit(fn, signal) {
|
|
200
298
|
return enqueue(fn, signal)
|
|
201
299
|
}
|
|
202
|
-
return /** @type {import("../public.js").Limiter} */(Object.defineProperties(
|
|
203
|
-
limit,
|
|
204
|
-
{
|
|
205
|
-
acquire: {
|
|
206
|
-
value: (
|
|
207
|
-
/** @type {AbortSignal=} */ signal
|
|
208
|
-
) => enqueue(void 0, signal)
|
|
209
|
-
},
|
|
210
|
-
key: { value: key },
|
|
211
|
-
pending: {
|
|
212
|
-
get: () => peek()?.queue.length ?? 0
|
|
213
|
-
},
|
|
214
|
-
running: {
|
|
215
|
-
get: () => peek()?.running ?? 0
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
))/**/
|
|
219
300
|
}
|
package/src/offload.js
CHANGED
|
@@ -1,31 +1,54 @@
|
|
|
1
1
|
/** @import { NodeWorker, OffloadJob, OffloadWorker } from "../private.js" */
|
|
2
2
|
import { CancelError } from "./errors.js"
|
|
3
|
+
import { offload_module, to_error } from "./offload_module.js"
|
|
3
4
|
import { check_options } from "./options.js"
|
|
4
5
|
import { noop, on_abort } from "./signal.js"
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
* @param {unknown} target
|
|
8
|
+
* @param {unknown=} options
|
|
9
|
+
* @returns {never}
|
|
10
|
+
*/
|
|
11
|
+
function create_offload(target, options) {
|
|
12
|
+
if (typeof target == "function") {
|
|
13
|
+
return /** @type {never} */(offload_function(
|
|
14
|
+
/** @type {(...args: never[]) => unknown} */(target)/**/,
|
|
15
|
+
/** @type {{ concurrency?: number | undefined } | undefined} */(options)/**/
|
|
16
|
+
))/**/
|
|
17
|
+
}
|
|
18
|
+
if (typeof target == "string" || target instanceof URL) {
|
|
19
|
+
return /** @type {never} */(offload_module(
|
|
20
|
+
target,
|
|
21
|
+
/** @type {import("../public.js").OffloadModuleOptions | undefined} */(options)/**/
|
|
22
|
+
))/**/
|
|
23
|
+
}
|
|
24
|
+
throw TypeError(
|
|
25
|
+
"offload() needs a function, or the URL of a worker module"
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
/** @type {import("../public.js").OffloadFunction} */
|
|
29
|
+
export const offload = create_offload
|
|
30
|
+
/**
|
|
12
31
|
* @template {unknown[]} A
|
|
13
32
|
* @template R
|
|
14
33
|
* @param {(...args: A) => R} fn
|
|
15
34
|
* @param {{ concurrency?: number | undefined }=} options
|
|
16
35
|
* @returns {import("../public.js").Offloaded<A, R>}
|
|
17
36
|
*/
|
|
18
|
-
|
|
19
|
-
if (typeof fn != "function")
|
|
37
|
+
function offload_function(fn, options = {}) {
|
|
38
|
+
if (typeof fn != "function") {
|
|
39
|
+
throw TypeError("offload() needs a function")
|
|
40
|
+
}
|
|
20
41
|
check_options(
|
|
21
42
|
options,
|
|
22
43
|
[ "concurrency" ],
|
|
23
44
|
"offload"
|
|
24
45
|
)
|
|
25
46
|
const concurrency = options.concurrency ?? 1
|
|
26
|
-
if (!(concurrency === Infinity || Number.isInteger(concurrency) && concurrency >= 1))
|
|
27
|
-
|
|
28
|
-
|
|
47
|
+
if (!(concurrency === Infinity || Number.isInteger(concurrency) && concurrency >= 1)) {
|
|
48
|
+
throw TypeError(
|
|
49
|
+
"The concurrency of offload() must be a positive integer or Infinity"
|
|
50
|
+
)
|
|
51
|
+
}
|
|
29
52
|
const source = String(fn)
|
|
30
53
|
/** @type {OffloadJob[]} */
|
|
31
54
|
const queue = []
|
|
@@ -34,6 +57,26 @@ export function offload(fn, options = {}) {
|
|
|
34
57
|
/** @type {string | undefined} */
|
|
35
58
|
let url
|
|
36
59
|
let count = 0
|
|
60
|
+
const run = /** @type {import("../public.js").Offloaded<never[], unknown>} */(/** @type {unknown} */(call))/**/
|
|
61
|
+
run.close = () => {
|
|
62
|
+
for (const job of queue.splice(0)) {
|
|
63
|
+
job.reject(
|
|
64
|
+
new CancelError("The offload was closed")
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
for (const worker of workers) {
|
|
68
|
+
worker.job?.reject(
|
|
69
|
+
new CancelError("The offload was closed")
|
|
70
|
+
)
|
|
71
|
+
worker.stop()
|
|
72
|
+
}
|
|
73
|
+
workers.clear()
|
|
74
|
+
if (url) {
|
|
75
|
+
URL.revokeObjectURL(url)
|
|
76
|
+
}
|
|
77
|
+
url = void 0
|
|
78
|
+
}
|
|
79
|
+
return /** @type {import("../public.js").Offloaded<A, R>} */(/** @type {unknown} */(run))/**/
|
|
37
80
|
/**
|
|
38
81
|
* @param {...unknown} args
|
|
39
82
|
* @returns {import("../public.js").OffloadCall<unknown>}
|
|
@@ -41,14 +84,16 @@ export function offload(fn, options = {}) {
|
|
|
41
84
|
function call(...args) {
|
|
42
85
|
const last = args.at(-1)
|
|
43
86
|
const signal = last instanceof AbortSignal ? last : void 0
|
|
44
|
-
if (signal)
|
|
87
|
+
if (signal) {
|
|
88
|
+
args.pop()
|
|
89
|
+
}
|
|
45
90
|
let unlink = noop
|
|
46
91
|
/** @type {OffloadJob} */
|
|
47
92
|
const job = {
|
|
48
93
|
args,
|
|
49
94
|
id: ++count,
|
|
50
|
-
reject:
|
|
51
|
-
resolve:
|
|
95
|
+
reject: noop,
|
|
96
|
+
resolve: noop
|
|
52
97
|
}
|
|
53
98
|
const promise = new Promise(
|
|
54
99
|
(resolve, reject) => {
|
|
@@ -62,12 +107,15 @@ export function offload(fn, options = {}) {
|
|
|
62
107
|
}
|
|
63
108
|
}
|
|
64
109
|
)
|
|
65
|
-
if (signal?.aborted)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
110
|
+
if (signal?.aborted) {
|
|
111
|
+
job.reject(new CancelError(signal.reason))
|
|
112
|
+
} else {
|
|
113
|
+
if (signal) {
|
|
114
|
+
unlink = on_abort(
|
|
115
|
+
signal,
|
|
116
|
+
() => cancel_job(job, signal.reason)
|
|
117
|
+
)
|
|
118
|
+
}
|
|
71
119
|
queue.push(job)
|
|
72
120
|
dispatch()
|
|
73
121
|
}
|
|
@@ -86,9 +134,13 @@ export function offload(fn, options = {}) {
|
|
|
86
134
|
*/
|
|
87
135
|
function cancel_job(job, reason) {
|
|
88
136
|
const index = queue.indexOf(job)
|
|
89
|
-
if (index >= 0)
|
|
137
|
+
if (index >= 0) {
|
|
138
|
+
queue.splice(index, 1)
|
|
139
|
+
}
|
|
90
140
|
for (const worker of workers) {
|
|
91
|
-
if (worker.job != job)
|
|
141
|
+
if (worker.job != job) {
|
|
142
|
+
continue
|
|
143
|
+
}
|
|
92
144
|
worker.job = void 0
|
|
93
145
|
worker.stop()
|
|
94
146
|
workers.delete(worker)
|
|
@@ -98,18 +150,20 @@ export function offload(fn, options = {}) {
|
|
|
98
150
|
)
|
|
99
151
|
dispatch()
|
|
100
152
|
}
|
|
101
|
-
/**
|
|
102
|
-
* @returns {void}
|
|
103
|
-
*/
|
|
153
|
+
/** @returns {void} */
|
|
104
154
|
function dispatch() {
|
|
105
155
|
while (queue.length) {
|
|
106
156
|
let worker = [ ...workers ].find(item => !item.job)
|
|
107
157
|
if (!worker) {
|
|
108
|
-
if (workers.size >= concurrency)
|
|
158
|
+
if (workers.size >= concurrency) {
|
|
159
|
+
return
|
|
160
|
+
}
|
|
109
161
|
try {
|
|
110
162
|
worker = spawn()
|
|
111
163
|
} catch (error) {
|
|
112
|
-
for (const job of queue.splice(0))
|
|
164
|
+
for (const job of queue.splice(0)) {
|
|
165
|
+
job.reject(error)
|
|
166
|
+
}
|
|
113
167
|
return
|
|
114
168
|
}
|
|
115
169
|
}
|
|
@@ -132,40 +186,14 @@ export function offload(fn, options = {}) {
|
|
|
132
186
|
worker.idle?.()
|
|
133
187
|
dispatch()
|
|
134
188
|
}
|
|
135
|
-
/**
|
|
136
|
-
* @returns {OffloadWorker}
|
|
137
|
-
*/
|
|
189
|
+
/** @returns {OffloadWorker} */
|
|
138
190
|
function spawn() {
|
|
139
191
|
const threads = /** @type {{ process?: { getBuiltinModule?: (name: string) => unknown } }} */(globalThis)/**/.process?.getBuiltinModule?.("node:worker_threads")
|
|
140
192
|
/** @type {OffloadWorker} */
|
|
141
193
|
const worker = {
|
|
142
194
|
job: void 0,
|
|
143
|
-
send:
|
|
144
|
-
stop:
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* @param {unknown} error
|
|
148
|
-
* @returns {void}
|
|
149
|
-
*/
|
|
150
|
-
function crash(error) {
|
|
151
|
-
const { job } = worker
|
|
152
|
-
worker.job = void 0
|
|
153
|
-
worker.stop()
|
|
154
|
-
workers.delete(worker)
|
|
155
|
-
job?.reject(to_error(error))
|
|
156
|
-
dispatch()
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* @param {[number, boolean, unknown]} message
|
|
160
|
-
* @returns {void}
|
|
161
|
-
*/
|
|
162
|
-
function receive([ id, ok, value ]) {
|
|
163
|
-
const { job } = worker
|
|
164
|
-
if (!job || job.id != id) return
|
|
165
|
-
worker.job = void 0
|
|
166
|
-
if (ok) job.resolve(value)
|
|
167
|
-
else job.reject(to_error(value))
|
|
168
|
-
finish(worker)
|
|
195
|
+
send: noop,
|
|
196
|
+
stop: noop
|
|
169
197
|
}
|
|
170
198
|
if (threads) {
|
|
171
199
|
const { Worker } = /** @type {{ Worker: new (code: string, options: { eval: true }) => NodeWorker }} */(threads)/**/
|
|
@@ -201,28 +229,43 @@ export function offload(fn, options = {}) {
|
|
|
201
229
|
}
|
|
202
230
|
worker.send = message => thread.postMessage(message)
|
|
203
231
|
worker.stop = () => thread.terminate()
|
|
204
|
-
} else
|
|
205
|
-
|
|
206
|
-
|
|
232
|
+
} else {
|
|
233
|
+
throw TypeError(
|
|
234
|
+
"offload() needs Worker, or worker_threads of Node.js 20.16 or later"
|
|
235
|
+
)
|
|
236
|
+
}
|
|
207
237
|
workers.add(worker)
|
|
208
238
|
return worker
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
worker.job?.reject(
|
|
217
|
-
new CancelError("The offload was closed")
|
|
218
|
-
)
|
|
239
|
+
/**
|
|
240
|
+
* @param {unknown} error
|
|
241
|
+
* @returns {void}
|
|
242
|
+
*/
|
|
243
|
+
function crash(error) {
|
|
244
|
+
const { job } = worker
|
|
245
|
+
worker.job = void 0
|
|
219
246
|
worker.stop()
|
|
247
|
+
workers.delete(worker)
|
|
248
|
+
job?.reject(to_error(error))
|
|
249
|
+
dispatch()
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* @param {[number, boolean, unknown]} message
|
|
253
|
+
* @returns {void}
|
|
254
|
+
*/
|
|
255
|
+
function receive([ id, ok, value ]) {
|
|
256
|
+
const { job } = worker
|
|
257
|
+
if (!job || job.id != id) {
|
|
258
|
+
return
|
|
259
|
+
}
|
|
260
|
+
worker.job = void 0
|
|
261
|
+
if (ok) {
|
|
262
|
+
job.resolve(value)
|
|
263
|
+
} else {
|
|
264
|
+
job.reject(to_error(value))
|
|
265
|
+
}
|
|
266
|
+
finish(worker)
|
|
220
267
|
}
|
|
221
|
-
workers.clear()
|
|
222
|
-
if (url) URL.revokeObjectURL(url)
|
|
223
|
-
url = void 0
|
|
224
268
|
}
|
|
225
|
-
return /** @type {import("../public.js").Offloaded<A, R>} */(/** @type {unknown} */(run))/**/
|
|
226
269
|
}
|
|
227
270
|
/**
|
|
228
271
|
* @param {string} post
|
|
@@ -240,16 +283,4 @@ function reply(post) {
|
|
|
240
283
|
}
|
|
241
284
|
}
|
|
242
285
|
}`
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* @param {unknown} value
|
|
246
|
-
* @returns {unknown}
|
|
247
|
-
*/
|
|
248
|
-
function to_error(value) {
|
|
249
|
-
if (value instanceof Error || value == null || typeof value != "object" || !("message" in value)) return value
|
|
250
|
-
const error = Error(String(value.message))
|
|
251
|
-
error.name = String(
|
|
252
|
-
/** @type {{ name?: unknown }} */(value)/**/.name ?? "Error"
|
|
253
|
-
)
|
|
254
|
-
return error
|
|
255
286
|
}
|