@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/cjs/index.cjs
CHANGED
|
@@ -50,6 +50,7 @@ var import_isolated_vm = __toESM(require("isolated-vm"));
|
|
|
50
50
|
var import_isolate_core = require("@ricsam/isolate-core");
|
|
51
51
|
var import_stream_state = require("./stream-state.cjs");
|
|
52
52
|
var instanceStateMap = new WeakMap;
|
|
53
|
+
var passthruBodies = new WeakMap;
|
|
53
54
|
var nextInstanceId = 1;
|
|
54
55
|
function getInstanceStateMapForContext(context) {
|
|
55
56
|
let map = instanceStateMap.get(context);
|
|
@@ -59,6 +60,14 @@ function getInstanceStateMapForContext(context) {
|
|
|
59
60
|
}
|
|
60
61
|
return map;
|
|
61
62
|
}
|
|
63
|
+
function getPassthruBodiesForContext(context) {
|
|
64
|
+
let map = passthruBodies.get(context);
|
|
65
|
+
if (!map) {
|
|
66
|
+
map = new Map;
|
|
67
|
+
passthruBodies.set(context, map);
|
|
68
|
+
}
|
|
69
|
+
return map;
|
|
70
|
+
}
|
|
62
71
|
var headersCode = `
|
|
63
72
|
(function() {
|
|
64
73
|
class Headers {
|
|
@@ -414,77 +423,79 @@ var hostBackedStreamCode = `
|
|
|
414
423
|
(function() {
|
|
415
424
|
const _streamIds = new WeakMap();
|
|
416
425
|
|
|
417
|
-
|
|
426
|
+
// Polyfill values() on ReadableStream if not available (older V8 versions)
|
|
427
|
+
if (typeof ReadableStream.prototype.values !== 'function') {
|
|
428
|
+
ReadableStream.prototype.values = function(options) {
|
|
429
|
+
const reader = this.getReader();
|
|
430
|
+
return {
|
|
431
|
+
async next() {
|
|
432
|
+
const { value, done } = await reader.read();
|
|
433
|
+
if (done) {
|
|
434
|
+
reader.releaseLock();
|
|
435
|
+
return { value: undefined, done: true };
|
|
436
|
+
}
|
|
437
|
+
return { value, done: false };
|
|
438
|
+
},
|
|
439
|
+
async return(value) {
|
|
440
|
+
reader.releaseLock();
|
|
441
|
+
return { value, done: true };
|
|
442
|
+
},
|
|
443
|
+
[Symbol.asyncIterator]() {
|
|
444
|
+
return this;
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Create a proper ReadableStream subclass that reports as "ReadableStream"
|
|
451
|
+
class HostBackedReadableStream extends ReadableStream {
|
|
418
452
|
constructor(streamId) {
|
|
419
453
|
if (streamId === undefined) {
|
|
420
454
|
streamId = __Stream_create();
|
|
421
455
|
}
|
|
422
|
-
_streamIds.set(this, streamId);
|
|
423
|
-
}
|
|
424
456
|
|
|
425
|
-
|
|
426
|
-
return _streamIds.get(this);
|
|
427
|
-
}
|
|
457
|
+
let closed = false;
|
|
428
458
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
459
|
+
super({
|
|
460
|
+
async pull(controller) {
|
|
461
|
+
if (closed) return;
|
|
432
462
|
|
|
433
|
-
return {
|
|
434
|
-
read: async () => {
|
|
435
|
-
if (released) {
|
|
436
|
-
throw new TypeError("Reader has been released");
|
|
437
|
-
}
|
|
438
463
|
const resultJson = __Stream_pull_ref.applySyncPromise(undefined, [streamId]);
|
|
439
464
|
const result = JSON.parse(resultJson);
|
|
440
465
|
|
|
441
466
|
if (result.done) {
|
|
442
|
-
|
|
467
|
+
closed = true;
|
|
468
|
+
controller.close();
|
|
469
|
+
return;
|
|
443
470
|
}
|
|
444
|
-
|
|
445
|
-
},
|
|
446
|
-
|
|
447
|
-
releaseLock: () => {
|
|
448
|
-
released = true;
|
|
449
|
-
},
|
|
450
|
-
|
|
451
|
-
get closed() {
|
|
452
|
-
return new Promise(() => {});
|
|
471
|
+
controller.enqueue(new Uint8Array(result.value));
|
|
453
472
|
},
|
|
454
|
-
|
|
455
|
-
|
|
473
|
+
cancel(reason) {
|
|
474
|
+
closed = true;
|
|
456
475
|
__Stream_error(streamId, String(reason || "cancelled"));
|
|
457
476
|
}
|
|
458
|
-
};
|
|
459
|
-
}
|
|
477
|
+
});
|
|
460
478
|
|
|
461
|
-
|
|
462
|
-
__Stream_error(this._getStreamId(), String(reason || "cancelled"));
|
|
479
|
+
_streamIds.set(this, streamId);
|
|
463
480
|
}
|
|
464
481
|
|
|
465
|
-
|
|
466
|
-
|
|
482
|
+
// Override to report as ReadableStream for spec compliance
|
|
483
|
+
get [Symbol.toStringTag]() {
|
|
484
|
+
return 'ReadableStream';
|
|
467
485
|
}
|
|
468
486
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
try {
|
|
472
|
-
while (true) {
|
|
473
|
-
const { value, done } = await reader.read();
|
|
474
|
-
if (done) return;
|
|
475
|
-
yield value;
|
|
476
|
-
}
|
|
477
|
-
} finally {
|
|
478
|
-
reader.releaseLock();
|
|
479
|
-
}
|
|
487
|
+
_getStreamId() {
|
|
488
|
+
return _streamIds.get(this);
|
|
480
489
|
}
|
|
481
490
|
|
|
482
|
-
// Static method to create from existing stream ID
|
|
483
491
|
static _fromStreamId(streamId) {
|
|
484
492
|
return new HostBackedReadableStream(streamId);
|
|
485
493
|
}
|
|
486
494
|
}
|
|
487
495
|
|
|
496
|
+
// Make constructor.name return 'ReadableStream' for spec compliance
|
|
497
|
+
Object.defineProperty(HostBackedReadableStream, 'name', { value: 'ReadableStream' });
|
|
498
|
+
|
|
488
499
|
globalThis.HostBackedReadableStream = HostBackedReadableStream;
|
|
489
500
|
})();
|
|
490
501
|
`;
|
|
@@ -647,8 +658,12 @@ function setupResponse(context, stateMap) {
|
|
|
647
658
|
// Mark as needing async Blob handling - will be read in constructor
|
|
648
659
|
return { __isBlob: true, blob: body };
|
|
649
660
|
}
|
|
650
|
-
// Handle
|
|
651
|
-
if (body instanceof
|
|
661
|
+
// Handle HostBackedReadableStream specially - preserve streamId
|
|
662
|
+
if (body instanceof HostBackedReadableStream) {
|
|
663
|
+
return { __isHostStream: true, stream: body, streamId: body._getStreamId() };
|
|
664
|
+
}
|
|
665
|
+
// Handle native ReadableStream
|
|
666
|
+
if (body instanceof ReadableStream) {
|
|
652
667
|
return { __isStream: true, stream: body };
|
|
653
668
|
}
|
|
654
669
|
// Try to convert to string
|
|
@@ -660,6 +675,7 @@ function setupResponse(context, stateMap) {
|
|
|
660
675
|
#headers;
|
|
661
676
|
#streamId = null;
|
|
662
677
|
#blobInitPromise = null; // For async Blob body initialization
|
|
678
|
+
#cachedBody = null; // Memoized body stream for spec compliance
|
|
663
679
|
|
|
664
680
|
constructor(body, init = {}) {
|
|
665
681
|
// Handle internal construction from instance ID
|
|
@@ -703,7 +719,27 @@ function setupResponse(context, stateMap) {
|
|
|
703
719
|
return;
|
|
704
720
|
}
|
|
705
721
|
|
|
706
|
-
// Handle
|
|
722
|
+
// Handle HostBackedReadableStream - reuse existing streamId for pass-through
|
|
723
|
+
if (preparedBody && preparedBody.__isHostStream) {
|
|
724
|
+
// Reuse the existing streamId to preserve the pass-through body mapping
|
|
725
|
+
this.#streamId = preparedBody.streamId;
|
|
726
|
+
const status = init.status ?? 200;
|
|
727
|
+
const statusText = init.statusText ?? '';
|
|
728
|
+
const headers = new Headers(init.headers);
|
|
729
|
+
const headersArray = Array.from(headers.entries());
|
|
730
|
+
|
|
731
|
+
this.#instanceId = __Response_constructStreaming(
|
|
732
|
+
this.#streamId,
|
|
733
|
+
status,
|
|
734
|
+
statusText,
|
|
735
|
+
headersArray
|
|
736
|
+
);
|
|
737
|
+
this.#headers = headers;
|
|
738
|
+
// Don't pump - the body is already backed by this streamId
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// Handle native ReadableStream body
|
|
707
743
|
if (preparedBody && preparedBody.__isStream) {
|
|
708
744
|
this.#streamId = __Stream_create();
|
|
709
745
|
const status = init.status ?? 200;
|
|
@@ -802,9 +838,15 @@ function setupResponse(context, stateMap) {
|
|
|
802
838
|
}
|
|
803
839
|
|
|
804
840
|
get body() {
|
|
841
|
+
// Return cached body if available (WHATWG spec requires same object on repeated access)
|
|
842
|
+
if (this.#cachedBody !== null) {
|
|
843
|
+
return this.#cachedBody;
|
|
844
|
+
}
|
|
845
|
+
|
|
805
846
|
const streamId = __Response_getStreamId(this.#instanceId);
|
|
806
847
|
if (streamId !== null) {
|
|
807
|
-
|
|
848
|
+
this.#cachedBody = HostBackedReadableStream._fromStreamId(streamId);
|
|
849
|
+
return this.#cachedBody;
|
|
808
850
|
}
|
|
809
851
|
|
|
810
852
|
// Fallback: create host-backed stream from buffered body
|
|
@@ -817,7 +859,8 @@ function setupResponse(context, stateMap) {
|
|
|
817
859
|
}
|
|
818
860
|
__Stream_close(newStreamId);
|
|
819
861
|
|
|
820
|
-
|
|
862
|
+
this.#cachedBody = HostBackedReadableStream._fromStreamId(newStreamId);
|
|
863
|
+
return this.#cachedBody;
|
|
821
864
|
}
|
|
822
865
|
|
|
823
866
|
async text() {
|
|
@@ -1384,7 +1427,8 @@ function setupRequest(context, stateMap) {
|
|
|
1384
1427
|
`;
|
|
1385
1428
|
context.evalSync(requestCode);
|
|
1386
1429
|
}
|
|
1387
|
-
|
|
1430
|
+
var FETCH_STREAM_THRESHOLD = 64 * 1024;
|
|
1431
|
+
function setupFetchFunction(context, stateMap, streamRegistry, options) {
|
|
1388
1432
|
const global = context.global;
|
|
1389
1433
|
const fetchRef = new import_isolated_vm.default.Reference(async (url, method, headersJson, bodyJson, signalAborted) => {
|
|
1390
1434
|
if (signalAborted) {
|
|
@@ -1400,6 +1444,69 @@ function setupFetchFunction(context, stateMap, options) {
|
|
|
1400
1444
|
});
|
|
1401
1445
|
const onFetch = options?.onFetch ?? fetch;
|
|
1402
1446
|
const nativeResponse = await onFetch(nativeRequest);
|
|
1447
|
+
const contentLength = nativeResponse.headers.get("content-length");
|
|
1448
|
+
const knownSize = contentLength ? parseInt(contentLength, 10) : null;
|
|
1449
|
+
const isCallbackStream = nativeResponse.__isCallbackStream;
|
|
1450
|
+
const isNetworkResponse = nativeResponse.url && (nativeResponse.url.startsWith("http://") || nativeResponse.url.startsWith("https://"));
|
|
1451
|
+
const shouldStream = nativeResponse.body && (isCallbackStream || isNetworkResponse && (knownSize === null || knownSize > FETCH_STREAM_THRESHOLD));
|
|
1452
|
+
if (shouldStream && nativeResponse.body) {
|
|
1453
|
+
if (isCallbackStream) {
|
|
1454
|
+
const streamId2 = streamRegistry.create();
|
|
1455
|
+
const passthruMap = getPassthruBodiesForContext(context);
|
|
1456
|
+
passthruMap.set(streamId2, nativeResponse.body);
|
|
1457
|
+
const instanceId3 = nextInstanceId++;
|
|
1458
|
+
const state3 = {
|
|
1459
|
+
status: nativeResponse.status,
|
|
1460
|
+
statusText: nativeResponse.statusText,
|
|
1461
|
+
headers: Array.from(nativeResponse.headers.entries()),
|
|
1462
|
+
body: new Uint8Array(0),
|
|
1463
|
+
bodyUsed: false,
|
|
1464
|
+
type: "default",
|
|
1465
|
+
url: nativeResponse.url,
|
|
1466
|
+
redirected: nativeResponse.redirected,
|
|
1467
|
+
streamId: streamId2
|
|
1468
|
+
};
|
|
1469
|
+
stateMap.set(instanceId3, state3);
|
|
1470
|
+
return instanceId3;
|
|
1471
|
+
}
|
|
1472
|
+
const streamId = streamRegistry.create();
|
|
1473
|
+
const instanceId2 = nextInstanceId++;
|
|
1474
|
+
const state2 = {
|
|
1475
|
+
status: nativeResponse.status,
|
|
1476
|
+
statusText: nativeResponse.statusText,
|
|
1477
|
+
headers: Array.from(nativeResponse.headers.entries()),
|
|
1478
|
+
body: new Uint8Array(0),
|
|
1479
|
+
bodyUsed: false,
|
|
1480
|
+
type: "default",
|
|
1481
|
+
url: nativeResponse.url,
|
|
1482
|
+
redirected: nativeResponse.redirected,
|
|
1483
|
+
streamId
|
|
1484
|
+
};
|
|
1485
|
+
stateMap.set(instanceId2, state2);
|
|
1486
|
+
const reader = nativeResponse.body.getReader();
|
|
1487
|
+
(async () => {
|
|
1488
|
+
try {
|
|
1489
|
+
while (true) {
|
|
1490
|
+
const { done, value } = await reader.read();
|
|
1491
|
+
if (done) {
|
|
1492
|
+
streamRegistry.close(streamId);
|
|
1493
|
+
break;
|
|
1494
|
+
}
|
|
1495
|
+
if (value) {
|
|
1496
|
+
while (streamRegistry.isQueueFull(streamId)) {
|
|
1497
|
+
await new Promise((r) => setTimeout(r, 1));
|
|
1498
|
+
}
|
|
1499
|
+
streamRegistry.push(streamId, value);
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
} catch (err) {
|
|
1503
|
+
streamRegistry.error(streamId, err);
|
|
1504
|
+
} finally {
|
|
1505
|
+
reader.releaseLock();
|
|
1506
|
+
}
|
|
1507
|
+
})();
|
|
1508
|
+
return instanceId2;
|
|
1509
|
+
}
|
|
1403
1510
|
const responseBody = await nativeResponse.arrayBuffer();
|
|
1404
1511
|
const responseBodyArray = Array.from(new Uint8Array(responseBody));
|
|
1405
1512
|
const instanceId = nextInstanceId++;
|
|
@@ -1573,7 +1680,7 @@ async function setupFetch(context, options) {
|
|
|
1573
1680
|
context.evalSync(hostBackedStreamCode);
|
|
1574
1681
|
setupResponse(context, stateMap);
|
|
1575
1682
|
setupRequest(context, stateMap);
|
|
1576
|
-
setupFetchFunction(context, stateMap, options);
|
|
1683
|
+
setupFetchFunction(context, stateMap, streamRegistry, options);
|
|
1577
1684
|
const serveState = {
|
|
1578
1685
|
pendingUpgrade: null,
|
|
1579
1686
|
activeConnections: new Map
|
|
@@ -1629,6 +1736,12 @@ async function setupFetch(context, options) {
|
|
|
1629
1736
|
const request = Request._fromInstanceId(${requestInstanceId});
|
|
1630
1737
|
const server = new __Server__();
|
|
1631
1738
|
const response = await Promise.resolve(__serveOptions__.fetch(request, server));
|
|
1739
|
+
if (response == null) {
|
|
1740
|
+
throw new TypeError("fetch handler did not return a Response object (got " + (response === null ? "null" : "undefined") + ")");
|
|
1741
|
+
}
|
|
1742
|
+
if (typeof response._getInstanceId !== 'function') {
|
|
1743
|
+
throw new TypeError("fetch handler must return a Response object (got " + (typeof response) + ")");
|
|
1744
|
+
}
|
|
1632
1745
|
return response._getInstanceId();
|
|
1633
1746
|
})()
|
|
1634
1747
|
`, { promise: true });
|
|
@@ -1636,6 +1749,22 @@ async function setupFetch(context, options) {
|
|
|
1636
1749
|
if (!responseState) {
|
|
1637
1750
|
throw new Error("Response state not found");
|
|
1638
1751
|
}
|
|
1752
|
+
if (responseState.streamId !== null) {
|
|
1753
|
+
const passthruMap = getPassthruBodiesForContext(context);
|
|
1754
|
+
const passthruBody = passthruMap.get(responseState.streamId);
|
|
1755
|
+
if (passthruBody) {
|
|
1756
|
+
passthruMap.delete(responseState.streamId);
|
|
1757
|
+
const responseHeaders2 = new Headers(responseState.headers);
|
|
1758
|
+
const status2 = responseState.status === 101 ? 200 : responseState.status;
|
|
1759
|
+
const response2 = new Response(passthruBody, {
|
|
1760
|
+
status: status2,
|
|
1761
|
+
statusText: responseState.statusText,
|
|
1762
|
+
headers: responseHeaders2
|
|
1763
|
+
});
|
|
1764
|
+
response2._originalStatus = responseState.status;
|
|
1765
|
+
return response2;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1639
1768
|
if (responseState.streamId !== null) {
|
|
1640
1769
|
const responseStreamId = responseState.streamId;
|
|
1641
1770
|
let streamDone = false;
|
|
@@ -1697,12 +1826,17 @@ async function setupFetch(context, options) {
|
|
|
1697
1826
|
response._originalStatus = responseState.status;
|
|
1698
1827
|
return response;
|
|
1699
1828
|
} finally {
|
|
1829
|
+
if (requestStreamId !== null) {
|
|
1830
|
+
const startTime = Date.now();
|
|
1831
|
+
let streamState = streamRegistry.get(requestStreamId);
|
|
1832
|
+
while (streamState && !streamState.closed && !streamState.errored && Date.now() - startTime < 100) {
|
|
1833
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
1834
|
+
streamState = streamRegistry.get(requestStreamId);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1700
1837
|
if (streamCleanup) {
|
|
1701
1838
|
await streamCleanup();
|
|
1702
1839
|
}
|
|
1703
|
-
if (requestStreamId !== null) {
|
|
1704
|
-
streamRegistry.delete(requestStreamId);
|
|
1705
|
-
}
|
|
1706
1840
|
}
|
|
1707
1841
|
},
|
|
1708
1842
|
getUpgradeRequest() {
|
|
@@ -1817,4 +1951,4 @@ async function setupFetch(context, options) {
|
|
|
1817
1951
|
};
|
|
1818
1952
|
}
|
|
1819
1953
|
|
|
1820
|
-
//# debugId=
|
|
1954
|
+
//# debugId=B1E0412945057BF364756E2164756E21
|