@ricsam/isolate-test-utils 0.1.10 → 0.1.12

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.
Files changed (45) hide show
  1. package/dist/cjs/fetch-context.cjs +66 -0
  2. package/dist/cjs/fetch-context.cjs.map +10 -0
  3. package/dist/cjs/fs-context.cjs +73 -0
  4. package/dist/cjs/fs-context.cjs.map +10 -0
  5. package/dist/cjs/index.cjs +121 -0
  6. package/dist/cjs/index.cjs.map +10 -0
  7. package/{src/mock-fs.ts → dist/cjs/mock-fs.cjs} +63 -97
  8. package/dist/cjs/mock-fs.cjs.map +10 -0
  9. package/dist/cjs/native-input-test.cjs +347 -0
  10. package/dist/cjs/native-input-test.cjs.map +10 -0
  11. package/dist/cjs/package.json +5 -0
  12. package/dist/cjs/runtime-context.cjs +97 -0
  13. package/dist/cjs/runtime-context.cjs.map +10 -0
  14. package/dist/cjs/server.cjs +109 -0
  15. package/dist/cjs/server.cjs.map +10 -0
  16. package/dist/mjs/fetch-context.mjs +23 -0
  17. package/dist/mjs/fetch-context.mjs.map +10 -0
  18. package/dist/mjs/fs-context.mjs +30 -0
  19. package/dist/mjs/fs-context.mjs.map +10 -0
  20. package/dist/mjs/index.mjs +78 -0
  21. package/dist/mjs/index.mjs.map +10 -0
  22. package/dist/mjs/mock-fs.mjs +181 -0
  23. package/dist/mjs/mock-fs.mjs.map +10 -0
  24. package/{src/native-input-test.ts → dist/mjs/native-input-test.mjs} +60 -169
  25. package/dist/mjs/native-input-test.mjs.map +10 -0
  26. package/dist/mjs/package.json +5 -0
  27. package/dist/mjs/runtime-context.mjs +67 -0
  28. package/dist/mjs/runtime-context.mjs.map +10 -0
  29. package/dist/mjs/server.mjs +79 -0
  30. package/dist/mjs/server.mjs.map +10 -0
  31. package/dist/types/fetch-context.d.ts +7 -0
  32. package/dist/types/fs-context.d.ts +30 -0
  33. package/dist/types/index.d.ts +88 -0
  34. package/dist/types/mock-fs.d.ts +59 -0
  35. package/dist/types/native-input-test.d.ts +29 -0
  36. package/dist/types/runtime-context.d.ts +73 -0
  37. package/dist/types/server.d.ts +53 -0
  38. package/package.json +45 -18
  39. package/CHANGELOG.md +0 -119
  40. package/src/fetch-context.ts +0 -33
  41. package/src/fs-context.ts +0 -65
  42. package/src/index.test.ts +0 -472
  43. package/src/index.ts +0 -177
  44. package/src/runtime-context.ts +0 -148
  45. package/src/server.ts +0 -150
@@ -1,50 +1,14 @@
1
+ // packages/test-utils/src/native-input-test.ts
1
2
  import ivm from "isolated-vm";
2
-
3
- export interface TestRuntime {
4
- logs: Record<string, unknown>;
5
- result: unknown;
6
- }
7
-
8
- export interface TestRunner {
9
- input(inputs: Record<string, unknown>): TestRuntime;
10
- }
11
-
12
- /**
13
- * Run isolate code with native objects as input and capture logs
14
- *
15
- * This utility allows testing whether native objects passed INTO the isolate
16
- * behave like isolate instances. It converts native web API classes (Headers,
17
- * Request, Response, URL, Blob, File, FormData) to their isolate equivalents
18
- * before executing the test code.
19
- *
20
- * @example
21
- * const runtime = runTestCode(ctx.context, `
22
- * const headers = testingInput.headers;
23
- * log("instanceof", headers instanceof Headers);
24
- * log("contentType", headers.get("content-type"));
25
- * `).input({
26
- * headers: new Headers({ "content-type": "application/json" })
27
- * });
28
- *
29
- * expect(runtime.logs.instanceof).toBe(true);
30
- * expect(runtime.logs.contentType).toBe("application/json");
31
- */
32
- export function runTestCode(context: ivm.Context, code: string): TestRunner {
3
+ function runTestCode(context, code) {
33
4
  return {
34
- input(inputs: Record<string, unknown>): TestRuntime {
35
- const logs: Record<string, unknown> = {};
36
-
37
- // Setup log capture - log(tag, value) stores as logs[tag] = value
38
- // Values are unmarshalled back to native types for bidirectional testing
39
- const logCallback = new ivm.Callback(
40
- (tag: string, valueJson: string) => {
41
- const value = JSON.parse(valueJson);
42
- logs[tag] = unmarshalFromJson(value);
43
- }
44
- );
5
+ input(inputs) {
6
+ const logs = {};
7
+ const logCallback = new ivm.Callback((tag, valueJson) => {
8
+ const value = JSON.parse(valueJson);
9
+ logs[tag] = unmarshalFromJson(value);
10
+ });
45
11
  context.global.setSync("__log_callback__", logCallback);
46
-
47
- // Create a wrapper log function that serializes values
48
12
  context.evalSync(`
49
13
  globalThis.log = function(tag, value) {
50
14
  __log_callback__(tag, JSON.stringify(__serializeForLog__(value)));
@@ -120,16 +84,11 @@ export function runTestCode(context: ivm.Context, code: string): TestRunner {
120
84
  return value;
121
85
  };
122
86
  `);
123
-
124
- // Marshal inputs with special handling for native web API classes
125
87
  marshalInputs(context, inputs);
126
-
127
- // Run the code
128
- let returnValue: unknown = undefined;
88
+ let returnValue = undefined;
129
89
  try {
130
90
  returnValue = context.evalSync(code);
131
91
  } catch (error) {
132
- // Clean up before re-throwing
133
92
  context.evalSync(`
134
93
  delete globalThis.testingInput;
135
94
  delete globalThis.log;
@@ -138,59 +97,34 @@ export function runTestCode(context: ivm.Context, code: string): TestRunner {
138
97
  `);
139
98
  throw error;
140
99
  }
141
-
142
- // Cleanup
143
100
  context.evalSync(`
144
101
  delete globalThis.testingInput;
145
102
  delete globalThis.log;
146
103
  delete globalThis.__log_callback__;
147
104
  delete globalThis.__serializeForLog__;
148
105
  `);
149
-
150
106
  return { logs, result: returnValue };
151
- },
107
+ }
152
108
  };
153
109
  }
154
-
155
- /**
156
- * Marshal inputs into the isolate, converting native web API classes
157
- */
158
- function marshalInputs(
159
- context: ivm.Context,
160
- inputs: Record<string, unknown>
161
- ): void {
162
- // Create the testingInput object in the isolate
110
+ function marshalInputs(context, inputs) {
163
111
  context.evalSync(`globalThis.testingInput = {};`);
164
-
165
112
  for (const [key, value] of Object.entries(inputs)) {
166
113
  marshalValue(context, `testingInput.${key}`, value);
167
114
  }
168
115
  }
169
-
170
- /**
171
- * Marshal a single value into the isolate at the given path
172
- */
173
- function marshalValue(
174
- context: ivm.Context,
175
- path: string,
176
- value: unknown
177
- ): void {
178
- // Check for native Headers
116
+ function marshalValue(context, path, value) {
179
117
  if (value instanceof Headers) {
180
- const pairs: [string, string][] = [];
118
+ const pairs = [];
181
119
  value.forEach((v, k) => pairs.push([k, v]));
182
120
  const pairsJson = JSON.stringify(pairs);
183
121
  context.evalSync(`${path} = new Headers(${pairsJson});`);
184
122
  return;
185
123
  }
186
-
187
- // Check for native Request
188
124
  if (value instanceof Request) {
189
- // First marshal the headers
190
- const headerPairs: [string, string][] = [];
125
+ const headerPairs = [];
191
126
  value.headers.forEach((v, k) => headerPairs.push([k, v]));
192
127
  const headersJson = JSON.stringify(headerPairs);
193
-
194
128
  const urlJson = JSON.stringify(value.url);
195
129
  const methodJson = JSON.stringify(value.method);
196
130
  const modeJson = JSON.stringify(value.mode);
@@ -200,7 +134,6 @@ function marshalValue(
200
134
  const referrerJson = JSON.stringify(value.referrer);
201
135
  const referrerPolicyJson = JSON.stringify(value.referrerPolicy);
202
136
  const integrityJson = JSON.stringify(value.integrity);
203
-
204
137
  context.evalSync(`
205
138
  ${path} = new Request(${urlJson}, {
206
139
  method: ${methodJson},
@@ -216,16 +149,12 @@ function marshalValue(
216
149
  `);
217
150
  return;
218
151
  }
219
-
220
- // Check for native Response
221
152
  if (value instanceof Response) {
222
- const headerPairs: [string, string][] = [];
153
+ const headerPairs = [];
223
154
  value.headers.forEach((v, k) => headerPairs.push([k, v]));
224
155
  const headersJson = JSON.stringify(headerPairs);
225
-
226
156
  const statusJson = JSON.stringify(value.status);
227
157
  const statusTextJson = JSON.stringify(value.statusText);
228
-
229
158
  context.evalSync(`
230
159
  ${path} = new Response(null, {
231
160
  status: ${statusJson},
@@ -235,16 +164,12 @@ function marshalValue(
235
164
  `);
236
165
  return;
237
166
  }
238
-
239
- // Check for native FormData
240
167
  if (value instanceof FormData) {
241
168
  context.evalSync(`${path} = new FormData();`);
242
-
243
169
  for (const [key, entryValue] of value.entries()) {
244
170
  const keyJson = JSON.stringify(key);
245
-
246
171
  if (typeof entryValue !== "string") {
247
- const file = entryValue as File;
172
+ const file = entryValue;
248
173
  const nameJson = JSON.stringify(file.name);
249
174
  const typeJson = JSON.stringify(file.type);
250
175
  const lastModifiedJson = JSON.stringify(file.lastModified);
@@ -252,162 +177,128 @@ function marshalValue(
252
177
  ${path}.append(${keyJson}, new File([], ${nameJson}, { type: ${typeJson}, lastModified: ${lastModifiedJson} }));
253
178
  `);
254
179
  } else {
255
- const valueJson = JSON.stringify(entryValue);
256
- context.evalSync(`${path}.append(${keyJson}, ${valueJson});`);
180
+ const valueJson2 = JSON.stringify(entryValue);
181
+ context.evalSync(`${path}.append(${keyJson}, ${valueJson2});`);
257
182
  }
258
183
  }
259
184
  return;
260
185
  }
261
-
262
- // Check for native URL
263
186
  if (value instanceof URL) {
264
187
  const hrefJson = JSON.stringify(value.href);
265
188
  context.evalSync(`${path} = new URL(${hrefJson});`);
266
189
  return;
267
190
  }
268
-
269
- // Check for native File (before Blob, since File extends Blob)
270
191
  if (value instanceof File) {
271
192
  const nameJson = JSON.stringify(value.name);
272
193
  const typeJson = JSON.stringify(value.type);
273
194
  const lastModifiedJson = JSON.stringify(value.lastModified);
274
- context.evalSync(
275
- `${path} = new File([], ${nameJson}, { type: ${typeJson}, lastModified: ${lastModifiedJson} });`
276
- );
195
+ context.evalSync(`${path} = new File([], ${nameJson}, { type: ${typeJson}, lastModified: ${lastModifiedJson} });`);
277
196
  return;
278
197
  }
279
-
280
- // Check for native Blob
281
198
  if (value instanceof Blob) {
282
199
  const typeJson = JSON.stringify(value.type);
283
200
  context.evalSync(`${path} = new Blob([], { type: ${typeJson} });`);
284
201
  return;
285
202
  }
286
-
287
- // Handle arrays recursively
288
203
  if (Array.isArray(value)) {
289
204
  context.evalSync(`${path} = [];`);
290
- for (let i = 0; i < value.length; i++) {
205
+ for (let i = 0;i < value.length; i++) {
291
206
  marshalValue(context, `${path}[${i}]`, value[i]);
292
207
  }
293
208
  return;
294
209
  }
295
-
296
- // Handle plain objects recursively
297
210
  if (value && typeof value === "object" && value.constructor === Object) {
298
211
  context.evalSync(`${path} = {};`);
299
212
  for (const [key, val] of Object.entries(value)) {
300
- // Use bracket notation for safe property access
301
213
  marshalValue(context, `${path}[${JSON.stringify(key)}]`, val);
302
214
  }
303
215
  return;
304
216
  }
305
-
306
- // For primitives, set directly via JSON
307
217
  const valueJson = JSON.stringify(value);
308
218
  context.evalSync(`${path} = ${valueJson};`);
309
219
  }
310
-
311
- /**
312
- * Unmarshal a value from JSON, converting special __type__ markers back to native instances
313
- */
314
- function unmarshalFromJson(value: unknown): unknown {
220
+ function unmarshalFromJson(value) {
315
221
  if (value === null || value === undefined) {
316
222
  return value;
317
223
  }
318
-
319
224
  if (Array.isArray(value)) {
320
225
  return value.map((v) => unmarshalFromJson(v));
321
226
  }
322
-
323
227
  if (typeof value === "object") {
324
- const obj = value as Record<string, unknown>;
325
-
326
- // Check for special type markers
228
+ const obj = value;
327
229
  if (obj.__type__ === "Headers") {
328
- const pairs = obj.pairs as [string, string][];
329
- const headers = new Headers();
230
+ const pairs = obj.pairs;
231
+ const headers = new Headers;
330
232
  for (const [k, v] of pairs) {
331
233
  headers.append(k, v);
332
234
  }
333
235
  return headers;
334
236
  }
335
-
336
237
  if (obj.__type__ === "Request") {
337
- const headers = new Headers();
338
- for (const [k, v] of obj.headers as [string, string][]) {
238
+ const headers = new Headers;
239
+ for (const [k, v] of obj.headers) {
339
240
  headers.append(k, v);
340
241
  }
341
- return new Request(obj.url as string, {
342
- method: obj.method as string,
242
+ return new Request(obj.url, {
243
+ method: obj.method,
343
244
  headers,
344
- mode: obj.mode as Request["mode"],
345
- credentials: obj.credentials as Request["credentials"],
346
- cache: obj.cache as Request["cache"],
347
- redirect: obj.redirect as Request["redirect"],
348
- referrer: obj.referrer as string,
349
- referrerPolicy: obj.referrerPolicy as Request["referrerPolicy"],
350
- integrity: obj.integrity as string,
245
+ mode: obj.mode,
246
+ credentials: obj.credentials,
247
+ cache: obj.cache,
248
+ redirect: obj.redirect,
249
+ referrer: obj.referrer,
250
+ referrerPolicy: obj.referrerPolicy,
251
+ integrity: obj.integrity
351
252
  });
352
253
  }
353
-
354
254
  if (obj.__type__ === "Response") {
355
- const headers = new Headers();
356
- for (const [k, v] of obj.headers as [string, string][]) {
255
+ const headers = new Headers;
256
+ for (const [k, v] of obj.headers) {
357
257
  headers.append(k, v);
358
258
  }
359
259
  return new Response(null, {
360
- status: obj.status as number,
361
- statusText: obj.statusText as string,
362
- headers,
260
+ status: obj.status,
261
+ statusText: obj.statusText,
262
+ headers
363
263
  });
364
264
  }
365
-
366
265
  if (obj.__type__ === "FormData") {
367
- const formData = new FormData();
368
- for (const [k, v] of obj.entries as [string, unknown][]) {
369
- if (
370
- typeof v === "object" &&
371
- v !== null &&
372
- (v as Record<string, unknown>).__type__ === "File"
373
- ) {
374
- const fileObj = v as Record<string, unknown>;
375
- formData.append(
376
- k,
377
- new File([], fileObj.name as string, {
378
- type: fileObj.type as string,
379
- lastModified: fileObj.lastModified as number,
380
- })
381
- );
266
+ const formData = new FormData;
267
+ for (const [k, v] of obj.entries) {
268
+ if (typeof v === "object" && v !== null && v.__type__ === "File") {
269
+ const fileObj = v;
270
+ formData.append(k, new File([], fileObj.name, {
271
+ type: fileObj.type,
272
+ lastModified: fileObj.lastModified
273
+ }));
382
274
  } else {
383
- formData.append(k, v as string);
275
+ formData.append(k, v);
384
276
  }
385
277
  }
386
278
  return formData;
387
279
  }
388
-
389
280
  if (obj.__type__ === "URL") {
390
- return new URL(obj.href as string);
281
+ return new URL(obj.href);
391
282
  }
392
-
393
283
  if (obj.__type__ === "File") {
394
- return new File([], obj.name as string, {
395
- type: obj.type as string,
396
- lastModified: obj.lastModified as number,
284
+ return new File([], obj.name, {
285
+ type: obj.type,
286
+ lastModified: obj.lastModified
397
287
  });
398
288
  }
399
-
400
289
  if (obj.__type__ === "Blob") {
401
- return new Blob([], { type: obj.type as string });
290
+ return new Blob([], { type: obj.type });
402
291
  }
403
-
404
- // Plain object - recursively unmarshal properties
405
- const result: Record<string, unknown> = {};
292
+ const result = {};
406
293
  for (const [k, v] of Object.entries(obj)) {
407
294
  result[k] = unmarshalFromJson(v);
408
295
  }
409
296
  return result;
410
297
  }
411
-
412
298
  return value;
413
299
  }
300
+ export {
301
+ runTestCode
302
+ };
303
+
304
+ //# debugId=EDB1526900BA89BD64756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/native-input-test.ts"],
4
+ "sourcesContent": [
5
+ "import ivm from \"isolated-vm\";\n\nexport interface TestRuntime {\n logs: Record<string, unknown>;\n result: unknown;\n}\n\nexport interface TestRunner {\n input(inputs: Record<string, unknown>): TestRuntime;\n}\n\n/**\n * Run isolate code with native objects as input and capture logs\n *\n * This utility allows testing whether native objects passed INTO the isolate\n * behave like isolate instances. It converts native web API classes (Headers,\n * Request, Response, URL, Blob, File, FormData) to their isolate equivalents\n * before executing the test code.\n *\n * @example\n * const runtime = runTestCode(ctx.context, `\n * const headers = testingInput.headers;\n * log(\"instanceof\", headers instanceof Headers);\n * log(\"contentType\", headers.get(\"content-type\"));\n * `).input({\n * headers: new Headers({ \"content-type\": \"application/json\" })\n * });\n *\n * expect(runtime.logs.instanceof).toBe(true);\n * expect(runtime.logs.contentType).toBe(\"application/json\");\n */\nexport function runTestCode(context: ivm.Context, code: string): TestRunner {\n return {\n input(inputs: Record<string, unknown>): TestRuntime {\n const logs: Record<string, unknown> = {};\n\n // Setup log capture - log(tag, value) stores as logs[tag] = value\n // Values are unmarshalled back to native types for bidirectional testing\n const logCallback = new ivm.Callback(\n (tag: string, valueJson: string) => {\n const value = JSON.parse(valueJson);\n logs[tag] = unmarshalFromJson(value);\n }\n );\n context.global.setSync(\"__log_callback__\", logCallback);\n\n // Create a wrapper log function that serializes values\n context.evalSync(`\n globalThis.log = function(tag, value) {\n __log_callback__(tag, JSON.stringify(__serializeForLog__(value)));\n };\n\n globalThis.__serializeForLog__ = function(value) {\n if (typeof Headers !== 'undefined' && value instanceof Headers) {\n const pairs = [];\n for (const [k, v] of value) pairs.push([k, v]);\n return { __type__: 'Headers', pairs };\n }\n if (typeof Request !== 'undefined' && value instanceof Request) {\n const headers = [];\n for (const [k, v] of value.headers) headers.push([k, v]);\n return {\n __type__: 'Request',\n url: value.url,\n method: value.method,\n headers,\n mode: value.mode,\n credentials: value.credentials,\n cache: value.cache,\n redirect: value.redirect,\n referrer: value.referrer,\n referrerPolicy: value.referrerPolicy,\n integrity: value.integrity,\n };\n }\n if (typeof Response !== 'undefined' && value instanceof Response) {\n const headers = [];\n for (const [k, v] of value.headers) headers.push([k, v]);\n return {\n __type__: 'Response',\n status: value.status,\n statusText: value.statusText,\n ok: value.ok,\n headers,\n type: value.type,\n redirected: value.redirected,\n url: value.url,\n };\n }\n if (typeof FormData !== 'undefined' && value instanceof FormData) {\n const entries = [];\n for (const [k, v] of value) {\n if (typeof File !== 'undefined' && v instanceof File) {\n entries.push([k, { __type__: 'File', name: v.name, type: v.type, lastModified: v.lastModified }]);\n } else {\n entries.push([k, v]);\n }\n }\n return { __type__: 'FormData', entries };\n }\n if (value instanceof URL) {\n return { __type__: 'URL', href: value.href };\n }\n if (value instanceof File) {\n return { __type__: 'File', name: value.name, type: value.type, lastModified: value.lastModified };\n }\n if (value instanceof Blob) {\n return { __type__: 'Blob', type: value.type, size: value.size };\n }\n if (Array.isArray(value)) {\n return value.map(v => __serializeForLog__(v));\n }\n if (value && typeof value === 'object' && value.constructor === Object) {\n const result = {};\n for (const [k, v] of Object.entries(value)) {\n result[k] = __serializeForLog__(v);\n }\n return result;\n }\n return value;\n };\n `);\n\n // Marshal inputs with special handling for native web API classes\n marshalInputs(context, inputs);\n\n // Run the code\n let returnValue: unknown = undefined;\n try {\n returnValue = context.evalSync(code);\n } catch (error) {\n // Clean up before re-throwing\n context.evalSync(`\n delete globalThis.testingInput;\n delete globalThis.log;\n delete globalThis.__log_callback__;\n delete globalThis.__serializeForLog__;\n `);\n throw error;\n }\n\n // Cleanup\n context.evalSync(`\n delete globalThis.testingInput;\n delete globalThis.log;\n delete globalThis.__log_callback__;\n delete globalThis.__serializeForLog__;\n `);\n\n return { logs, result: returnValue };\n },\n };\n}\n\n/**\n * Marshal inputs into the isolate, converting native web API classes\n */\nfunction marshalInputs(\n context: ivm.Context,\n inputs: Record<string, unknown>\n): void {\n // Create the testingInput object in the isolate\n context.evalSync(`globalThis.testingInput = {};`);\n\n for (const [key, value] of Object.entries(inputs)) {\n marshalValue(context, `testingInput.${key}`, value);\n }\n}\n\n/**\n * Marshal a single value into the isolate at the given path\n */\nfunction marshalValue(\n context: ivm.Context,\n path: string,\n value: unknown\n): void {\n // Check for native Headers\n if (value instanceof Headers) {\n const pairs: [string, string][] = [];\n value.forEach((v, k) => pairs.push([k, v]));\n const pairsJson = JSON.stringify(pairs);\n context.evalSync(`${path} = new Headers(${pairsJson});`);\n return;\n }\n\n // Check for native Request\n if (value instanceof Request) {\n // First marshal the headers\n const headerPairs: [string, string][] = [];\n value.headers.forEach((v, k) => headerPairs.push([k, v]));\n const headersJson = JSON.stringify(headerPairs);\n\n const urlJson = JSON.stringify(value.url);\n const methodJson = JSON.stringify(value.method);\n const modeJson = JSON.stringify(value.mode);\n const credentialsJson = JSON.stringify(value.credentials);\n const cacheJson = JSON.stringify(value.cache);\n const redirectJson = JSON.stringify(value.redirect);\n const referrerJson = JSON.stringify(value.referrer);\n const referrerPolicyJson = JSON.stringify(value.referrerPolicy);\n const integrityJson = JSON.stringify(value.integrity);\n\n context.evalSync(`\n ${path} = new Request(${urlJson}, {\n method: ${methodJson},\n headers: new Headers(${headersJson}),\n mode: ${modeJson},\n credentials: ${credentialsJson},\n cache: ${cacheJson},\n redirect: ${redirectJson},\n referrer: ${referrerJson},\n referrerPolicy: ${referrerPolicyJson},\n integrity: ${integrityJson},\n });\n `);\n return;\n }\n\n // Check for native Response\n if (value instanceof Response) {\n const headerPairs: [string, string][] = [];\n value.headers.forEach((v, k) => headerPairs.push([k, v]));\n const headersJson = JSON.stringify(headerPairs);\n\n const statusJson = JSON.stringify(value.status);\n const statusTextJson = JSON.stringify(value.statusText);\n\n context.evalSync(`\n ${path} = new Response(null, {\n status: ${statusJson},\n statusText: ${statusTextJson},\n headers: new Headers(${headersJson}),\n });\n `);\n return;\n }\n\n // Check for native FormData\n if (value instanceof FormData) {\n context.evalSync(`${path} = new FormData();`);\n\n for (const [key, entryValue] of value.entries()) {\n const keyJson = JSON.stringify(key);\n\n if (typeof entryValue !== \"string\") {\n const file = entryValue as File;\n const nameJson = JSON.stringify(file.name);\n const typeJson = JSON.stringify(file.type);\n const lastModifiedJson = JSON.stringify(file.lastModified);\n context.evalSync(`\n ${path}.append(${keyJson}, new File([], ${nameJson}, { type: ${typeJson}, lastModified: ${lastModifiedJson} }));\n `);\n } else {\n const valueJson = JSON.stringify(entryValue);\n context.evalSync(`${path}.append(${keyJson}, ${valueJson});`);\n }\n }\n return;\n }\n\n // Check for native URL\n if (value instanceof URL) {\n const hrefJson = JSON.stringify(value.href);\n context.evalSync(`${path} = new URL(${hrefJson});`);\n return;\n }\n\n // Check for native File (before Blob, since File extends Blob)\n if (value instanceof File) {\n const nameJson = JSON.stringify(value.name);\n const typeJson = JSON.stringify(value.type);\n const lastModifiedJson = JSON.stringify(value.lastModified);\n context.evalSync(\n `${path} = new File([], ${nameJson}, { type: ${typeJson}, lastModified: ${lastModifiedJson} });`\n );\n return;\n }\n\n // Check for native Blob\n if (value instanceof Blob) {\n const typeJson = JSON.stringify(value.type);\n context.evalSync(`${path} = new Blob([], { type: ${typeJson} });`);\n return;\n }\n\n // Handle arrays recursively\n if (Array.isArray(value)) {\n context.evalSync(`${path} = [];`);\n for (let i = 0; i < value.length; i++) {\n marshalValue(context, `${path}[${i}]`, value[i]);\n }\n return;\n }\n\n // Handle plain objects recursively\n if (value && typeof value === \"object\" && value.constructor === Object) {\n context.evalSync(`${path} = {};`);\n for (const [key, val] of Object.entries(value)) {\n // Use bracket notation for safe property access\n marshalValue(context, `${path}[${JSON.stringify(key)}]`, val);\n }\n return;\n }\n\n // For primitives, set directly via JSON\n const valueJson = JSON.stringify(value);\n context.evalSync(`${path} = ${valueJson};`);\n}\n\n/**\n * Unmarshal a value from JSON, converting special __type__ markers back to native instances\n */\nfunction unmarshalFromJson(value: unknown): unknown {\n if (value === null || value === undefined) {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map((v) => unmarshalFromJson(v));\n }\n\n if (typeof value === \"object\") {\n const obj = value as Record<string, unknown>;\n\n // Check for special type markers\n if (obj.__type__ === \"Headers\") {\n const pairs = obj.pairs as [string, string][];\n const headers = new Headers();\n for (const [k, v] of pairs) {\n headers.append(k, v);\n }\n return headers;\n }\n\n if (obj.__type__ === \"Request\") {\n const headers = new Headers();\n for (const [k, v] of obj.headers as [string, string][]) {\n headers.append(k, v);\n }\n return new Request(obj.url as string, {\n method: obj.method as string,\n headers,\n mode: obj.mode as Request[\"mode\"],\n credentials: obj.credentials as Request[\"credentials\"],\n cache: obj.cache as Request[\"cache\"],\n redirect: obj.redirect as Request[\"redirect\"],\n referrer: obj.referrer as string,\n referrerPolicy: obj.referrerPolicy as Request[\"referrerPolicy\"],\n integrity: obj.integrity as string,\n });\n }\n\n if (obj.__type__ === \"Response\") {\n const headers = new Headers();\n for (const [k, v] of obj.headers as [string, string][]) {\n headers.append(k, v);\n }\n return new Response(null, {\n status: obj.status as number,\n statusText: obj.statusText as string,\n headers,\n });\n }\n\n if (obj.__type__ === \"FormData\") {\n const formData = new FormData();\n for (const [k, v] of obj.entries as [string, unknown][]) {\n if (\n typeof v === \"object\" &&\n v !== null &&\n (v as Record<string, unknown>).__type__ === \"File\"\n ) {\n const fileObj = v as Record<string, unknown>;\n formData.append(\n k,\n new File([], fileObj.name as string, {\n type: fileObj.type as string,\n lastModified: fileObj.lastModified as number,\n })\n );\n } else {\n formData.append(k, v as string);\n }\n }\n return formData;\n }\n\n if (obj.__type__ === \"URL\") {\n return new URL(obj.href as string);\n }\n\n if (obj.__type__ === \"File\") {\n return new File([], obj.name as string, {\n type: obj.type as string,\n lastModified: obj.lastModified as number,\n });\n }\n\n if (obj.__type__ === \"Blob\") {\n return new Blob([], { type: obj.type as string });\n }\n\n // Plain object - recursively unmarshal properties\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n result[k] = unmarshalFromJson(v);\n }\n return result;\n }\n\n return value;\n}\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "EDB1526900BA89BD64756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "@ricsam/isolate-test-utils",
3
+ "version": "0.1.12",
4
+ "type": "module"
5
+ }
@@ -0,0 +1,67 @@
1
+ // packages/test-utils/src/runtime-context.ts
2
+ import { MockFileSystem } from "./mock-fs.mjs";
3
+ import { createRuntime } from "@ricsam/isolate-runtime";
4
+ import { clearAllInstanceState } from "@ricsam/isolate-core";
5
+ async function createRuntimeTestContext(options) {
6
+ const opts = options ?? {};
7
+ clearAllInstanceState();
8
+ const logs = [];
9
+ const fetchCalls = [];
10
+ let mockResponse = { status: 200, body: "" };
11
+ let storedResult = undefined;
12
+ const mockFs = new MockFileSystem;
13
+ const runtime = await createRuntime({
14
+ console: {
15
+ onEntry: (entry) => {
16
+ if (entry.type === "output") {
17
+ logs.push({ level: entry.level, stdout: entry.stdout });
18
+ } else if (entry.type === "assert") {
19
+ logs.push({ level: "error", stdout: entry.stdout });
20
+ }
21
+ }
22
+ },
23
+ fetch: async (request) => {
24
+ fetchCalls.push({
25
+ url: request.url,
26
+ method: request.method,
27
+ headers: [...request.headers.entries()]
28
+ });
29
+ return new Response(mockResponse.body ?? "", {
30
+ status: mockResponse.status ?? 200,
31
+ headers: mockResponse.headers
32
+ });
33
+ },
34
+ fs: opts.fs ? { getDirectory: async () => mockFs } : undefined,
35
+ customFunctions: {
36
+ setResult: {
37
+ fn: (value) => {
38
+ storedResult = value;
39
+ },
40
+ type: "sync"
41
+ }
42
+ }
43
+ });
44
+ return {
45
+ eval: runtime.eval.bind(runtime),
46
+ clearTimers: runtime.timers.clearAll.bind(runtime.timers),
47
+ dispatchRequest: runtime.fetch.dispatchRequest.bind(runtime.fetch),
48
+ dispose: runtime.dispose.bind(runtime),
49
+ logs,
50
+ fetchCalls,
51
+ setMockResponse(response) {
52
+ mockResponse = response;
53
+ },
54
+ mockFs,
55
+ getResult() {
56
+ return storedResult;
57
+ },
58
+ clearResult() {
59
+ storedResult = undefined;
60
+ }
61
+ };
62
+ }
63
+ export {
64
+ createRuntimeTestContext
65
+ };
66
+
67
+ //# debugId=D0AF38671454769564756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/runtime-context.ts"],
4
+ "sourcesContent": [
5
+ "import { MockFileSystem } from \"./mock-fs.mjs\";\nimport { createRuntime } from \"@ricsam/isolate-runtime\";\nimport { clearAllInstanceState } from \"@ricsam/isolate-core\";\n\nexport interface MockResponse {\n status?: number;\n body?: string;\n headers?: Record<string, string>;\n}\n\nexport interface RuntimeTestContextOptions {\n /** Enable file system APIs with mock file system */\n fs?: boolean;\n}\n\nexport interface RuntimeTestContext {\n /** Execute code in the runtime (ES module mode, supports top-level await) */\n eval(code: string): Promise<void>;\n /** Clear all pending timers */\n clearTimers(): void;\n /** Dispatch an HTTP request to the serve() handler */\n dispatchRequest(request: Request): Promise<Response>;\n /** Dispose all resources */\n dispose(): Promise<void>;\n /** Captured console.log calls */\n logs: Array<{ level: string; stdout: string }>;\n /** Captured fetch calls */\n fetchCalls: Array<{ url: string; method: string; headers: [string, string][] }>;\n /** Set the mock response for the next fetch call */\n setMockResponse(response: MockResponse): void;\n /** Mock file system (only available if fs option is true) */\n mockFs: MockFileSystem;\n /**\n * Get a result from the isolate. Call `await setResult(value)` in your eval code\n * to pass a value back to the host.\n */\n getResult<T = unknown>(): T | undefined;\n /** Clear the stored result */\n clearResult(): void;\n}\n\n/**\n * Create a full runtime test context with all APIs set up.\n * Includes console logging capture, fetch mocking, and optionally file system.\n *\n * @example\n * const ctx = await createRuntimeTestContext({ fs: true });\n *\n * // Set up mock response for fetch\n * ctx.setMockResponse({ status: 200, body: '{\"data\": \"test\"}' });\n *\n * // Run code and pass result back via setResult\n * await ctx.eval(`\n * console.log(\"Starting fetch...\");\n * const response = await fetch(\"https://api.example.com/data\");\n * const data = await response.json();\n * console.log(\"Got data:\", data);\n * setResult(data);\n * `);\n *\n * // Get the result\n * console.log(ctx.getResult()); // { data: \"test\" }\n *\n * // Check logs\n * console.log(ctx.logs); // [{ level: \"log\", stdout: \"Starting fetch...\" }, ...]\n *\n * // Check fetch calls\n * console.log(ctx.fetchCalls); // [{ url: \"https://api.example.com/data\", method: \"GET\", ... }]\n *\n * await ctx.dispose();\n */\nexport async function createRuntimeTestContext(\n options?: RuntimeTestContextOptions\n): Promise<RuntimeTestContext> {\n const opts = options ?? {};\n\n // Clear any previous instance state\n clearAllInstanceState();\n\n // State for capturing logs and fetch calls\n const logs: Array<{ level: string; stdout: string }> = [];\n const fetchCalls: Array<{\n url: string;\n method: string;\n headers: [string, string][];\n }> = [];\n\n let mockResponse: MockResponse = { status: 200, body: \"\" };\n let storedResult: unknown = undefined;\n\n // Create mock file system\n const mockFs = new MockFileSystem();\n\n // Create runtime with configured handlers\n const runtime = await createRuntime({\n console: {\n onEntry: (entry) => {\n if (entry.type === \"output\") {\n logs.push({ level: entry.level, stdout: entry.stdout });\n } else if (entry.type === \"assert\") {\n logs.push({ level: \"error\", stdout: entry.stdout });\n }\n },\n },\n fetch: async (request: Request) => {\n // Capture fetch call\n fetchCalls.push({\n url: request.url,\n method: request.method,\n headers: [...request.headers.entries()],\n });\n\n // Return mock response\n return new Response(mockResponse.body ?? \"\", {\n status: mockResponse.status ?? 200,\n headers: mockResponse.headers,\n });\n },\n fs: opts.fs ? { getDirectory: async () => mockFs } : undefined,\n customFunctions: {\n setResult: {\n fn: (value: unknown) => {\n storedResult = value;\n },\n type: 'sync',\n },\n },\n });\n\n return {\n eval: runtime.eval.bind(runtime),\n clearTimers: runtime.timers.clearAll.bind(runtime.timers),\n dispatchRequest: runtime.fetch.dispatchRequest.bind(runtime.fetch),\n dispose: runtime.dispose.bind(runtime),\n logs,\n fetchCalls,\n setMockResponse(response: MockResponse) {\n mockResponse = response;\n },\n mockFs,\n getResult<T = unknown>(): T | undefined {\n return storedResult as T | undefined;\n },\n clearResult() {\n storedResult = undefined;\n },\n };\n}\n"
6
+ ],
7
+ "mappings": ";AAAA;AACA;AACA;AAqEA,eAAsB,wBAAwB,CAC5C,SAC6B;AAAA,EAC7B,MAAM,OAAO,WAAW,CAAC;AAAA,EAGzB,sBAAsB;AAAA,EAGtB,MAAM,OAAiD,CAAC;AAAA,EACxD,MAAM,aAID,CAAC;AAAA,EAEN,IAAI,eAA6B,EAAE,QAAQ,KAAK,MAAM,GAAG;AAAA,EACzD,IAAI,eAAwB;AAAA,EAG5B,MAAM,SAAS,IAAI;AAAA,EAGnB,MAAM,UAAU,MAAM,cAAc;AAAA,IAClC,SAAS;AAAA,MACP,SAAS,CAAC,UAAU;AAAA,QAClB,IAAI,MAAM,SAAS,UAAU;AAAA,UAC3B,KAAK,KAAK,EAAE,OAAO,MAAM,OAAO,QAAQ,MAAM,OAAO,CAAC;AAAA,QACxD,EAAO,SAAI,MAAM,SAAS,UAAU;AAAA,UAClC,KAAK,KAAK,EAAE,OAAO,SAAS,QAAQ,MAAM,OAAO,CAAC;AAAA,QACpD;AAAA;AAAA,IAEJ;AAAA,IACA,OAAO,OAAO,YAAqB;AAAA,MAEjC,WAAW,KAAK;AAAA,QACd,KAAK,QAAQ;AAAA,QACb,QAAQ,QAAQ;AAAA,QAChB,SAAS,CAAC,GAAG,QAAQ,QAAQ,QAAQ,CAAC;AAAA,MACxC,CAAC;AAAA,MAGD,OAAO,IAAI,SAAS,aAAa,QAAQ,IAAI;AAAA,QAC3C,QAAQ,aAAa,UAAU;AAAA,QAC/B,SAAS,aAAa;AAAA,MACxB,CAAC;AAAA;AAAA,IAEH,IAAI,KAAK,KAAK,EAAE,cAAc,YAAY,OAAO,IAAI;AAAA,IACrD,iBAAiB;AAAA,MACf,WAAW;AAAA,QACT,IAAI,CAAC,UAAmB;AAAA,UACtB,eAAe;AAAA;AAAA,QAEjB,MAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF,CAAC;AAAA,EAED,OAAO;AAAA,IACL,MAAM,QAAQ,KAAK,KAAK,OAAO;AAAA,IAC/B,aAAa,QAAQ,OAAO,SAAS,KAAK,QAAQ,MAAM;AAAA,IACxD,iBAAiB,QAAQ,MAAM,gBAAgB,KAAK,QAAQ,KAAK;AAAA,IACjE,SAAS,QAAQ,QAAQ,KAAK,OAAO;AAAA,IACrC;AAAA,IACA;AAAA,IACA,eAAe,CAAC,UAAwB;AAAA,MACtC,eAAe;AAAA;AAAA,IAEjB;AAAA,IACA,SAAsB,GAAkB;AAAA,MACtC,OAAO;AAAA;AAAA,IAET,WAAW,GAAG;AAAA,MACZ,eAAe;AAAA;AAAA,EAEnB;AAAA;",
8
+ "debugId": "D0AF38671454769564756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,79 @@
1
+ // packages/test-utils/src/server.ts
2
+ import { createServer } from "node:http";
3
+ async function startIntegrationServer(port) {
4
+ const responses = new Map;
5
+ const requests = [];
6
+ let defaultResponse = { status: 404, body: "Not Found" };
7
+ const server = createServer(async (req, res) => {
8
+ const path = req.url ?? "/";
9
+ const method = req.method ?? "GET";
10
+ const chunks = [];
11
+ for await (const chunk of req) {
12
+ chunks.push(chunk);
13
+ }
14
+ const body = chunks.length > 0 ? Buffer.concat(chunks).toString() : undefined;
15
+ const headers = {};
16
+ for (const [key, value] of Object.entries(req.headers)) {
17
+ if (typeof value === "string") {
18
+ headers[key] = value;
19
+ } else if (Array.isArray(value)) {
20
+ headers[key] = value.join(", ");
21
+ }
22
+ }
23
+ requests.push({ method, path, headers, body });
24
+ const mockResponse = responses.get(path) ?? defaultResponse;
25
+ res.statusCode = mockResponse.status ?? 200;
26
+ if (mockResponse.headers) {
27
+ for (const [key, value] of Object.entries(mockResponse.headers)) {
28
+ res.setHeader(key, value);
29
+ }
30
+ }
31
+ res.end(mockResponse.body ?? "");
32
+ });
33
+ const actualPort = await new Promise((resolve, reject) => {
34
+ server.listen(port ?? 0, () => {
35
+ const address = server.address();
36
+ if (address && typeof address === "object") {
37
+ resolve(address.port);
38
+ } else {
39
+ reject(new Error("Failed to get server address"));
40
+ }
41
+ });
42
+ server.on("error", reject);
43
+ });
44
+ return {
45
+ url: `http://localhost:${actualPort}`,
46
+ port: actualPort,
47
+ async close() {
48
+ return new Promise((resolve, reject) => {
49
+ server.close((err) => {
50
+ if (err)
51
+ reject(err);
52
+ else
53
+ resolve();
54
+ });
55
+ });
56
+ },
57
+ setResponse(path, response) {
58
+ responses.set(path, response);
59
+ },
60
+ setDefaultResponse(response) {
61
+ defaultResponse = response;
62
+ },
63
+ getRequests() {
64
+ return [...requests];
65
+ },
66
+ clearRequests() {
67
+ requests.length = 0;
68
+ },
69
+ clearResponses() {
70
+ responses.clear();
71
+ defaultResponse = { status: 404, body: "Not Found" };
72
+ }
73
+ };
74
+ }
75
+ export {
76
+ startIntegrationServer
77
+ };
78
+
79
+ //# debugId=9B0013141DCE45BE64756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/server.ts"],
4
+ "sourcesContent": [
5
+ "import { createServer, type Server, type IncomingMessage, type ServerResponse } from \"node:http\";\n\nexport interface MockServerResponse {\n status?: number;\n body?: string;\n headers?: Record<string, string>;\n}\n\nexport interface RecordedRequest {\n method: string;\n path: string;\n headers: Record<string, string>;\n body?: string;\n}\n\nexport interface IntegrationServer {\n /** The base URL of the server (e.g., \"http://localhost:3000\") */\n url: string;\n /** The port the server is listening on */\n port: number;\n /** Close the server */\n close(): Promise<void>;\n /** Set the response for a specific path */\n setResponse(path: string, response: MockServerResponse): void;\n /** Set a default response for any unmatched path */\n setDefaultResponse(response: MockServerResponse): void;\n /** Get all recorded requests */\n getRequests(): RecordedRequest[];\n /** Clear all recorded requests */\n clearRequests(): void;\n /** Clear all configured responses */\n clearResponses(): void;\n}\n\n/**\n * Start an HTTP server for integration tests.\n * Useful for testing fetch operations against a real server.\n *\n * @example\n * const server = await startIntegrationServer();\n *\n * server.setResponse(\"/api/data\", {\n * status: 200,\n * body: JSON.stringify({ message: \"Hello\" }),\n * headers: { \"Content-Type\": \"application/json\" }\n * });\n *\n * // In your test\n * const response = await fetch(`${server.url}/api/data`);\n * const data = await response.json();\n *\n * // Check what requests were made\n * const requests = server.getRequests();\n * console.log(requests[0].path); // \"/api/data\"\n *\n * await server.close();\n */\nexport async function startIntegrationServer(\n port?: number\n): Promise<IntegrationServer> {\n const responses = new Map<string, MockServerResponse>();\n const requests: RecordedRequest[] = [];\n let defaultResponse: MockServerResponse = { status: 404, body: \"Not Found\" };\n\n const server: Server = createServer(\n async (req: IncomingMessage, res: ServerResponse) => {\n const path = req.url ?? \"/\";\n const method = req.method ?? \"GET\";\n\n // Read request body\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n chunks.push(chunk as Buffer);\n }\n const body = chunks.length > 0 ? Buffer.concat(chunks).toString() : undefined;\n\n // Record the request\n const headers: Record<string, string> = {};\n for (const [key, value] of Object.entries(req.headers)) {\n if (typeof value === \"string\") {\n headers[key] = value;\n } else if (Array.isArray(value)) {\n headers[key] = value.join(\", \");\n }\n }\n requests.push({ method, path, headers, body });\n\n // Find and send response\n const mockResponse = responses.get(path) ?? defaultResponse;\n\n res.statusCode = mockResponse.status ?? 200;\n\n if (mockResponse.headers) {\n for (const [key, value] of Object.entries(mockResponse.headers)) {\n res.setHeader(key, value);\n }\n }\n\n res.end(mockResponse.body ?? \"\");\n }\n );\n\n // Find an available port\n const actualPort = await new Promise<number>((resolve, reject) => {\n server.listen(port ?? 0, () => {\n const address = server.address();\n if (address && typeof address === \"object\") {\n resolve(address.port);\n } else {\n reject(new Error(\"Failed to get server address\"));\n }\n });\n server.on(\"error\", reject);\n });\n\n return {\n url: `http://localhost:${actualPort}`,\n port: actualPort,\n\n async close() {\n return new Promise((resolve, reject) => {\n server.close((err) => {\n if (err) reject(err);\n else resolve();\n });\n });\n },\n\n setResponse(path: string, response: MockServerResponse) {\n responses.set(path, response);\n },\n\n setDefaultResponse(response: MockServerResponse) {\n defaultResponse = response;\n },\n\n getRequests() {\n return [...requests];\n },\n\n clearRequests() {\n requests.length = 0;\n },\n\n clearResponses() {\n responses.clear();\n defaultResponse = { status: 404, body: \"Not Found\" };\n },\n };\n}\n"
6
+ ],
7
+ "mappings": ";AAAA;AAyDA,eAAsB,sBAAsB,CAC1C,MAC4B;AAAA,EAC5B,MAAM,YAAY,IAAI;AAAA,EACtB,MAAM,WAA8B,CAAC;AAAA,EACrC,IAAI,kBAAsC,EAAE,QAAQ,KAAK,MAAM,YAAY;AAAA,EAE3E,MAAM,SAAiB,aACrB,OAAO,KAAsB,QAAwB;AAAA,IACnD,MAAM,OAAO,IAAI,OAAO;AAAA,IACxB,MAAM,SAAS,IAAI,UAAU;AAAA,IAG7B,MAAM,SAAmB,CAAC;AAAA,IAC1B,iBAAiB,SAAS,KAAK;AAAA,MAC7B,OAAO,KAAK,KAAe;AAAA,IAC7B;AAAA,IACA,MAAM,OAAO,OAAO,SAAS,IAAI,OAAO,OAAO,MAAM,EAAE,SAAS,IAAI;AAAA,IAGpE,MAAM,UAAkC,CAAC;AAAA,IACzC,YAAY,KAAK,UAAU,OAAO,QAAQ,IAAI,OAAO,GAAG;AAAA,MACtD,IAAI,OAAO,UAAU,UAAU;AAAA,QAC7B,QAAQ,OAAO;AAAA,MACjB,EAAO,SAAI,MAAM,QAAQ,KAAK,GAAG;AAAA,QAC/B,QAAQ,OAAO,MAAM,KAAK,IAAI;AAAA,MAChC;AAAA,IACF;AAAA,IACA,SAAS,KAAK,EAAE,QAAQ,MAAM,SAAS,KAAK,CAAC;AAAA,IAG7C,MAAM,eAAe,UAAU,IAAI,IAAI,KAAK;AAAA,IAE5C,IAAI,aAAa,aAAa,UAAU;AAAA,IAExC,IAAI,aAAa,SAAS;AAAA,MACxB,YAAY,KAAK,UAAU,OAAO,QAAQ,aAAa,OAAO,GAAG;AAAA,QAC/D,IAAI,UAAU,KAAK,KAAK;AAAA,MAC1B;AAAA,IACF;AAAA,IAEA,IAAI,IAAI,aAAa,QAAQ,EAAE;AAAA,GAEnC;AAAA,EAGA,MAAM,aAAa,MAAM,IAAI,QAAgB,CAAC,SAAS,WAAW;AAAA,IAChE,OAAO,OAAO,QAAQ,GAAG,MAAM;AAAA,MAC7B,MAAM,UAAU,OAAO,QAAQ;AAAA,MAC/B,IAAI,WAAW,OAAO,YAAY,UAAU;AAAA,QAC1C,QAAQ,QAAQ,IAAI;AAAA,MACtB,EAAO;AAAA,QACL,OAAO,IAAI,MAAM,8BAA8B,CAAC;AAAA;AAAA,KAEnD;AAAA,IACD,OAAO,GAAG,SAAS,MAAM;AAAA,GAC1B;AAAA,EAED,OAAO;AAAA,IACL,KAAK,oBAAoB;AAAA,IACzB,MAAM;AAAA,SAEA,MAAK,GAAG;AAAA,MACZ,OAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AAAA,QACtC,OAAO,MAAM,CAAC,QAAQ;AAAA,UACpB,IAAI;AAAA,YAAK,OAAO,GAAG;AAAA,UACd;AAAA,oBAAQ;AAAA,SACd;AAAA,OACF;AAAA;AAAA,IAGH,WAAW,CAAC,MAAc,UAA8B;AAAA,MACtD,UAAU,IAAI,MAAM,QAAQ;AAAA;AAAA,IAG9B,kBAAkB,CAAC,UAA8B;AAAA,MAC/C,kBAAkB;AAAA;AAAA,IAGpB,WAAW,GAAG;AAAA,MACZ,OAAO,CAAC,GAAG,QAAQ;AAAA;AAAA,IAGrB,aAAa,GAAG;AAAA,MACd,SAAS,SAAS;AAAA;AAAA,IAGpB,cAAc,GAAG;AAAA,MACf,UAAU,MAAM;AAAA,MAChB,kBAAkB,EAAE,QAAQ,KAAK,MAAM,YAAY;AAAA;AAAA,EAEvD;AAAA;",
8
+ "debugId": "9B0013141DCE45BE64756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,7 @@
1
+ import type { TestContext } from "./index.ts";
2
+ export interface FetchTestContext extends TestContext {
3
+ }
4
+ /**
5
+ * Create a test context with fetch APIs set up (Headers, Request, Response, FormData, fetch)
6
+ */
7
+ export declare function createFetchTestContext(): Promise<FetchTestContext>;