@ricsam/isolate-fetch 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -0
- package/dist/cjs/index.cjs +1800 -0
- package/dist/cjs/index.cjs.map +10 -0
- package/dist/cjs/package.json +5 -0
- package/dist/cjs/stream-state.cjs +230 -0
- package/dist/cjs/stream-state.cjs.map +10 -0
- package/{src/index.ts → dist/mjs/index.mjs} +357 -923
- package/dist/mjs/index.mjs.map +10 -0
- package/dist/mjs/package.json +5 -0
- package/dist/mjs/stream-state.mjs +199 -0
- package/dist/mjs/stream-state.mjs.map +10 -0
- package/dist/types/index.d.ts +63 -0
- package/dist/types/isolate.d.ts +267 -0
- package/dist/types/stream-state.d.ts +61 -0
- package/package.json +41 -13
- package/CHANGELOG.md +0 -9
- package/src/debug-delayed.test.ts +0 -89
- package/src/debug-streaming.test.ts +0 -81
- package/src/download-streaming-simple.test.ts +0 -167
- package/src/download-streaming.test.ts +0 -286
- package/src/form-data.test.ts +0 -824
- package/src/formdata.test.ts +0 -212
- package/src/headers.test.ts +0 -582
- package/src/host-backed-stream.test.ts +0 -363
- package/src/index.test.ts +0 -274
- package/src/integration.test.ts +0 -665
- package/src/request.test.ts +0 -482
- package/src/response.test.ts +0 -520
- package/src/serve.test.ts +0 -425
- package/src/stream-state.test.ts +0 -338
- package/src/stream-state.ts +0 -337
- package/src/upload-streaming.test.ts +0 -373
- package/src/websocket.test.ts +0 -627
- package/tsconfig.json +0 -8
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global Type Definitions for @ricsam/isolate-fetch
|
|
3
|
+
*
|
|
4
|
+
* These types define the globals injected by setupFetch() into an isolated-vm context.
|
|
5
|
+
* Use these types to typecheck user code that will run inside the V8 isolate.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // Typecheck isolate code with serve()
|
|
9
|
+
* type WebSocketData = { id: number; connectedAt: number };
|
|
10
|
+
*
|
|
11
|
+
* serve({
|
|
12
|
+
* fetch(request, server) {
|
|
13
|
+
* if (request.url.includes("/ws")) {
|
|
14
|
+
* // server.upgrade knows data should be WebSocketData
|
|
15
|
+
* server.upgrade(request, { data: { id: 123, connectedAt: Date.now() } });
|
|
16
|
+
* return new Response(null, { status: 101 });
|
|
17
|
+
* }
|
|
18
|
+
* return new Response("Hello!");
|
|
19
|
+
* },
|
|
20
|
+
* websocket: {
|
|
21
|
+
* // Type hint - specifies the type of ws.data
|
|
22
|
+
* data: {} as WebSocketData,
|
|
23
|
+
* message(ws, message) {
|
|
24
|
+
* // ws.data is typed as WebSocketData
|
|
25
|
+
* console.log("User", ws.data.id, "says:", message);
|
|
26
|
+
* ws.send("Echo: " + message);
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
* });
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
export {};
|
|
33
|
+
|
|
34
|
+
declare global {
|
|
35
|
+
// ============================================
|
|
36
|
+
// Standard Fetch API (from lib.dom)
|
|
37
|
+
// ============================================
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Headers class for HTTP headers manipulation.
|
|
41
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Headers
|
|
42
|
+
*/
|
|
43
|
+
const Headers: typeof globalThis.Headers;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Request class for HTTP requests.
|
|
47
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Request
|
|
48
|
+
*/
|
|
49
|
+
const Request: typeof globalThis.Request;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Response class for HTTP responses.
|
|
53
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Response
|
|
54
|
+
*/
|
|
55
|
+
const Response: typeof globalThis.Response;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* AbortController for cancelling fetch requests.
|
|
59
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/AbortController
|
|
60
|
+
*/
|
|
61
|
+
const AbortController: typeof globalThis.AbortController;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* AbortSignal for listening to abort events.
|
|
65
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal
|
|
66
|
+
*/
|
|
67
|
+
const AbortSignal: typeof globalThis.AbortSignal;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* FormData for constructing form data.
|
|
71
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/FormData
|
|
72
|
+
*/
|
|
73
|
+
const FormData: typeof globalThis.FormData;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Fetch function for making HTTP requests.
|
|
77
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/fetch
|
|
78
|
+
*/
|
|
79
|
+
function fetch(
|
|
80
|
+
input: RequestInfo | URL,
|
|
81
|
+
init?: RequestInit
|
|
82
|
+
): Promise<Response>;
|
|
83
|
+
|
|
84
|
+
// ============================================
|
|
85
|
+
// Isolate-specific: serve() API
|
|
86
|
+
// ============================================
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Server interface for handling WebSocket upgrades within serve() handlers.
|
|
90
|
+
*
|
|
91
|
+
* @typeParam T - The type of data associated with WebSocket connections
|
|
92
|
+
*/
|
|
93
|
+
interface Server<T = unknown> {
|
|
94
|
+
/**
|
|
95
|
+
* Upgrade an HTTP request to a WebSocket connection.
|
|
96
|
+
*
|
|
97
|
+
* @param request - The incoming HTTP request to upgrade
|
|
98
|
+
* @param options - Optional data to associate with the WebSocket connection
|
|
99
|
+
* @returns true if upgrade will proceed, false otherwise
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* serve({
|
|
103
|
+
* fetch(request, server) {
|
|
104
|
+
* if (server.upgrade(request, { data: { userId: 123 } })) {
|
|
105
|
+
* return new Response(null, { status: 101 });
|
|
106
|
+
* }
|
|
107
|
+
* return new Response("Upgrade failed", { status: 400 });
|
|
108
|
+
* }
|
|
109
|
+
* });
|
|
110
|
+
*/
|
|
111
|
+
upgrade(request: Request, options?: { data?: T }): boolean;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* ServerWebSocket interface for WebSocket connections within serve() handlers.
|
|
116
|
+
*
|
|
117
|
+
* @typeParam T - The type of data associated with this WebSocket connection
|
|
118
|
+
*/
|
|
119
|
+
interface ServerWebSocket<T = unknown> {
|
|
120
|
+
/**
|
|
121
|
+
* User data associated with this connection.
|
|
122
|
+
* Set via `server.upgrade(request, { data: ... })`.
|
|
123
|
+
*/
|
|
124
|
+
readonly data: T;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Send a message to the client.
|
|
128
|
+
*
|
|
129
|
+
* @param message - The message to send (string, ArrayBuffer, or Uint8Array)
|
|
130
|
+
*/
|
|
131
|
+
send(message: string | ArrayBuffer | Uint8Array): void;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Close the WebSocket connection.
|
|
135
|
+
*
|
|
136
|
+
* @param code - Optional close code (default: 1000)
|
|
137
|
+
* @param reason - Optional close reason
|
|
138
|
+
*/
|
|
139
|
+
close(code?: number, reason?: string): void;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* WebSocket ready state.
|
|
143
|
+
* - 0: CONNECTING
|
|
144
|
+
* - 1: OPEN
|
|
145
|
+
* - 2: CLOSING
|
|
146
|
+
* - 3: CLOSED
|
|
147
|
+
*/
|
|
148
|
+
readonly readyState: number;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Options for the serve() function.
|
|
153
|
+
*
|
|
154
|
+
* @typeParam T - The type of data associated with WebSocket connections
|
|
155
|
+
*/
|
|
156
|
+
interface ServeOptions<T = unknown> {
|
|
157
|
+
/**
|
|
158
|
+
* Handler for HTTP requests.
|
|
159
|
+
*
|
|
160
|
+
* @param request - The incoming HTTP request
|
|
161
|
+
* @param server - Server interface for WebSocket upgrades
|
|
162
|
+
* @returns Response or Promise resolving to Response
|
|
163
|
+
*/
|
|
164
|
+
fetch(request: Request, server: Server<T>): Response | Promise<Response>;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* WebSocket event handlers.
|
|
168
|
+
*/
|
|
169
|
+
websocket?: {
|
|
170
|
+
/**
|
|
171
|
+
* Type hint for WebSocket data. The value is not used at runtime.
|
|
172
|
+
* Specifies the type of `ws.data` for all handlers and `server.upgrade()`.
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* websocket: {
|
|
176
|
+
* data: {} as { userId: string },
|
|
177
|
+
* message(ws, message) {
|
|
178
|
+
* // ws.data.userId is typed as string
|
|
179
|
+
* }
|
|
180
|
+
* }
|
|
181
|
+
*/
|
|
182
|
+
data?: T;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Called when a WebSocket connection is opened.
|
|
186
|
+
*
|
|
187
|
+
* @param ws - The WebSocket connection
|
|
188
|
+
*/
|
|
189
|
+
open?(ws: ServerWebSocket<T>): void | Promise<void>;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Called when a message is received.
|
|
193
|
+
*
|
|
194
|
+
* @param ws - The WebSocket connection
|
|
195
|
+
* @param message - The received message (string or ArrayBuffer)
|
|
196
|
+
*/
|
|
197
|
+
message?(
|
|
198
|
+
ws: ServerWebSocket<T>,
|
|
199
|
+
message: string | ArrayBuffer
|
|
200
|
+
): void | Promise<void>;
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Called when the connection is closed.
|
|
204
|
+
*
|
|
205
|
+
* @param ws - The WebSocket connection
|
|
206
|
+
* @param code - The close code
|
|
207
|
+
* @param reason - The close reason
|
|
208
|
+
*/
|
|
209
|
+
close?(
|
|
210
|
+
ws: ServerWebSocket<T>,
|
|
211
|
+
code: number,
|
|
212
|
+
reason: string
|
|
213
|
+
): void | Promise<void>;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Called when an error occurs.
|
|
217
|
+
*
|
|
218
|
+
* @param ws - The WebSocket connection
|
|
219
|
+
* @param error - The error that occurred
|
|
220
|
+
*/
|
|
221
|
+
error?(ws: ServerWebSocket<T>, error: Error): void | Promise<void>;
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Register an HTTP server handler in the isolate.
|
|
227
|
+
*
|
|
228
|
+
* Only one serve() handler can be active at a time.
|
|
229
|
+
* Calling serve() again replaces the previous handler.
|
|
230
|
+
*
|
|
231
|
+
* @param options - Server configuration including fetch handler and optional WebSocket handlers
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* type WsData = { connectedAt: number };
|
|
235
|
+
*
|
|
236
|
+
* serve({
|
|
237
|
+
* fetch(request, server) {
|
|
238
|
+
* const url = new URL(request.url);
|
|
239
|
+
*
|
|
240
|
+
* if (url.pathname === "/ws") {
|
|
241
|
+
* if (server.upgrade(request, { data: { connectedAt: Date.now() } })) {
|
|
242
|
+
* return new Response(null, { status: 101 });
|
|
243
|
+
* }
|
|
244
|
+
* }
|
|
245
|
+
*
|
|
246
|
+
* if (url.pathname === "/api/hello") {
|
|
247
|
+
* return Response.json({ message: "Hello!" });
|
|
248
|
+
* }
|
|
249
|
+
*
|
|
250
|
+
* return new Response("Not Found", { status: 404 });
|
|
251
|
+
* },
|
|
252
|
+
* websocket: {
|
|
253
|
+
* data: {} as WsData,
|
|
254
|
+
* open(ws) {
|
|
255
|
+
* console.log("Connected at:", ws.data.connectedAt);
|
|
256
|
+
* },
|
|
257
|
+
* message(ws, message) {
|
|
258
|
+
* ws.send("Echo: " + message);
|
|
259
|
+
* },
|
|
260
|
+
* close(ws, code, reason) {
|
|
261
|
+
* console.log("Closed:", code, reason);
|
|
262
|
+
* }
|
|
263
|
+
* }
|
|
264
|
+
* });
|
|
265
|
+
*/
|
|
266
|
+
function serve<T = unknown>(options: ServeOptions<T>): void;
|
|
267
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import ivm from "isolated-vm";
|
|
2
|
+
export interface StreamState {
|
|
3
|
+
/** Buffered chunks waiting to be read */
|
|
4
|
+
queue: Uint8Array[];
|
|
5
|
+
/** Total bytes in queue (for backpressure) */
|
|
6
|
+
queueSize: number;
|
|
7
|
+
/** Stream has been closed (no more data) */
|
|
8
|
+
closed: boolean;
|
|
9
|
+
/** Stream encountered an error */
|
|
10
|
+
errored: boolean;
|
|
11
|
+
/** The error value if errored */
|
|
12
|
+
errorValue: unknown;
|
|
13
|
+
/** A pull is waiting for data */
|
|
14
|
+
pullWaiting: boolean;
|
|
15
|
+
/** Resolve function for waiting pull */
|
|
16
|
+
pullResolve: ((chunk: Uint8Array | null) => void) | null;
|
|
17
|
+
/** Reject function for waiting pull */
|
|
18
|
+
pullReject: ((error: unknown) => void) | null;
|
|
19
|
+
}
|
|
20
|
+
export interface StreamStateRegistry {
|
|
21
|
+
/** Create a new stream and return its ID */
|
|
22
|
+
create(): number;
|
|
23
|
+
/** Get stream state by ID */
|
|
24
|
+
get(streamId: number): StreamState | undefined;
|
|
25
|
+
/** Push a chunk to the stream's queue */
|
|
26
|
+
push(streamId: number, chunk: Uint8Array): boolean;
|
|
27
|
+
/** Pull a chunk from the stream (returns Promise that resolves when data available) */
|
|
28
|
+
pull(streamId: number): Promise<{
|
|
29
|
+
value: Uint8Array;
|
|
30
|
+
done: false;
|
|
31
|
+
} | {
|
|
32
|
+
done: true;
|
|
33
|
+
}>;
|
|
34
|
+
/** Close the stream (no more data) */
|
|
35
|
+
close(streamId: number): void;
|
|
36
|
+
/** Error the stream */
|
|
37
|
+
error(streamId: number, errorValue: unknown): void;
|
|
38
|
+
/** Check if stream queue is above high-water mark */
|
|
39
|
+
isQueueFull(streamId: number): boolean;
|
|
40
|
+
/** Delete stream state (cleanup) */
|
|
41
|
+
delete(streamId: number): void;
|
|
42
|
+
/** Clear all streams (context cleanup) */
|
|
43
|
+
clear(): void;
|
|
44
|
+
}
|
|
45
|
+
/** Maximum bytes to buffer before backpressure kicks in */
|
|
46
|
+
export declare const HIGH_WATER_MARK: number;
|
|
47
|
+
/** Maximum number of chunks in queue */
|
|
48
|
+
export declare const MAX_QUEUE_CHUNKS = 16;
|
|
49
|
+
export declare function createStreamStateRegistry(): StreamStateRegistry;
|
|
50
|
+
export declare function getStreamRegistryForContext(context: ivm.Context): StreamStateRegistry;
|
|
51
|
+
export declare function clearStreamRegistryForContext(context: ivm.Context): void;
|
|
52
|
+
/**
|
|
53
|
+
* Start reading from a native ReadableStream and push to host queue.
|
|
54
|
+
* Respects backpressure by pausing when queue is full.
|
|
55
|
+
*
|
|
56
|
+
* @param nativeStream The native ReadableStream to read from
|
|
57
|
+
* @param streamId The stream ID in the registry
|
|
58
|
+
* @param registry The stream state registry
|
|
59
|
+
* @returns Async cleanup function to cancel the reader
|
|
60
|
+
*/
|
|
61
|
+
export declare function startNativeStreamReader(nativeStream: ReadableStream<Uint8Array>, streamId: number, registry: StreamStateRegistry): () => Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ricsam/isolate-fetch",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"types": "./src/index.ts",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"main": "./dist/cjs/index.cjs",
|
|
5
|
+
"types": "./dist/types/index.d.ts",
|
|
7
6
|
"exports": {
|
|
8
7
|
".": {
|
|
9
|
-
"
|
|
10
|
-
"
|
|
8
|
+
"types": "./dist/types/index.d.ts",
|
|
9
|
+
"require": "./dist/cjs/index.cjs",
|
|
10
|
+
"import": "./dist/mjs/index.mjs"
|
|
11
|
+
},
|
|
12
|
+
"./isolate": {
|
|
13
|
+
"types": "./dist/types/isolate.d.ts"
|
|
11
14
|
}
|
|
12
15
|
},
|
|
13
16
|
"scripts": {
|
|
@@ -19,12 +22,37 @@
|
|
|
19
22
|
"@ricsam/isolate-core": "*",
|
|
20
23
|
"isolated-vm": "^6"
|
|
21
24
|
},
|
|
22
|
-
"devDependencies": {
|
|
23
|
-
"@ricsam/isolate-test-utils": "*",
|
|
24
|
-
"@types/node": "^24",
|
|
25
|
-
"typescript": "^5"
|
|
26
|
-
},
|
|
27
25
|
"peerDependencies": {
|
|
28
26
|
"isolated-vm": "^6"
|
|
29
|
-
}
|
|
30
|
-
|
|
27
|
+
},
|
|
28
|
+
"author": "Richard Samuelsson",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/ricsam/isolate.git"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/ricsam/isolate/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/ricsam/isolate#readme",
|
|
38
|
+
"keywords": [
|
|
39
|
+
"isolated-vm",
|
|
40
|
+
"sandbox",
|
|
41
|
+
"javascript",
|
|
42
|
+
"runtime",
|
|
43
|
+
"fetch",
|
|
44
|
+
"filesystem",
|
|
45
|
+
"streams",
|
|
46
|
+
"v8",
|
|
47
|
+
"isolate"
|
|
48
|
+
],
|
|
49
|
+
"description": "Fetch API implementation for isolated-vm V8 sandbox",
|
|
50
|
+
"module": "./dist/mjs/index.mjs",
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"files": [
|
|
55
|
+
"dist",
|
|
56
|
+
"README.md"
|
|
57
|
+
]
|
|
58
|
+
}
|
package/CHANGELOG.md
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { test, describe, beforeEach, afterEach, it } from "node:test";
|
|
2
|
-
import assert from "node:assert";
|
|
3
|
-
import ivm from "isolated-vm";
|
|
4
|
-
import {
|
|
5
|
-
setupFetch,
|
|
6
|
-
clearAllInstanceState,
|
|
7
|
-
type FetchHandle,
|
|
8
|
-
} from "./index.ts";
|
|
9
|
-
import { setupTimers, type TimersHandle } from "@ricsam/isolate-timers";
|
|
10
|
-
import { setupConsole } from "@ricsam/isolate-console";
|
|
11
|
-
import { clearStreamRegistryForContext } from "./stream-state.ts";
|
|
12
|
-
|
|
13
|
-
describe("Debug Delayed Streaming", () => {
|
|
14
|
-
it("delayed streaming response with setTimeout", { timeout: 10000 }, async () => {
|
|
15
|
-
const isolate = new ivm.Isolate();
|
|
16
|
-
const context = await isolate.createContext();
|
|
17
|
-
clearAllInstanceState();
|
|
18
|
-
|
|
19
|
-
const logs: string[] = [];
|
|
20
|
-
await setupConsole(context, {
|
|
21
|
-
onLog: (level, ...args) => {
|
|
22
|
-
logs.push(`[${level}] ${args.join(' ')}`);
|
|
23
|
-
console.log(`[ISOLATE] ${args.join(' ')}`);
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
const timersHandle = await setupTimers(context);
|
|
28
|
-
const fetchHandle = await setupFetch(context);
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
context.evalSync(`
|
|
32
|
-
console.log('Setting up serve');
|
|
33
|
-
serve({
|
|
34
|
-
async fetch(request) {
|
|
35
|
-
console.log('Fetch handler called');
|
|
36
|
-
let count = 0;
|
|
37
|
-
const stream = new ReadableStream({
|
|
38
|
-
async pull(controller) {
|
|
39
|
-
console.log('pull() called, count =', count);
|
|
40
|
-
if (count < 3) {
|
|
41
|
-
console.log('Starting setTimeout');
|
|
42
|
-
await new Promise(r => setTimeout(r, 10));
|
|
43
|
-
console.log('setTimeout resolved');
|
|
44
|
-
const data = "delayed" + count;
|
|
45
|
-
console.log('Enqueuing:', data);
|
|
46
|
-
controller.enqueue(new TextEncoder().encode(data));
|
|
47
|
-
count++;
|
|
48
|
-
console.log('Enqueued, returning');
|
|
49
|
-
} else {
|
|
50
|
-
console.log('Closing stream');
|
|
51
|
-
controller.close();
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
console.log('Creating Response');
|
|
56
|
-
const response = new Response(stream);
|
|
57
|
-
console.log('Response created, returning');
|
|
58
|
-
return response;
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
console.log('Serve registered');
|
|
62
|
-
`);
|
|
63
|
-
|
|
64
|
-
console.log('Calling dispatchRequest');
|
|
65
|
-
const response = await fetchHandle.dispatchRequest(
|
|
66
|
-
new Request("http://test/"),
|
|
67
|
-
{
|
|
68
|
-
tick: async () => {
|
|
69
|
-
// Advance virtual time by 50ms each tick to process timers
|
|
70
|
-
await timersHandle.tick(50);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
console.log('Got response, status:', response.status);
|
|
76
|
-
console.log('Calling response.text()');
|
|
77
|
-
|
|
78
|
-
const text = await response.text();
|
|
79
|
-
console.log('Got text:', text);
|
|
80
|
-
assert.strictEqual(text, "delayed0delayed1delayed2");
|
|
81
|
-
} finally {
|
|
82
|
-
fetchHandle.dispose();
|
|
83
|
-
timersHandle.dispose();
|
|
84
|
-
clearStreamRegistryForContext(context);
|
|
85
|
-
context.release();
|
|
86
|
-
isolate.dispose();
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
});
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { test, describe, beforeEach, afterEach, it } from "node:test";
|
|
2
|
-
import assert from "node:assert";
|
|
3
|
-
import ivm from "isolated-vm";
|
|
4
|
-
import {
|
|
5
|
-
setupFetch,
|
|
6
|
-
clearAllInstanceState,
|
|
7
|
-
type FetchHandle,
|
|
8
|
-
} from "./index.ts";
|
|
9
|
-
import { setupTimers, type TimersHandle } from "@ricsam/isolate-timers";
|
|
10
|
-
import { setupConsole } from "@ricsam/isolate-console";
|
|
11
|
-
import { clearStreamRegistryForContext } from "./stream-state.ts";
|
|
12
|
-
|
|
13
|
-
describe("Debug Streaming", () => {
|
|
14
|
-
it("debug pull-based stream", { timeout: 5000 }, async () => {
|
|
15
|
-
const isolate = new ivm.Isolate();
|
|
16
|
-
const context = await isolate.createContext();
|
|
17
|
-
clearAllInstanceState();
|
|
18
|
-
|
|
19
|
-
const logs: string[] = [];
|
|
20
|
-
await setupConsole(context, {
|
|
21
|
-
onLog: (level, ...args) => {
|
|
22
|
-
logs.push(`[${level}] ${args.join(' ')}`);
|
|
23
|
-
console.log(`[ISOLATE] ${args.join(' ')}`);
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
const timersHandle = await setupTimers(context);
|
|
28
|
-
const fetchHandle = await setupFetch(context);
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
context.evalSync(`
|
|
32
|
-
console.log('Setting up serve');
|
|
33
|
-
serve({
|
|
34
|
-
async fetch(request) {
|
|
35
|
-
console.log('Fetch handler called');
|
|
36
|
-
let count = 0;
|
|
37
|
-
const stream = new ReadableStream({
|
|
38
|
-
pull(controller) {
|
|
39
|
-
console.log('pull() called, count =', count);
|
|
40
|
-
if (count < 3) {
|
|
41
|
-
const data = "chunk" + count;
|
|
42
|
-
console.log('Enqueuing:', data);
|
|
43
|
-
controller.enqueue(new TextEncoder().encode(data));
|
|
44
|
-
count++;
|
|
45
|
-
} else {
|
|
46
|
-
console.log('Closing stream');
|
|
47
|
-
controller.close();
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
console.log('Creating Response');
|
|
52
|
-
const response = new Response(stream);
|
|
53
|
-
console.log('Response created, returning');
|
|
54
|
-
return response;
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
console.log('Serve registered');
|
|
58
|
-
`);
|
|
59
|
-
|
|
60
|
-
console.log('Calling dispatchRequest');
|
|
61
|
-
const response = await fetchHandle.dispatchRequest(
|
|
62
|
-
new Request("http://test/"),
|
|
63
|
-
{ tick: () => timersHandle.tick() }
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
console.log('Got response, status:', response.status);
|
|
67
|
-
console.log('Response body:', response.body);
|
|
68
|
-
console.log('Calling response.text()');
|
|
69
|
-
|
|
70
|
-
const text = await response.text();
|
|
71
|
-
console.log('Got text:', text);
|
|
72
|
-
assert.strictEqual(text, "chunk0chunk1chunk2");
|
|
73
|
-
} finally {
|
|
74
|
-
fetchHandle.dispose();
|
|
75
|
-
timersHandle.dispose();
|
|
76
|
-
clearStreamRegistryForContext(context);
|
|
77
|
-
context.release();
|
|
78
|
-
isolate.dispose();
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
});
|