@ricsam/quickjs-fetch 0.2.8 → 0.2.9

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/handle.ts"],
4
4
  "sourcesContent": [
5
- "import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport { marshal, getInstanceState, setInstanceState, getInstanceStateById } from \"@ricsam/quickjs-core\";\nimport type {\n StateMap,\n FetchHandle,\n ServeState,\n UpgradeRequest,\n WebSocketCommand,\n ServerWebSocketState,\n RequestState,\n ResponseState,\n} from \"./types.mjs\";\nimport { createRequestStateFromNative } from \"./globals/request.mjs\";\nimport { responseStateToNative } from \"./globals/response.mjs\";\nimport { headersStateToNative } from \"./globals/headers.mjs\";\n\n/**\n * Internal state of a QuickJS ReadableStream\n * Used to directly access stream queue for bridging to native streams\n */\ninterface ReadableStreamInternalState {\n queue: unknown[];\n closeRequested: boolean;\n closed: boolean;\n errored: boolean;\n errorValue: unknown;\n}\n\n/**\n * Create a native ReadableStream that reads from a QuickJS stream's internal state\n * This avoids needing a QuickJS handle by directly accessing the stream's queue\n */\nfunction createNativeStreamFromState(\n context: QuickJSContext,\n streamInstanceId: number\n): ReadableStream<Uint8Array> {\n let done = false;\n\n return new ReadableStream<Uint8Array>({\n async pull(controller) {\n while (!done) {\n // Pump QuickJS event loop to process async writes\n context.runtime.executePendingJobs();\n\n const state = getInstanceStateById<ReadableStreamInternalState>(streamInstanceId);\n\n if (!state) {\n controller.close();\n done = true;\n return;\n }\n\n // Check for error\n if (state.errored) {\n controller.error(state.errorValue);\n done = true;\n return;\n }\n\n // Check for data in queue\n if (state.queue && state.queue.length > 0) {\n const chunk = state.queue.shift();\n\n // Convert chunk to Uint8Array\n let bytes: Uint8Array;\n if (chunk instanceof Uint8Array) {\n bytes = chunk;\n } else if (typeof chunk === \"string\") {\n bytes = new TextEncoder().encode(chunk);\n } else if (chunk && typeof chunk === \"object\" && \"0\" in chunk) {\n // Handle unmarshalled Uint8Array (object with numeric keys)\n const obj = chunk as Record<string, number>;\n const keys = Object.keys(obj).filter(k => !isNaN(parseInt(k))).sort((a, b) => parseInt(a) - parseInt(b));\n const values = keys.map(k => obj[k]) as number[];\n bytes = new Uint8Array(values);\n } else {\n // Skip unknown chunk types\n continue;\n }\n\n controller.enqueue(bytes);\n return;\n }\n\n // Check if closed\n if (state.closeRequested || state.closed) {\n controller.close();\n done = true;\n return;\n }\n\n // Yield to event loop, then poll again\n await new Promise(r => setTimeout(r, 0));\n }\n },\n\n cancel() {\n done = true;\n }\n });\n}\n\n/**\n * Create a proper Request instance in QuickJS with the given state\n */\nfunction createRequestInstance(\n context: QuickJSContext,\n stateMap: StateMap,\n requestState: RequestState\n): QuickJSHandle {\n // Create the Request instance using evalCode (classes must be called with 'new')\n // Pass the URL as the constructor argument - minimal construction\n const urlHandle = context.newString(requestState.url);\n context.setProp(context.global, \"__requestUrl__\", urlHandle);\n urlHandle.dispose();\n\n const requestResult = context.evalCode(`new Request(__requestUrl__)`);\n\n // Clean up temporary global\n context.setProp(context.global, \"__requestUrl__\", context.undefined);\n\n if (requestResult.error) {\n const error = context.dump(requestResult.error);\n requestResult.error.dispose();\n throw new Error(`Failed to create Request: ${JSON.stringify(error)}`);\n }\n\n const requestHandle = requestResult.value;\n\n // Overwrite the internal state with our full RequestState\n setInstanceState(context, requestHandle, requestState);\n\n return requestHandle;\n}\n\n/**\n * Create the FetchHandle implementation\n */\nexport function createFetchHandle(\n context: QuickJSContext,\n stateMap: StateMap,\n serveState: ServeState\n): FetchHandle {\n const wsCommandCallbacks = new Set<(cmd: WebSocketCommand) => void>();\n\n return {\n stateMap,\n\n async dispatchRequest(request: Request): Promise<Response> {\n if (!serveState.fetchHandler) {\n throw new Error(\"No serve() handler registered\");\n }\n\n // Clear any previous upgrade request\n serveState.pendingUpgrade = null;\n\n // Convert native Request to RequestState\n const requestState = await createRequestStateFromNative(request);\n\n // Create proper QuickJS Request instance with internal state\n const requestHandle = createRequestInstance(context, stateMap, requestState);\n\n // Create Server instance using evalCode (classes must be called with 'new')\n const serverResult = context.evalCode(`new __Server__()`);\n if (serverResult.error) {\n requestHandle.dispose();\n const error = context.dump(serverResult.error);\n serverResult.error.dispose();\n throw new Error(`Failed to create Server: ${error}`);\n }\n const serverHandle = serverResult.value;\n\n try {\n // Call the fetch handler\n const result = context.callFunction(\n serveState.fetchHandler,\n context.undefined,\n requestHandle,\n serverHandle\n );\n\n if (result.error) {\n const error = context.dump(result.error);\n result.error.dispose();\n throw new Error(`Fetch handler error: ${JSON.stringify(error)}`);\n }\n\n let responseHandle = result.value;\n\n // Check if result is a Promise\n const typeofResult = context.typeof(responseHandle);\n if (typeofResult === \"object\") {\n // Check for .then method (Promise-like)\n const thenHandle = context.getProp(responseHandle, \"then\");\n const hasThen = context.typeof(thenHandle) === \"function\";\n thenHandle.dispose();\n\n if (hasThen) {\n // Execute pending jobs BEFORE resolvePromise to allow\n // JavaScript-native async functions to schedule their resolution.\n // This is critical for async functions that don't use host-side awaits.\n context.runtime.executePendingJobs();\n\n // For pure JS async functions (without host-side awaits), we need to\n // wrap resolvePromise in a race with repeated executePendingJobs calls.\n // The sync variant of QuickJS needs this to process microtasks.\n const resolved = await Promise.race([\n context.resolvePromise(responseHandle),\n (async () => {\n // Keep executing pending jobs until the promise resolves\n // This handles pure JS async functions that don't trigger host callbacks\n for (let i = 0; i < 1000; i++) {\n await new Promise((r) => setTimeout(r, 0));\n context.runtime.executePendingJobs();\n }\n // If we get here, something is very wrong\n throw new Error(\"Promise resolution timeout\");\n })(),\n ]);\n responseHandle.dispose();\n context.runtime.executePendingJobs();\n\n if (resolved.error) {\n const error = context.dump(resolved.error);\n resolved.error.dispose();\n throw new Error(`Fetch handler promise rejected: ${JSON.stringify(error)}`);\n }\n responseHandle = resolved.value;\n }\n }\n\n // Get the ResponseState directly from the instance\n const responseState = getInstanceState<ResponseState>(context, responseHandle);\n responseHandle.dispose();\n\n if (!responseState) {\n throw new Error(\"Failed to get Response state\");\n }\n\n // Check if streaming response\n if (responseState.bodyType === \"stream\" && responseState.streamInstanceId !== undefined) {\n const nativeStream = createNativeStreamFromState(\n context,\n responseState.streamInstanceId\n );\n\n return new Response(nativeStream, {\n status: responseState.status,\n statusText: responseState.statusText,\n headers: headersStateToNative(responseState.headersState),\n });\n }\n\n // Convert to native Response (non-streaming)\n return responseStateToNative(responseState);\n } finally {\n requestHandle.dispose();\n serverHandle.dispose();\n }\n },\n\n getUpgradeRequest(): UpgradeRequest | null {\n return serveState.pendingUpgrade;\n },\n\n /**\n * Dispatch WebSocket open event to the QuickJS handler.\n *\n * **IMPORTANT:** An `open` handler MUST be defined in the serve() websocket\n * options for the connection to be tracked. Without an open handler, the\n * connection is not stored and subsequent message/close/error events will\n * be silently ignored.\n *\n * @param connectionId - Unique identifier for this WebSocket connection (host-assigned)\n * @param data - Optional user data from server.upgrade() to associate with the connection\n */\n dispatchWebSocketOpen(connectionId: string, data?: unknown): void {\n if (!serveState.websocketHandlers.open) {\n return;\n }\n\n // Create ServerWebSocket instance using evalCode (classes must be called with 'new')\n // Pass arguments through temporary globals\n const connectionIdHandle = context.newString(connectionId);\n const dataHandle = marshal(context, data);\n context.setProp(context.global, \"__wsConnectionId__\", connectionIdHandle);\n context.setProp(context.global, \"__wsData__\", dataHandle);\n connectionIdHandle.dispose();\n dataHandle.dispose();\n\n const wsResult = context.evalCode(`new __ServerWebSocket__(__wsConnectionId__, __wsData__)`);\n\n // Clean up temporary globals\n context.setProp(context.global, \"__wsConnectionId__\", context.undefined);\n context.setProp(context.global, \"__wsData__\", context.undefined);\n\n if (wsResult.error) {\n wsResult.error.dispose();\n return;\n }\n\n const wsHandle = wsResult.value;\n\n // Store the connection\n serveState.activeConnections.set(connectionId, {\n data,\n readyState: 1,\n connectionId,\n wsHandle,\n });\n\n // Call the open handler\n const result = context.callFunction(\n serveState.websocketHandlers.open,\n context.undefined,\n wsHandle\n );\n\n if (result.error) {\n result.error.dispose();\n } else {\n result.value.dispose();\n }\n\n context.runtime.executePendingJobs();\n },\n\n /**\n * Dispatch WebSocket message event to the QuickJS handler.\n *\n * Requires the connection to have been tracked via dispatchWebSocketOpen().\n * If no message handler is defined, or the connection is not tracked, this is a no-op.\n *\n * @param connectionId - The connection ID from dispatchWebSocketOpen()\n * @param message - The message content (string or binary data)\n */\n dispatchWebSocketMessage(\n connectionId: string,\n message: string | ArrayBuffer\n ): void {\n if (!serveState.websocketHandlers.message) {\n return;\n }\n\n const connection = serveState.activeConnections.get(connectionId);\n if (!connection || !connection.wsHandle) {\n return;\n }\n\n const messageHandle =\n typeof message === \"string\"\n ? context.newString(message)\n : context.newArrayBuffer(message);\n\n const result = context.callFunction(\n serveState.websocketHandlers.message,\n context.undefined,\n connection.wsHandle,\n messageHandle\n );\n\n messageHandle.dispose();\n\n if (result.error) {\n result.error.dispose();\n } else {\n result.value.dispose();\n }\n\n context.runtime.executePendingJobs();\n },\n\n /**\n * Dispatch WebSocket close event to the QuickJS handler and clean up the connection.\n *\n * Updates the connection ready state to CLOSED (3), calls the close handler if defined,\n * disposes the WebSocket handle, and removes the connection from tracking.\n *\n * @param connectionId - The connection ID from dispatchWebSocketOpen()\n * @param code - The WebSocket close code (e.g., 1000 for normal closure)\n * @param reason - The close reason string\n */\n dispatchWebSocketClose(\n connectionId: string,\n code: number,\n reason: string\n ): void {\n const connection = serveState.activeConnections.get(connectionId);\n if (!connection || !connection.wsHandle) {\n serveState.activeConnections.delete(connectionId);\n return;\n }\n\n if (!serveState.websocketHandlers.close) {\n // No close handler, but still need to cleanup the connection\n connection.wsHandle.dispose();\n serveState.activeConnections.delete(connectionId);\n return;\n }\n\n // Update ready state\n connection.readyState = 3; // CLOSED\n\n const codeHandle = context.newNumber(code);\n const reasonHandle = context.newString(reason);\n\n const result = context.callFunction(\n serveState.websocketHandlers.close,\n context.undefined,\n connection.wsHandle,\n codeHandle,\n reasonHandle\n );\n\n codeHandle.dispose();\n reasonHandle.dispose();\n\n if (result.error) {\n result.error.dispose();\n } else {\n result.value.dispose();\n }\n\n // Cleanup\n connection.wsHandle.dispose();\n serveState.activeConnections.delete(connectionId);\n\n context.runtime.executePendingJobs();\n },\n\n /**\n * Dispatch WebSocket error event to the QuickJS handler.\n *\n * Requires the connection to have been tracked via dispatchWebSocketOpen().\n * If no error handler is defined, or the connection is not tracked, this is a no-op.\n *\n * @param connectionId - The connection ID from dispatchWebSocketOpen()\n * @param error - The error that occurred\n */\n dispatchWebSocketError(connectionId: string, error: Error): void {\n if (!serveState.websocketHandlers.error) {\n return;\n }\n\n const connection = serveState.activeConnections.get(connectionId);\n if (!connection || !connection.wsHandle) {\n return;\n }\n\n const errorHandle = marshal(context, {\n name: error.name,\n message: error.message,\n });\n\n const result = context.callFunction(\n serveState.websocketHandlers.error,\n context.undefined,\n connection.wsHandle,\n errorHandle\n );\n\n errorHandle.dispose();\n\n if (result.error) {\n result.error.dispose();\n } else {\n result.value.dispose();\n }\n\n context.runtime.executePendingJobs();\n },\n\n /**\n * Register a callback for outgoing WebSocket commands from QuickJS.\n *\n * Called when QuickJS code calls ws.send() or ws.close() on a ServerWebSocket.\n * The callback receives command objects that should be forwarded to the actual WebSocket.\n *\n * @param callback - Function to handle outgoing WebSocket commands\n * @returns Unsubscribe function to remove the callback\n */\n onWebSocketCommand(\n callback: (command: WebSocketCommand) => void\n ): () => void {\n wsCommandCallbacks.add(callback);\n return () => wsCommandCallbacks.delete(callback);\n },\n\n hasServeHandler(): boolean {\n return serveState.fetchHandler !== null;\n },\n\n dispose(): void {\n // Dispose WebSocket handler handles\n if (serveState.websocketHandlers.open) {\n serveState.websocketHandlers.open.dispose();\n serveState.websocketHandlers.open = undefined;\n }\n if (serveState.websocketHandlers.message) {\n serveState.websocketHandlers.message.dispose();\n serveState.websocketHandlers.message = undefined;\n }\n if (serveState.websocketHandlers.close) {\n serveState.websocketHandlers.close.dispose();\n serveState.websocketHandlers.close = undefined;\n }\n if (serveState.websocketHandlers.error) {\n serveState.websocketHandlers.error.dispose();\n serveState.websocketHandlers.error = undefined;\n }\n\n // Dispose fetch handler handle\n if (serveState.fetchHandler) {\n serveState.fetchHandler.dispose();\n serveState.fetchHandler = null;\n }\n\n // Dispose active WebSocket connection handles\n for (const connection of serveState.activeConnections.values()) {\n if (connection.wsHandle) {\n connection.wsHandle.dispose();\n }\n }\n serveState.activeConnections.clear();\n\n // Note: __Server__ and __ServerWebSocket__ are on global and will be cleaned up by context.dispose()\n },\n };\n}\n"
5
+ "import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport { marshal, getInstanceState, setInstanceState, getInstanceStateById } from \"@ricsam/quickjs-core\";\nimport type {\n StateMap,\n FetchHandle,\n ServeState,\n UpgradeRequest,\n WebSocketCommand,\n ServerWebSocketState,\n RequestState,\n ResponseState,\n} from \"./types.mjs\";\nimport { createRequestStateFromNative } from \"./globals/request.mjs\";\nimport { responseStateToNative } from \"./globals/response.mjs\";\nimport { headersStateToNative } from \"./globals/headers.mjs\";\n\n/**\n * Internal state of a QuickJS ReadableStream\n * Used to directly access stream queue for bridging to native streams\n */\ninterface ReadableStreamInternalState {\n queue: unknown[];\n closeRequested: boolean;\n closed: boolean;\n errored: boolean;\n errorValue: unknown;\n}\n\n/**\n * Create a native ReadableStream that reads from a QuickJS stream's internal state\n * This avoids needing a QuickJS handle by directly accessing the stream's queue\n */\nfunction createNativeStreamFromState(\n context: QuickJSContext,\n streamInstanceId: number\n): ReadableStream<Uint8Array> {\n let done = false;\n\n return new ReadableStream<Uint8Array>({\n async pull(controller) {\n while (!done) {\n // Pump QuickJS event loop to process async writes\n context.runtime.executePendingJobs();\n\n const state = getInstanceStateById<ReadableStreamInternalState>(streamInstanceId);\n\n if (!state) {\n controller.close();\n done = true;\n return;\n }\n\n // Check for error\n if (state.errored) {\n controller.error(state.errorValue);\n done = true;\n return;\n }\n\n // Check for data in queue\n if (state.queue && state.queue.length > 0) {\n const chunk = state.queue.shift();\n\n // Convert chunk to Uint8Array\n let bytes: Uint8Array;\n if (chunk instanceof Uint8Array) {\n bytes = chunk;\n } else if (typeof chunk === \"string\") {\n bytes = new TextEncoder().encode(chunk);\n } else if (chunk && typeof chunk === \"object\" && \"0\" in chunk) {\n // Handle unmarshalled Uint8Array (object with numeric keys)\n const obj = chunk as Record<string, number>;\n const keys = Object.keys(obj).filter(k => !isNaN(parseInt(k))).sort((a, b) => parseInt(a) - parseInt(b));\n const values = keys.map(k => obj[k]) as number[];\n bytes = new Uint8Array(values);\n } else {\n // Skip unknown chunk types\n continue;\n }\n\n controller.enqueue(bytes);\n return;\n }\n\n // Check if closed\n if (state.closeRequested || state.closed) {\n controller.close();\n done = true;\n return;\n }\n\n // Yield to event loop, then poll again\n await new Promise(r => setTimeout(r, 0));\n }\n },\n\n cancel() {\n done = true;\n }\n });\n}\n\n/**\n * Create a proper Request instance in QuickJS with the given state\n */\nfunction createRequestInstance(\n context: QuickJSContext,\n stateMap: StateMap,\n requestState: RequestState\n): QuickJSHandle {\n // Create the Request instance using evalCode (classes must be called with 'new')\n // Pass the URL as the constructor argument - minimal construction\n const urlHandle = context.newString(requestState.url);\n context.setProp(context.global, \"__requestUrl__\", urlHandle);\n urlHandle.dispose();\n\n const requestResult = context.evalCode(`new Request(__requestUrl__)`);\n\n // Clean up temporary global\n context.setProp(context.global, \"__requestUrl__\", context.undefined);\n\n if (requestResult.error) {\n const error = context.dump(requestResult.error);\n requestResult.error.dispose();\n throw new Error(`Failed to create Request: ${JSON.stringify(error)}`);\n }\n\n const requestHandle = requestResult.value;\n\n // Overwrite the internal state with our full RequestState\n setInstanceState(context, requestHandle, requestState);\n\n return requestHandle;\n}\n\n/**\n * Create the FetchHandle implementation\n */\nexport function createFetchHandle(\n context: QuickJSContext,\n stateMap: StateMap,\n serveState: ServeState\n): FetchHandle {\n const wsCommandCallbacks = new Set<(cmd: WebSocketCommand) => void>();\n\n return {\n stateMap,\n\n async dispatchRequest(request: Request): Promise<Response> {\n if (!serveState.fetchHandler) {\n throw new Error(\"No serve() handler registered\");\n }\n\n // Clear any previous upgrade request\n serveState.pendingUpgrade = null;\n\n // Convert native Request to RequestState\n const requestState = createRequestStateFromNative(request);\n\n // Create proper QuickJS Request instance with internal state\n const requestHandle = createRequestInstance(context, stateMap, requestState);\n\n // Create Server instance using evalCode (classes must be called with 'new')\n const serverResult = context.evalCode(`new __Server__()`);\n if (serverResult.error) {\n requestHandle.dispose();\n const error = context.dump(serverResult.error);\n serverResult.error.dispose();\n throw new Error(`Failed to create Server: ${error}`);\n }\n const serverHandle = serverResult.value;\n\n try {\n // Call the fetch handler\n const result = context.callFunction(\n serveState.fetchHandler,\n context.undefined,\n requestHandle,\n serverHandle\n );\n\n if (result.error) {\n const error = context.dump(result.error);\n result.error.dispose();\n throw new Error(`Fetch handler error: ${JSON.stringify(error)}`);\n }\n\n let responseHandle = result.value;\n\n // Check if result is a Promise\n const typeofResult = context.typeof(responseHandle);\n if (typeofResult === \"object\") {\n // Check for .then method (Promise-like)\n const thenHandle = context.getProp(responseHandle, \"then\");\n const hasThen = context.typeof(thenHandle) === \"function\";\n thenHandle.dispose();\n\n if (hasThen) {\n // Execute pending jobs BEFORE resolvePromise to allow\n // JavaScript-native async functions to schedule their resolution.\n // This is critical for async functions that don't use host-side awaits.\n context.runtime.executePendingJobs();\n\n // For pure JS async functions (without host-side awaits), we need to\n // wrap resolvePromise in a race with repeated executePendingJobs calls.\n // The sync variant of QuickJS needs this to process microtasks.\n const resolved = await Promise.race([\n context.resolvePromise(responseHandle),\n (async () => {\n // Keep executing pending jobs until the promise resolves\n // This handles pure JS async functions that don't trigger host callbacks\n for (let i = 0; i < 1000; i++) {\n await new Promise((r) => setTimeout(r, 0));\n context.runtime.executePendingJobs();\n }\n // If we get here, something is very wrong\n throw new Error(\"Promise resolution timeout\");\n })(),\n ]);\n responseHandle.dispose();\n context.runtime.executePendingJobs();\n\n if (resolved.error) {\n const error = context.dump(resolved.error);\n resolved.error.dispose();\n throw new Error(`Fetch handler promise rejected: ${JSON.stringify(error)}`);\n }\n responseHandle = resolved.value;\n }\n }\n\n // Get the ResponseState directly from the instance\n const responseState = getInstanceState<ResponseState>(context, responseHandle);\n responseHandle.dispose();\n\n if (!responseState) {\n throw new Error(\"Failed to get Response state\");\n }\n\n // Check if streaming response\n if (responseState.bodyType === \"stream\" && responseState.streamInstanceId !== undefined) {\n const nativeStream = createNativeStreamFromState(\n context,\n responseState.streamInstanceId\n );\n\n return new Response(nativeStream, {\n status: responseState.status,\n statusText: responseState.statusText,\n headers: headersStateToNative(responseState.headersState),\n });\n }\n\n // Convert to native Response (non-streaming)\n return responseStateToNative(responseState);\n } finally {\n requestHandle.dispose();\n serverHandle.dispose();\n }\n },\n\n getUpgradeRequest(): UpgradeRequest | null {\n return serveState.pendingUpgrade;\n },\n\n /**\n * Dispatch WebSocket open event to the QuickJS handler.\n *\n * **IMPORTANT:** An `open` handler MUST be defined in the serve() websocket\n * options for the connection to be tracked. Without an open handler, the\n * connection is not stored and subsequent message/close/error events will\n * be silently ignored.\n *\n * @param connectionId - Unique identifier for this WebSocket connection (host-assigned)\n * @param data - Optional user data from server.upgrade() to associate with the connection\n */\n dispatchWebSocketOpen(connectionId: string, data?: unknown): void {\n if (!serveState.websocketHandlers.open) {\n return;\n }\n\n // Create ServerWebSocket instance using evalCode (classes must be called with 'new')\n // Pass arguments through temporary globals\n const connectionIdHandle = context.newString(connectionId);\n const dataHandle = marshal(context, data);\n context.setProp(context.global, \"__wsConnectionId__\", connectionIdHandle);\n context.setProp(context.global, \"__wsData__\", dataHandle);\n connectionIdHandle.dispose();\n dataHandle.dispose();\n\n const wsResult = context.evalCode(`new __ServerWebSocket__(__wsConnectionId__, __wsData__)`);\n\n // Clean up temporary globals\n context.setProp(context.global, \"__wsConnectionId__\", context.undefined);\n context.setProp(context.global, \"__wsData__\", context.undefined);\n\n if (wsResult.error) {\n wsResult.error.dispose();\n return;\n }\n\n const wsHandle = wsResult.value;\n\n // Store the connection\n serveState.activeConnections.set(connectionId, {\n data,\n readyState: 1,\n connectionId,\n wsHandle,\n });\n\n // Call the open handler\n const result = context.callFunction(\n serveState.websocketHandlers.open,\n context.undefined,\n wsHandle\n );\n\n if (result.error) {\n result.error.dispose();\n } else {\n result.value.dispose();\n }\n\n context.runtime.executePendingJobs();\n },\n\n /**\n * Dispatch WebSocket message event to the QuickJS handler.\n *\n * Requires the connection to have been tracked via dispatchWebSocketOpen().\n * If no message handler is defined, or the connection is not tracked, this is a no-op.\n *\n * @param connectionId - The connection ID from dispatchWebSocketOpen()\n * @param message - The message content (string or binary data)\n */\n dispatchWebSocketMessage(\n connectionId: string,\n message: string | ArrayBuffer\n ): void {\n if (!serveState.websocketHandlers.message) {\n return;\n }\n\n const connection = serveState.activeConnections.get(connectionId);\n if (!connection || !connection.wsHandle) {\n return;\n }\n\n const messageHandle =\n typeof message === \"string\"\n ? context.newString(message)\n : context.newArrayBuffer(message);\n\n const result = context.callFunction(\n serveState.websocketHandlers.message,\n context.undefined,\n connection.wsHandle,\n messageHandle\n );\n\n messageHandle.dispose();\n\n if (result.error) {\n result.error.dispose();\n } else {\n result.value.dispose();\n }\n\n context.runtime.executePendingJobs();\n },\n\n /**\n * Dispatch WebSocket close event to the QuickJS handler and clean up the connection.\n *\n * Updates the connection ready state to CLOSED (3), calls the close handler if defined,\n * disposes the WebSocket handle, and removes the connection from tracking.\n *\n * @param connectionId - The connection ID from dispatchWebSocketOpen()\n * @param code - The WebSocket close code (e.g., 1000 for normal closure)\n * @param reason - The close reason string\n */\n dispatchWebSocketClose(\n connectionId: string,\n code: number,\n reason: string\n ): void {\n const connection = serveState.activeConnections.get(connectionId);\n if (!connection || !connection.wsHandle) {\n serveState.activeConnections.delete(connectionId);\n return;\n }\n\n if (!serveState.websocketHandlers.close) {\n // No close handler, but still need to cleanup the connection\n connection.wsHandle.dispose();\n serveState.activeConnections.delete(connectionId);\n return;\n }\n\n // Update ready state\n connection.readyState = 3; // CLOSED\n\n const codeHandle = context.newNumber(code);\n const reasonHandle = context.newString(reason);\n\n const result = context.callFunction(\n serveState.websocketHandlers.close,\n context.undefined,\n connection.wsHandle,\n codeHandle,\n reasonHandle\n );\n\n codeHandle.dispose();\n reasonHandle.dispose();\n\n if (result.error) {\n result.error.dispose();\n } else {\n result.value.dispose();\n }\n\n // Cleanup\n connection.wsHandle.dispose();\n serveState.activeConnections.delete(connectionId);\n\n context.runtime.executePendingJobs();\n },\n\n /**\n * Dispatch WebSocket error event to the QuickJS handler.\n *\n * Requires the connection to have been tracked via dispatchWebSocketOpen().\n * If no error handler is defined, or the connection is not tracked, this is a no-op.\n *\n * @param connectionId - The connection ID from dispatchWebSocketOpen()\n * @param error - The error that occurred\n */\n dispatchWebSocketError(connectionId: string, error: Error): void {\n if (!serveState.websocketHandlers.error) {\n return;\n }\n\n const connection = serveState.activeConnections.get(connectionId);\n if (!connection || !connection.wsHandle) {\n return;\n }\n\n const errorHandle = marshal(context, {\n name: error.name,\n message: error.message,\n });\n\n const result = context.callFunction(\n serveState.websocketHandlers.error,\n context.undefined,\n connection.wsHandle,\n errorHandle\n );\n\n errorHandle.dispose();\n\n if (result.error) {\n result.error.dispose();\n } else {\n result.value.dispose();\n }\n\n context.runtime.executePendingJobs();\n },\n\n /**\n * Register a callback for outgoing WebSocket commands from QuickJS.\n *\n * Called when QuickJS code calls ws.send() or ws.close() on a ServerWebSocket.\n * The callback receives command objects that should be forwarded to the actual WebSocket.\n *\n * @param callback - Function to handle outgoing WebSocket commands\n * @returns Unsubscribe function to remove the callback\n */\n onWebSocketCommand(\n callback: (command: WebSocketCommand) => void\n ): () => void {\n wsCommandCallbacks.add(callback);\n return () => wsCommandCallbacks.delete(callback);\n },\n\n hasServeHandler(): boolean {\n return serveState.fetchHandler !== null;\n },\n\n dispose(): void {\n // Dispose WebSocket handler handles\n if (serveState.websocketHandlers.open) {\n serveState.websocketHandlers.open.dispose();\n serveState.websocketHandlers.open = undefined;\n }\n if (serveState.websocketHandlers.message) {\n serveState.websocketHandlers.message.dispose();\n serveState.websocketHandlers.message = undefined;\n }\n if (serveState.websocketHandlers.close) {\n serveState.websocketHandlers.close.dispose();\n serveState.websocketHandlers.close = undefined;\n }\n if (serveState.websocketHandlers.error) {\n serveState.websocketHandlers.error.dispose();\n serveState.websocketHandlers.error = undefined;\n }\n\n // Dispose fetch handler handle\n if (serveState.fetchHandler) {\n serveState.fetchHandler.dispose();\n serveState.fetchHandler = null;\n }\n\n // Dispose active WebSocket connection handles\n for (const connection of serveState.activeConnections.values()) {\n if (connection.wsHandle) {\n connection.wsHandle.dispose();\n }\n }\n serveState.activeConnections.clear();\n\n // Note: __Server__ and __ServerWebSocket__ are on global and will be cleaned up by context.dispose()\n },\n };\n}\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "ED91E5C4CED38FC864756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "802249127874F14F64756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/quickjs-fetch",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "type": "module"
5
5
  }
@@ -31,6 +31,30 @@ function setupFetch(context, options = {}) {
31
31
  }
32
32
  };
33
33
  const streamFactory = (source) => createReadableStream(context, stateMap, source);
34
+ const streamHelpers = {
35
+ createStream: streamFactory,
36
+ createEmptyStream: () => {
37
+ const globalKey = `__uploadStream_${Date.now()}_${Math.random().toString(36).slice(2)}__`;
38
+ const result = context.evalCode(`
39
+ (function() {
40
+ const stream = new ReadableStream();
41
+ globalThis["${globalKey}"] = stream;
42
+ return stream.__instanceId__;
43
+ })()
44
+ `);
45
+ if (result.error) {
46
+ const error = context.dump(result.error);
47
+ result.error.dispose();
48
+ throw new Error(`Failed to create empty ReadableStream: ${JSON.stringify(error)}`);
49
+ }
50
+ const instanceId = context.getNumber(result.value);
51
+ result.value.dispose();
52
+ return { instanceId, globalKey };
53
+ },
54
+ pumpEventLoop: () => {
55
+ context.runtime.executePendingJobs();
56
+ }
57
+ };
34
58
  const HeadersClass = createHeadersClass(context, stateMap);
35
59
  context.setProp(context.global, "Headers", HeadersClass);
36
60
  HeadersClass.dispose();
@@ -44,9 +68,39 @@ function setupFetch(context, options = {}) {
44
68
  } else {
45
69
  iteratorResult.value.dispose();
46
70
  }
47
- const RequestClass = createRequestClass(context, stateMap, streamFactory);
71
+ const RequestClass = createRequestClass(context, stateMap, streamHelpers);
48
72
  context.setProp(context.global, "Request", RequestClass);
49
73
  RequestClass.dispose();
74
+ const bodyGetterWrapperResult = context.evalCode(`
75
+ (function() {
76
+ const originalBodyDescriptor = Object.getOwnPropertyDescriptor(Request.prototype, 'body');
77
+ if (originalBodyDescriptor && originalBodyDescriptor.get) {
78
+ const originalGetter = originalBodyDescriptor.get;
79
+ Object.defineProperty(Request.prototype, 'body', {
80
+ get: function() {
81
+ const result = originalGetter.call(this);
82
+ // Check if result is an upload stream marker
83
+ if (typeof result === 'string' && result.startsWith('__UPLOAD_STREAM_KEY__:')) {
84
+ const globalKey = result.slice('__UPLOAD_STREAM_KEY__:'.length);
85
+ const stream = globalThis[globalKey];
86
+ // Don't delete the global - keep it around for subsequent calls
87
+ // The wrapper will cache this in _cachedBodyStream for future calls
88
+ return stream || null;
89
+ }
90
+ return result;
91
+ },
92
+ configurable: true,
93
+ enumerable: true
94
+ });
95
+ }
96
+ })();
97
+ `);
98
+ if (bodyGetterWrapperResult.error) {
99
+ console.error("Failed to wrap body getter:", context.dump(bodyGetterWrapperResult.error));
100
+ bodyGetterWrapperResult.error.dispose();
101
+ } else {
102
+ bodyGetterWrapperResult.value.dispose();
103
+ }
50
104
  const ResponseClass = createResponseClass(context, stateMap, streamFactory);
51
105
  context.setProp(context.global, "Response", ResponseClass);
52
106
  ResponseClass.dispose();
@@ -91,4 +145,4 @@ export {
91
145
  setupFetch
92
146
  };
93
147
 
94
- //# debugId=E9661F932BB0303C64756E2164756E21
148
+ //# debugId=540F333C1CD9EF6B64756E2164756E21
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../../src/setup.ts"],
4
4
  "sourcesContent": [
5
- "import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport { setupCore, createStateMap, createReadableStream } from \"@ricsam/quickjs-core\";\nimport type { SetupFetchOptions, FetchHandle, ServeState } from \"./types.mjs\";\nimport { createHeadersClass } from \"./globals/headers.mjs\";\nimport { createRequestClass, addRequestFormDataMethod } from \"./globals/request.mjs\";\nimport { createResponseClass, addResponseStaticMethods } from \"./globals/response.mjs\";\nimport { setupAbortControllerAndSignal } from \"./globals/abort-controller.mjs\";\nimport { createFormDataClass, addFormDataFileMethods } from \"./globals/form-data.mjs\";\nimport { createFetchFunction } from \"./globals/fetch.mjs\";\nimport {\n createServeFunction,\n createServerClass,\n createServerWebSocketClass,\n} from \"./globals/serve.mjs\";\nimport { createFetchHandle } from \"./handle.mjs\";\n\n/**\n * Setup Fetch API in a QuickJS context\n *\n * Injects the following globals:\n * - fetch\n * - Request\n * - Response\n * - Headers\n * - AbortController\n * - AbortSignal\n * - serve\n * - FormData\n *\n * Also sets up Core APIs (Streams, Blob, File) if not already present.\n *\n * **Private globals (internal use):**\n * - `__Server__` - Server class for serve() handler, instantiated via evalCode\n * - `__ServerWebSocket__` - WebSocket class for connection handling\n * - `__scheduleTimeout__` - Host function to schedule AbortSignal.timeout()\n * - `__checkTimeout__` - Host function to check if timeout elapsed\n *\n * These private globals follow the `__Name__` convention and are required for\n * JavaScript code in QuickJS to create class instances via evalCode.\n * See PATTERNS.md section 5 for details.\n *\n * @example\n * const handle = setupFetch(context, {\n * onFetch: async (request) => {\n * // Proxy to real fetch\n * return fetch(request);\n * }\n * });\n *\n * context.evalCode(`\n * serve({\n * fetch(request, server) {\n * return new Response(\"Hello!\");\n * }\n * });\n * `);\n *\n * const response = await handle.dispatchRequest(\n * new Request(\"http://localhost/\")\n * );\n */\nexport function setupFetch(\n context: QuickJSContext,\n options: SetupFetchOptions = {}\n): FetchHandle {\n // Setup core if not already done\n const coreHandle =\n options.coreHandle ??\n setupCore(context, {\n stateMap: options.stateMap,\n });\n\n const stateMap = options.stateMap ?? coreHandle.stateMap;\n\n // Create serve state\n const serveState: ServeState = {\n fetchHandler: null,\n websocketHandlers: {},\n pendingUpgrade: null,\n activeConnections: new Map(),\n };\n\n // WebSocket command dispatcher\n const wsCommandCallbacks = new Set<\n (cmd: import(\"./types.mjs\").WebSocketCommand) => void\n >();\n const dispatchWsCommand = (cmd: import(\"./types.mjs\").WebSocketCommand) => {\n for (const cb of wsCommandCallbacks) {\n cb(cmd);\n }\n };\n\n // Create stream factory for Request/Response body\n const streamFactory = (source: UnderlyingSource) =>\n createReadableStream(context, stateMap, source);\n\n // Create Headers class\n const HeadersClass = createHeadersClass(context, stateMap);\n context.setProp(context.global, \"Headers\", HeadersClass);\n HeadersClass.dispose();\n\n // Add Symbol.iterator support for Headers (for...of, Array.from, spread)\n const iteratorResult = context.evalCode(`\n Headers.prototype[Symbol.iterator] = function() {\n return this.entries()[Symbol.iterator]();\n };\n `);\n if (iteratorResult.error) {\n iteratorResult.error.dispose();\n } else {\n iteratorResult.value.dispose();\n }\n\n // Create Request class\n const RequestClass = createRequestClass(context, stateMap, streamFactory);\n context.setProp(context.global, \"Request\", RequestClass);\n RequestClass.dispose();\n\n // Create Response class\n const ResponseClass = createResponseClass(context, stateMap, streamFactory);\n context.setProp(context.global, \"Response\", ResponseClass);\n ResponseClass.dispose();\n\n // Add Response static methods (must be after Response and Headers are on global)\n addResponseStaticMethods(context);\n\n // Create AbortSignal and AbortController classes (pure QuickJS implementation)\n setupAbortControllerAndSignal(context);\n\n // Create FormData class\n const FormDataClass = createFormDataClass(context, stateMap);\n context.setProp(context.global, \"FormData\", FormDataClass);\n FormDataClass.dispose();\n\n // Add Symbol.iterator support for FormData (for...of, Array.from, spread)\n const formDataIteratorResult = context.evalCode(`\n FormData.prototype[Symbol.iterator] = function() {\n return this.entries()[Symbol.iterator]();\n };\n `);\n if (formDataIteratorResult.error) {\n formDataIteratorResult.error.dispose();\n } else {\n formDataIteratorResult.value.dispose();\n }\n\n // Add Request.formData() method that returns a proper FormData instance\n // Must be after both Request and FormData are on global\n addRequestFormDataMethod(context);\n\n // Add FormData.get()/getAll() overrides to reconstruct File instances\n // Must be after both FormData and File are on global\n addFormDataFileMethods(context);\n\n // Create ServerWebSocket class (internal, for WebSocket handling)\n const ServerWebSocketClass = createServerWebSocketClass(\n context,\n stateMap,\n dispatchWsCommand\n );\n // Set on global with internal name so we can use evalCode to instantiate\n context.setProp(context.global, \"__ServerWebSocket__\", ServerWebSocketClass);\n ServerWebSocketClass.dispose();\n // Note: ServerWebSocketClass handle is now owned by global\n\n // Create Server class (internal, passed to fetch handler)\n const ServerClass = createServerClass(context, stateMap, serveState);\n // Set on global with internal name so we can use evalCode to instantiate\n context.setProp(context.global, \"__Server__\", ServerClass);\n ServerClass.dispose();\n // Note: ServerClass handle is now owned by global\n\n // Create fetch function\n const fetchFn = createFetchFunction(context, options.onFetch);\n context.setProp(context.global, \"fetch\", fetchFn);\n fetchFn.dispose();\n\n // Create serve function\n const serveFn = createServeFunction(context, stateMap, serveState);\n context.setProp(context.global, \"serve\", serveFn);\n serveFn.dispose();\n\n // Create and return the handle\n const fetchHandle = createFetchHandle(\n context,\n stateMap,\n serveState\n );\n\n // Wire up WebSocket command callbacks\n const originalOnWebSocketCommand = fetchHandle.onWebSocketCommand;\n fetchHandle.onWebSocketCommand = (callback) => {\n wsCommandCallbacks.add(callback);\n return () => wsCommandCallbacks.delete(callback);\n };\n\n return fetchHandle;\n}\n"
5
+ "import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport { setupCore, createStateMap, createReadableStream } from \"@ricsam/quickjs-core\";\nimport type { SetupFetchOptions, FetchHandle, ServeState } from \"./types.mjs\";\nimport { createHeadersClass } from \"./globals/headers.mjs\";\nimport { createRequestClass, addRequestFormDataMethod } from \"./globals/request.mjs\";\nimport { createResponseClass, addResponseStaticMethods } from \"./globals/response.mjs\";\nimport { setupAbortControllerAndSignal } from \"./globals/abort-controller.mjs\";\nimport { createFormDataClass, addFormDataFileMethods } from \"./globals/form-data.mjs\";\nimport { createFetchFunction } from \"./globals/fetch.mjs\";\nimport {\n createServeFunction,\n createServerClass,\n createServerWebSocketClass,\n} from \"./globals/serve.mjs\";\nimport { createFetchHandle } from \"./handle.mjs\";\n\n/**\n * Setup Fetch API in a QuickJS context\n *\n * Injects the following globals:\n * - fetch\n * - Request\n * - Response\n * - Headers\n * - AbortController\n * - AbortSignal\n * - serve\n * - FormData\n *\n * Also sets up Core APIs (Streams, Blob, File) if not already present.\n *\n * **Private globals (internal use):**\n * - `__Server__` - Server class for serve() handler, instantiated via evalCode\n * - `__ServerWebSocket__` - WebSocket class for connection handling\n * - `__scheduleTimeout__` - Host function to schedule AbortSignal.timeout()\n * - `__checkTimeout__` - Host function to check if timeout elapsed\n *\n * These private globals follow the `__Name__` convention and are required for\n * JavaScript code in QuickJS to create class instances via evalCode.\n * See PATTERNS.md section 5 for details.\n *\n * @example\n * const handle = setupFetch(context, {\n * onFetch: async (request) => {\n * // Proxy to real fetch\n * return fetch(request);\n * }\n * });\n *\n * context.evalCode(`\n * serve({\n * fetch(request, server) {\n * return new Response(\"Hello!\");\n * }\n * });\n * `);\n *\n * const response = await handle.dispatchRequest(\n * new Request(\"http://localhost/\")\n * );\n */\nexport function setupFetch(\n context: QuickJSContext,\n options: SetupFetchOptions = {}\n): FetchHandle {\n // Setup core if not already done\n const coreHandle =\n options.coreHandle ??\n setupCore(context, {\n stateMap: options.stateMap,\n });\n\n const stateMap = options.stateMap ?? coreHandle.stateMap;\n\n // Create serve state\n const serveState: ServeState = {\n fetchHandler: null,\n websocketHandlers: {},\n pendingUpgrade: null,\n activeConnections: new Map(),\n };\n\n // WebSocket command dispatcher\n const wsCommandCallbacks = new Set<\n (cmd: import(\"./types.mjs\").WebSocketCommand) => void\n >();\n const dispatchWsCommand = (cmd: import(\"./types.mjs\").WebSocketCommand) => {\n for (const cb of wsCommandCallbacks) {\n cb(cmd);\n }\n };\n\n // Create stream factory for Request/Response body\n const streamFactory = (source: UnderlyingSource) =>\n createReadableStream(context, stateMap, source);\n\n // Create stream helpers for upload streaming\n const streamHelpers = {\n createStream: streamFactory,\n createEmptyStream: (): { instanceId: number; globalKey: string } => {\n // Create a pure QuickJS ReadableStream with no source (no host callbacks)\n // Store it on a unique global key so it can be retrieved from JS without\n // returning a handle through the callback (which causes lifetime issues)\n const globalKey = `__uploadStream_${Date.now()}_${Math.random().toString(36).slice(2)}__`;\n const result = context.evalCode(`\n (function() {\n const stream = new ReadableStream();\n globalThis[\"${globalKey}\"] = stream;\n return stream.__instanceId__;\n })()\n `);\n if (result.error) {\n const error = context.dump(result.error);\n result.error.dispose();\n throw new Error(`Failed to create empty ReadableStream: ${JSON.stringify(error)}`);\n }\n const instanceId = context.getNumber(result.value);\n result.value.dispose();\n\n return { instanceId, globalKey };\n },\n pumpEventLoop: () => {\n context.runtime.executePendingJobs();\n },\n };\n\n // Create Headers class\n const HeadersClass = createHeadersClass(context, stateMap);\n context.setProp(context.global, \"Headers\", HeadersClass);\n HeadersClass.dispose();\n\n // Add Symbol.iterator support for Headers (for...of, Array.from, spread)\n const iteratorResult = context.evalCode(`\n Headers.prototype[Symbol.iterator] = function() {\n return this.entries()[Symbol.iterator]();\n };\n `);\n if (iteratorResult.error) {\n iteratorResult.error.dispose();\n } else {\n iteratorResult.value.dispose();\n }\n\n // Create Request class\n const RequestClass = createRequestClass(context, stateMap, streamHelpers);\n context.setProp(context.global, \"Request\", RequestClass);\n RequestClass.dispose();\n\n // Wrap the body getter to resolve upload stream markers\n // This works around the issue of returning QuickJSHandle from __hostCall__\n const bodyGetterWrapperResult = context.evalCode(`\n (function() {\n const originalBodyDescriptor = Object.getOwnPropertyDescriptor(Request.prototype, 'body');\n if (originalBodyDescriptor && originalBodyDescriptor.get) {\n const originalGetter = originalBodyDescriptor.get;\n Object.defineProperty(Request.prototype, 'body', {\n get: function() {\n const result = originalGetter.call(this);\n // Check if result is an upload stream marker\n if (typeof result === 'string' && result.startsWith('__UPLOAD_STREAM_KEY__:')) {\n const globalKey = result.slice('__UPLOAD_STREAM_KEY__:'.length);\n const stream = globalThis[globalKey];\n // Don't delete the global - keep it around for subsequent calls\n // The wrapper will cache this in _cachedBodyStream for future calls\n return stream || null;\n }\n return result;\n },\n configurable: true,\n enumerable: true\n });\n }\n })();\n `);\n if (bodyGetterWrapperResult.error) {\n console.error(\"Failed to wrap body getter:\", context.dump(bodyGetterWrapperResult.error));\n bodyGetterWrapperResult.error.dispose();\n } else {\n bodyGetterWrapperResult.value.dispose();\n }\n\n // Create Response class\n const ResponseClass = createResponseClass(context, stateMap, streamFactory);\n context.setProp(context.global, \"Response\", ResponseClass);\n ResponseClass.dispose();\n\n // Add Response static methods (must be after Response and Headers are on global)\n addResponseStaticMethods(context);\n\n // Create AbortSignal and AbortController classes (pure QuickJS implementation)\n setupAbortControllerAndSignal(context);\n\n // Create FormData class\n const FormDataClass = createFormDataClass(context, stateMap);\n context.setProp(context.global, \"FormData\", FormDataClass);\n FormDataClass.dispose();\n\n // Add Symbol.iterator support for FormData (for...of, Array.from, spread)\n const formDataIteratorResult = context.evalCode(`\n FormData.prototype[Symbol.iterator] = function() {\n return this.entries()[Symbol.iterator]();\n };\n `);\n if (formDataIteratorResult.error) {\n formDataIteratorResult.error.dispose();\n } else {\n formDataIteratorResult.value.dispose();\n }\n\n // Add Request.formData() method that returns a proper FormData instance\n // Must be after both Request and FormData are on global\n addRequestFormDataMethod(context);\n\n // Add FormData.get()/getAll() overrides to reconstruct File instances\n // Must be after both FormData and File are on global\n addFormDataFileMethods(context);\n\n // Create ServerWebSocket class (internal, for WebSocket handling)\n const ServerWebSocketClass = createServerWebSocketClass(\n context,\n stateMap,\n dispatchWsCommand\n );\n // Set on global with internal name so we can use evalCode to instantiate\n context.setProp(context.global, \"__ServerWebSocket__\", ServerWebSocketClass);\n ServerWebSocketClass.dispose();\n // Note: ServerWebSocketClass handle is now owned by global\n\n // Create Server class (internal, passed to fetch handler)\n const ServerClass = createServerClass(context, stateMap, serveState);\n // Set on global with internal name so we can use evalCode to instantiate\n context.setProp(context.global, \"__Server__\", ServerClass);\n ServerClass.dispose();\n // Note: ServerClass handle is now owned by global\n\n // Create fetch function\n const fetchFn = createFetchFunction(context, options.onFetch);\n context.setProp(context.global, \"fetch\", fetchFn);\n fetchFn.dispose();\n\n // Create serve function\n const serveFn = createServeFunction(context, stateMap, serveState);\n context.setProp(context.global, \"serve\", serveFn);\n serveFn.dispose();\n\n // Create and return the handle\n const fetchHandle = createFetchHandle(\n context,\n stateMap,\n serveState\n );\n\n // Wire up WebSocket command callbacks\n const originalOnWebSocketCommand = fetchHandle.onWebSocketCommand;\n fetchHandle.onWebSocketCommand = (callback) => {\n wsCommandCallbacks.add(callback);\n return () => wsCommandCallbacks.delete(callback);\n };\n\n return fetchHandle;\n}\n"
6
6
  ],
7
- "mappings": ";;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAKA;AA+CO,SAAS,UAAU,CACxB,SACA,UAA6B,CAAC,GACjB;AAAA,EAEb,MAAM,aACJ,QAAQ,cACR,UAAU,SAAS;AAAA,IACjB,UAAU,QAAQ;AAAA,EACpB,CAAC;AAAA,EAEH,MAAM,WAAW,QAAQ,YAAY,WAAW;AAAA,EAGhD,MAAM,aAAyB;AAAA,IAC7B,cAAc;AAAA,IACd,mBAAmB,CAAC;AAAA,IACpB,gBAAgB;AAAA,IAChB,mBAAmB,IAAI;AAAA,EACzB;AAAA,EAGA,MAAM,qBAAqB,IAAI;AAAA,EAG/B,MAAM,oBAAoB,CAAC,QAAgD;AAAA,IACzE,WAAW,MAAM,oBAAoB;AAAA,MACnC,GAAG,GAAG;AAAA,IACR;AAAA;AAAA,EAIF,MAAM,gBAAgB,CAAC,WACrB,qBAAqB,SAAS,UAAU,MAAM;AAAA,EAGhD,MAAM,eAAe,mBAAmB,SAAS,QAAQ;AAAA,EACzD,QAAQ,QAAQ,QAAQ,QAAQ,WAAW,YAAY;AAAA,EACvD,aAAa,QAAQ;AAAA,EAGrB,MAAM,iBAAiB,QAAQ,SAAS;AAAA;AAAA;AAAA;AAAA,GAIvC;AAAA,EACD,IAAI,eAAe,OAAO;AAAA,IACxB,eAAe,MAAM,QAAQ;AAAA,EAC/B,EAAO;AAAA,IACL,eAAe,MAAM,QAAQ;AAAA;AAAA,EAI/B,MAAM,eAAe,mBAAmB,SAAS,UAAU,aAAa;AAAA,EACxE,QAAQ,QAAQ,QAAQ,QAAQ,WAAW,YAAY;AAAA,EACvD,aAAa,QAAQ;AAAA,EAGrB,MAAM,gBAAgB,oBAAoB,SAAS,UAAU,aAAa;AAAA,EAC1E,QAAQ,QAAQ,QAAQ,QAAQ,YAAY,aAAa;AAAA,EACzD,cAAc,QAAQ;AAAA,EAGtB,yBAAyB,OAAO;AAAA,EAGhC,8BAA8B,OAAO;AAAA,EAGrC,MAAM,gBAAgB,oBAAoB,SAAS,QAAQ;AAAA,EAC3D,QAAQ,QAAQ,QAAQ,QAAQ,YAAY,aAAa;AAAA,EACzD,cAAc,QAAQ;AAAA,EAGtB,MAAM,yBAAyB,QAAQ,SAAS;AAAA;AAAA;AAAA;AAAA,GAI/C;AAAA,EACD,IAAI,uBAAuB,OAAO;AAAA,IAChC,uBAAuB,MAAM,QAAQ;AAAA,EACvC,EAAO;AAAA,IACL,uBAAuB,MAAM,QAAQ;AAAA;AAAA,EAKvC,yBAAyB,OAAO;AAAA,EAIhC,uBAAuB,OAAO;AAAA,EAG9B,MAAM,uBAAuB,2BAC3B,SACA,UACA,iBACF;AAAA,EAEA,QAAQ,QAAQ,QAAQ,QAAQ,uBAAuB,oBAAoB;AAAA,EAC3E,qBAAqB,QAAQ;AAAA,EAI7B,MAAM,cAAc,kBAAkB,SAAS,UAAU,UAAU;AAAA,EAEnE,QAAQ,QAAQ,QAAQ,QAAQ,cAAc,WAAW;AAAA,EACzD,YAAY,QAAQ;AAAA,EAIpB,MAAM,UAAU,oBAAoB,SAAS,QAAQ,OAAO;AAAA,EAC5D,QAAQ,QAAQ,QAAQ,QAAQ,SAAS,OAAO;AAAA,EAChD,QAAQ,QAAQ;AAAA,EAGhB,MAAM,UAAU,oBAAoB,SAAS,UAAU,UAAU;AAAA,EACjE,QAAQ,QAAQ,QAAQ,QAAQ,SAAS,OAAO;AAAA,EAChD,QAAQ,QAAQ;AAAA,EAGhB,MAAM,cAAc,kBAClB,SACA,UACA,UACF;AAAA,EAGA,MAAM,6BAA6B,YAAY;AAAA,EAC/C,YAAY,qBAAqB,CAAC,aAAa;AAAA,IAC7C,mBAAmB,IAAI,QAAQ;AAAA,IAC/B,OAAO,MAAM,mBAAmB,OAAO,QAAQ;AAAA;AAAA,EAGjD,OAAO;AAAA;",
8
- "debugId": "E9661F932BB0303C64756E2164756E21",
7
+ "mappings": ";;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAKA;AA+CO,SAAS,UAAU,CACxB,SACA,UAA6B,CAAC,GACjB;AAAA,EAEb,MAAM,aACJ,QAAQ,cACR,UAAU,SAAS;AAAA,IACjB,UAAU,QAAQ;AAAA,EACpB,CAAC;AAAA,EAEH,MAAM,WAAW,QAAQ,YAAY,WAAW;AAAA,EAGhD,MAAM,aAAyB;AAAA,IAC7B,cAAc;AAAA,IACd,mBAAmB,CAAC;AAAA,IACpB,gBAAgB;AAAA,IAChB,mBAAmB,IAAI;AAAA,EACzB;AAAA,EAGA,MAAM,qBAAqB,IAAI;AAAA,EAG/B,MAAM,oBAAoB,CAAC,QAAgD;AAAA,IACzE,WAAW,MAAM,oBAAoB;AAAA,MACnC,GAAG,GAAG;AAAA,IACR;AAAA;AAAA,EAIF,MAAM,gBAAgB,CAAC,WACrB,qBAAqB,SAAS,UAAU,MAAM;AAAA,EAGhD,MAAM,gBAAgB;AAAA,IACpB,cAAc;AAAA,IACd,mBAAmB,MAAiD;AAAA,MAIlE,MAAM,YAAY,kBAAkB,KAAK,IAAI,KAAK,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC;AAAA,MACpF,MAAM,SAAS,QAAQ,SAAS;AAAA;AAAA;AAAA,wBAGd;AAAA;AAAA;AAAA,OAGjB;AAAA,MACD,IAAI,OAAO,OAAO;AAAA,QAChB,MAAM,QAAQ,QAAQ,KAAK,OAAO,KAAK;AAAA,QACvC,OAAO,MAAM,QAAQ;AAAA,QACrB,MAAM,IAAI,MAAM,0CAA0C,KAAK,UAAU,KAAK,GAAG;AAAA,MACnF;AAAA,MACA,MAAM,aAAa,QAAQ,UAAU,OAAO,KAAK;AAAA,MACjD,OAAO,MAAM,QAAQ;AAAA,MAErB,OAAO,EAAE,YAAY,UAAU;AAAA;AAAA,IAEjC,eAAe,MAAM;AAAA,MACnB,QAAQ,QAAQ,mBAAmB;AAAA;AAAA,EAEvC;AAAA,EAGA,MAAM,eAAe,mBAAmB,SAAS,QAAQ;AAAA,EACzD,QAAQ,QAAQ,QAAQ,QAAQ,WAAW,YAAY;AAAA,EACvD,aAAa,QAAQ;AAAA,EAGrB,MAAM,iBAAiB,QAAQ,SAAS;AAAA;AAAA;AAAA;AAAA,GAIvC;AAAA,EACD,IAAI,eAAe,OAAO;AAAA,IACxB,eAAe,MAAM,QAAQ;AAAA,EAC/B,EAAO;AAAA,IACL,eAAe,MAAM,QAAQ;AAAA;AAAA,EAI/B,MAAM,eAAe,mBAAmB,SAAS,UAAU,aAAa;AAAA,EACxE,QAAQ,QAAQ,QAAQ,QAAQ,WAAW,YAAY;AAAA,EACvD,aAAa,QAAQ;AAAA,EAIrB,MAAM,0BAA0B,QAAQ,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAuBhD;AAAA,EACD,IAAI,wBAAwB,OAAO;AAAA,IACjC,QAAQ,MAAM,+BAA+B,QAAQ,KAAK,wBAAwB,KAAK,CAAC;AAAA,IACxF,wBAAwB,MAAM,QAAQ;AAAA,EACxC,EAAO;AAAA,IACL,wBAAwB,MAAM,QAAQ;AAAA;AAAA,EAIxC,MAAM,gBAAgB,oBAAoB,SAAS,UAAU,aAAa;AAAA,EAC1E,QAAQ,QAAQ,QAAQ,QAAQ,YAAY,aAAa;AAAA,EACzD,cAAc,QAAQ;AAAA,EAGtB,yBAAyB,OAAO;AAAA,EAGhC,8BAA8B,OAAO;AAAA,EAGrC,MAAM,gBAAgB,oBAAoB,SAAS,QAAQ;AAAA,EAC3D,QAAQ,QAAQ,QAAQ,QAAQ,YAAY,aAAa;AAAA,EACzD,cAAc,QAAQ;AAAA,EAGtB,MAAM,yBAAyB,QAAQ,SAAS;AAAA;AAAA;AAAA;AAAA,GAI/C;AAAA,EACD,IAAI,uBAAuB,OAAO;AAAA,IAChC,uBAAuB,MAAM,QAAQ;AAAA,EACvC,EAAO;AAAA,IACL,uBAAuB,MAAM,QAAQ;AAAA;AAAA,EAKvC,yBAAyB,OAAO;AAAA,EAIhC,uBAAuB,OAAO;AAAA,EAG9B,MAAM,uBAAuB,2BAC3B,SACA,UACA,iBACF;AAAA,EAEA,QAAQ,QAAQ,QAAQ,QAAQ,uBAAuB,oBAAoB;AAAA,EAC3E,qBAAqB,QAAQ;AAAA,EAI7B,MAAM,cAAc,kBAAkB,SAAS,UAAU,UAAU;AAAA,EAEnE,QAAQ,QAAQ,QAAQ,QAAQ,cAAc,WAAW;AAAA,EACzD,YAAY,QAAQ;AAAA,EAIpB,MAAM,UAAU,oBAAoB,SAAS,QAAQ,OAAO;AAAA,EAC5D,QAAQ,QAAQ,QAAQ,QAAQ,SAAS,OAAO;AAAA,EAChD,QAAQ,QAAQ;AAAA,EAGhB,MAAM,UAAU,oBAAoB,SAAS,UAAU,UAAU;AAAA,EACjE,QAAQ,QAAQ,QAAQ,QAAQ,SAAS,OAAO;AAAA,EAChD,QAAQ,QAAQ;AAAA,EAGhB,MAAM,cAAc,kBAClB,SACA,UACA,UACF;AAAA,EAGA,MAAM,6BAA6B,YAAY;AAAA,EAC/C,YAAY,qBAAqB,CAAC,aAAa;AAAA,IAC7C,mBAAmB,IAAI,QAAQ;AAAA,IAC/B,OAAO,MAAM,mBAAmB,OAAO,QAAQ;AAAA;AAAA,EAGjD,OAAO;AAAA;",
8
+ "debugId": "540F333C1CD9EF6B64756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -0,0 +1,145 @@
1
+ // @bun
2
+ // packages/fetch/src/upload-stream-queue.ts
3
+ import {
4
+ nextInstanceId,
5
+ registerInstance,
6
+ getInstanceStateById,
7
+ cleanupInstanceStateById
8
+ } from "@ricsam/quickjs-core";
9
+ var MAX_QUEUE_CHUNKS = 10;
10
+ var CHUNK_SIZE = 64 * 1024;
11
+ function createUploadQueue() {
12
+ const instanceId = nextInstanceId();
13
+ const queue = {
14
+ chunks: [],
15
+ closed: false,
16
+ error: undefined,
17
+ paused: false,
18
+ resumeCallback: undefined,
19
+ cancelCallback: undefined
20
+ };
21
+ registerInstance(instanceId, "UploadStreamQueue", queue);
22
+ return instanceId;
23
+ }
24
+ function getUploadQueue(instanceId) {
25
+ return getInstanceStateById(instanceId);
26
+ }
27
+ function cleanupUploadQueue(instanceId) {
28
+ cleanupInstanceStateById(instanceId);
29
+ }
30
+ function pushToQuickJSStream(streamInstanceId, chunk) {
31
+ const state = getInstanceStateById(streamInstanceId);
32
+ if (!state) {
33
+ return false;
34
+ }
35
+ if (state.closeRequested || state.closed || state.errored) {
36
+ return false;
37
+ }
38
+ state.queue.push(chunk);
39
+ if (state.reader && state.reader.readRequests.length > 0) {
40
+ const request = state.reader.readRequests.shift();
41
+ const value = state.queue.shift();
42
+ request.resolve({ value, done: false });
43
+ }
44
+ return true;
45
+ }
46
+ function closeQuickJSStream(streamInstanceId) {
47
+ const state = getInstanceStateById(streamInstanceId);
48
+ if (!state) {
49
+ return;
50
+ }
51
+ if (state.closeRequested || state.closed) {
52
+ return;
53
+ }
54
+ state.closeRequested = true;
55
+ if (state.queue.length === 0) {
56
+ state.closed = true;
57
+ if (state.reader) {
58
+ state.reader.closedPromiseResolvers.resolve();
59
+ for (const request of state.reader.readRequests) {
60
+ request.resolve({ value: undefined, done: true });
61
+ }
62
+ state.reader.readRequests = [];
63
+ }
64
+ }
65
+ }
66
+ function errorQuickJSStream(streamInstanceId, error) {
67
+ const state = getInstanceStateById(streamInstanceId);
68
+ if (!state) {
69
+ return;
70
+ }
71
+ if (state.errored || state.closed) {
72
+ return;
73
+ }
74
+ state.errored = true;
75
+ state.errorValue = { name: error.name, message: error.message };
76
+ if (state.reader) {
77
+ state.reader.closedPromiseResolvers.reject(state.errorValue);
78
+ for (const request of state.reader.readRequests) {
79
+ request.reject(state.errorValue);
80
+ }
81
+ state.reader.readRequests = [];
82
+ }
83
+ }
84
+ function isQuickJSStreamQueueFull(streamInstanceId) {
85
+ const state = getInstanceStateById(streamInstanceId);
86
+ if (!state) {
87
+ return true;
88
+ }
89
+ return state.queue.length >= MAX_QUEUE_CHUNKS;
90
+ }
91
+ function startNativeStreamReader(nativeStream, quickjsStreamInstanceId, onChunkPushed) {
92
+ const reader = nativeStream.getReader();
93
+ let cancelled = false;
94
+ (async () => {
95
+ try {
96
+ while (!cancelled) {
97
+ if (isQuickJSStreamQueueFull(quickjsStreamInstanceId)) {
98
+ await new Promise((resolve) => setTimeout(resolve, 1));
99
+ continue;
100
+ }
101
+ const { done, value } = await reader.read();
102
+ if (done) {
103
+ closeQuickJSStream(quickjsStreamInstanceId);
104
+ onChunkPushed?.();
105
+ break;
106
+ }
107
+ if (value) {
108
+ if (value.length > CHUNK_SIZE) {
109
+ let offset = 0;
110
+ while (offset < value.length) {
111
+ const chunk = value.slice(offset, offset + CHUNK_SIZE);
112
+ pushToQuickJSStream(quickjsStreamInstanceId, chunk);
113
+ offset += chunk.length;
114
+ }
115
+ } else {
116
+ pushToQuickJSStream(quickjsStreamInstanceId, value);
117
+ }
118
+ onChunkPushed?.();
119
+ }
120
+ }
121
+ } catch (error) {
122
+ if (!cancelled) {
123
+ const err = error instanceof Error ? error : new Error(String(error));
124
+ errorQuickJSStream(quickjsStreamInstanceId, err);
125
+ onChunkPushed?.();
126
+ }
127
+ }
128
+ })();
129
+ return () => {
130
+ cancelled = true;
131
+ reader.cancel().catch(() => {});
132
+ };
133
+ }
134
+ export {
135
+ startNativeStreamReader,
136
+ pushToQuickJSStream,
137
+ isQuickJSStreamQueueFull,
138
+ getUploadQueue,
139
+ errorQuickJSStream,
140
+ createUploadQueue,
141
+ closeQuickJSStream,
142
+ cleanupUploadQueue
143
+ };
144
+
145
+ //# debugId=E26FBAE75EE5DFA264756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/upload-stream-queue.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * Upload stream queue management for bridging native ReadableStreams to QuickJS.\n *\n * This module implements the \"Inverted Direct State Access\" pattern:\n * - Native stream reader reads data and pushes directly to QuickJS stream's internal queue\n * - QuickJS stream's read() method reads from the queue\n * - No async callbacks with QuickJS handles, avoiding handle lifetime issues\n *\n * The pattern is the reverse of download streaming (handle.ts:createNativeStreamFromState):\n * - Download: QuickJS enqueues → host polls queue\n * - Upload: Host pushes to queue → QuickJS reads\n */\n\nimport type { UploadStreamQueue } from \"./types.mjs\";\nimport {\n nextInstanceId,\n registerInstance,\n getInstanceStateById,\n cleanupInstanceStateById,\n} from \"@ricsam/quickjs-core\";\n\n/** Maximum chunks to buffer before applying backpressure */\nconst MAX_QUEUE_CHUNKS = 10;\n/** Chunk size for reading from native stream (64KB, consistent with FS streaming) */\nconst CHUNK_SIZE = 64 * 1024;\n\n/**\n * Internal state of a QuickJS ReadableStream.\n * This mirrors the structure in core/streams/readable-stream.ts.\n */\ninterface ReadableStreamInternalState {\n locked: boolean;\n queue: unknown[];\n closeRequested: boolean;\n closed: boolean;\n errored: boolean;\n errorValue: unknown;\n reader: ReadableStreamReaderState | null;\n}\n\ninterface ReadableStreamReaderState {\n readRequests: Array<{\n resolve: (result: { value: unknown; done: boolean }) => void;\n reject: (e: unknown) => void;\n }>;\n closedPromiseResolvers: {\n resolve: () => void;\n reject: (e: unknown) => void;\n };\n}\n\n/**\n * Create a new upload queue and register it in instance state.\n * Returns the instance ID for accessing the queue via getUploadQueue().\n */\nexport function createUploadQueue(): number {\n const instanceId = nextInstanceId();\n const queue: UploadStreamQueue = {\n chunks: [],\n closed: false,\n error: undefined,\n paused: false,\n resumeCallback: undefined,\n cancelCallback: undefined,\n };\n\n registerInstance(instanceId, \"UploadStreamQueue\", queue);\n return instanceId;\n}\n\n/**\n * Get an upload queue by its instance ID.\n */\nexport function getUploadQueue(instanceId: number): UploadStreamQueue | undefined {\n return getInstanceStateById<UploadStreamQueue>(instanceId);\n}\n\n/**\n * Clean up an upload queue when no longer needed.\n */\nexport function cleanupUploadQueue(instanceId: number): void {\n cleanupInstanceStateById(instanceId);\n}\n\n/**\n * Push a chunk to a QuickJS ReadableStream's internal queue.\n * If there are pending read requests, fulfills the first one immediately.\n *\n * @param streamInstanceId The instance ID of the QuickJS ReadableStream\n * @param chunk The data chunk to enqueue\n * @returns true if the chunk was successfully enqueued\n */\nexport function pushToQuickJSStream(\n streamInstanceId: number,\n chunk: Uint8Array\n): boolean {\n const state = getInstanceStateById<ReadableStreamInternalState>(streamInstanceId);\n if (!state) {\n return false;\n }\n\n if (state.closeRequested || state.closed || state.errored) {\n return false;\n }\n\n // Push chunk to queue\n state.queue.push(chunk);\n\n // If there are pending read requests, fulfill the first one\n if (state.reader && state.reader.readRequests.length > 0) {\n const request = state.reader.readRequests.shift()!;\n const value = state.queue.shift();\n request.resolve({ value, done: false });\n }\n\n return true;\n}\n\n/**\n * Close a QuickJS ReadableStream.\n * If there are pending read requests, fulfills them with done: true.\n *\n * @param streamInstanceId The instance ID of the QuickJS ReadableStream\n */\nexport function closeQuickJSStream(streamInstanceId: number): void {\n const state = getInstanceStateById<ReadableStreamInternalState>(streamInstanceId);\n if (!state) {\n return;\n }\n\n if (state.closeRequested || state.closed) {\n return;\n }\n\n state.closeRequested = true;\n\n if (state.queue.length === 0) {\n state.closed = true;\n if (state.reader) {\n state.reader.closedPromiseResolvers.resolve();\n // Resolve any pending reads with done: true\n for (const request of state.reader.readRequests) {\n request.resolve({ value: undefined, done: true });\n }\n state.reader.readRequests = [];\n }\n }\n}\n\n/**\n * Error a QuickJS ReadableStream.\n *\n * @param streamInstanceId The instance ID of the QuickJS ReadableStream\n * @param error The error to propagate\n */\nexport function errorQuickJSStream(streamInstanceId: number, error: Error): void {\n const state = getInstanceStateById<ReadableStreamInternalState>(streamInstanceId);\n if (!state) {\n return;\n }\n\n if (state.errored || state.closed) {\n return;\n }\n\n state.errored = true;\n state.errorValue = { name: error.name, message: error.message };\n\n if (state.reader) {\n state.reader.closedPromiseResolvers.reject(state.errorValue);\n for (const request of state.reader.readRequests) {\n request.reject(state.errorValue);\n }\n state.reader.readRequests = [];\n }\n}\n\n/**\n * Check if a QuickJS ReadableStream's queue is full (for backpressure).\n *\n * @param streamInstanceId The instance ID of the QuickJS ReadableStream\n * @returns true if the queue has reached capacity\n */\nexport function isQuickJSStreamQueueFull(streamInstanceId: number): boolean {\n const state = getInstanceStateById<ReadableStreamInternalState>(streamInstanceId);\n if (!state) {\n return true; // Treat missing stream as full\n }\n return state.queue.length >= MAX_QUEUE_CHUNKS;\n}\n\n/**\n * Start a background reader that reads from a native ReadableStream\n * and pushes chunks directly to a QuickJS ReadableStream.\n *\n * The reader respects backpressure by pausing when the QuickJS queue is full\n * (10+ chunks) and resuming when QuickJS consumes chunks.\n *\n * @param nativeStream The native ReadableStream to read from\n * @param quickjsStreamInstanceId The instance ID of the QuickJS ReadableStream\n * @param onChunkPushed Optional callback called after each chunk is pushed\n * @returns A cleanup function to cancel the reader\n */\nexport function startNativeStreamReader(\n nativeStream: ReadableStream<Uint8Array>,\n quickjsStreamInstanceId: number,\n onChunkPushed?: () => void\n): () => void {\n const reader = nativeStream.getReader();\n let cancelled = false;\n\n // Start reading in background\n (async () => {\n try {\n while (!cancelled) {\n // Check for backpressure\n if (isQuickJSStreamQueueFull(quickjsStreamInstanceId)) {\n // Wait a bit for the queue to drain\n await new Promise<void>((resolve) => setTimeout(resolve, 1));\n continue;\n }\n\n const { done, value } = await reader.read();\n\n if (done) {\n closeQuickJSStream(quickjsStreamInstanceId);\n onChunkPushed?.();\n break;\n }\n\n if (value) {\n // Split large chunks into CHUNK_SIZE pieces for consistent streaming\n if (value.length > CHUNK_SIZE) {\n let offset = 0;\n while (offset < value.length) {\n const chunk = value.slice(offset, offset + CHUNK_SIZE);\n pushToQuickJSStream(quickjsStreamInstanceId, chunk);\n offset += chunk.length;\n }\n } else {\n pushToQuickJSStream(quickjsStreamInstanceId, value);\n }\n onChunkPushed?.();\n }\n }\n } catch (error) {\n if (!cancelled) {\n const err = error instanceof Error ? error : new Error(String(error));\n errorQuickJSStream(quickjsStreamInstanceId, err);\n onChunkPushed?.();\n }\n }\n })();\n\n // Return cleanup function\n return () => {\n cancelled = true;\n reader.cancel().catch(() => {\n // Ignore cancel errors\n });\n };\n}\n\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "E26FBAE75EE5DFA264756E2164756E21",
9
+ "names": []
10
+ }
@@ -5,10 +5,22 @@ import type { RequestState } from "../types.ts";
5
5
  * Type for the stream factory function
6
6
  */
7
7
  type StreamFactory = (source: UnderlyingSource) => QuickJSHandle;
8
+ /**
9
+ * Stream helpers passed to the body getter for upload streaming
10
+ */
11
+ interface StreamHelpers {
12
+ createStream: StreamFactory;
13
+ /** Creates an empty ReadableStream, stores on global, returns instanceId and globalKey */
14
+ createEmptyStream: () => {
15
+ instanceId: number;
16
+ globalKey: string;
17
+ };
18
+ pumpEventLoop: () => void;
19
+ }
8
20
  /**
9
21
  * Create the Request class for QuickJS
10
22
  */
11
- export declare function createRequestClass(context: QuickJSContext, stateMap: StateMap, createStream?: StreamFactory): QuickJSHandle;
23
+ export declare function createRequestClass(context: QuickJSContext, stateMap: StateMap, streamHelpers?: StreamHelpers): QuickJSHandle;
12
24
  /**
13
25
  * Add the formData() method to Request.prototype via evalCode.
14
26
  *
@@ -19,7 +31,13 @@ export declare function createRequestClass(context: QuickJSContext, stateMap: St
19
31
  */
20
32
  export declare function addRequestFormDataMethod(context: QuickJSContext): void;
21
33
  /**
22
- * Create a RequestState from a native Request object
34
+ * Create a RequestState from a native Request object.
35
+ *
36
+ * This is now synchronous - we preserve the native ReadableStream instead of
37
+ * buffering it. The stream is consumed lazily when QuickJS code accesses
38
+ * request.body or calls request.text()/json()/arrayBuffer().
39
+ *
40
+ * This enables streaming large uploads (1GB+) without buffering.
23
41
  */
24
- export declare function createRequestStateFromNative(request: Request): Promise<RequestState>;
42
+ export declare function createRequestStateFromNative(request: Request): RequestState;
25
43
  export {};
@@ -141,6 +141,8 @@ export interface RequestState {
141
141
  referrer: string;
142
142
  referrerPolicy: string;
143
143
  signal: AbortSignalState | null;
144
+ /** Native ReadableStream for streaming request bodies (upload streaming) */
145
+ nativeBodyStream?: ReadableStream<Uint8Array>;
144
146
  }
145
147
  export interface ResponseState {
146
148
  status: number;
@@ -178,3 +180,23 @@ export interface FormDataEntry {
178
180
  export interface FormDataState {
179
181
  entries: FormDataEntry[];
180
182
  }
183
+ /**
184
+ * Host-side queue for upload stream bridging.
185
+ * Native ReadableStream reader pushes chunks here.
186
+ * QuickJS ReadableStream's synchronous pull() reads from here.
187
+ * This avoids async callbacks with QuickJS handles.
188
+ */
189
+ export interface UploadStreamQueue {
190
+ /** Buffered chunks waiting to be read by QuickJS */
191
+ chunks: Uint8Array[];
192
+ /** Whether the native stream has closed */
193
+ closed: boolean;
194
+ /** Error from native stream if any */
195
+ error?: Error;
196
+ /** Whether the native reader should pause (backpressure) */
197
+ paused: boolean;
198
+ /** Callback to resume native reader when queue is drained */
199
+ resumeCallback?: () => void;
200
+ /** Callback to cancel native reader when QuickJS stream is cancelled */
201
+ cancelCallback?: () => void;
202
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Upload stream queue management for bridging native ReadableStreams to QuickJS.
3
+ *
4
+ * This module implements the "Inverted Direct State Access" pattern:
5
+ * - Native stream reader reads data and pushes directly to QuickJS stream's internal queue
6
+ * - QuickJS stream's read() method reads from the queue
7
+ * - No async callbacks with QuickJS handles, avoiding handle lifetime issues
8
+ *
9
+ * The pattern is the reverse of download streaming (handle.ts:createNativeStreamFromState):
10
+ * - Download: QuickJS enqueues → host polls queue
11
+ * - Upload: Host pushes to queue → QuickJS reads
12
+ */
13
+ import type { UploadStreamQueue } from "./types.ts";
14
+ /**
15
+ * Create a new upload queue and register it in instance state.
16
+ * Returns the instance ID for accessing the queue via getUploadQueue().
17
+ */
18
+ export declare function createUploadQueue(): number;
19
+ /**
20
+ * Get an upload queue by its instance ID.
21
+ */
22
+ export declare function getUploadQueue(instanceId: number): UploadStreamQueue | undefined;
23
+ /**
24
+ * Clean up an upload queue when no longer needed.
25
+ */
26
+ export declare function cleanupUploadQueue(instanceId: number): void;
27
+ /**
28
+ * Push a chunk to a QuickJS ReadableStream's internal queue.
29
+ * If there are pending read requests, fulfills the first one immediately.
30
+ *
31
+ * @param streamInstanceId The instance ID of the QuickJS ReadableStream
32
+ * @param chunk The data chunk to enqueue
33
+ * @returns true if the chunk was successfully enqueued
34
+ */
35
+ export declare function pushToQuickJSStream(streamInstanceId: number, chunk: Uint8Array): boolean;
36
+ /**
37
+ * Close a QuickJS ReadableStream.
38
+ * If there are pending read requests, fulfills them with done: true.
39
+ *
40
+ * @param streamInstanceId The instance ID of the QuickJS ReadableStream
41
+ */
42
+ export declare function closeQuickJSStream(streamInstanceId: number): void;
43
+ /**
44
+ * Error a QuickJS ReadableStream.
45
+ *
46
+ * @param streamInstanceId The instance ID of the QuickJS ReadableStream
47
+ * @param error The error to propagate
48
+ */
49
+ export declare function errorQuickJSStream(streamInstanceId: number, error: Error): void;
50
+ /**
51
+ * Check if a QuickJS ReadableStream's queue is full (for backpressure).
52
+ *
53
+ * @param streamInstanceId The instance ID of the QuickJS ReadableStream
54
+ * @returns true if the queue has reached capacity
55
+ */
56
+ export declare function isQuickJSStreamQueueFull(streamInstanceId: number): boolean;
57
+ /**
58
+ * Start a background reader that reads from a native ReadableStream
59
+ * and pushes chunks directly to a QuickJS ReadableStream.
60
+ *
61
+ * The reader respects backpressure by pausing when the QuickJS queue is full
62
+ * (10+ chunks) and resuming when QuickJS consumes chunks.
63
+ *
64
+ * @param nativeStream The native ReadableStream to read from
65
+ * @param quickjsStreamInstanceId The instance ID of the QuickJS ReadableStream
66
+ * @param onChunkPushed Optional callback called after each chunk is pushed
67
+ * @returns A cleanup function to cancel the reader
68
+ */
69
+ export declare function startNativeStreamReader(nativeStream: ReadableStream<Uint8Array>, quickjsStreamInstanceId: number, onChunkPushed?: () => void): () => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ricsam/quickjs-fetch",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "main": "./dist/cjs/index.cjs",
5
5
  "types": "./dist/types/index.d.ts",
6
6
  "exports": {
@@ -16,7 +16,7 @@
16
16
  "typecheck": "tsc --noEmit"
17
17
  },
18
18
  "dependencies": {
19
- "@ricsam/quickjs-core": "^0.2.7",
19
+ "@ricsam/quickjs-core": "^0.2.8",
20
20
  "quickjs-emscripten": "^0.31.0"
21
21
  },
22
22
  "peerDependencies": {