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
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
/** @import { NodeWorker, OffloadReply, OffloadTransport } from "../private.js" */
|
|
2
|
+
import { CancelError } from "./errors.js"
|
|
3
|
+
import { check_options } from "./options.js"
|
|
4
|
+
import { noop } from "./signal.js"
|
|
5
|
+
/**
|
|
6
|
+
* @template Q, R, D, B
|
|
7
|
+
* @param {string | URL} target
|
|
8
|
+
* @param {import("../public.js").OffloadModuleOptions<D, B>=} options
|
|
9
|
+
* @returns {import("../public.js").OffloadModule<Q, R>}
|
|
10
|
+
*/
|
|
11
|
+
export function offload_module(target, options = {}) {
|
|
12
|
+
const url = typeof target == "string" ? new URL(target) : target
|
|
13
|
+
check_options(
|
|
14
|
+
options,
|
|
15
|
+
[ "data", "delay", "onBatch" ],
|
|
16
|
+
"offload"
|
|
17
|
+
)
|
|
18
|
+
const { data, delay = 0, onBatch } = options
|
|
19
|
+
if (typeof delay != "number" || !(Number.isFinite(delay) && delay >= 0)) {
|
|
20
|
+
throw TypeError(
|
|
21
|
+
"The delay of offload() must be a non-negative number of milliseconds"
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
if (onBatch !== void 0 && typeof onBatch != "function") {
|
|
25
|
+
throw TypeError(
|
|
26
|
+
"The onBatch of offload() must be a function"
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
/** @type {Map<number, { reject: (error: unknown) => void, resolve: (value: R) => void }>} */
|
|
30
|
+
const pending = new Map()
|
|
31
|
+
/** @type {[number, Q][]} */
|
|
32
|
+
let outbox = []
|
|
33
|
+
/** @type {OffloadTransport} */
|
|
34
|
+
let transport = {
|
|
35
|
+
post: noop,
|
|
36
|
+
ref: noop,
|
|
37
|
+
stop: () => Promise.resolve()
|
|
38
|
+
}
|
|
39
|
+
/** @type {{ error: unknown } | undefined} */
|
|
40
|
+
let failure
|
|
41
|
+
/** @type {Promise<void> | undefined} */
|
|
42
|
+
let closing
|
|
43
|
+
/** @type {((outcome: { error: unknown } | undefined) => void) | undefined} */
|
|
44
|
+
let finish_closing
|
|
45
|
+
let chain = Promise.resolve()
|
|
46
|
+
let count = 0
|
|
47
|
+
let closed = false
|
|
48
|
+
let flushing = false
|
|
49
|
+
let is_ready = false
|
|
50
|
+
let resolve_ready = noop
|
|
51
|
+
/** @type {(error: unknown) => void} */
|
|
52
|
+
let reject_ready = noop
|
|
53
|
+
const ready = new Promise(
|
|
54
|
+
(resolve, reject) => {
|
|
55
|
+
resolve_ready = () => resolve(void 0)
|
|
56
|
+
reject_ready = reject
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
ready.catch(noop)
|
|
60
|
+
try {
|
|
61
|
+
transport = open_thread(
|
|
62
|
+
url,
|
|
63
|
+
data,
|
|
64
|
+
delay,
|
|
65
|
+
receive,
|
|
66
|
+
error => fail(to_error(error))
|
|
67
|
+
)
|
|
68
|
+
} catch (error) {
|
|
69
|
+
fail(error)
|
|
70
|
+
}
|
|
71
|
+
update_ref()
|
|
72
|
+
return {
|
|
73
|
+
call,
|
|
74
|
+
close: () => closing ??= shutdown(),
|
|
75
|
+
ready: /** @type {Promise<void>} */(ready)/**/
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* @param {OffloadReply[]} entries
|
|
79
|
+
* @returns {void}
|
|
80
|
+
*/
|
|
81
|
+
function apply(entries) {
|
|
82
|
+
for (const [ id, ok, value ] of entries) {
|
|
83
|
+
settle(
|
|
84
|
+
id,
|
|
85
|
+
ok,
|
|
86
|
+
ok ? value : to_error(value)
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* @param {Q} request
|
|
92
|
+
* @returns {Promise<R>}
|
|
93
|
+
*/
|
|
94
|
+
function call(request) {
|
|
95
|
+
if (failure) {
|
|
96
|
+
return Promise.reject(failure.error)
|
|
97
|
+
}
|
|
98
|
+
if (closed) {
|
|
99
|
+
return Promise.reject(
|
|
100
|
+
new CancelError("The offload was closed")
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
return new Promise(
|
|
104
|
+
(resolve, reject) => {
|
|
105
|
+
const id = ++count
|
|
106
|
+
pending.set(id, { reject, resolve })
|
|
107
|
+
outbox.push([ id, request ])
|
|
108
|
+
update_ref()
|
|
109
|
+
if (!flushing) {
|
|
110
|
+
flushing = true
|
|
111
|
+
queueMicrotask(flush)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* @param {unknown} error
|
|
118
|
+
* @returns {void}
|
|
119
|
+
*/
|
|
120
|
+
function fail(error) {
|
|
121
|
+
if (failure) {
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
failure = { error }
|
|
125
|
+
reject_ready(error)
|
|
126
|
+
const jobs = [ ...pending.values() ]
|
|
127
|
+
pending.clear()
|
|
128
|
+
outbox = []
|
|
129
|
+
for (const job of jobs) {
|
|
130
|
+
job.reject(error)
|
|
131
|
+
}
|
|
132
|
+
void transport.stop()
|
|
133
|
+
update_ref()
|
|
134
|
+
finish_closing?.({ error })
|
|
135
|
+
}
|
|
136
|
+
/** @returns {void} */
|
|
137
|
+
function flush() {
|
|
138
|
+
flushing = false
|
|
139
|
+
if (!is_ready || failure || !outbox.length) {
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
const calls = outbox
|
|
143
|
+
outbox = []
|
|
144
|
+
try {
|
|
145
|
+
transport.post([ "calls", calls ])
|
|
146
|
+
} catch {
|
|
147
|
+
for (const entry of calls) {
|
|
148
|
+
try {
|
|
149
|
+
transport.post([ "calls", [ entry ] ])
|
|
150
|
+
} catch (error) {
|
|
151
|
+
settle(entry[0], false, error)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* @param {unknown[]} message
|
|
158
|
+
* @returns {void}
|
|
159
|
+
*/
|
|
160
|
+
function receive(message) {
|
|
161
|
+
const [ kind, first, second ] = message
|
|
162
|
+
if (kind == "ready") {
|
|
163
|
+
is_ready = true
|
|
164
|
+
resolve_ready()
|
|
165
|
+
update_ref()
|
|
166
|
+
flush()
|
|
167
|
+
} else if (kind == "failed") {
|
|
168
|
+
fail(to_error(first))
|
|
169
|
+
} else if (kind == "replies") {
|
|
170
|
+
replied(
|
|
171
|
+
/** @type {OffloadReply[]} */(first)/**/,
|
|
172
|
+
second
|
|
173
|
+
)
|
|
174
|
+
} else if (kind == "closed") {
|
|
175
|
+
finish_closing?.(
|
|
176
|
+
first ? { error: to_error(second) } : void 0
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* @param {OffloadReply[]} entries
|
|
182
|
+
* @param {unknown} value
|
|
183
|
+
* @returns {void}
|
|
184
|
+
*/
|
|
185
|
+
function replied(entries, value) {
|
|
186
|
+
if (!onBatch) {
|
|
187
|
+
apply(entries)
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
chain = chain.then(
|
|
191
|
+
async () => {
|
|
192
|
+
if (value !== void 0) {
|
|
193
|
+
try {
|
|
194
|
+
await onBatch(/** @type {B} */(value)/**/)
|
|
195
|
+
} catch (error) {
|
|
196
|
+
for (const [ id ] of entries) {
|
|
197
|
+
settle(id, false, error)
|
|
198
|
+
}
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
apply(entries)
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* @param {number} id
|
|
208
|
+
* @param {boolean} ok
|
|
209
|
+
* @param {unknown} value
|
|
210
|
+
* @returns {void}
|
|
211
|
+
*/
|
|
212
|
+
function settle(id, ok, value) {
|
|
213
|
+
const job = pending.get(id)
|
|
214
|
+
if (!job) {
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
pending.delete(id)
|
|
218
|
+
update_ref()
|
|
219
|
+
if (ok) {
|
|
220
|
+
job.resolve(/** @type {R} */(value)/**/)
|
|
221
|
+
} else {
|
|
222
|
+
job.reject(value)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/** @returns {Promise<void>} */
|
|
226
|
+
async function shutdown() {
|
|
227
|
+
closed = true
|
|
228
|
+
update_ref()
|
|
229
|
+
await ready.catch(noop)
|
|
230
|
+
/** @type {{ error: unknown } | undefined} */
|
|
231
|
+
let outcome
|
|
232
|
+
if (!failure) {
|
|
233
|
+
flush()
|
|
234
|
+
outcome = await new Promise(
|
|
235
|
+
resolve => {
|
|
236
|
+
finish_closing = resolve
|
|
237
|
+
try {
|
|
238
|
+
transport.post([ "close" ])
|
|
239
|
+
} catch (error) {
|
|
240
|
+
resolve({ error })
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
await chain
|
|
246
|
+
await transport.stop()
|
|
247
|
+
if (outcome) {
|
|
248
|
+
throw outcome.error
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/** @returns {void} */
|
|
252
|
+
function update_ref() {
|
|
253
|
+
transport.ref(
|
|
254
|
+
pending.size > 0 || closed || !is_ready && !failure
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* @param {URL} url
|
|
260
|
+
* @param {unknown} data
|
|
261
|
+
* @param {number} delay
|
|
262
|
+
* @param {(message: unknown[]) => void} on_message
|
|
263
|
+
* @param {(error: unknown) => void} on_failure
|
|
264
|
+
* @returns {OffloadTransport}
|
|
265
|
+
*/
|
|
266
|
+
function open_thread(
|
|
267
|
+
url,
|
|
268
|
+
data,
|
|
269
|
+
delay,
|
|
270
|
+
on_message,
|
|
271
|
+
on_failure
|
|
272
|
+
) {
|
|
273
|
+
const threads = /** @type {{ process?: { getBuiltinModule?: (name: string) => unknown } }} */(globalThis)/**/.process?.getBuiltinModule?.("node:worker_threads")
|
|
274
|
+
const rest = `() => import(${JSON.stringify(url.href)}), ${delay}`
|
|
275
|
+
if (threads) {
|
|
276
|
+
const { Worker } = /** @type {{ Worker: new (code: string, options: { eval: true, workerData: unknown }) => NodeWorker }} */(threads)/**/
|
|
277
|
+
const thread = new Worker(
|
|
278
|
+
`const { parentPort, workerData } = process.getBuiltinModule("node:worker_threads");\n(${worker_main})(message => parentPort.postMessage(message), listener => parentPort.on("message", listener), fn => setImmediate(fn), ${rest}, workerData)`,
|
|
279
|
+
{ eval: true, workerData: data }
|
|
280
|
+
)
|
|
281
|
+
let stopped = false
|
|
282
|
+
thread.on("message", on_message)
|
|
283
|
+
thread.on("error", on_failure)
|
|
284
|
+
thread.on(
|
|
285
|
+
"exit",
|
|
286
|
+
code => {
|
|
287
|
+
if (!stopped) {
|
|
288
|
+
on_failure(
|
|
289
|
+
Error(
|
|
290
|
+
`The worker of offload() exited with code ${code}`
|
|
291
|
+
)
|
|
292
|
+
)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
)
|
|
296
|
+
thread.unref()
|
|
297
|
+
return {
|
|
298
|
+
post: message => thread.postMessage(message),
|
|
299
|
+
ref: on => on ? thread.ref() : thread.unref(),
|
|
300
|
+
stop: () => {
|
|
301
|
+
stopped = true
|
|
302
|
+
return thread.terminate().then(noop)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (typeof Worker == "function") {
|
|
307
|
+
const source = URL.createObjectURL(
|
|
308
|
+
new Blob(
|
|
309
|
+
[
|
|
310
|
+
`self.onmessage = ({ data: [ , data ] }) => (${worker_main})(message => self.postMessage(message), listener => { self.onmessage = ({ data }) => listener(data) }, fn => setTimeout(fn, 0), ${rest}, data)`
|
|
311
|
+
],
|
|
312
|
+
{ type: "text/javascript" }
|
|
313
|
+
)
|
|
314
|
+
)
|
|
315
|
+
const thread = new Worker(source, { type: "module" })
|
|
316
|
+
thread.onmessage = event => on_message(event.data)
|
|
317
|
+
thread.onerror = event => {
|
|
318
|
+
event.preventDefault()
|
|
319
|
+
on_failure(
|
|
320
|
+
event.error ?? Error(event.message)
|
|
321
|
+
)
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
thread.postMessage([ "init", data ])
|
|
325
|
+
} catch (error) {
|
|
326
|
+
thread.terminate()
|
|
327
|
+
URL.revokeObjectURL(source)
|
|
328
|
+
throw error
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
post: message => thread.postMessage(message),
|
|
332
|
+
ref: noop,
|
|
333
|
+
stop: () => {
|
|
334
|
+
thread.terminate()
|
|
335
|
+
URL.revokeObjectURL(source)
|
|
336
|
+
return Promise.resolve()
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
throw TypeError(
|
|
341
|
+
"offload() needs Worker, or worker_threads of Node.js 20.16 or later"
|
|
342
|
+
)
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* @param {unknown} value
|
|
346
|
+
* @returns {unknown}
|
|
347
|
+
*/
|
|
348
|
+
export function to_error(value) {
|
|
349
|
+
if (value instanceof Error || value == null || typeof value != "object" || !("message" in value)) {
|
|
350
|
+
return value
|
|
351
|
+
}
|
|
352
|
+
const error = Error(String(value.message))
|
|
353
|
+
error.name = String(
|
|
354
|
+
/** @type {{ name?: unknown }} */(value)/**/.name ?? "Error"
|
|
355
|
+
)
|
|
356
|
+
return error
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* @param {(message: unknown[]) => void} post
|
|
360
|
+
* @param {(listener: (message: unknown[]) => void) => void} listen
|
|
361
|
+
* @param {(fn: () => void) => void} defer
|
|
362
|
+
* @param {() => Promise<{ default?: unknown }>} import_module
|
|
363
|
+
* @param {number} delay
|
|
364
|
+
* @param {unknown} data
|
|
365
|
+
* @returns {void}
|
|
366
|
+
*/
|
|
367
|
+
function worker_main(
|
|
368
|
+
post,
|
|
369
|
+
listen,
|
|
370
|
+
defer,
|
|
371
|
+
import_module,
|
|
372
|
+
delay,
|
|
373
|
+
data
|
|
374
|
+
) {
|
|
375
|
+
/** @type {import("../public.js").OffloadHandlers<unknown, unknown>} */
|
|
376
|
+
let handlers = { call: () => void 0 }
|
|
377
|
+
/** @type {[number, unknown][]} */
|
|
378
|
+
const queue = []
|
|
379
|
+
let waiting = false
|
|
380
|
+
let running = false
|
|
381
|
+
let closing = false
|
|
382
|
+
listen(
|
|
383
|
+
message => {
|
|
384
|
+
if (message[0] == "close") {
|
|
385
|
+
closing = true
|
|
386
|
+
settle()
|
|
387
|
+
} else {
|
|
388
|
+
queue.push(
|
|
389
|
+
.../** @type {[number, unknown][]} */(message[1])/**/
|
|
390
|
+
)
|
|
391
|
+
wait()
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
)
|
|
395
|
+
load().then(
|
|
396
|
+
() => post([ "ready" ]),
|
|
397
|
+
error => send("failed", error)
|
|
398
|
+
)
|
|
399
|
+
/**
|
|
400
|
+
* @param {[number, unknown][]} batch
|
|
401
|
+
* @returns {Promise<void>}
|
|
402
|
+
*/
|
|
403
|
+
async function execute(batch) {
|
|
404
|
+
/** @type {Promise<OffloadReply>[]} */
|
|
405
|
+
const outcomes = []
|
|
406
|
+
/** @type {Promise<void> | undefined} */
|
|
407
|
+
let started
|
|
408
|
+
let failed = false
|
|
409
|
+
/** @type {unknown} */
|
|
410
|
+
let failure
|
|
411
|
+
/** @type {unknown} */
|
|
412
|
+
let value
|
|
413
|
+
try {
|
|
414
|
+
value = handlers.batch ? await handlers.batch(run) : await run()
|
|
415
|
+
} catch (error) {
|
|
416
|
+
failed = true
|
|
417
|
+
failure = error
|
|
418
|
+
}
|
|
419
|
+
if (started) {
|
|
420
|
+
await started
|
|
421
|
+
} else if (!failed) {
|
|
422
|
+
failed = true
|
|
423
|
+
failure = Error(
|
|
424
|
+
"The batch function of offload() did not call run()"
|
|
425
|
+
)
|
|
426
|
+
}
|
|
427
|
+
/** @type {OffloadReply[]} */
|
|
428
|
+
const entries = failed
|
|
429
|
+
? batch.map(
|
|
430
|
+
([ id ]) => [ id, false, failure ]
|
|
431
|
+
)
|
|
432
|
+
: await Promise.all(outcomes)
|
|
433
|
+
reply(entries, failed ? void 0 : value)
|
|
434
|
+
/**
|
|
435
|
+
* @param {number} id
|
|
436
|
+
* @param {unknown} request
|
|
437
|
+
* @returns {Promise<OffloadReply>}
|
|
438
|
+
*/
|
|
439
|
+
function invoke(id, request) {
|
|
440
|
+
try {
|
|
441
|
+
return Promise.resolve(handlers.call(request)).then(
|
|
442
|
+
result => [ id, true, result ],
|
|
443
|
+
error => [ id, false, error ]
|
|
444
|
+
)
|
|
445
|
+
} catch (error) {
|
|
446
|
+
return Promise.resolve([ id, false, error ])
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
/** @returns {Promise<void>} */
|
|
450
|
+
function run() {
|
|
451
|
+
started ??= start()
|
|
452
|
+
return started
|
|
453
|
+
}
|
|
454
|
+
/** @returns {Promise<void>} */
|
|
455
|
+
function start() {
|
|
456
|
+
for (const [ id, request ] of batch) {
|
|
457
|
+
outcomes.push(invoke(id, request))
|
|
458
|
+
}
|
|
459
|
+
return Promise.all(outcomes).then(() => void 0)
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
/** @returns {Promise<void>} */
|
|
463
|
+
async function finish() {
|
|
464
|
+
try {
|
|
465
|
+
await handlers.close?.()
|
|
466
|
+
} catch (error) {
|
|
467
|
+
try {
|
|
468
|
+
post([ "closed", true, error ])
|
|
469
|
+
} catch {
|
|
470
|
+
post(
|
|
471
|
+
[ "closed", true, plain(error) ]
|
|
472
|
+
)
|
|
473
|
+
}
|
|
474
|
+
return
|
|
475
|
+
}
|
|
476
|
+
post([ "closed", false ])
|
|
477
|
+
}
|
|
478
|
+
/** @returns {void} */
|
|
479
|
+
function fire() {
|
|
480
|
+
waiting = false
|
|
481
|
+
running = true
|
|
482
|
+
execute(queue.splice(0)).then(
|
|
483
|
+
() => {
|
|
484
|
+
running = false
|
|
485
|
+
settle()
|
|
486
|
+
}
|
|
487
|
+
)
|
|
488
|
+
}
|
|
489
|
+
/** @returns {Promise<void>} */
|
|
490
|
+
async function load() {
|
|
491
|
+
const setup = (await import_module()).default
|
|
492
|
+
if (typeof setup != "function") {
|
|
493
|
+
throw TypeError(
|
|
494
|
+
"The module of offload() must export a setup function by default"
|
|
495
|
+
)
|
|
496
|
+
}
|
|
497
|
+
handlers = await setup(data)
|
|
498
|
+
if (typeof handlers?.call != "function") {
|
|
499
|
+
throw TypeError(
|
|
500
|
+
"The setup function of the module of offload() must return an object with a call function"
|
|
501
|
+
)
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* @param {unknown} error
|
|
506
|
+
* @returns {{ message: string, name: string }}
|
|
507
|
+
*/
|
|
508
|
+
function plain(error) {
|
|
509
|
+
/** @type {{ message?: unknown, name?: unknown }} */
|
|
510
|
+
const { message, name } = Object(error)
|
|
511
|
+
return {
|
|
512
|
+
message: String(message ?? error),
|
|
513
|
+
name: String(name ?? "Error")
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* @param {OffloadReply[]} entries
|
|
518
|
+
* @param {unknown} value
|
|
519
|
+
* @returns {void}
|
|
520
|
+
*/
|
|
521
|
+
function reply(entries, value) {
|
|
522
|
+
try {
|
|
523
|
+
post([ "replies", entries, value ])
|
|
524
|
+
} catch {
|
|
525
|
+
let shipped = value
|
|
526
|
+
try {
|
|
527
|
+
structuredClone(value)
|
|
528
|
+
} catch (clone_error) {
|
|
529
|
+
shipped = void 0
|
|
530
|
+
entries = entries.map(
|
|
531
|
+
([ id ]) => [ id, false, plain(clone_error) ]
|
|
532
|
+
)
|
|
533
|
+
}
|
|
534
|
+
post(
|
|
535
|
+
[
|
|
536
|
+
"replies",
|
|
537
|
+
entries.map(
|
|
538
|
+
entry => {
|
|
539
|
+
try {
|
|
540
|
+
structuredClone(entry)
|
|
541
|
+
return entry
|
|
542
|
+
} catch (clone_error) {
|
|
543
|
+
return [
|
|
544
|
+
entry[0],
|
|
545
|
+
false,
|
|
546
|
+
plain(
|
|
547
|
+
entry[1] ? clone_error : entry[2]
|
|
548
|
+
)
|
|
549
|
+
]
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
),
|
|
553
|
+
shipped
|
|
554
|
+
]
|
|
555
|
+
)
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* @param {string} kind
|
|
560
|
+
* @param {unknown} error
|
|
561
|
+
* @returns {void}
|
|
562
|
+
*/
|
|
563
|
+
function send(kind, error) {
|
|
564
|
+
try {
|
|
565
|
+
post([ kind, error ])
|
|
566
|
+
} catch {
|
|
567
|
+
post([ kind, plain(error) ])
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
/** @returns {void} */
|
|
571
|
+
function settle() {
|
|
572
|
+
if (running || waiting) {
|
|
573
|
+
return
|
|
574
|
+
}
|
|
575
|
+
if (queue.length) {
|
|
576
|
+
wait()
|
|
577
|
+
} else if (closing) {
|
|
578
|
+
closing = false
|
|
579
|
+
finish()
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
/** @returns {void} */
|
|
583
|
+
function wait() {
|
|
584
|
+
if (waiting || running || !queue.length) {
|
|
585
|
+
return
|
|
586
|
+
}
|
|
587
|
+
waiting = true
|
|
588
|
+
if (delay > 0) {
|
|
589
|
+
setTimeout(fire, delay)
|
|
590
|
+
} else {
|
|
591
|
+
defer(fire)
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
package/src/options.js
CHANGED
|
@@ -6,10 +6,14 @@
|
|
|
6
6
|
* @returns {void}
|
|
7
7
|
*/
|
|
8
8
|
export function check_options(options, keys, kind, prefix = "") {
|
|
9
|
-
if (options == null || typeof options != "object")
|
|
9
|
+
if (options == null || typeof options != "object") {
|
|
10
|
+
return
|
|
11
|
+
}
|
|
10
12
|
for (const key of Object.keys(options)) {
|
|
11
|
-
if (!keys.includes(key))
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
if (!keys.includes(key)) {
|
|
14
|
+
throw TypeError(
|
|
15
|
+
`Unknown ${kind} option "${prefix}${key}"`
|
|
16
|
+
)
|
|
17
|
+
}
|
|
14
18
|
}
|
|
15
19
|
}
|