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/http/sse.js
CHANGED
|
@@ -63,11 +63,15 @@ const stable_delay = 5000
|
|
|
63
63
|
*/
|
|
64
64
|
function check_sse_options(options) {
|
|
65
65
|
for (const key of Object.keys(options)) {
|
|
66
|
-
if (request_only_keys.has(key))
|
|
66
|
+
if (request_only_keys.has(key)) {
|
|
67
|
+
throw TypeError(`Unknown sse option "${key}"`)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (options.as != null && options.as != "json" && options.as != "text") {
|
|
71
|
+
throw TypeError(
|
|
72
|
+
"The as of sse() must be \"json\" or \"text\""
|
|
73
|
+
)
|
|
67
74
|
}
|
|
68
|
-
if (options.as != null && options.as != "json" && options.as != "text") throw TypeError(
|
|
69
|
-
"The as of sse() must be \"json\" or \"text\""
|
|
70
|
-
)
|
|
71
75
|
check_reconnect(options.reconnect, "sse")
|
|
72
76
|
}
|
|
73
77
|
/**
|
|
@@ -76,8 +80,8 @@ function check_sse_options(options) {
|
|
|
76
80
|
*/
|
|
77
81
|
function is_reconnectable(error) {
|
|
78
82
|
return error instanceof NetworkError
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
|| error instanceof TimeoutError
|
|
84
|
+
|| error instanceof HttpError && (error.status < 400 || error.status >= 500 || sse_retry_statuses.has(error.status))
|
|
81
85
|
}
|
|
82
86
|
/**
|
|
83
87
|
* @param {string} data
|
|
@@ -121,6 +125,45 @@ export function sse(c, path, params, options) {
|
|
|
121
125
|
}
|
|
122
126
|
)
|
|
123
127
|
: noop
|
|
128
|
+
return {
|
|
129
|
+
[Symbol.asyncIterator]() {
|
|
130
|
+
const controller = new AbortController()
|
|
131
|
+
current = controller
|
|
132
|
+
return watch_records(
|
|
133
|
+
connect(controller),
|
|
134
|
+
left => {
|
|
135
|
+
if (!left) {
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
controller.abort(
|
|
139
|
+
new CancelError("The event stream was closed")
|
|
140
|
+
)
|
|
141
|
+
if (current == controller) {
|
|
142
|
+
set_status(
|
|
143
|
+
closed.signal.aborted ? "closed" : "idle"
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
},
|
|
149
|
+
/**
|
|
150
|
+
* @param {unknown} reason
|
|
151
|
+
* @returns {void}
|
|
152
|
+
*/
|
|
153
|
+
cancel(reason) {
|
|
154
|
+
unwatch()
|
|
155
|
+
closed.abort(
|
|
156
|
+
new CancelError(reason ?? "Cancelled")
|
|
157
|
+
)
|
|
158
|
+
set_status("closed")
|
|
159
|
+
},
|
|
160
|
+
get lastEventId() {
|
|
161
|
+
return last_event_id
|
|
162
|
+
},
|
|
163
|
+
get status() {
|
|
164
|
+
return options.signal?.aborted ? "closed" : stream_status
|
|
165
|
+
}
|
|
166
|
+
}
|
|
124
167
|
/**
|
|
125
168
|
* @param {AbortController} controller
|
|
126
169
|
* @returns {AsyncGenerator<import("../../public.js").ServerEvent<unknown>, void, undefined>}
|
|
@@ -133,19 +176,16 @@ export function sse(c, path, params, options) {
|
|
|
133
176
|
let last_error
|
|
134
177
|
/** @type {number | undefined} */
|
|
135
178
|
let server_retry
|
|
136
|
-
/**
|
|
137
|
-
* @param {import("../../public.js").ConnectionStatus} next
|
|
138
|
-
* @returns {void}
|
|
139
|
-
*/
|
|
140
|
-
function show(next) {
|
|
141
|
-
if (current == controller && !controller.signal.aborted) set_status(next)
|
|
142
|
-
}
|
|
143
179
|
try {
|
|
144
|
-
if (controller.signal.aborted)
|
|
180
|
+
if (controller.signal.aborted) {
|
|
181
|
+
return
|
|
182
|
+
}
|
|
145
183
|
show("connecting")
|
|
146
|
-
if (reconnect && options.body instanceof ReadableStream)
|
|
147
|
-
|
|
148
|
-
|
|
184
|
+
if (reconnect && options.body instanceof ReadableStream) {
|
|
185
|
+
throw TypeError(
|
|
186
|
+
"An event stream with a ReadableStream body cannot reconnect: set reconnect to false"
|
|
187
|
+
)
|
|
188
|
+
}
|
|
149
189
|
for (;;) {
|
|
150
190
|
const connection = new AbortController()
|
|
151
191
|
let opened_at = 0
|
|
@@ -155,10 +195,12 @@ export function sse(c, path, params, options) {
|
|
|
155
195
|
try {
|
|
156
196
|
const headers = new Headers(options.headers)
|
|
157
197
|
headers.set("Accept", "text/event-stream")
|
|
158
|
-
if (last_event_id)
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
198
|
+
if (last_event_id) {
|
|
199
|
+
headers.set(
|
|
200
|
+
"Last-Event-ID",
|
|
201
|
+
to_byte_string(last_event_id)
|
|
202
|
+
)
|
|
203
|
+
}
|
|
162
204
|
/** @type {import("../../public.js").RawResponse<unknown>} */
|
|
163
205
|
const {
|
|
164
206
|
data,
|
|
@@ -183,7 +225,9 @@ export function sse(c, path, params, options) {
|
|
|
183
225
|
true
|
|
184
226
|
)
|
|
185
227
|
.raw()
|
|
186
|
-
if (status == 204)
|
|
228
|
+
if (status == 204) {
|
|
229
|
+
return
|
|
230
|
+
}
|
|
187
231
|
if (!event_stream_regex.test(
|
|
188
232
|
response_headers.get("Content-Type") ?? ""
|
|
189
233
|
)) {
|
|
@@ -196,9 +240,11 @@ export function sse(c, path, params, options) {
|
|
|
196
240
|
show("open")
|
|
197
241
|
const records = /** @type {AsyncIterable<StreamRecord>} */(data)/**/
|
|
198
242
|
for await (const record of records) {
|
|
199
|
-
if (record.type == "retry")
|
|
200
|
-
|
|
201
|
-
else {
|
|
243
|
+
if (record.type == "retry") {
|
|
244
|
+
server_retry = record.retry
|
|
245
|
+
} else if (record.type == "id") {
|
|
246
|
+
last_event_id = record.id
|
|
247
|
+
} else {
|
|
202
248
|
last_event_id = record.event.id
|
|
203
249
|
const event_data = as_json ? read_json(record.event.data) : record.event.data
|
|
204
250
|
if (!parse) {
|
|
@@ -226,11 +272,17 @@ export function sse(c, path, params, options) {
|
|
|
226
272
|
}
|
|
227
273
|
failures = 0
|
|
228
274
|
} catch (error) {
|
|
229
|
-
if (controller.signal.aborted)
|
|
275
|
+
if (controller.signal.aborted) {
|
|
276
|
+
return
|
|
277
|
+
}
|
|
230
278
|
last_error = error
|
|
231
279
|
report(c, error)
|
|
232
|
-
if (opened_at && Date.now() - opened_at >= stable_delay)
|
|
233
|
-
|
|
280
|
+
if (opened_at && Date.now() - opened_at >= stable_delay) {
|
|
281
|
+
failures = 0
|
|
282
|
+
}
|
|
283
|
+
if (error === fatal || !reconnect || !is_reconnectable(error) || ++failures > reconnect_count) {
|
|
284
|
+
throw error
|
|
285
|
+
}
|
|
234
286
|
} finally {
|
|
235
287
|
unlink_connection()
|
|
236
288
|
connection.abort(
|
|
@@ -239,79 +291,65 @@ export function sse(c, path, params, options) {
|
|
|
239
291
|
)
|
|
240
292
|
)
|
|
241
293
|
}
|
|
242
|
-
if (!reconnect || controller.signal.aborted)
|
|
294
|
+
if (!reconnect || controller.signal.aborted) {
|
|
295
|
+
return
|
|
296
|
+
}
|
|
243
297
|
show("connecting")
|
|
244
298
|
const delay = failures
|
|
245
299
|
? get_reconnect_wait(reconnect, failures, last_error)
|
|
246
300
|
: server_retry ?? 3000
|
|
247
|
-
if (delay == null)
|
|
301
|
+
if (delay == null) {
|
|
302
|
+
throw last_error
|
|
303
|
+
}
|
|
248
304
|
await wait(
|
|
249
305
|
delay,
|
|
250
306
|
controller.signal,
|
|
251
307
|
!!failures && read_retry_after(last_error) == null
|
|
252
308
|
)
|
|
253
|
-
if (controller.signal.aborted)
|
|
309
|
+
if (controller.signal.aborted) {
|
|
310
|
+
return
|
|
311
|
+
}
|
|
254
312
|
}
|
|
255
313
|
} catch (error) {
|
|
256
|
-
if (error !== last_error)
|
|
314
|
+
if (error !== last_error) {
|
|
315
|
+
report(c, error)
|
|
316
|
+
}
|
|
257
317
|
throw error
|
|
258
318
|
} finally {
|
|
259
|
-
if (current == controller)
|
|
260
|
-
|
|
261
|
-
|
|
319
|
+
if (current == controller) {
|
|
320
|
+
set_status(
|
|
321
|
+
closed.signal.aborted ? "closed" : "idle"
|
|
322
|
+
)
|
|
323
|
+
}
|
|
262
324
|
unlink()
|
|
263
325
|
unlink_closed()
|
|
264
326
|
controller.abort(
|
|
265
327
|
new CancelError("The event stream was closed")
|
|
266
328
|
)
|
|
267
329
|
}
|
|
330
|
+
/**
|
|
331
|
+
* @param {import("../../public.js").ConnectionStatus} next
|
|
332
|
+
* @returns {void}
|
|
333
|
+
*/
|
|
334
|
+
function show(next) {
|
|
335
|
+
if (current == controller && !controller.signal.aborted) {
|
|
336
|
+
set_status(next)
|
|
337
|
+
}
|
|
338
|
+
}
|
|
268
339
|
}
|
|
269
340
|
/**
|
|
270
341
|
* @param {import("../../public.js").ConnectionStatus} next
|
|
271
342
|
* @returns {void}
|
|
272
343
|
*/
|
|
273
344
|
function set_status(next) {
|
|
274
|
-
if (stream_status == next)
|
|
345
|
+
if (stream_status == next) {
|
|
346
|
+
return
|
|
347
|
+
}
|
|
275
348
|
stream_status = next
|
|
276
349
|
try {
|
|
277
350
|
options.onStatus?.(next)
|
|
278
351
|
} catch {}
|
|
279
352
|
}
|
|
280
|
-
return {
|
|
281
|
-
[Symbol.asyncIterator]() {
|
|
282
|
-
const controller = new AbortController()
|
|
283
|
-
current = controller
|
|
284
|
-
return watch_records(
|
|
285
|
-
connect(controller),
|
|
286
|
-
left => {
|
|
287
|
-
if (!left) return
|
|
288
|
-
controller.abort(
|
|
289
|
-
new CancelError("The event stream was closed")
|
|
290
|
-
)
|
|
291
|
-
if (current == controller) set_status(
|
|
292
|
-
closed.signal.aborted ? "closed" : "idle"
|
|
293
|
-
)
|
|
294
|
-
}
|
|
295
|
-
)
|
|
296
|
-
},
|
|
297
|
-
/**
|
|
298
|
-
* @param {unknown} reason
|
|
299
|
-
* @returns {void}
|
|
300
|
-
*/
|
|
301
|
-
cancel(reason) {
|
|
302
|
-
unwatch()
|
|
303
|
-
closed.abort(
|
|
304
|
-
new CancelError(reason ?? "Cancelled")
|
|
305
|
-
)
|
|
306
|
-
set_status("closed")
|
|
307
|
-
},
|
|
308
|
-
get lastEventId() {
|
|
309
|
-
return last_event_id
|
|
310
|
-
},
|
|
311
|
-
get status() {
|
|
312
|
-
return options.signal?.aborted ? "closed" : stream_status
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
353
|
}
|
|
316
354
|
/**
|
|
317
355
|
* @param {number} delay
|
|
@@ -330,15 +368,16 @@ function wait(delay, signal, online) {
|
|
|
330
368
|
resolve()
|
|
331
369
|
return
|
|
332
370
|
}
|
|
371
|
+
const timer = setTimeout(done, clamp_delay(delay))
|
|
372
|
+
const unwatch = on_abort(signal, done)
|
|
373
|
+
globalThis.addEventListener("online", done)
|
|
374
|
+
/** @returns {void} */
|
|
333
375
|
function done() {
|
|
334
376
|
clearTimeout(timer)
|
|
335
377
|
unwatch()
|
|
336
378
|
globalThis.removeEventListener("online", done)
|
|
337
379
|
resolve()
|
|
338
380
|
}
|
|
339
|
-
const timer = setTimeout(done, clamp_delay(delay))
|
|
340
|
-
const unwatch = on_abort(signal, done)
|
|
341
|
-
globalThis.addEventListener("online", done)
|
|
342
381
|
}
|
|
343
382
|
)
|
|
344
383
|
}
|
package/src/http/throttle.js
CHANGED
|
@@ -24,15 +24,20 @@ export async function throttle(c, key, ms, cancel, signal) {
|
|
|
24
24
|
try {
|
|
25
25
|
await sleep(Math.max(wait, 0), signal)
|
|
26
26
|
} finally {
|
|
27
|
-
if (current.cancel == cancel)
|
|
27
|
+
if (current.cancel == cancel) {
|
|
28
|
+
current.cancel = void 0
|
|
29
|
+
}
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
current.next = Date.now() + ms
|
|
31
|
-
|
|
33
|
+
/** @type {{ unref?: () => void }} */
|
|
34
|
+
const sweeper = setTimeout(
|
|
32
35
|
() => {
|
|
33
|
-
if (!current.cancel && Date.now() >= current.next && c.shared.throttles.get(key) == current)
|
|
36
|
+
if (!current.cancel && Date.now() >= current.next && c.shared.throttles.get(key) == current) {
|
|
37
|
+
c.shared.throttles.delete(key)
|
|
38
|
+
}
|
|
34
39
|
},
|
|
35
40
|
clamp_delay(ms)
|
|
36
|
-
)
|
|
41
|
+
)
|
|
37
42
|
sweeper.unref?.()
|
|
38
43
|
}
|
package/src/http/url.js
CHANGED
|
@@ -10,13 +10,19 @@ const trailing_slash_regex = /\/+$/
|
|
|
10
10
|
* @returns {string}
|
|
11
11
|
*/
|
|
12
12
|
function add_query(url, query, first) {
|
|
13
|
-
if (!query)
|
|
13
|
+
if (!query) {
|
|
14
|
+
return url
|
|
15
|
+
}
|
|
14
16
|
const hash_index = url.indexOf("#")
|
|
15
17
|
const without_hash = hash_index < 0 ? url : url.slice(0, hash_index)
|
|
16
18
|
const hash = hash_index < 0 ? "" : url.slice(hash_index)
|
|
17
19
|
const query_index = without_hash.indexOf("?")
|
|
18
|
-
if (query_index < 0)
|
|
19
|
-
|
|
20
|
+
if (query_index < 0) {
|
|
21
|
+
return without_hash + "?" + query + hash
|
|
22
|
+
}
|
|
23
|
+
if (!first) {
|
|
24
|
+
return without_hash + "&" + query + hash
|
|
25
|
+
}
|
|
20
26
|
const existing = without_hash.slice(query_index + 1)
|
|
21
27
|
return without_hash.slice(0, query_index + 1) + query + (existing ? "&" + existing : "") + hash
|
|
22
28
|
}
|
|
@@ -27,17 +33,26 @@ function add_query(url, query, first) {
|
|
|
27
33
|
* @returns {void}
|
|
28
34
|
*/
|
|
29
35
|
function append_query(pairs, key, value) {
|
|
30
|
-
if (value == null)
|
|
36
|
+
if (value == null) {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
31
39
|
if (Array.isArray(value)) {
|
|
32
|
-
for (const item of /** @type {readonly import("../../public.js").QueryValue[]} */(value)/**/)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
for (const item of /** @type {readonly import("../../public.js").QueryValue[]} */(value)/**/) {
|
|
41
|
+
append_query(pairs, key, item)
|
|
42
|
+
}
|
|
43
|
+
} else if (value instanceof Date) {
|
|
44
|
+
pairs.push([ key, value.toISOString() ])
|
|
45
|
+
} else if (typeof value == "object") {
|
|
46
|
+
for (const name of Object.keys(value)) {
|
|
47
|
+
append_query(
|
|
48
|
+
pairs,
|
|
49
|
+
`${key}[${name}]`,
|
|
50
|
+
/** @type {{ readonly [key: string]: import("../../public.js").QueryValue }} */(value)/**/[name]
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
pairs.push([ key, String(value) ])
|
|
55
|
+
}
|
|
41
56
|
}
|
|
42
57
|
/**
|
|
43
58
|
* @param {string | undefined} base
|
|
@@ -57,6 +72,23 @@ export function build_url(
|
|
|
57
72
|
const rest = { ...source }
|
|
58
73
|
/** @type {string[]} */
|
|
59
74
|
const used = []
|
|
75
|
+
let url = fill(path)
|
|
76
|
+
if (base && !absolute_regex.test(url)) {
|
|
77
|
+
const split_index = base.search(/[?#]/)
|
|
78
|
+
const base_path = fill(
|
|
79
|
+
split_index < 0 ? base : base.slice(0, split_index)
|
|
80
|
+
)
|
|
81
|
+
const base_query = split_index < 0 || base[split_index] != "?"
|
|
82
|
+
? ""
|
|
83
|
+
: /** @type {string} */(base.slice(split_index + 1).split("#")[0])/**/
|
|
84
|
+
const trimmed = url.replace(leading_slash_regex, "")
|
|
85
|
+
url = base_path.replace(trailing_slash_regex, "") + (trimmed && !trimmed.startsWith("?") ? "/" : "") + trimmed
|
|
86
|
+
url = add_query(url, base_query, true)
|
|
87
|
+
}
|
|
88
|
+
for (const name of used) {
|
|
89
|
+
delete rest[name]
|
|
90
|
+
}
|
|
91
|
+
return add_query(url, serialize(rest), false)
|
|
60
92
|
/**
|
|
61
93
|
* @param {string} text
|
|
62
94
|
* @returns {string}
|
|
@@ -73,33 +105,22 @@ export function build_url(
|
|
|
73
105
|
: value instanceof Date
|
|
74
106
|
? value.toISOString()
|
|
75
107
|
: String(value)
|
|
76
|
-
if (!value_text)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
)
|
|
108
|
+
if (!value_text) {
|
|
109
|
+
throw TypeError(
|
|
110
|
+
`Missing path parameter "${name}" for ${text}`
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
if (dot_segment_regex.test(value_text)) {
|
|
114
|
+
throw TypeError(
|
|
115
|
+
`Path parameter "${name}" cannot be "${value_text}"`
|
|
116
|
+
)
|
|
117
|
+
}
|
|
82
118
|
used.push(name)
|
|
83
119
|
return "/" + encodeURIComponent(value_text)
|
|
84
120
|
}
|
|
85
121
|
)
|
|
86
122
|
return split_index < 0 ? filled : filled + text.slice(split_index)
|
|
87
123
|
}
|
|
88
|
-
let url = fill(path)
|
|
89
|
-
if (base && !absolute_regex.test(url)) {
|
|
90
|
-
const split_index = base.search(/[?#]/)
|
|
91
|
-
const base_path = fill(
|
|
92
|
-
split_index < 0 ? base : base.slice(0, split_index)
|
|
93
|
-
)
|
|
94
|
-
const base_query = split_index < 0 || base[split_index] != "?"
|
|
95
|
-
? ""
|
|
96
|
-
: base.slice(split_index + 1).split("#")[0] ?? ""
|
|
97
|
-
const trimmed = url.replace(leading_slash_regex, "")
|
|
98
|
-
url = base_path.replace(trailing_slash_regex, "") + (trimmed && !trimmed.startsWith("?") ? "/" : "") + trimmed
|
|
99
|
-
url = add_query(url, base_query, true)
|
|
100
|
-
}
|
|
101
|
-
for (const name of used) delete rest[name]
|
|
102
|
-
return add_query(url, serialize(rest), false)
|
|
103
124
|
}
|
|
104
125
|
/**
|
|
105
126
|
* @param {import("../../public.js").QueryParams} params
|
|
@@ -108,7 +129,9 @@ export function build_url(
|
|
|
108
129
|
export function serialize_query(params) {
|
|
109
130
|
/** @type {[string, string][]} */
|
|
110
131
|
const pairs = []
|
|
111
|
-
for (const key of Object.keys(params))
|
|
132
|
+
for (const key of Object.keys(params)) {
|
|
133
|
+
append_query(pairs, key, params[key])
|
|
134
|
+
}
|
|
112
135
|
return pairs.map(
|
|
113
136
|
([ key, value ]) => encodeURIComponent(key) + "=" + encodeURIComponent(value)
|
|
114
137
|
)
|
package/src/http/xhr.js
CHANGED
|
@@ -3,7 +3,7 @@ const header_line_regex = /\r?\n/
|
|
|
3
3
|
const null_body_statuses = new Set([ 101, 103, 204, 205, 304 ])
|
|
4
4
|
/**
|
|
5
5
|
* @param {string} url
|
|
6
|
-
* @param {RequestInit & { headers: Headers }} init
|
|
6
|
+
* @param {RequestInit & { headers: Headers, method: string }} init
|
|
7
7
|
* @param {(progress: import("../../public.js").Progress) => void} on_upload
|
|
8
8
|
* @returns {Promise<Response>}
|
|
9
9
|
*/
|
|
@@ -12,24 +12,7 @@ export function send_with_xhr(url, init, on_upload) {
|
|
|
12
12
|
(resolve, reject) => {
|
|
13
13
|
const xhr = new XMLHttpRequest()
|
|
14
14
|
const signal = init.signal
|
|
15
|
-
|
|
16
|
-
return xhr.abort()
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* @param {() => void} settle
|
|
20
|
-
* @returns {() => void}
|
|
21
|
-
*/
|
|
22
|
-
function finish(settle) {
|
|
23
|
-
return () => {
|
|
24
|
-
signal?.removeEventListener("abort", abort)
|
|
25
|
-
try {
|
|
26
|
-
settle()
|
|
27
|
-
} catch (error) {
|
|
28
|
-
reject(error)
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
xhr.open(init.method ?? "GET", url)
|
|
15
|
+
xhr.open(init.method, url)
|
|
33
16
|
init.headers.forEach(
|
|
34
17
|
(value, key) => xhr.setRequestHeader(key, value)
|
|
35
18
|
)
|
|
@@ -51,15 +34,19 @@ export function send_with_xhr(url, init, on_upload) {
|
|
|
51
34
|
xhr.ontimeout = xhr.onerror
|
|
52
35
|
xhr.onload = finish(
|
|
53
36
|
() => {
|
|
54
|
-
if (!xhr.status)
|
|
37
|
+
if (!xhr.status) {
|
|
38
|
+
throw TypeError("Network request failed")
|
|
39
|
+
}
|
|
55
40
|
/** @type {Headers} */
|
|
56
41
|
const headers = new Headers()
|
|
57
42
|
for (const line of xhr.getAllResponseHeaders().split(header_line_regex)) {
|
|
58
43
|
const index = line.indexOf(":")
|
|
59
|
-
if (index > 0)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
44
|
+
if (index > 0) {
|
|
45
|
+
headers.append(
|
|
46
|
+
line.slice(0, index).trim(),
|
|
47
|
+
line.slice(index + 1).trim()
|
|
48
|
+
)
|
|
49
|
+
}
|
|
63
50
|
}
|
|
64
51
|
resolve(
|
|
65
52
|
new Response(
|
|
@@ -81,6 +68,24 @@ export function send_with_xhr(url, init, on_upload) {
|
|
|
81
68
|
xhr.send(
|
|
82
69
|
/** @type {XMLHttpRequestBodyInit | null} */(init.body ?? null)/**/
|
|
83
70
|
)
|
|
71
|
+
/** @returns {void} */
|
|
72
|
+
function abort() {
|
|
73
|
+
return xhr.abort()
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* @param {() => void} settle
|
|
77
|
+
* @returns {() => void}
|
|
78
|
+
*/
|
|
79
|
+
function finish(settle) {
|
|
80
|
+
return () => {
|
|
81
|
+
signal?.removeEventListener("abort", abort)
|
|
82
|
+
try {
|
|
83
|
+
settle()
|
|
84
|
+
} catch (error) {
|
|
85
|
+
reject(error)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
84
89
|
}
|
|
85
90
|
)
|
|
86
91
|
}
|
package/src/index.js
CHANGED
package/src/key.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @returns {string}
|
|
3
|
-
*/
|
|
1
|
+
/** @returns {string} */
|
|
4
2
|
export function random_key() {
|
|
5
3
|
return globalThis.crypto?.randomUUID?.()
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
?? Date.now().toString(36) + Math.random().toString(36)
|
|
5
|
+
.slice(2)
|
|
8
6
|
}
|