@webqit/fetch-plus 0.1.31 → 0.1.32
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/dist/main.js +2 -1
- package/dist/main.js.map +3 -3
- package/package.json +1 -1
- package/src/LiveResponse.js +5 -8
- package/src/messageParserMixin.js +149 -0
package/package.json
CHANGED
package/src/LiveResponse.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { _isObject, _isTypeObject } from '@webqit/util/js/index.js';
|
|
2
2
|
import { Observer, ListenerRegistry, Descriptor } from '@webqit/observer';
|
|
3
3
|
import { BroadcastChannelPlus, WebSocketPort, MessagePortPlus } from '@webqit/port-plus';
|
|
4
|
-
import { isTypeStream, _meta, _wq } from './messageParserMixin.js';
|
|
4
|
+
import { isTypeStream, _meta, _wq, isAsyncIterable, isGenerator } from './messageParserMixin.js';
|
|
5
5
|
import { ResponsePlus } from './ResponsePlus.js';
|
|
6
6
|
|
|
7
7
|
export class LiveResponse extends EventTarget {
|
|
@@ -461,10 +461,13 @@ export class LiveResponse extends EventTarget {
|
|
|
461
461
|
throw new Error(`frameClosure is not supported for responses.`);
|
|
462
462
|
}
|
|
463
463
|
frame.donePromise = execReplaceWithResponse(frame, body, frameOptions);
|
|
464
|
-
} else if (isGenerator(body)) {
|
|
464
|
+
} else if (isAsyncIterable(body) || isGenerator(body)) {
|
|
465
465
|
if (frameClosure) {
|
|
466
466
|
throw new Error(`frameClosure is not supported for generators.`);
|
|
467
467
|
}
|
|
468
|
+
if (!isGenerator(body)) {
|
|
469
|
+
body = body[Symbol.asyncIterator]();
|
|
470
|
+
}
|
|
468
471
|
frame.donePromise = execReplaceWithGenerator(frame, body, frameOptions);
|
|
469
472
|
} else if (body instanceof LiveProgramHandleX) {
|
|
470
473
|
if (frameClosure) {
|
|
@@ -575,12 +578,6 @@ export class LiveResponse extends EventTarget {
|
|
|
575
578
|
}
|
|
576
579
|
}
|
|
577
580
|
|
|
578
|
-
export const isGenerator = (obj) => {
|
|
579
|
-
return typeof obj?.next === 'function' &&
|
|
580
|
-
typeof obj?.throw === 'function' &&
|
|
581
|
-
typeof obj?.return === 'function';
|
|
582
|
-
};
|
|
583
|
-
|
|
584
581
|
export class ReplaceEvent extends Event {
|
|
585
582
|
|
|
586
583
|
[Symbol.toStringTag] = 'ReplaceEvent';
|
|
@@ -24,6 +24,14 @@ export function messageParserMixin(superClass) {
|
|
|
24
24
|
|
|
25
25
|
// Process body
|
|
26
26
|
let body = httpMessageInit.body;
|
|
27
|
+
|
|
28
|
+
if (isAsyncIterable(body) || isGenerator(body)) {
|
|
29
|
+
body = asyncIterableToStream(body);
|
|
30
|
+
const type = 'ReadableStream';
|
|
31
|
+
headers['content-type'] ??= 'application/octet-stream';
|
|
32
|
+
return { body, headers: new Headers(headers), $type: type };
|
|
33
|
+
}
|
|
34
|
+
|
|
27
35
|
let type = [null, undefined].includes(body) ? null : dataType(body);
|
|
28
36
|
|
|
29
37
|
// Binary bodies
|
|
@@ -203,6 +211,8 @@ export function dataType(value) {
|
|
|
203
211
|
return null;
|
|
204
212
|
}
|
|
205
213
|
|
|
214
|
+
// --------------
|
|
215
|
+
|
|
206
216
|
export function isTypeReadable(obj) {
|
|
207
217
|
return (
|
|
208
218
|
obj !== null &&
|
|
@@ -217,3 +227,142 @@ export function isTypeStream(obj) {
|
|
|
217
227
|
return obj instanceof ReadableStream
|
|
218
228
|
|| isTypeReadable(obj);
|
|
219
229
|
}
|
|
230
|
+
|
|
231
|
+
// --------------
|
|
232
|
+
|
|
233
|
+
export const isGenerator = (obj) => {
|
|
234
|
+
return typeof obj?.next === 'function'
|
|
235
|
+
//&& typeof obj?.throw === 'function'
|
|
236
|
+
//&& typeof obj?.return === 'function';
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export function isAsyncIterable(obj) {
|
|
240
|
+
return (
|
|
241
|
+
obj !== null &&
|
|
242
|
+
typeof obj === 'object' &&
|
|
243
|
+
typeof obj[Symbol.asyncIterator] === 'function'
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function asyncIterableToStream(iterable) {
|
|
248
|
+
if (!isAsyncIterable(iterable) && !isGenerator(body)) {
|
|
249
|
+
throw new TypeError('Body must be an async iterable.');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const iterator = isGenerator(iterable) ? iterable : iterable[Symbol.asyncIterator]();
|
|
253
|
+
const encoder = new TextEncoder();
|
|
254
|
+
let finished = false;
|
|
255
|
+
|
|
256
|
+
const closeIterator = async (reason) => {
|
|
257
|
+
if (finished) return;
|
|
258
|
+
finished = true;
|
|
259
|
+
|
|
260
|
+
if (typeof iterator.return === 'function') {
|
|
261
|
+
try {
|
|
262
|
+
await iterator.return(reason);
|
|
263
|
+
} catch { }
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const encodeChunk = (value) => {
|
|
268
|
+
if (value == null) return null;
|
|
269
|
+
|
|
270
|
+
// Binary passthrough
|
|
271
|
+
if (value instanceof Uint8Array) {
|
|
272
|
+
return value;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Text passthrough
|
|
276
|
+
if (typeof value === 'string') {
|
|
277
|
+
return encoder.encode(value);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// JSON (objects, arrays, numbers, booleans)
|
|
281
|
+
if (
|
|
282
|
+
typeof value === 'object' ||
|
|
283
|
+
typeof value === 'number' ||
|
|
284
|
+
typeof value === 'boolean'
|
|
285
|
+
) {
|
|
286
|
+
return encoder.encode(JSON.stringify(value) + '\n');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
throw new TypeError(
|
|
290
|
+
`Unsupported chunk type in async iterable: ${typeof value}`
|
|
291
|
+
);
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
return new ReadableStream({
|
|
295
|
+
async pull(controller) {
|
|
296
|
+
try {
|
|
297
|
+
const { value, done } = await iterator.next();
|
|
298
|
+
|
|
299
|
+
if (done) {
|
|
300
|
+
await closeIterator();
|
|
301
|
+
controller.close();
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const chunk = encodeChunk(value);
|
|
306
|
+
if (chunk) controller.enqueue(chunk);
|
|
307
|
+
|
|
308
|
+
} catch (err) {
|
|
309
|
+
await closeIterator();
|
|
310
|
+
controller.error(err);
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
async cancel(reason) {
|
|
315
|
+
await closeIterator(reason);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function streamToAsyncIterable(stream, { parse = null } = {}) {
|
|
321
|
+
const reader = stream.getReader();
|
|
322
|
+
const decoder = new TextDecoder();
|
|
323
|
+
|
|
324
|
+
let finished = false;
|
|
325
|
+
let buffer = '';
|
|
326
|
+
|
|
327
|
+
const close = async () => {
|
|
328
|
+
if (finished) return;
|
|
329
|
+
finished = true;
|
|
330
|
+
try {
|
|
331
|
+
await reader.cancel();
|
|
332
|
+
} catch { }
|
|
333
|
+
reader.releaseLock();
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
async *[Symbol.asyncIterator]() {
|
|
338
|
+
try {
|
|
339
|
+
while (true) {
|
|
340
|
+
const { value, done } = await reader.read();
|
|
341
|
+
if (done) break;
|
|
342
|
+
|
|
343
|
+
if (parse === 'ndjson') {
|
|
344
|
+
buffer += decoder.decode(value, { stream: true });
|
|
345
|
+
|
|
346
|
+
let lines = buffer.split('\n');
|
|
347
|
+
buffer = lines.pop(); // incomplete fragment
|
|
348
|
+
|
|
349
|
+
for (const line of lines) {
|
|
350
|
+
if (line.trim()) yield JSON.parse(line);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
yield value;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (parse === 'ndjson' && buffer.trim()) {
|
|
360
|
+
yield JSON.parse(buffer);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
} finally {
|
|
364
|
+
await close();
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
}
|