@ricsam/isolate-fetch 0.1.9 → 0.1.11
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/cjs/consistency/origins.cjs +518 -0
- package/dist/cjs/consistency/origins.cjs.map +10 -0
- package/dist/cjs/index.cjs +189 -55
- package/dist/cjs/index.cjs.map +3 -3
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/consistency/origins.mjs +488 -0
- package/dist/mjs/consistency/origins.mjs.map +10 -0
- package/dist/mjs/index.mjs +189 -55
- package/dist/mjs/index.mjs.map +3 -3
- package/dist/mjs/package.json +1 -1
- package/dist/types/consistency/origins.d.ts +179 -0
- package/package.json +1 -1
package/dist/mjs/index.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
startNativeStreamReader
|
|
7
7
|
} from "./stream-state.mjs";
|
|
8
8
|
var instanceStateMap = new WeakMap;
|
|
9
|
+
var passthruBodies = new WeakMap;
|
|
9
10
|
var nextInstanceId = 1;
|
|
10
11
|
function getInstanceStateMapForContext(context) {
|
|
11
12
|
let map = instanceStateMap.get(context);
|
|
@@ -15,6 +16,14 @@ function getInstanceStateMapForContext(context) {
|
|
|
15
16
|
}
|
|
16
17
|
return map;
|
|
17
18
|
}
|
|
19
|
+
function getPassthruBodiesForContext(context) {
|
|
20
|
+
let map = passthruBodies.get(context);
|
|
21
|
+
if (!map) {
|
|
22
|
+
map = new Map;
|
|
23
|
+
passthruBodies.set(context, map);
|
|
24
|
+
}
|
|
25
|
+
return map;
|
|
26
|
+
}
|
|
18
27
|
var headersCode = `
|
|
19
28
|
(function() {
|
|
20
29
|
class Headers {
|
|
@@ -370,77 +379,79 @@ var hostBackedStreamCode = `
|
|
|
370
379
|
(function() {
|
|
371
380
|
const _streamIds = new WeakMap();
|
|
372
381
|
|
|
373
|
-
|
|
382
|
+
// Polyfill values() on ReadableStream if not available (older V8 versions)
|
|
383
|
+
if (typeof ReadableStream.prototype.values !== 'function') {
|
|
384
|
+
ReadableStream.prototype.values = function(options) {
|
|
385
|
+
const reader = this.getReader();
|
|
386
|
+
return {
|
|
387
|
+
async next() {
|
|
388
|
+
const { value, done } = await reader.read();
|
|
389
|
+
if (done) {
|
|
390
|
+
reader.releaseLock();
|
|
391
|
+
return { value: undefined, done: true };
|
|
392
|
+
}
|
|
393
|
+
return { value, done: false };
|
|
394
|
+
},
|
|
395
|
+
async return(value) {
|
|
396
|
+
reader.releaseLock();
|
|
397
|
+
return { value, done: true };
|
|
398
|
+
},
|
|
399
|
+
[Symbol.asyncIterator]() {
|
|
400
|
+
return this;
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Create a proper ReadableStream subclass that reports as "ReadableStream"
|
|
407
|
+
class HostBackedReadableStream extends ReadableStream {
|
|
374
408
|
constructor(streamId) {
|
|
375
409
|
if (streamId === undefined) {
|
|
376
410
|
streamId = __Stream_create();
|
|
377
411
|
}
|
|
378
|
-
_streamIds.set(this, streamId);
|
|
379
|
-
}
|
|
380
412
|
|
|
381
|
-
|
|
382
|
-
return _streamIds.get(this);
|
|
383
|
-
}
|
|
413
|
+
let closed = false;
|
|
384
414
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
415
|
+
super({
|
|
416
|
+
async pull(controller) {
|
|
417
|
+
if (closed) return;
|
|
388
418
|
|
|
389
|
-
return {
|
|
390
|
-
read: async () => {
|
|
391
|
-
if (released) {
|
|
392
|
-
throw new TypeError("Reader has been released");
|
|
393
|
-
}
|
|
394
419
|
const resultJson = __Stream_pull_ref.applySyncPromise(undefined, [streamId]);
|
|
395
420
|
const result = JSON.parse(resultJson);
|
|
396
421
|
|
|
397
422
|
if (result.done) {
|
|
398
|
-
|
|
423
|
+
closed = true;
|
|
424
|
+
controller.close();
|
|
425
|
+
return;
|
|
399
426
|
}
|
|
400
|
-
|
|
401
|
-
},
|
|
402
|
-
|
|
403
|
-
releaseLock: () => {
|
|
404
|
-
released = true;
|
|
405
|
-
},
|
|
406
|
-
|
|
407
|
-
get closed() {
|
|
408
|
-
return new Promise(() => {});
|
|
427
|
+
controller.enqueue(new Uint8Array(result.value));
|
|
409
428
|
},
|
|
410
|
-
|
|
411
|
-
|
|
429
|
+
cancel(reason) {
|
|
430
|
+
closed = true;
|
|
412
431
|
__Stream_error(streamId, String(reason || "cancelled"));
|
|
413
432
|
}
|
|
414
|
-
};
|
|
415
|
-
}
|
|
433
|
+
});
|
|
416
434
|
|
|
417
|
-
|
|
418
|
-
__Stream_error(this._getStreamId(), String(reason || "cancelled"));
|
|
435
|
+
_streamIds.set(this, streamId);
|
|
419
436
|
}
|
|
420
437
|
|
|
421
|
-
|
|
422
|
-
|
|
438
|
+
// Override to report as ReadableStream for spec compliance
|
|
439
|
+
get [Symbol.toStringTag]() {
|
|
440
|
+
return 'ReadableStream';
|
|
423
441
|
}
|
|
424
442
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
try {
|
|
428
|
-
while (true) {
|
|
429
|
-
const { value, done } = await reader.read();
|
|
430
|
-
if (done) return;
|
|
431
|
-
yield value;
|
|
432
|
-
}
|
|
433
|
-
} finally {
|
|
434
|
-
reader.releaseLock();
|
|
435
|
-
}
|
|
443
|
+
_getStreamId() {
|
|
444
|
+
return _streamIds.get(this);
|
|
436
445
|
}
|
|
437
446
|
|
|
438
|
-
// Static method to create from existing stream ID
|
|
439
447
|
static _fromStreamId(streamId) {
|
|
440
448
|
return new HostBackedReadableStream(streamId);
|
|
441
449
|
}
|
|
442
450
|
}
|
|
443
451
|
|
|
452
|
+
// Make constructor.name return 'ReadableStream' for spec compliance
|
|
453
|
+
Object.defineProperty(HostBackedReadableStream, 'name', { value: 'ReadableStream' });
|
|
454
|
+
|
|
444
455
|
globalThis.HostBackedReadableStream = HostBackedReadableStream;
|
|
445
456
|
})();
|
|
446
457
|
`;
|
|
@@ -603,8 +614,12 @@ function setupResponse(context, stateMap) {
|
|
|
603
614
|
// Mark as needing async Blob handling - will be read in constructor
|
|
604
615
|
return { __isBlob: true, blob: body };
|
|
605
616
|
}
|
|
606
|
-
// Handle
|
|
607
|
-
if (body instanceof
|
|
617
|
+
// Handle HostBackedReadableStream specially - preserve streamId
|
|
618
|
+
if (body instanceof HostBackedReadableStream) {
|
|
619
|
+
return { __isHostStream: true, stream: body, streamId: body._getStreamId() };
|
|
620
|
+
}
|
|
621
|
+
// Handle native ReadableStream
|
|
622
|
+
if (body instanceof ReadableStream) {
|
|
608
623
|
return { __isStream: true, stream: body };
|
|
609
624
|
}
|
|
610
625
|
// Try to convert to string
|
|
@@ -616,6 +631,7 @@ function setupResponse(context, stateMap) {
|
|
|
616
631
|
#headers;
|
|
617
632
|
#streamId = null;
|
|
618
633
|
#blobInitPromise = null; // For async Blob body initialization
|
|
634
|
+
#cachedBody = null; // Memoized body stream for spec compliance
|
|
619
635
|
|
|
620
636
|
constructor(body, init = {}) {
|
|
621
637
|
// Handle internal construction from instance ID
|
|
@@ -659,7 +675,27 @@ function setupResponse(context, stateMap) {
|
|
|
659
675
|
return;
|
|
660
676
|
}
|
|
661
677
|
|
|
662
|
-
// Handle
|
|
678
|
+
// Handle HostBackedReadableStream - reuse existing streamId for pass-through
|
|
679
|
+
if (preparedBody && preparedBody.__isHostStream) {
|
|
680
|
+
// Reuse the existing streamId to preserve the pass-through body mapping
|
|
681
|
+
this.#streamId = preparedBody.streamId;
|
|
682
|
+
const status = init.status ?? 200;
|
|
683
|
+
const statusText = init.statusText ?? '';
|
|
684
|
+
const headers = new Headers(init.headers);
|
|
685
|
+
const headersArray = Array.from(headers.entries());
|
|
686
|
+
|
|
687
|
+
this.#instanceId = __Response_constructStreaming(
|
|
688
|
+
this.#streamId,
|
|
689
|
+
status,
|
|
690
|
+
statusText,
|
|
691
|
+
headersArray
|
|
692
|
+
);
|
|
693
|
+
this.#headers = headers;
|
|
694
|
+
// Don't pump - the body is already backed by this streamId
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Handle native ReadableStream body
|
|
663
699
|
if (preparedBody && preparedBody.__isStream) {
|
|
664
700
|
this.#streamId = __Stream_create();
|
|
665
701
|
const status = init.status ?? 200;
|
|
@@ -758,9 +794,15 @@ function setupResponse(context, stateMap) {
|
|
|
758
794
|
}
|
|
759
795
|
|
|
760
796
|
get body() {
|
|
797
|
+
// Return cached body if available (WHATWG spec requires same object on repeated access)
|
|
798
|
+
if (this.#cachedBody !== null) {
|
|
799
|
+
return this.#cachedBody;
|
|
800
|
+
}
|
|
801
|
+
|
|
761
802
|
const streamId = __Response_getStreamId(this.#instanceId);
|
|
762
803
|
if (streamId !== null) {
|
|
763
|
-
|
|
804
|
+
this.#cachedBody = HostBackedReadableStream._fromStreamId(streamId);
|
|
805
|
+
return this.#cachedBody;
|
|
764
806
|
}
|
|
765
807
|
|
|
766
808
|
// Fallback: create host-backed stream from buffered body
|
|
@@ -773,7 +815,8 @@ function setupResponse(context, stateMap) {
|
|
|
773
815
|
}
|
|
774
816
|
__Stream_close(newStreamId);
|
|
775
817
|
|
|
776
|
-
|
|
818
|
+
this.#cachedBody = HostBackedReadableStream._fromStreamId(newStreamId);
|
|
819
|
+
return this.#cachedBody;
|
|
777
820
|
}
|
|
778
821
|
|
|
779
822
|
async text() {
|
|
@@ -1340,7 +1383,8 @@ function setupRequest(context, stateMap) {
|
|
|
1340
1383
|
`;
|
|
1341
1384
|
context.evalSync(requestCode);
|
|
1342
1385
|
}
|
|
1343
|
-
|
|
1386
|
+
var FETCH_STREAM_THRESHOLD = 64 * 1024;
|
|
1387
|
+
function setupFetchFunction(context, stateMap, streamRegistry, options) {
|
|
1344
1388
|
const global = context.global;
|
|
1345
1389
|
const fetchRef = new ivm.Reference(async (url, method, headersJson, bodyJson, signalAborted) => {
|
|
1346
1390
|
if (signalAborted) {
|
|
@@ -1356,6 +1400,69 @@ function setupFetchFunction(context, stateMap, options) {
|
|
|
1356
1400
|
});
|
|
1357
1401
|
const onFetch = options?.onFetch ?? fetch;
|
|
1358
1402
|
const nativeResponse = await onFetch(nativeRequest);
|
|
1403
|
+
const contentLength = nativeResponse.headers.get("content-length");
|
|
1404
|
+
const knownSize = contentLength ? parseInt(contentLength, 10) : null;
|
|
1405
|
+
const isCallbackStream = nativeResponse.__isCallbackStream;
|
|
1406
|
+
const isNetworkResponse = nativeResponse.url && (nativeResponse.url.startsWith("http://") || nativeResponse.url.startsWith("https://"));
|
|
1407
|
+
const shouldStream = nativeResponse.body && (isCallbackStream || isNetworkResponse && (knownSize === null || knownSize > FETCH_STREAM_THRESHOLD));
|
|
1408
|
+
if (shouldStream && nativeResponse.body) {
|
|
1409
|
+
if (isCallbackStream) {
|
|
1410
|
+
const streamId2 = streamRegistry.create();
|
|
1411
|
+
const passthruMap = getPassthruBodiesForContext(context);
|
|
1412
|
+
passthruMap.set(streamId2, nativeResponse.body);
|
|
1413
|
+
const instanceId3 = nextInstanceId++;
|
|
1414
|
+
const state3 = {
|
|
1415
|
+
status: nativeResponse.status,
|
|
1416
|
+
statusText: nativeResponse.statusText,
|
|
1417
|
+
headers: Array.from(nativeResponse.headers.entries()),
|
|
1418
|
+
body: new Uint8Array(0),
|
|
1419
|
+
bodyUsed: false,
|
|
1420
|
+
type: "default",
|
|
1421
|
+
url: nativeResponse.url,
|
|
1422
|
+
redirected: nativeResponse.redirected,
|
|
1423
|
+
streamId: streamId2
|
|
1424
|
+
};
|
|
1425
|
+
stateMap.set(instanceId3, state3);
|
|
1426
|
+
return instanceId3;
|
|
1427
|
+
}
|
|
1428
|
+
const streamId = streamRegistry.create();
|
|
1429
|
+
const instanceId2 = nextInstanceId++;
|
|
1430
|
+
const state2 = {
|
|
1431
|
+
status: nativeResponse.status,
|
|
1432
|
+
statusText: nativeResponse.statusText,
|
|
1433
|
+
headers: Array.from(nativeResponse.headers.entries()),
|
|
1434
|
+
body: new Uint8Array(0),
|
|
1435
|
+
bodyUsed: false,
|
|
1436
|
+
type: "default",
|
|
1437
|
+
url: nativeResponse.url,
|
|
1438
|
+
redirected: nativeResponse.redirected,
|
|
1439
|
+
streamId
|
|
1440
|
+
};
|
|
1441
|
+
stateMap.set(instanceId2, state2);
|
|
1442
|
+
const reader = nativeResponse.body.getReader();
|
|
1443
|
+
(async () => {
|
|
1444
|
+
try {
|
|
1445
|
+
while (true) {
|
|
1446
|
+
const { done, value } = await reader.read();
|
|
1447
|
+
if (done) {
|
|
1448
|
+
streamRegistry.close(streamId);
|
|
1449
|
+
break;
|
|
1450
|
+
}
|
|
1451
|
+
if (value) {
|
|
1452
|
+
while (streamRegistry.isQueueFull(streamId)) {
|
|
1453
|
+
await new Promise((r) => setTimeout(r, 1));
|
|
1454
|
+
}
|
|
1455
|
+
streamRegistry.push(streamId, value);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
} catch (err) {
|
|
1459
|
+
streamRegistry.error(streamId, err);
|
|
1460
|
+
} finally {
|
|
1461
|
+
reader.releaseLock();
|
|
1462
|
+
}
|
|
1463
|
+
})();
|
|
1464
|
+
return instanceId2;
|
|
1465
|
+
}
|
|
1359
1466
|
const responseBody = await nativeResponse.arrayBuffer();
|
|
1360
1467
|
const responseBodyArray = Array.from(new Uint8Array(responseBody));
|
|
1361
1468
|
const instanceId = nextInstanceId++;
|
|
@@ -1529,7 +1636,7 @@ async function setupFetch(context, options) {
|
|
|
1529
1636
|
context.evalSync(hostBackedStreamCode);
|
|
1530
1637
|
setupResponse(context, stateMap);
|
|
1531
1638
|
setupRequest(context, stateMap);
|
|
1532
|
-
setupFetchFunction(context, stateMap, options);
|
|
1639
|
+
setupFetchFunction(context, stateMap, streamRegistry, options);
|
|
1533
1640
|
const serveState = {
|
|
1534
1641
|
pendingUpgrade: null,
|
|
1535
1642
|
activeConnections: new Map
|
|
@@ -1585,6 +1692,12 @@ async function setupFetch(context, options) {
|
|
|
1585
1692
|
const request = Request._fromInstanceId(${requestInstanceId});
|
|
1586
1693
|
const server = new __Server__();
|
|
1587
1694
|
const response = await Promise.resolve(__serveOptions__.fetch(request, server));
|
|
1695
|
+
if (response == null) {
|
|
1696
|
+
throw new TypeError("fetch handler did not return a Response object (got " + (response === null ? "null" : "undefined") + ")");
|
|
1697
|
+
}
|
|
1698
|
+
if (typeof response._getInstanceId !== 'function') {
|
|
1699
|
+
throw new TypeError("fetch handler must return a Response object (got " + (typeof response) + ")");
|
|
1700
|
+
}
|
|
1588
1701
|
return response._getInstanceId();
|
|
1589
1702
|
})()
|
|
1590
1703
|
`, { promise: true });
|
|
@@ -1592,6 +1705,22 @@ async function setupFetch(context, options) {
|
|
|
1592
1705
|
if (!responseState) {
|
|
1593
1706
|
throw new Error("Response state not found");
|
|
1594
1707
|
}
|
|
1708
|
+
if (responseState.streamId !== null) {
|
|
1709
|
+
const passthruMap = getPassthruBodiesForContext(context);
|
|
1710
|
+
const passthruBody = passthruMap.get(responseState.streamId);
|
|
1711
|
+
if (passthruBody) {
|
|
1712
|
+
passthruMap.delete(responseState.streamId);
|
|
1713
|
+
const responseHeaders2 = new Headers(responseState.headers);
|
|
1714
|
+
const status2 = responseState.status === 101 ? 200 : responseState.status;
|
|
1715
|
+
const response2 = new Response(passthruBody, {
|
|
1716
|
+
status: status2,
|
|
1717
|
+
statusText: responseState.statusText,
|
|
1718
|
+
headers: responseHeaders2
|
|
1719
|
+
});
|
|
1720
|
+
response2._originalStatus = responseState.status;
|
|
1721
|
+
return response2;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1595
1724
|
if (responseState.streamId !== null) {
|
|
1596
1725
|
const responseStreamId = responseState.streamId;
|
|
1597
1726
|
let streamDone = false;
|
|
@@ -1653,12 +1782,17 @@ async function setupFetch(context, options) {
|
|
|
1653
1782
|
response._originalStatus = responseState.status;
|
|
1654
1783
|
return response;
|
|
1655
1784
|
} finally {
|
|
1785
|
+
if (requestStreamId !== null) {
|
|
1786
|
+
const startTime = Date.now();
|
|
1787
|
+
let streamState = streamRegistry.get(requestStreamId);
|
|
1788
|
+
while (streamState && !streamState.closed && !streamState.errored && Date.now() - startTime < 100) {
|
|
1789
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
1790
|
+
streamState = streamRegistry.get(requestStreamId);
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1656
1793
|
if (streamCleanup) {
|
|
1657
1794
|
await streamCleanup();
|
|
1658
1795
|
}
|
|
1659
|
-
if (requestStreamId !== null) {
|
|
1660
|
-
streamRegistry.delete(requestStreamId);
|
|
1661
|
-
}
|
|
1662
1796
|
}
|
|
1663
1797
|
},
|
|
1664
1798
|
getUpgradeRequest() {
|
|
@@ -1777,4 +1911,4 @@ export {
|
|
|
1777
1911
|
clearAllInstanceState
|
|
1778
1912
|
};
|
|
1779
1913
|
|
|
1780
|
-
//# debugId=
|
|
1914
|
+
//# debugId=5C3C175E215A892A64756E2164756E21
|