@ricsam/isolate-fetch 0.1.11 → 0.1.13

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.
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../../src/stream-state.ts"],
4
4
  "sourcesContent": [
5
- "import ivm from \"isolated-vm\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface StreamState {\n /** Buffered chunks waiting to be read */\n queue: Uint8Array[];\n\n /** Total bytes in queue (for backpressure) */\n queueSize: number;\n\n /** Stream has been closed (no more data) */\n closed: boolean;\n\n /** Stream encountered an error */\n errored: boolean;\n\n /** The error value if errored */\n errorValue: unknown;\n\n /** A pull is waiting for data */\n pullWaiting: boolean;\n\n /** Resolve function for waiting pull */\n pullResolve: ((chunk: Uint8Array | null) => void) | null;\n\n /** Reject function for waiting pull */\n pullReject: ((error: unknown) => void) | null;\n}\n\nexport interface StreamStateRegistry {\n /** Create a new stream and return its ID */\n create(): number;\n\n /** Get stream state by ID */\n get(streamId: number): StreamState | undefined;\n\n /** Push a chunk to the stream's queue */\n push(streamId: number, chunk: Uint8Array): boolean;\n\n /** Pull a chunk from the stream (returns Promise that resolves when data available) */\n pull(\n streamId: number\n ): Promise<{ value: Uint8Array; done: false } | { done: true }>;\n\n /** Close the stream (no more data) */\n close(streamId: number): void;\n\n /** Error the stream */\n error(streamId: number, errorValue: unknown): void;\n\n /** Check if stream queue is above high-water mark */\n isQueueFull(streamId: number): boolean;\n\n /** Delete stream state (cleanup) */\n delete(streamId: number): void;\n\n /** Clear all streams (context cleanup) */\n clear(): void;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Maximum bytes to buffer before backpressure kicks in */\nexport const HIGH_WATER_MARK = 64 * 1024; // 64KB\n\n/** Maximum number of chunks in queue */\nexport const MAX_QUEUE_CHUNKS = 16;\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\nexport function createStreamStateRegistry(): StreamStateRegistry {\n const streams = new Map<number, StreamState>();\n let nextStreamId = 1;\n\n return {\n create(): number {\n const streamId = nextStreamId++;\n streams.set(streamId, {\n queue: [],\n queueSize: 0,\n closed: false,\n errored: false,\n errorValue: undefined,\n pullWaiting: false,\n pullResolve: null,\n pullReject: null,\n });\n return streamId;\n },\n\n get(streamId: number): StreamState | undefined {\n return streams.get(streamId);\n },\n\n push(streamId: number, chunk: Uint8Array): boolean {\n const state = streams.get(streamId);\n if (!state) return false;\n if (state.closed || state.errored) return false;\n\n // If a pull is waiting, deliver directly\n if (state.pullWaiting && state.pullResolve) {\n state.pullWaiting = false;\n const resolve = state.pullResolve;\n state.pullResolve = null;\n state.pullReject = null;\n resolve(chunk);\n return true;\n }\n\n // Otherwise queue the chunk\n state.queue.push(chunk);\n state.queueSize += chunk.length;\n return true;\n },\n\n async pull(\n streamId: number\n ): Promise<{ value: Uint8Array; done: false } | { done: true }> {\n const state = streams.get(streamId);\n if (!state) {\n return { done: true };\n }\n\n // If queue has data, return it first (even if stream is errored)\n if (state.queue.length > 0) {\n const chunk = state.queue.shift()!;\n state.queueSize -= chunk.length;\n return { value: chunk, done: false };\n }\n\n // If errored (and queue is empty), throw\n if (state.errored) {\n throw state.errorValue;\n }\n\n // If closed and queue empty, we're done\n if (state.closed) {\n return { done: true };\n }\n\n // Wait for data\n return new Promise((resolve, reject) => {\n state.pullWaiting = true;\n state.pullResolve = (chunk) => {\n if (chunk === null) {\n resolve({ done: true });\n } else {\n resolve({ value: chunk, done: false });\n }\n };\n state.pullReject = reject;\n });\n },\n\n close(streamId: number): void {\n const state = streams.get(streamId);\n if (!state) return;\n\n state.closed = true;\n\n // If a pull is waiting, resolve with done\n if (state.pullWaiting && state.pullResolve) {\n state.pullWaiting = false;\n const resolve = state.pullResolve;\n state.pullResolve = null;\n state.pullReject = null;\n resolve(null);\n }\n },\n\n error(streamId: number, errorValue: unknown): void {\n const state = streams.get(streamId);\n if (!state) return;\n\n state.errored = true;\n state.errorValue = errorValue;\n\n // If a pull is waiting, reject it\n if (state.pullWaiting && state.pullReject) {\n state.pullWaiting = false;\n const reject = state.pullReject;\n state.pullResolve = null;\n state.pullReject = null;\n reject(errorValue);\n }\n },\n\n isQueueFull(streamId: number): boolean {\n const state = streams.get(streamId);\n if (!state) return true;\n return (\n state.queueSize >= HIGH_WATER_MARK ||\n state.queue.length >= MAX_QUEUE_CHUNKS\n );\n },\n\n delete(streamId: number): void {\n const state = streams.get(streamId);\n if (state && state.pullWaiting && state.pullReject) {\n state.pullReject(new Error(\"Stream deleted\"));\n }\n streams.delete(streamId);\n },\n\n clear(): void {\n for (const [streamId] of streams) {\n this.delete(streamId);\n }\n },\n };\n}\n\n// ============================================================================\n// Context-Scoped Registry\n// ============================================================================\n\nconst contextRegistries = new WeakMap<ivm.Context, StreamStateRegistry>();\n\nexport function getStreamRegistryForContext(\n context: ivm.Context\n): StreamStateRegistry {\n let registry = contextRegistries.get(context);\n if (!registry) {\n registry = createStreamStateRegistry();\n contextRegistries.set(context, registry);\n }\n return registry;\n}\n\nexport function clearStreamRegistryForContext(context: ivm.Context): void {\n const registry = contextRegistries.get(context);\n if (registry) {\n registry.clear();\n contextRegistries.delete(context);\n }\n}\n\n// ============================================================================\n// Native Stream Reader\n// ============================================================================\n\n/**\n * Start reading from a native ReadableStream and push to host queue.\n * Respects backpressure by pausing when queue is full.\n *\n * @param nativeStream The native ReadableStream to read from\n * @param streamId The stream ID in the registry\n * @param registry The stream state registry\n * @returns Async cleanup function to cancel the reader\n */\nexport function startNativeStreamReader(\n nativeStream: ReadableStream<Uint8Array>,\n streamId: number,\n registry: StreamStateRegistry\n): () => Promise<void> {\n let cancelled = false;\n let reader: ReadableStreamDefaultReader<Uint8Array> | null = null;\n let readLoopPromise: Promise<void> | null = null;\n\n const CHUNK_SIZE = 64 * 1024; // 64KB max chunk size\n\n async function readLoop() {\n try {\n reader = nativeStream.getReader();\n\n while (!cancelled) {\n // Respect backpressure - wait if queue is full\n while (registry.isQueueFull(streamId) && !cancelled) {\n await new Promise((resolve) => setTimeout(resolve, 1));\n }\n if (cancelled) break;\n\n const { done, value } = await reader.read();\n\n if (done) {\n registry.close(streamId);\n break;\n }\n\n if (value) {\n // Split large chunks to maintain granularity\n if (value.length > CHUNK_SIZE) {\n for (let offset = 0; offset < value.length; offset += CHUNK_SIZE) {\n const chunk = value.slice(\n offset,\n Math.min(offset + CHUNK_SIZE, value.length)\n );\n registry.push(streamId, chunk);\n }\n } else {\n registry.push(streamId, value);\n }\n }\n }\n } catch (error) {\n registry.error(streamId, error);\n } finally {\n if (reader) {\n try {\n reader.releaseLock();\n } catch {\n // Ignore release errors\n }\n }\n }\n }\n\n // Start the read loop and save the promise\n readLoopPromise = readLoop();\n\n // Return async cleanup function\n return async () => {\n cancelled = true;\n if (reader) {\n try {\n await reader.cancel();\n } catch {\n // Ignore cancel errors\n }\n }\n // Wait for read loop to finish\n if (readLoopPromise) {\n try {\n await readLoopPromise;\n } catch {\n // Ignore read loop errors during cleanup\n }\n }\n };\n}\n"
5
+ "import ivm from \"isolated-vm\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface StreamState {\n /** Buffered chunks waiting to be read */\n queue: Uint8Array[];\n\n /** Total bytes in queue (for backpressure) */\n queueSize: number;\n\n /** Stream has been closed (no more data) */\n closed: boolean;\n\n /** Stream encountered an error */\n errored: boolean;\n\n /** The error value if errored */\n errorValue: unknown;\n\n /** A pull is waiting for data */\n pullWaiting: boolean;\n\n /** Resolve function for waiting pull */\n pullResolve: ((chunk: Uint8Array | null) => void) | null;\n\n /** Reject function for waiting pull */\n pullReject: ((error: unknown) => void) | null;\n}\n\nexport interface StreamStateRegistry {\n /** Create a new stream and return its ID */\n create(): number;\n\n /** Get stream state by ID */\n get(streamId: number): StreamState | undefined;\n\n /** Push a chunk to the stream's queue */\n push(streamId: number, chunk: Uint8Array): boolean;\n\n /** Pull a chunk from the stream (returns Promise that resolves when data available) */\n pull(\n streamId: number\n ): Promise<{ value: Uint8Array; done: false } | { done: true }>;\n\n /** Close the stream (no more data) */\n close(streamId: number): void;\n\n /** Error the stream */\n error(streamId: number, errorValue: unknown): void;\n\n /** Check if stream queue is above high-water mark */\n isQueueFull(streamId: number): boolean;\n\n /** Delete stream state (cleanup) */\n delete(streamId: number): void;\n\n /** Clear all streams (context cleanup) */\n clear(): void;\n\n /** Cancel a stream and call its cleanup function */\n cancel(streamId: number): void;\n\n /** Register a cleanup function for a stream */\n setCleanup(streamId: number, cleanup: () => Promise<void>): void;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Maximum bytes to buffer before backpressure kicks in */\nexport const HIGH_WATER_MARK = 64 * 1024; // 64KB\n\n/** Maximum number of chunks in queue */\nexport const MAX_QUEUE_CHUNKS = 16;\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\nexport function createStreamStateRegistry(): StreamStateRegistry {\n const streams = new Map<number, StreamState>();\n const cleanups = new Map<number, () => Promise<void>>();\n let nextStreamId = 1;\n\n return {\n create(): number {\n const streamId = nextStreamId++;\n streams.set(streamId, {\n queue: [],\n queueSize: 0,\n closed: false,\n errored: false,\n errorValue: undefined,\n pullWaiting: false,\n pullResolve: null,\n pullReject: null,\n });\n return streamId;\n },\n\n get(streamId: number): StreamState | undefined {\n return streams.get(streamId);\n },\n\n push(streamId: number, chunk: Uint8Array): boolean {\n const state = streams.get(streamId);\n if (!state) return false;\n if (state.closed || state.errored) return false;\n\n // If a pull is waiting, deliver directly\n if (state.pullWaiting && state.pullResolve) {\n state.pullWaiting = false;\n const resolve = state.pullResolve;\n state.pullResolve = null;\n state.pullReject = null;\n resolve(chunk);\n return true;\n }\n\n // Otherwise queue the chunk\n state.queue.push(chunk);\n state.queueSize += chunk.length;\n return true;\n },\n\n async pull(\n streamId: number\n ): Promise<{ value: Uint8Array; done: false } | { done: true }> {\n const state = streams.get(streamId);\n if (!state) {\n return { done: true };\n }\n\n // If queue has data, return it first (even if stream is errored)\n if (state.queue.length > 0) {\n const chunk = state.queue.shift()!;\n state.queueSize -= chunk.length;\n return { value: chunk, done: false };\n }\n\n // If errored (and queue is empty), throw\n if (state.errored) {\n throw state.errorValue;\n }\n\n // If closed and queue empty, we're done\n if (state.closed) {\n return { done: true };\n }\n\n // Wait for data\n return new Promise((resolve, reject) => {\n state.pullWaiting = true;\n state.pullResolve = (chunk) => {\n if (chunk === null) {\n resolve({ done: true });\n } else {\n resolve({ value: chunk, done: false });\n }\n };\n state.pullReject = reject;\n });\n },\n\n close(streamId: number): void {\n const state = streams.get(streamId);\n if (!state) return;\n\n state.closed = true;\n\n // If a pull is waiting, resolve with done\n if (state.pullWaiting && state.pullResolve) {\n state.pullWaiting = false;\n const resolve = state.pullResolve;\n state.pullResolve = null;\n state.pullReject = null;\n resolve(null);\n }\n },\n\n error(streamId: number, errorValue: unknown): void {\n const state = streams.get(streamId);\n if (!state) return;\n\n state.errored = true;\n state.errorValue = errorValue;\n\n // If a pull is waiting, reject it\n if (state.pullWaiting && state.pullReject) {\n state.pullWaiting = false;\n const reject = state.pullReject;\n state.pullResolve = null;\n state.pullReject = null;\n reject(errorValue);\n }\n },\n\n isQueueFull(streamId: number): boolean {\n const state = streams.get(streamId);\n if (!state) return true;\n return (\n state.queueSize >= HIGH_WATER_MARK ||\n state.queue.length >= MAX_QUEUE_CHUNKS\n );\n },\n\n delete(streamId: number): void {\n const state = streams.get(streamId);\n if (state && state.pullWaiting && state.pullReject) {\n state.pullReject(new Error(\"Stream deleted\"));\n }\n streams.delete(streamId);\n cleanups.delete(streamId);\n },\n\n clear(): void {\n for (const [streamId] of streams) {\n this.delete(streamId);\n }\n },\n\n cancel(streamId: number): void {\n this.close(streamId);\n const cleanup = cleanups.get(streamId);\n if (cleanup) {\n cleanup().catch(() => {});\n cleanups.delete(streamId);\n }\n },\n\n setCleanup(streamId: number, cleanup: () => Promise<void>): void {\n cleanups.set(streamId, cleanup);\n },\n };\n}\n\n// ============================================================================\n// Context-Scoped Registry\n// ============================================================================\n\nconst contextRegistries = new WeakMap<ivm.Context, StreamStateRegistry>();\n\nexport function getStreamRegistryForContext(\n context: ivm.Context\n): StreamStateRegistry {\n let registry = contextRegistries.get(context);\n if (!registry) {\n registry = createStreamStateRegistry();\n contextRegistries.set(context, registry);\n }\n return registry;\n}\n\nexport function clearStreamRegistryForContext(context: ivm.Context): void {\n const registry = contextRegistries.get(context);\n if (registry) {\n registry.clear();\n contextRegistries.delete(context);\n }\n}\n\n// ============================================================================\n// Native Stream Reader\n// ============================================================================\n\n/**\n * Start reading from a native ReadableStream and push to host queue.\n * Respects backpressure by pausing when queue is full.\n *\n * @param nativeStream The native ReadableStream to read from\n * @param streamId The stream ID in the registry\n * @param registry The stream state registry\n * @returns Async cleanup function to cancel the reader\n */\nexport function startNativeStreamReader(\n nativeStream: ReadableStream<Uint8Array>,\n streamId: number,\n registry: StreamStateRegistry\n): () => Promise<void> {\n let cancelled = false;\n let reader: ReadableStreamDefaultReader<Uint8Array> | null = null;\n let readLoopPromise: Promise<void> | null = null;\n\n const CHUNK_SIZE = 64 * 1024; // 64KB max chunk size\n\n async function readLoop() {\n try {\n reader = nativeStream.getReader();\n\n while (!cancelled) {\n // Respect backpressure - wait if queue is full\n while (registry.isQueueFull(streamId) && !cancelled) {\n await new Promise((resolve) => setTimeout(resolve, 1));\n }\n if (cancelled) break;\n\n const { done, value } = await reader.read();\n\n if (done) {\n registry.close(streamId);\n break;\n }\n\n if (value) {\n // Split large chunks to maintain granularity\n if (value.length > CHUNK_SIZE) {\n for (let offset = 0; offset < value.length; offset += CHUNK_SIZE) {\n const chunk = value.slice(\n offset,\n Math.min(offset + CHUNK_SIZE, value.length)\n );\n registry.push(streamId, chunk);\n }\n } else {\n registry.push(streamId, value);\n }\n }\n }\n } catch (error) {\n registry.error(streamId, error);\n } finally {\n if (reader) {\n try {\n reader.releaseLock();\n } catch {\n // Ignore release errors\n }\n }\n }\n }\n\n // Start the read loop and save the promise\n readLoopPromise = readLoop();\n\n // Return async cleanup function\n return async () => {\n cancelled = true;\n if (reader) {\n try {\n await reader.cancel();\n } catch {\n // Ignore cancel errors\n }\n }\n // Wait for read loop to finish\n if (readLoopPromise) {\n try {\n await readLoopPromise;\n } catch {\n // Ignore read loop errors during cleanup\n }\n }\n };\n}\n"
6
6
  ],
7
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoEO,IAAM,kBAAkB,KAAK;AAG7B,IAAM,mBAAmB;AAMzB,SAAS,yBAAyB,GAAwB;AAAA,EAC/D,MAAM,UAAU,IAAI;AAAA,EACpB,IAAI,eAAe;AAAA,EAEnB,OAAO;AAAA,IACL,MAAM,GAAW;AAAA,MACf,MAAM,WAAW;AAAA,MACjB,QAAQ,IAAI,UAAU;AAAA,QACpB,OAAO,CAAC;AAAA,QACR,WAAW;AAAA,QACX,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,YAAY;AAAA,QACZ,aAAa;AAAA,QACb,aAAa;AAAA,QACb,YAAY;AAAA,MACd,CAAC;AAAA,MACD,OAAO;AAAA;AAAA,IAGT,GAAG,CAAC,UAA2C;AAAA,MAC7C,OAAO,QAAQ,IAAI,QAAQ;AAAA;AAAA,IAG7B,IAAI,CAAC,UAAkB,OAA4B;AAAA,MACjD,MAAM,QAAQ,QAAQ,IAAI,QAAQ;AAAA,MAClC,IAAI,CAAC;AAAA,QAAO,OAAO;AAAA,MACnB,IAAI,MAAM,UAAU,MAAM;AAAA,QAAS,OAAO;AAAA,MAG1C,IAAI,MAAM,eAAe,MAAM,aAAa;AAAA,QAC1C,MAAM,cAAc;AAAA,QACpB,MAAM,UAAU,MAAM;AAAA,QACtB,MAAM,cAAc;AAAA,QACpB,MAAM,aAAa;AAAA,QACnB,QAAQ,KAAK;AAAA,QACb,OAAO;AAAA,MACT;AAAA,MAGA,MAAM,MAAM,KAAK,KAAK;AAAA,MACtB,MAAM,aAAa,MAAM;AAAA,MACzB,OAAO;AAAA;AAAA,SAGH,KAAI,CACR,UAC8D;AAAA,MAC9D,MAAM,QAAQ,QAAQ,IAAI,QAAQ;AAAA,MAClC,IAAI,CAAC,OAAO;AAAA,QACV,OAAO,EAAE,MAAM,KAAK;AAAA,MACtB;AAAA,MAGA,IAAI,MAAM,MAAM,SAAS,GAAG;AAAA,QAC1B,MAAM,QAAQ,MAAM,MAAM,MAAM;AAAA,QAChC,MAAM,aAAa,MAAM;AAAA,QACzB,OAAO,EAAE,OAAO,OAAO,MAAM,MAAM;AAAA,MACrC;AAAA,MAGA,IAAI,MAAM,SAAS;AAAA,QACjB,MAAM,MAAM;AAAA,MACd;AAAA,MAGA,IAAI,MAAM,QAAQ;AAAA,QAChB,OAAO,EAAE,MAAM,KAAK;AAAA,MACtB;AAAA,MAGA,OAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AAAA,QACtC,MAAM,cAAc;AAAA,QACpB,MAAM,cAAc,CAAC,UAAU;AAAA,UAC7B,IAAI,UAAU,MAAM;AAAA,YAClB,QAAQ,EAAE,MAAM,KAAK,CAAC;AAAA,UACxB,EAAO;AAAA,YACL,QAAQ,EAAE,OAAO,OAAO,MAAM,MAAM,CAAC;AAAA;AAAA;AAAA,QAGzC,MAAM,aAAa;AAAA,OACpB;AAAA;AAAA,IAGH,KAAK,CAAC,UAAwB;AAAA,MAC5B,MAAM,QAAQ,QAAQ,IAAI,QAAQ;AAAA,MAClC,IAAI,CAAC;AAAA,QAAO;AAAA,MAEZ,MAAM,SAAS;AAAA,MAGf,IAAI,MAAM,eAAe,MAAM,aAAa;AAAA,QAC1C,MAAM,cAAc;AAAA,QACpB,MAAM,UAAU,MAAM;AAAA,QACtB,MAAM,cAAc;AAAA,QACpB,MAAM,aAAa;AAAA,QACnB,QAAQ,IAAI;AAAA,MACd;AAAA;AAAA,IAGF,KAAK,CAAC,UAAkB,YAA2B;AAAA,MACjD,MAAM,QAAQ,QAAQ,IAAI,QAAQ;AAAA,MAClC,IAAI,CAAC;AAAA,QAAO;AAAA,MAEZ,MAAM,UAAU;AAAA,MAChB,MAAM,aAAa;AAAA,MAGnB,IAAI,MAAM,eAAe,MAAM,YAAY;AAAA,QACzC,MAAM,cAAc;AAAA,QACpB,MAAM,SAAS,MAAM;AAAA,QACrB,MAAM,cAAc;AAAA,QACpB,MAAM,aAAa;AAAA,QACnB,OAAO,UAAU;AAAA,MACnB;AAAA;AAAA,IAGF,WAAW,CAAC,UAA2B;AAAA,MACrC,MAAM,QAAQ,QAAQ,IAAI,QAAQ;AAAA,MAClC,IAAI,CAAC;AAAA,QAAO,OAAO;AAAA,MACnB,OACE,MAAM,aAAa,mBACnB,MAAM,MAAM,UAAU;AAAA;AAAA,IAI1B,MAAM,CAAC,UAAwB;AAAA,MAC7B,MAAM,QAAQ,QAAQ,IAAI,QAAQ;AAAA,MAClC,IAAI,SAAS,MAAM,eAAe,MAAM,YAAY;AAAA,QAClD,MAAM,WAAW,IAAI,MAAM,gBAAgB,CAAC;AAAA,MAC9C;AAAA,MACA,QAAQ,OAAO,QAAQ;AAAA;AAAA,IAGzB,KAAK,GAAS;AAAA,MACZ,YAAY,aAAa,SAAS;AAAA,QAChC,KAAK,OAAO,QAAQ;AAAA,MACtB;AAAA;AAAA,EAEJ;AAAA;AAOF,IAAM,oBAAoB,IAAI;AAEvB,SAAS,2BAA2B,CACzC,SACqB;AAAA,EACrB,IAAI,WAAW,kBAAkB,IAAI,OAAO;AAAA,EAC5C,IAAI,CAAC,UAAU;AAAA,IACb,WAAW,0BAA0B;AAAA,IACrC,kBAAkB,IAAI,SAAS,QAAQ;AAAA,EACzC;AAAA,EACA,OAAO;AAAA;AAGF,SAAS,6BAA6B,CAAC,SAA4B;AAAA,EACxE,MAAM,WAAW,kBAAkB,IAAI,OAAO;AAAA,EAC9C,IAAI,UAAU;AAAA,IACZ,SAAS,MAAM;AAAA,IACf,kBAAkB,OAAO,OAAO;AAAA,EAClC;AAAA;AAgBK,SAAS,uBAAuB,CACrC,cACA,UACA,UACqB;AAAA,EACrB,IAAI,YAAY;AAAA,EAChB,IAAI,SAAyD;AAAA,EAC7D,IAAI,kBAAwC;AAAA,EAE5C,MAAM,aAAa,KAAK;AAAA,EAExB,eAAe,QAAQ,GAAG;AAAA,IACxB,IAAI;AAAA,MACF,SAAS,aAAa,UAAU;AAAA,MAEhC,OAAO,CAAC,WAAW;AAAA,QAEjB,OAAO,SAAS,YAAY,QAAQ,KAAK,CAAC,WAAW;AAAA,UACnD,MAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,CAAC,CAAC;AAAA,QACvD;AAAA,QACA,IAAI;AAAA,UAAW;AAAA,QAEf,QAAQ,MAAM,UAAU,MAAM,OAAO,KAAK;AAAA,QAE1C,IAAI,MAAM;AAAA,UACR,SAAS,MAAM,QAAQ;AAAA,UACvB;AAAA,QACF;AAAA,QAEA,IAAI,OAAO;AAAA,UAET,IAAI,MAAM,SAAS,YAAY;AAAA,YAC7B,SAAS,SAAS,EAAG,SAAS,MAAM,QAAQ,UAAU,YAAY;AAAA,cAChE,MAAM,QAAQ,MAAM,MAClB,QACA,KAAK,IAAI,SAAS,YAAY,MAAM,MAAM,CAC5C;AAAA,cACA,SAAS,KAAK,UAAU,KAAK;AAAA,YAC/B;AAAA,UACF,EAAO;AAAA,YACL,SAAS,KAAK,UAAU,KAAK;AAAA;AAAA,QAEjC;AAAA,MACF;AAAA,MACA,OAAO,OAAO;AAAA,MACd,SAAS,MAAM,UAAU,KAAK;AAAA,cAC9B;AAAA,MACA,IAAI,QAAQ;AAAA,QACV,IAAI;AAAA,UACF,OAAO,YAAY;AAAA,UACnB,MAAM;AAAA,MAGV;AAAA;AAAA;AAAA,EAKJ,kBAAkB,SAAS;AAAA,EAG3B,OAAO,YAAY;AAAA,IACjB,YAAY;AAAA,IACZ,IAAI,QAAQ;AAAA,MACV,IAAI;AAAA,QACF,MAAM,OAAO,OAAO;AAAA,QACpB,MAAM;AAAA,IAGV;AAAA,IAEA,IAAI,iBAAiB;AAAA,MACnB,IAAI;AAAA,QACF,MAAM;AAAA,QACN,MAAM;AAAA,IAGV;AAAA;AAAA;",
8
- "debugId": "3BBE010F050C1BB964756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "56A6539DC27E874264756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -366,6 +366,9 @@ function setupStreamCallbacks(context, streamRegistry) {
366
366
  global.setSync("__Stream_isQueueFull", new ivm.Callback((streamId) => {
367
367
  return streamRegistry.isQueueFull(streamId);
368
368
  }));
369
+ global.setSync("__Stream_cancel", new ivm.Callback((streamId) => {
370
+ streamRegistry.cancel(streamId);
371
+ }));
369
372
  const pullRef = new ivm.Reference(async (streamId) => {
370
373
  const result = await streamRegistry.pull(streamId);
371
374
  if (result.done) {
@@ -416,7 +419,7 @@ var hostBackedStreamCode = `
416
419
  async pull(controller) {
417
420
  if (closed) return;
418
421
 
419
- const resultJson = __Stream_pull_ref.applySyncPromise(undefined, [streamId]);
422
+ const resultJson = await __Stream_pull_ref.apply(undefined, [streamId], { result: { promise: true, copy: true } });
420
423
  const result = JSON.parse(resultJson);
421
424
 
422
425
  if (result.done) {
@@ -428,7 +431,7 @@ var hostBackedStreamCode = `
428
431
  },
429
432
  cancel(reason) {
430
433
  closed = true;
431
- __Stream_error(streamId, String(reason || "cancelled"));
434
+ __Stream_cancel(streamId);
432
435
  }
433
436
  });
434
437
 
@@ -455,7 +458,7 @@ var hostBackedStreamCode = `
455
458
  globalThis.HostBackedReadableStream = HostBackedReadableStream;
456
459
  })();
457
460
  `;
458
- function setupResponse(context, stateMap) {
461
+ function setupResponse(context, stateMap, streamRegistry) {
459
462
  const global = context.global;
460
463
  global.setSync("__Response_construct", new ivm.Callback((bodyBytes, status, statusText, headers) => {
461
464
  const instanceId = nextInstanceId++;
@@ -535,6 +538,10 @@ function setupResponse(context, stateMap) {
535
538
  const state = stateMap.get(instanceId);
536
539
  return state?.type ?? "default";
537
540
  }));
541
+ global.setSync("__Response_get_nullBody", new ivm.Callback((instanceId) => {
542
+ const state = stateMap.get(instanceId);
543
+ return state?.nullBody ?? false;
544
+ }));
538
545
  global.setSync("__Response_setType", new ivm.Callback((instanceId, type) => {
539
546
  const state = stateMap.get(instanceId);
540
547
  if (state) {
@@ -568,6 +575,38 @@ function setupResponse(context, stateMap) {
568
575
  if (!state) {
569
576
  throw new Error("[TypeError]Cannot clone invalid Response");
570
577
  }
578
+ if (state.streamId !== null) {
579
+ const streamId1 = streamRegistry.create();
580
+ const streamId2 = streamRegistry.create();
581
+ const origStreamId = state.streamId;
582
+ (async () => {
583
+ try {
584
+ while (true) {
585
+ const result = await streamRegistry.pull(origStreamId);
586
+ if (result.done) {
587
+ streamRegistry.close(streamId1);
588
+ streamRegistry.close(streamId2);
589
+ break;
590
+ }
591
+ streamRegistry.push(streamId1, new Uint8Array(result.value));
592
+ streamRegistry.push(streamId2, new Uint8Array(result.value));
593
+ }
594
+ } catch (err) {
595
+ streamRegistry.error(streamId1, err);
596
+ streamRegistry.error(streamId2, err);
597
+ }
598
+ })();
599
+ state.streamId = streamId1;
600
+ const newId2 = nextInstanceId++;
601
+ const newState2 = {
602
+ ...state,
603
+ streamId: streamId2,
604
+ body: state.body ? new Uint8Array(state.body) : null,
605
+ bodyUsed: false
606
+ };
607
+ stateMap.set(newId2, newState2);
608
+ return newId2;
609
+ }
571
610
  const newId = nextInstanceId++;
572
611
  const newState = {
573
612
  ...state,
@@ -794,6 +833,11 @@ function setupResponse(context, stateMap) {
794
833
  }
795
834
 
796
835
  get body() {
836
+ // Null-body responses (204, 304, HEAD) must return null
837
+ if (__Response_get_nullBody(this.#instanceId)) {
838
+ return null;
839
+ }
840
+
797
841
  // Return cached body if available (WHATWG spec requires same object on repeated access)
798
842
  if (this.#cachedBody !== null) {
799
843
  return this.#cachedBody;
@@ -825,6 +869,26 @@ function setupResponse(context, stateMap) {
825
869
  } catch (err) {
826
870
  throw __decodeError(err);
827
871
  }
872
+ if (__Response_get_nullBody(this.#instanceId)) {
873
+ return "";
874
+ }
875
+ if (this.#streamId !== null) {
876
+ const reader = this.body.getReader();
877
+ const chunks = [];
878
+ while (true) {
879
+ const { done, value } = await reader.read();
880
+ if (done) break;
881
+ if (value) chunks.push(value);
882
+ }
883
+ const totalLength = chunks.reduce((acc, c) => acc + c.length, 0);
884
+ const result = new Uint8Array(totalLength);
885
+ let offset = 0;
886
+ for (const chunk of chunks) {
887
+ result.set(chunk, offset);
888
+ offset += chunk.length;
889
+ }
890
+ return new TextDecoder().decode(result);
891
+ }
828
892
  return __Response_text(this.#instanceId);
829
893
  }
830
894
 
@@ -1075,30 +1139,10 @@ function setupRequest(context, stateMap) {
1075
1139
  return Array.from(new TextEncoder().encode(body.toString()));
1076
1140
  }
1077
1141
  if (body instanceof FormData) {
1078
- // Check if FormData has any File/Blob entries
1079
- let hasFiles = false;
1080
- for (const [, value] of body.entries()) {
1081
- if (value instanceof File || value instanceof Blob) {
1082
- hasFiles = true;
1083
- break;
1084
- }
1085
- }
1086
-
1087
- if (hasFiles) {
1088
- // Serialize as multipart/form-data
1089
- const { body: bytes, contentType } = __serializeFormData(body);
1090
- globalThis.__pendingFormDataContentType = contentType;
1091
- return Array.from(bytes);
1092
- }
1093
-
1094
- // URL-encoded for string-only FormData
1095
- const parts = [];
1096
- body.forEach((value, key) => {
1097
- if (typeof value === 'string') {
1098
- parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
1099
- }
1100
- });
1101
- return Array.from(new TextEncoder().encode(parts.join('&')));
1142
+ // Always serialize as multipart/form-data per spec
1143
+ const { body: bytes, contentType } = __serializeFormData(body);
1144
+ globalThis.__pendingFormDataContentType = contentType;
1145
+ return Array.from(bytes);
1102
1146
  }
1103
1147
  // Try to convert to string
1104
1148
  return Array.from(new TextEncoder().encode(String(body)));
@@ -1196,7 +1240,7 @@ function setupRequest(context, stateMap) {
1196
1240
  if (globalThis.__pendingFormDataContentType) {
1197
1241
  headers.set('content-type', globalThis.__pendingFormDataContentType);
1198
1242
  delete globalThis.__pendingFormDataContentType;
1199
- } else if (body instanceof FormData && !headers.has('content-type')) {
1243
+ } else if (body instanceof URLSearchParams && !headers.has('content-type')) {
1200
1244
  headers.set('content-type', 'application/x-www-form-urlencoded');
1201
1245
  }
1202
1246
 
@@ -1386,32 +1430,56 @@ function setupRequest(context, stateMap) {
1386
1430
  var FETCH_STREAM_THRESHOLD = 64 * 1024;
1387
1431
  function setupFetchFunction(context, stateMap, streamRegistry, options) {
1388
1432
  const global = context.global;
1389
- const fetchRef = new ivm.Reference(async (url, method, headersJson, bodyJson, signalAborted) => {
1433
+ const fetchAbortControllers = new Map;
1434
+ global.setSync("__fetch_abort", new ivm.Callback((fetchId) => {
1435
+ const controller = fetchAbortControllers.get(fetchId);
1436
+ if (controller) {
1437
+ setImmediate(() => controller.abort());
1438
+ }
1439
+ }));
1440
+ const fetchRef = new ivm.Reference(async (url, method, headersJson, bodyJson, signalAborted, fetchId) => {
1390
1441
  if (signalAborted) {
1391
1442
  throw new Error("[AbortError]The operation was aborted.");
1392
1443
  }
1444
+ const hostController = new AbortController;
1445
+ fetchAbortControllers.set(fetchId, hostController);
1393
1446
  const headers = JSON.parse(headersJson);
1394
1447
  const bodyBytes = bodyJson ? JSON.parse(bodyJson) : null;
1395
1448
  const body = bodyBytes ? new Uint8Array(bodyBytes) : null;
1396
1449
  const nativeRequest = new Request(url, {
1397
1450
  method,
1398
1451
  headers,
1399
- body
1452
+ body,
1453
+ signal: hostController.signal
1400
1454
  });
1401
1455
  const onFetch = options?.onFetch ?? fetch;
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 = {
1456
+ try {
1457
+ let cleanupAbort;
1458
+ const abortPromise = new Promise((_, reject) => {
1459
+ if (hostController.signal.aborted) {
1460
+ reject(Object.assign(new Error("The operation was aborted."), { name: "AbortError" }));
1461
+ return;
1462
+ }
1463
+ const onAbort = () => {
1464
+ reject(Object.assign(new Error("The operation was aborted."), { name: "AbortError" }));
1465
+ };
1466
+ hostController.signal.addEventListener("abort", onAbort, { once: true });
1467
+ cleanupAbort = () => hostController.signal.removeEventListener("abort", onAbort);
1468
+ });
1469
+ abortPromise.catch(() => {});
1470
+ const nativeResponse = await Promise.race([onFetch(nativeRequest), abortPromise]);
1471
+ cleanupAbort?.();
1472
+ const status = nativeResponse.status;
1473
+ const isNullBody = status === 204 || status === 304 || method.toUpperCase() === "HEAD";
1474
+ const isCallbackStream = nativeResponse.__isCallbackStream;
1475
+ const isNetworkResponse = nativeResponse.url && (nativeResponse.url.startsWith("http://") || nativeResponse.url.startsWith("https://"));
1476
+ const shouldStream = !isNullBody && nativeResponse.body && (isCallbackStream || isNetworkResponse);
1477
+ if (shouldStream && nativeResponse.body) {
1478
+ const streamId = streamRegistry.create();
1479
+ const streamCleanupFn = startNativeStreamReader(nativeResponse.body, streamId, streamRegistry);
1480
+ streamRegistry.setCleanup(streamId, streamCleanupFn);
1481
+ const instanceId2 = nextInstanceId++;
1482
+ const state2 = {
1415
1483
  status: nativeResponse.status,
1416
1484
  statusText: nativeResponse.statusText,
1417
1485
  headers: Array.from(nativeResponse.headers.entries()),
@@ -1420,65 +1488,37 @@ function setupFetchFunction(context, stateMap, streamRegistry, options) {
1420
1488
  type: "default",
1421
1489
  url: nativeResponse.url,
1422
1490
  redirected: nativeResponse.redirected,
1423
- streamId: streamId2
1491
+ streamId,
1492
+ nullBody: isNullBody
1424
1493
  };
1425
- stateMap.set(instanceId3, state3);
1426
- return instanceId3;
1494
+ stateMap.set(instanceId2, state2);
1495
+ return instanceId2;
1427
1496
  }
1428
- const streamId = streamRegistry.create();
1429
- const instanceId2 = nextInstanceId++;
1430
- const state2 = {
1497
+ const responseBody = await nativeResponse.arrayBuffer();
1498
+ const responseBodyArray = Array.from(new Uint8Array(responseBody));
1499
+ const instanceId = nextInstanceId++;
1500
+ const state = {
1431
1501
  status: nativeResponse.status,
1432
1502
  statusText: nativeResponse.statusText,
1433
1503
  headers: Array.from(nativeResponse.headers.entries()),
1434
- body: new Uint8Array(0),
1504
+ body: new Uint8Array(responseBodyArray),
1435
1505
  bodyUsed: false,
1436
1506
  type: "default",
1437
1507
  url: nativeResponse.url,
1438
1508
  redirected: nativeResponse.redirected,
1439
- streamId
1509
+ streamId: null,
1510
+ nullBody: isNullBody
1440
1511
  };
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;
1512
+ stateMap.set(instanceId, state);
1513
+ return instanceId;
1514
+ } catch (err) {
1515
+ if (err instanceof Error && err.name === "AbortError") {
1516
+ throw new Error("[AbortError]The operation was aborted.");
1517
+ }
1518
+ throw err;
1519
+ } finally {
1520
+ fetchAbortControllers.delete(fetchId);
1465
1521
  }
1466
- const responseBody = await nativeResponse.arrayBuffer();
1467
- const responseBodyArray = Array.from(new Uint8Array(responseBody));
1468
- const instanceId = nextInstanceId++;
1469
- const state = {
1470
- status: nativeResponse.status,
1471
- statusText: nativeResponse.statusText,
1472
- headers: Array.from(nativeResponse.headers.entries()),
1473
- body: new Uint8Array(responseBodyArray),
1474
- bodyUsed: false,
1475
- type: "default",
1476
- url: nativeResponse.url,
1477
- redirected: nativeResponse.redirected,
1478
- streamId: null
1479
- };
1480
- stateMap.set(instanceId, state);
1481
- return instanceId;
1482
1522
  });
1483
1523
  global.setSync("__fetch_ref", fetchRef);
1484
1524
  const fetchCode = `
@@ -1496,7 +1536,28 @@ function setupFetchFunction(context, stateMap, streamRegistry, options) {
1496
1536
  return err;
1497
1537
  }
1498
1538
 
1499
- globalThis.fetch = function(input, init = {}) {
1539
+ let __nextFetchId = 1;
1540
+
1541
+ globalThis.fetch = async function(input, init = {}) {
1542
+ // Handle Blob and ReadableStream bodies before creating Request
1543
+ if (init.body instanceof Blob && !(init.body instanceof File)) {
1544
+ const buf = await init.body.arrayBuffer();
1545
+ init = Object.assign({}, init, { body: new Uint8Array(buf) });
1546
+ } else if (init.body instanceof ReadableStream) {
1547
+ const reader = init.body.getReader();
1548
+ const chunks = [];
1549
+ while (true) {
1550
+ const { done, value } = await reader.read();
1551
+ if (done) break;
1552
+ chunks.push(value);
1553
+ }
1554
+ const total = chunks.reduce((s, c) => s + c.length, 0);
1555
+ const buf = new Uint8Array(total);
1556
+ let off = 0;
1557
+ for (const c of chunks) { buf.set(c, off); off += c.length; }
1558
+ init = Object.assign({}, init, { body: buf });
1559
+ }
1560
+
1500
1561
  // Create Request from input
1501
1562
  const request = input instanceof Request ? input : new Request(input, init);
1502
1563
 
@@ -1504,20 +1565,34 @@ function setupFetchFunction(context, stateMap, streamRegistry, options) {
1504
1565
  const signal = init.signal ?? request.signal;
1505
1566
  const signalAborted = signal?.aborted ?? false;
1506
1567
 
1568
+ // Assign a fetch ID for abort tracking
1569
+ const fetchId = __nextFetchId++;
1570
+
1571
+ // Register abort listener if signal exists
1572
+ if (signal && !signalAborted) {
1573
+ signal.addEventListener('abort', () => { __fetch_abort(fetchId); });
1574
+ }
1575
+
1507
1576
  // Serialize headers and body to JSON for transfer
1508
1577
  const headersJson = JSON.stringify(Array.from(request.headers.entries()));
1509
1578
  const bodyBytes = request._getBodyBytes();
1510
1579
  const bodyJson = bodyBytes ? JSON.stringify(bodyBytes) : null;
1511
1580
 
1581
+ // Short-circuit: if signal is already aborted, throw without calling host
1582
+ if (signalAborted) {
1583
+ throw new DOMException('The operation was aborted.', 'AbortError');
1584
+ }
1585
+
1512
1586
  // Call host - returns just the response instance ID
1513
1587
  try {
1514
- const instanceId = __fetch_ref.applySyncPromise(undefined, [
1588
+ const instanceId = await __fetch_ref.apply(undefined, [
1515
1589
  request.url,
1516
1590
  request.method,
1517
1591
  headersJson,
1518
1592
  bodyJson,
1519
- signalAborted
1520
- ]);
1593
+ signalAborted,
1594
+ fetchId
1595
+ ], { result: { promise: true, copy: true } });
1521
1596
 
1522
1597
  // Construct Response from the instance ID
1523
1598
  return Response._fromInstanceId(instanceId);
@@ -1634,7 +1709,7 @@ async function setupFetch(context, options) {
1634
1709
  context.evalSync(multipartCode);
1635
1710
  setupStreamCallbacks(context, streamRegistry);
1636
1711
  context.evalSync(hostBackedStreamCode);
1637
- setupResponse(context, stateMap);
1712
+ setupResponse(context, stateMap, streamRegistry);
1638
1713
  setupRequest(context, stateMap);
1639
1714
  setupFetchFunction(context, stateMap, streamRegistry, options);
1640
1715
  const serveState = {
@@ -1757,8 +1832,7 @@ async function setupFetch(context, options) {
1757
1832
  },
1758
1833
  cancel() {
1759
1834
  streamDone = true;
1760
- streamRegistry.error(responseStreamId, new Error("Stream cancelled"));
1761
- streamRegistry.delete(responseStreamId);
1835
+ streamRegistry.cancel(responseStreamId);
1762
1836
  }
1763
1837
  });
1764
1838
  const responseHeaders2 = new Headers(responseState.headers);
@@ -1911,4 +1985,4 @@ export {
1911
1985
  clearAllInstanceState
1912
1986
  };
1913
1987
 
1914
- //# debugId=5C3C175E215A892A64756E2164756E21
1988
+ //# debugId=D6C7C0BA2C0C13D164756E2164756E21