@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.
@@ -1,121 +1,22 @@
1
+ // @bun
2
+ // packages/fetch/src/index.ts
1
3
  import ivm from "isolated-vm";
2
4
  import { setupCore, clearAllInstanceState } from "@ricsam/isolate-core";
3
5
  import {
4
6
  getStreamRegistryForContext,
5
- startNativeStreamReader,
6
- } from "./stream-state.ts";
7
- import type { StreamStateRegistry } from "./stream-state.ts";
8
-
9
- export { clearAllInstanceState };
10
-
11
- export interface FetchOptions {
12
- /** Handler for fetch requests from the isolate */
13
- onFetch?: (request: Request) => Promise<Response>;
14
- }
15
-
16
- // ============================================================================
17
- // Serve Types
18
- // ============================================================================
19
-
20
- export interface UpgradeRequest {
21
- requested: true;
22
- connectionId: string;
23
- }
24
-
25
- export interface WebSocketCommand {
26
- type: "message" | "close";
27
- connectionId: string;
28
- data?: string | ArrayBuffer;
29
- code?: number;
30
- reason?: string;
31
- }
32
-
33
- interface ServeState {
34
- pendingUpgrade: UpgradeRequest | null;
35
- activeConnections: Map<string, { connectionId: string }>;
36
- }
37
-
38
- export interface DispatchRequestOptions {
39
- /** Tick function to pump isolate timers - required for streaming responses */
40
- tick?: () => Promise<void>;
41
- }
42
-
43
- export interface FetchHandle {
44
- dispose(): void;
45
- /** Dispatch an HTTP request to the isolate's serve() handler */
46
- dispatchRequest(request: Request, options?: DispatchRequestOptions): Promise<Response>;
47
- /** Check if isolate requested WebSocket upgrade */
48
- getUpgradeRequest(): UpgradeRequest | null;
49
- /** Dispatch WebSocket open event to isolate */
50
- dispatchWebSocketOpen(connectionId: string): void;
51
- /** Dispatch WebSocket message event to isolate */
52
- dispatchWebSocketMessage(connectionId: string, message: string | ArrayBuffer): void;
53
- /** Dispatch WebSocket close event to isolate */
54
- dispatchWebSocketClose(connectionId: string, code: number, reason: string): void;
55
- /** Dispatch WebSocket error event to isolate */
56
- dispatchWebSocketError(connectionId: string, error: Error): void;
57
- /** Register callback for WebSocket commands from isolate */
58
- onWebSocketCommand(callback: (cmd: WebSocketCommand) => void): () => void;
59
- /** Check if serve() has been called */
60
- hasServeHandler(): boolean;
61
- /** Check if there are active WebSocket connections */
62
- hasActiveConnections(): boolean;
63
- }
64
-
65
- // ============================================================================
66
- // Instance State Management
67
- // ============================================================================
68
-
69
- const instanceStateMap = new WeakMap<ivm.Context, Map<number, unknown>>();
70
- let nextInstanceId = 1;
71
-
72
- function getInstanceStateMapForContext(
73
- context: ivm.Context
74
- ): Map<number, unknown> {
7
+ startNativeStreamReader
8
+ } from "./stream-state.mjs";
9
+ var instanceStateMap = new WeakMap;
10
+ var nextInstanceId = 1;
11
+ function getInstanceStateMapForContext(context) {
75
12
  let map = instanceStateMap.get(context);
76
13
  if (!map) {
77
- map = new Map();
14
+ map = new Map;
78
15
  instanceStateMap.set(context, map);
79
16
  }
80
17
  return map;
81
18
  }
82
-
83
- // ============================================================================
84
- // State Types
85
- // ============================================================================
86
-
87
- interface ResponseState {
88
- status: number;
89
- statusText: string;
90
- headers: [string, string][];
91
- body: Uint8Array | null;
92
- bodyUsed: boolean;
93
- type: string;
94
- url: string;
95
- redirected: boolean;
96
- streamId: number | null;
97
- }
98
-
99
- interface RequestState {
100
- method: string;
101
- url: string;
102
- headers: [string, string][];
103
- body: Uint8Array | null;
104
- bodyUsed: boolean;
105
- streamId: number | null;
106
- mode: string;
107
- credentials: string;
108
- cache: string;
109
- redirect: string;
110
- referrer: string;
111
- integrity: string;
112
- }
113
-
114
- // ============================================================================
115
- // Headers Implementation (Pure JS)
116
- // ============================================================================
117
-
118
- const headersCode = `
19
+ var headersCode = `
119
20
  (function() {
120
21
  class Headers {
121
22
  #headers = new Map(); // lowercase key -> [originalCase, values[]]
@@ -202,12 +103,7 @@ const headersCode = `
202
103
  globalThis.Headers = Headers;
203
104
  })();
204
105
  `;
205
-
206
- // ============================================================================
207
- // FormData Implementation (Pure JS)
208
- // ============================================================================
209
-
210
- const formDataCode = `
106
+ var formDataCode = `
211
107
  (function() {
212
108
  class FormData {
213
109
  #entries = []; // Array of [name, value]
@@ -285,12 +181,7 @@ const formDataCode = `
285
181
  globalThis.FormData = FormData;
286
182
  })();
287
183
  `;
288
-
289
- // ============================================================================
290
- // Multipart FormData Parsing/Serialization (Pure JS)
291
- // ============================================================================
292
-
293
- const multipartCode = `
184
+ var multipartCode = `
294
185
  (function() {
295
186
  // Find byte sequence in Uint8Array
296
187
  function findSequence(haystack, needle, start = 0) {
@@ -449,60 +340,25 @@ const multipartCode = `
449
340
  };
450
341
  })();
451
342
  `;
452
-
453
- // ============================================================================
454
- // Stream Callbacks (Host State)
455
- // ============================================================================
456
-
457
- function setupStreamCallbacks(
458
- context: ivm.Context,
459
- streamRegistry: StreamStateRegistry
460
- ): void {
343
+ function setupStreamCallbacks(context, streamRegistry) {
461
344
  const global = context.global;
462
-
463
- // Create stream (returns ID)
464
- global.setSync(
465
- "__Stream_create",
466
- new ivm.Callback(() => {
467
- return streamRegistry.create();
468
- })
469
- );
470
-
471
- // Push chunk (sync) - receives number[] from isolate
472
- global.setSync(
473
- "__Stream_push",
474
- new ivm.Callback((streamId: number, chunkArray: number[]) => {
475
- const chunk = new Uint8Array(chunkArray);
476
- return streamRegistry.push(streamId, chunk);
477
- })
478
- );
479
-
480
- // Close stream (sync)
481
- global.setSync(
482
- "__Stream_close",
483
- new ivm.Callback((streamId: number) => {
484
- streamRegistry.close(streamId);
485
- })
486
- );
487
-
488
- // Error stream (sync)
489
- global.setSync(
490
- "__Stream_error",
491
- new ivm.Callback((streamId: number, message: string) => {
492
- streamRegistry.error(streamId, new Error(message));
493
- })
494
- );
495
-
496
- // Check backpressure (sync)
497
- global.setSync(
498
- "__Stream_isQueueFull",
499
- new ivm.Callback((streamId: number) => {
500
- return streamRegistry.isQueueFull(streamId);
501
- })
502
- );
503
-
504
- // Pull chunk (async with applySyncPromise)
505
- const pullRef = new ivm.Reference(async (streamId: number) => {
345
+ global.setSync("__Stream_create", new ivm.Callback(() => {
346
+ return streamRegistry.create();
347
+ }));
348
+ global.setSync("__Stream_push", new ivm.Callback((streamId, chunkArray) => {
349
+ const chunk = new Uint8Array(chunkArray);
350
+ return streamRegistry.push(streamId, chunk);
351
+ }));
352
+ global.setSync("__Stream_close", new ivm.Callback((streamId) => {
353
+ streamRegistry.close(streamId);
354
+ }));
355
+ global.setSync("__Stream_error", new ivm.Callback((streamId, message) => {
356
+ streamRegistry.error(streamId, new Error(message));
357
+ }));
358
+ global.setSync("__Stream_isQueueFull", new ivm.Callback((streamId) => {
359
+ return streamRegistry.isQueueFull(streamId);
360
+ }));
361
+ const pullRef = new ivm.Reference(async (streamId) => {
506
362
  const result = await streamRegistry.pull(streamId);
507
363
  if (result.done) {
508
364
  return JSON.stringify({ done: true });
@@ -511,12 +367,7 @@ function setupStreamCallbacks(
511
367
  });
512
368
  global.setSync("__Stream_pull_ref", pullRef);
513
369
  }
514
-
515
- // ============================================================================
516
- // Host-Backed ReadableStream (Isolate Code)
517
- // ============================================================================
518
-
519
- const hostBackedStreamCode = `
370
+ var hostBackedStreamCode = `
520
371
  (function() {
521
372
  const _streamIds = new WeakMap();
522
373
 
@@ -581,233 +432,132 @@ const hostBackedStreamCode = `
581
432
  globalThis.HostBackedReadableStream = HostBackedReadableStream;
582
433
  })();
583
434
  `;
584
-
585
- // ============================================================================
586
- // Response Implementation (Host State + Isolate Class)
587
- // ============================================================================
588
-
589
- function setupResponse(
590
- context: ivm.Context,
591
- stateMap: Map<number, unknown>
592
- ): void {
435
+ function setupResponse(context, stateMap) {
593
436
  const global = context.global;
594
-
595
- // Register host callbacks
596
- global.setSync(
597
- "__Response_construct",
598
- new ivm.Callback(
599
- (
600
- bodyBytes: number[] | null,
601
- status: number,
602
- statusText: string,
603
- headers: [string, string][]
604
- ) => {
605
- const instanceId = nextInstanceId++;
606
- const body = bodyBytes ? new Uint8Array(bodyBytes) : null;
607
- const state: ResponseState = {
608
- status,
609
- statusText,
610
- headers,
611
- body,
612
- bodyUsed: false,
613
- type: "default",
614
- url: "",
615
- redirected: false,
616
- streamId: null,
617
- };
618
- stateMap.set(instanceId, state);
619
- return instanceId;
620
- }
621
- )
622
- );
623
-
624
- // Streaming Response constructor - creates Response with stream ID but no buffered body
625
- global.setSync(
626
- "__Response_constructStreaming",
627
- new ivm.Callback(
628
- (
629
- streamId: number,
630
- status: number,
631
- statusText: string,
632
- headers: [string, string][]
633
- ) => {
634
- const instanceId = nextInstanceId++;
635
- const state: ResponseState = {
636
- status,
637
- statusText,
638
- headers,
639
- body: null, // No buffered body - using stream
640
- bodyUsed: false,
641
- type: "default",
642
- url: "",
643
- redirected: false,
644
- streamId, // Stream ID for body
645
- };
646
- stateMap.set(instanceId, state);
647
- return instanceId;
648
- }
649
- )
650
- );
651
-
652
- global.setSync(
653
- "__Response_constructFromFetch",
654
- new ivm.Callback(
655
- (
656
- bodyBytes: number[] | null,
657
- status: number,
658
- statusText: string,
659
- headers: [string, string][],
660
- url: string,
661
- redirected: boolean
662
- ) => {
663
- const instanceId = nextInstanceId++;
664
- const body = bodyBytes ? new Uint8Array(bodyBytes) : null;
665
- const state: ResponseState = {
666
- status,
667
- statusText,
668
- headers,
669
- body,
670
- bodyUsed: false,
671
- type: "default",
672
- url,
673
- redirected,
674
- streamId: null,
675
- };
676
- stateMap.set(instanceId, state);
677
- return instanceId;
678
- }
679
- )
680
- );
681
-
682
- global.setSync(
683
- "__Response_get_status",
684
- new ivm.Callback((instanceId: number) => {
685
- const state = stateMap.get(instanceId) as ResponseState | undefined;
686
- return state?.status ?? 200;
687
- })
688
- );
689
-
690
- global.setSync(
691
- "__Response_get_statusText",
692
- new ivm.Callback((instanceId: number) => {
693
- const state = stateMap.get(instanceId) as ResponseState | undefined;
694
- return state?.statusText ?? "";
695
- })
696
- );
697
-
698
- global.setSync(
699
- "__Response_get_headers",
700
- new ivm.Callback((instanceId: number) => {
701
- const state = stateMap.get(instanceId) as ResponseState | undefined;
702
- return state?.headers ?? [];
703
- })
704
- );
705
-
706
- global.setSync(
707
- "__Response_get_bodyUsed",
708
- new ivm.Callback((instanceId: number) => {
709
- const state = stateMap.get(instanceId) as ResponseState | undefined;
710
- return state?.bodyUsed ?? false;
711
- })
712
- );
713
-
714
- global.setSync(
715
- "__Response_get_url",
716
- new ivm.Callback((instanceId: number) => {
717
- const state = stateMap.get(instanceId) as ResponseState | undefined;
718
- return state?.url ?? "";
719
- })
720
- );
721
-
722
- global.setSync(
723
- "__Response_get_redirected",
724
- new ivm.Callback((instanceId: number) => {
725
- const state = stateMap.get(instanceId) as ResponseState | undefined;
726
- return state?.redirected ?? false;
727
- })
728
- );
729
-
730
- global.setSync(
731
- "__Response_get_type",
732
- new ivm.Callback((instanceId: number) => {
733
- const state = stateMap.get(instanceId) as ResponseState | undefined;
734
- return state?.type ?? "default";
735
- })
736
- );
737
-
738
- global.setSync(
739
- "__Response_setType",
740
- new ivm.Callback((instanceId: number, type: string) => {
741
- const state = stateMap.get(instanceId) as ResponseState | undefined;
742
- if (state) {
743
- state.type = type;
744
- }
745
- })
746
- );
747
-
748
- global.setSync(
749
- "__Response_markBodyUsed",
750
- new ivm.Callback((instanceId: number) => {
751
- const state = stateMap.get(instanceId) as ResponseState | undefined;
752
- if (state) {
753
- if (state.bodyUsed) {
754
- throw new Error("[TypeError]Body has already been consumed");
755
- }
756
- state.bodyUsed = true;
757
- }
758
- })
759
- );
760
-
761
- global.setSync(
762
- "__Response_text",
763
- new ivm.Callback((instanceId: number) => {
764
- const state = stateMap.get(instanceId) as ResponseState | undefined;
765
- if (!state || !state.body) return "";
766
- return new TextDecoder().decode(state.body);
767
- })
768
- );
769
-
770
- global.setSync(
771
- "__Response_arrayBuffer",
772
- new ivm.Callback((instanceId: number) => {
773
- const state = stateMap.get(instanceId) as ResponseState | undefined;
774
- if (!state || !state.body) {
775
- return new ivm.ExternalCopy(new ArrayBuffer(0)).copyInto();
776
- }
777
- return new ivm.ExternalCopy(state.body.buffer.slice(
778
- state.body.byteOffset,
779
- state.body.byteOffset + state.body.byteLength
780
- )).copyInto();
781
- })
782
- );
783
-
784
- global.setSync(
785
- "__Response_clone",
786
- new ivm.Callback((instanceId: number) => {
787
- const state = stateMap.get(instanceId) as ResponseState | undefined;
788
- if (!state) {
789
- throw new Error("[TypeError]Cannot clone invalid Response");
790
- }
791
- const newId = nextInstanceId++;
792
- const newState: ResponseState = {
793
- ...state,
794
- body: state.body ? new Uint8Array(state.body) : null,
795
- bodyUsed: false,
796
- };
797
- stateMap.set(newId, newState);
798
- return newId;
799
- })
800
- );
801
-
802
- global.setSync(
803
- "__Response_getStreamId",
804
- new ivm.Callback((instanceId: number) => {
805
- const state = stateMap.get(instanceId) as ResponseState | undefined;
806
- return state?.streamId ?? null;
807
- })
808
- );
809
-
810
- // Inject Response class
437
+ global.setSync("__Response_construct", new ivm.Callback((bodyBytes, status, statusText, headers) => {
438
+ const instanceId = nextInstanceId++;
439
+ const body = bodyBytes ? new Uint8Array(bodyBytes) : null;
440
+ const state = {
441
+ status,
442
+ statusText,
443
+ headers,
444
+ body,
445
+ bodyUsed: false,
446
+ type: "default",
447
+ url: "",
448
+ redirected: false,
449
+ streamId: null
450
+ };
451
+ stateMap.set(instanceId, state);
452
+ return instanceId;
453
+ }));
454
+ global.setSync("__Response_constructStreaming", new ivm.Callback((streamId, status, statusText, headers) => {
455
+ const instanceId = nextInstanceId++;
456
+ const state = {
457
+ status,
458
+ statusText,
459
+ headers,
460
+ body: null,
461
+ bodyUsed: false,
462
+ type: "default",
463
+ url: "",
464
+ redirected: false,
465
+ streamId
466
+ };
467
+ stateMap.set(instanceId, state);
468
+ return instanceId;
469
+ }));
470
+ global.setSync("__Response_constructFromFetch", new ivm.Callback((bodyBytes, status, statusText, headers, url, redirected) => {
471
+ const instanceId = nextInstanceId++;
472
+ const body = bodyBytes ? new Uint8Array(bodyBytes) : null;
473
+ const state = {
474
+ status,
475
+ statusText,
476
+ headers,
477
+ body,
478
+ bodyUsed: false,
479
+ type: "default",
480
+ url,
481
+ redirected,
482
+ streamId: null
483
+ };
484
+ stateMap.set(instanceId, state);
485
+ return instanceId;
486
+ }));
487
+ global.setSync("__Response_get_status", new ivm.Callback((instanceId) => {
488
+ const state = stateMap.get(instanceId);
489
+ return state?.status ?? 200;
490
+ }));
491
+ global.setSync("__Response_get_statusText", new ivm.Callback((instanceId) => {
492
+ const state = stateMap.get(instanceId);
493
+ return state?.statusText ?? "";
494
+ }));
495
+ global.setSync("__Response_get_headers", new ivm.Callback((instanceId) => {
496
+ const state = stateMap.get(instanceId);
497
+ return state?.headers ?? [];
498
+ }));
499
+ global.setSync("__Response_get_bodyUsed", new ivm.Callback((instanceId) => {
500
+ const state = stateMap.get(instanceId);
501
+ return state?.bodyUsed ?? false;
502
+ }));
503
+ global.setSync("__Response_get_url", new ivm.Callback((instanceId) => {
504
+ const state = stateMap.get(instanceId);
505
+ return state?.url ?? "";
506
+ }));
507
+ global.setSync("__Response_get_redirected", new ivm.Callback((instanceId) => {
508
+ const state = stateMap.get(instanceId);
509
+ return state?.redirected ?? false;
510
+ }));
511
+ global.setSync("__Response_get_type", new ivm.Callback((instanceId) => {
512
+ const state = stateMap.get(instanceId);
513
+ return state?.type ?? "default";
514
+ }));
515
+ global.setSync("__Response_setType", new ivm.Callback((instanceId, type) => {
516
+ const state = stateMap.get(instanceId);
517
+ if (state) {
518
+ state.type = type;
519
+ }
520
+ }));
521
+ global.setSync("__Response_markBodyUsed", new ivm.Callback((instanceId) => {
522
+ const state = stateMap.get(instanceId);
523
+ if (state) {
524
+ if (state.bodyUsed) {
525
+ throw new Error("[TypeError]Body has already been consumed");
526
+ }
527
+ state.bodyUsed = true;
528
+ }
529
+ }));
530
+ global.setSync("__Response_text", new ivm.Callback((instanceId) => {
531
+ const state = stateMap.get(instanceId);
532
+ if (!state || !state.body)
533
+ return "";
534
+ return new TextDecoder().decode(state.body);
535
+ }));
536
+ global.setSync("__Response_arrayBuffer", new ivm.Callback((instanceId) => {
537
+ const state = stateMap.get(instanceId);
538
+ if (!state || !state.body) {
539
+ return new ivm.ExternalCopy(new ArrayBuffer(0)).copyInto();
540
+ }
541
+ return new ivm.ExternalCopy(state.body.buffer.slice(state.body.byteOffset, state.body.byteOffset + state.body.byteLength)).copyInto();
542
+ }));
543
+ global.setSync("__Response_clone", new ivm.Callback((instanceId) => {
544
+ const state = stateMap.get(instanceId);
545
+ if (!state) {
546
+ throw new Error("[TypeError]Cannot clone invalid Response");
547
+ }
548
+ const newId = nextInstanceId++;
549
+ const newState = {
550
+ ...state,
551
+ body: state.body ? new Uint8Array(state.body) : null,
552
+ bodyUsed: false
553
+ };
554
+ stateMap.set(newId, newState);
555
+ return newId;
556
+ }));
557
+ global.setSync("__Response_getStreamId", new ivm.Callback((instanceId) => {
558
+ const state = stateMap.get(instanceId);
559
+ return state?.streamId ?? null;
560
+ }));
811
561
  const responseCode = `
812
562
  (function() {
813
563
  const _responseInstanceIds = new WeakMap();
@@ -1129,210 +879,116 @@ function setupResponse(
1129
879
  globalThis.Response = Response;
1130
880
  })();
1131
881
  `;
1132
-
1133
882
  context.evalSync(responseCode);
1134
883
  }
1135
-
1136
- // ============================================================================
1137
- // Request Implementation (Host State + Isolate Class)
1138
- // ============================================================================
1139
-
1140
- function setupRequest(
1141
- context: ivm.Context,
1142
- stateMap: Map<number, unknown>
1143
- ): void {
884
+ function setupRequest(context, stateMap) {
1144
885
  const global = context.global;
1145
-
1146
- // Register host callbacks
1147
- global.setSync(
1148
- "__Request_construct",
1149
- new ivm.Callback(
1150
- (
1151
- url: string,
1152
- method: string,
1153
- headers: [string, string][],
1154
- bodyBytes: number[] | null,
1155
- mode: string,
1156
- credentials: string,
1157
- cache: string,
1158
- redirect: string,
1159
- referrer: string,
1160
- integrity: string
1161
- ) => {
1162
- const instanceId = nextInstanceId++;
1163
- const body = bodyBytes ? new Uint8Array(bodyBytes) : null;
1164
- const state: RequestState = {
1165
- url,
1166
- method,
1167
- headers,
1168
- body,
1169
- bodyUsed: false,
1170
- streamId: null,
1171
- mode,
1172
- credentials,
1173
- cache,
1174
- redirect,
1175
- referrer,
1176
- integrity,
1177
- };
1178
- stateMap.set(instanceId, state);
1179
- return instanceId;
1180
- }
1181
- )
1182
- );
1183
-
1184
- global.setSync(
1185
- "__Request_get_method",
1186
- new ivm.Callback((instanceId: number) => {
1187
- const state = stateMap.get(instanceId) as RequestState | undefined;
1188
- return state?.method ?? "GET";
1189
- })
1190
- );
1191
-
1192
- global.setSync(
1193
- "__Request_get_url",
1194
- new ivm.Callback((instanceId: number) => {
1195
- const state = stateMap.get(instanceId) as RequestState | undefined;
1196
- return state?.url ?? "";
1197
- })
1198
- );
1199
-
1200
- global.setSync(
1201
- "__Request_get_headers",
1202
- new ivm.Callback((instanceId: number) => {
1203
- const state = stateMap.get(instanceId) as RequestState | undefined;
1204
- return state?.headers ?? [];
1205
- })
1206
- );
1207
-
1208
- global.setSync(
1209
- "__Request_get_bodyUsed",
1210
- new ivm.Callback((instanceId: number) => {
1211
- const state = stateMap.get(instanceId) as RequestState | undefined;
1212
- return state?.bodyUsed ?? false;
1213
- })
1214
- );
1215
-
1216
- global.setSync(
1217
- "__Request_get_mode",
1218
- new ivm.Callback((instanceId: number) => {
1219
- const state = stateMap.get(instanceId) as RequestState | undefined;
1220
- return state?.mode ?? "cors";
1221
- })
1222
- );
1223
-
1224
- global.setSync(
1225
- "__Request_get_credentials",
1226
- new ivm.Callback((instanceId: number) => {
1227
- const state = stateMap.get(instanceId) as RequestState | undefined;
1228
- return state?.credentials ?? "same-origin";
1229
- })
1230
- );
1231
-
1232
- global.setSync(
1233
- "__Request_get_cache",
1234
- new ivm.Callback((instanceId: number) => {
1235
- const state = stateMap.get(instanceId) as RequestState | undefined;
1236
- return state?.cache ?? "default";
1237
- })
1238
- );
1239
-
1240
- global.setSync(
1241
- "__Request_get_redirect",
1242
- new ivm.Callback((instanceId: number) => {
1243
- const state = stateMap.get(instanceId) as RequestState | undefined;
1244
- return state?.redirect ?? "follow";
1245
- })
1246
- );
1247
-
1248
- global.setSync(
1249
- "__Request_get_referrer",
1250
- new ivm.Callback((instanceId: number) => {
1251
- const state = stateMap.get(instanceId) as RequestState | undefined;
1252
- return state?.referrer ?? "about:client";
1253
- })
1254
- );
1255
-
1256
- global.setSync(
1257
- "__Request_get_integrity",
1258
- new ivm.Callback((instanceId: number) => {
1259
- const state = stateMap.get(instanceId) as RequestState | undefined;
1260
- return state?.integrity ?? "";
1261
- })
1262
- );
1263
-
1264
- global.setSync(
1265
- "__Request_markBodyUsed",
1266
- new ivm.Callback((instanceId: number) => {
1267
- const state = stateMap.get(instanceId) as RequestState | undefined;
1268
- if (state) {
1269
- if (state.bodyUsed) {
1270
- throw new Error("[TypeError]Body has already been consumed");
1271
- }
1272
- state.bodyUsed = true;
1273
- }
1274
- })
1275
- );
1276
-
1277
- global.setSync(
1278
- "__Request_text",
1279
- new ivm.Callback((instanceId: number) => {
1280
- const state = stateMap.get(instanceId) as RequestState | undefined;
1281
- if (!state || !state.body) return "";
1282
- return new TextDecoder().decode(state.body);
1283
- })
1284
- );
1285
-
1286
- global.setSync(
1287
- "__Request_arrayBuffer",
1288
- new ivm.Callback((instanceId: number) => {
1289
- const state = stateMap.get(instanceId) as RequestState | undefined;
1290
- if (!state || !state.body) {
1291
- return new ivm.ExternalCopy(new ArrayBuffer(0)).copyInto();
1292
- }
1293
- return new ivm.ExternalCopy(state.body.buffer.slice(
1294
- state.body.byteOffset,
1295
- state.body.byteOffset + state.body.byteLength
1296
- )).copyInto();
1297
- })
1298
- );
1299
-
1300
- global.setSync(
1301
- "__Request_getBodyBytes",
1302
- new ivm.Callback((instanceId: number) => {
1303
- const state = stateMap.get(instanceId) as RequestState | undefined;
1304
- if (!state || !state.body) return null;
1305
- return Array.from(state.body);
1306
- })
1307
- );
1308
-
1309
- global.setSync(
1310
- "__Request_clone",
1311
- new ivm.Callback((instanceId: number) => {
1312
- const state = stateMap.get(instanceId) as RequestState | undefined;
1313
- if (!state) {
1314
- throw new Error("[TypeError]Cannot clone invalid Request");
1315
- }
1316
- const newId = nextInstanceId++;
1317
- const newState: RequestState = {
1318
- ...state,
1319
- body: state.body ? new Uint8Array(state.body) : null,
1320
- bodyUsed: false,
1321
- };
1322
- stateMap.set(newId, newState);
1323
- return newId;
1324
- })
1325
- );
1326
-
1327
- global.setSync(
1328
- "__Request_getStreamId",
1329
- new ivm.Callback((instanceId: number) => {
1330
- const state = stateMap.get(instanceId) as RequestState | undefined;
1331
- return state?.streamId ?? null;
1332
- })
1333
- );
1334
-
1335
- // Inject Request class
886
+ global.setSync("__Request_construct", new ivm.Callback((url, method, headers, bodyBytes, mode, credentials, cache, redirect, referrer, integrity) => {
887
+ const instanceId = nextInstanceId++;
888
+ const body = bodyBytes ? new Uint8Array(bodyBytes) : null;
889
+ const state = {
890
+ url,
891
+ method,
892
+ headers,
893
+ body,
894
+ bodyUsed: false,
895
+ streamId: null,
896
+ mode,
897
+ credentials,
898
+ cache,
899
+ redirect,
900
+ referrer,
901
+ integrity
902
+ };
903
+ stateMap.set(instanceId, state);
904
+ return instanceId;
905
+ }));
906
+ global.setSync("__Request_get_method", new ivm.Callback((instanceId) => {
907
+ const state = stateMap.get(instanceId);
908
+ return state?.method ?? "GET";
909
+ }));
910
+ global.setSync("__Request_get_url", new ivm.Callback((instanceId) => {
911
+ const state = stateMap.get(instanceId);
912
+ return state?.url ?? "";
913
+ }));
914
+ global.setSync("__Request_get_headers", new ivm.Callback((instanceId) => {
915
+ const state = stateMap.get(instanceId);
916
+ return state?.headers ?? [];
917
+ }));
918
+ global.setSync("__Request_get_bodyUsed", new ivm.Callback((instanceId) => {
919
+ const state = stateMap.get(instanceId);
920
+ return state?.bodyUsed ?? false;
921
+ }));
922
+ global.setSync("__Request_get_mode", new ivm.Callback((instanceId) => {
923
+ const state = stateMap.get(instanceId);
924
+ return state?.mode ?? "cors";
925
+ }));
926
+ global.setSync("__Request_get_credentials", new ivm.Callback((instanceId) => {
927
+ const state = stateMap.get(instanceId);
928
+ return state?.credentials ?? "same-origin";
929
+ }));
930
+ global.setSync("__Request_get_cache", new ivm.Callback((instanceId) => {
931
+ const state = stateMap.get(instanceId);
932
+ return state?.cache ?? "default";
933
+ }));
934
+ global.setSync("__Request_get_redirect", new ivm.Callback((instanceId) => {
935
+ const state = stateMap.get(instanceId);
936
+ return state?.redirect ?? "follow";
937
+ }));
938
+ global.setSync("__Request_get_referrer", new ivm.Callback((instanceId) => {
939
+ const state = stateMap.get(instanceId);
940
+ return state?.referrer ?? "about:client";
941
+ }));
942
+ global.setSync("__Request_get_integrity", new ivm.Callback((instanceId) => {
943
+ const state = stateMap.get(instanceId);
944
+ return state?.integrity ?? "";
945
+ }));
946
+ global.setSync("__Request_markBodyUsed", new ivm.Callback((instanceId) => {
947
+ const state = stateMap.get(instanceId);
948
+ if (state) {
949
+ if (state.bodyUsed) {
950
+ throw new Error("[TypeError]Body has already been consumed");
951
+ }
952
+ state.bodyUsed = true;
953
+ }
954
+ }));
955
+ global.setSync("__Request_text", new ivm.Callback((instanceId) => {
956
+ const state = stateMap.get(instanceId);
957
+ if (!state || !state.body)
958
+ return "";
959
+ return new TextDecoder().decode(state.body);
960
+ }));
961
+ global.setSync("__Request_arrayBuffer", new ivm.Callback((instanceId) => {
962
+ const state = stateMap.get(instanceId);
963
+ if (!state || !state.body) {
964
+ return new ivm.ExternalCopy(new ArrayBuffer(0)).copyInto();
965
+ }
966
+ return new ivm.ExternalCopy(state.body.buffer.slice(state.body.byteOffset, state.body.byteOffset + state.body.byteLength)).copyInto();
967
+ }));
968
+ global.setSync("__Request_getBodyBytes", new ivm.Callback((instanceId) => {
969
+ const state = stateMap.get(instanceId);
970
+ if (!state || !state.body)
971
+ return null;
972
+ return Array.from(state.body);
973
+ }));
974
+ global.setSync("__Request_clone", new ivm.Callback((instanceId) => {
975
+ const state = stateMap.get(instanceId);
976
+ if (!state) {
977
+ throw new Error("[TypeError]Cannot clone invalid Request");
978
+ }
979
+ const newId = nextInstanceId++;
980
+ const newState = {
981
+ ...state,
982
+ body: state.body ? new Uint8Array(state.body) : null,
983
+ bodyUsed: false
984
+ };
985
+ stateMap.set(newId, newState);
986
+ return newId;
987
+ }));
988
+ global.setSync("__Request_getStreamId", new ivm.Callback((instanceId) => {
989
+ const state = stateMap.get(instanceId);
990
+ return state?.streamId ?? null;
991
+ }));
1336
992
  const requestCode = `
1337
993
  (function() {
1338
994
  function __decodeError(err) {
@@ -1661,79 +1317,42 @@ function setupRequest(
1661
1317
  globalThis.Request = Request;
1662
1318
  })();
1663
1319
  `;
1664
-
1665
1320
  context.evalSync(requestCode);
1666
1321
  }
1667
-
1668
- // ============================================================================
1669
- // fetch Implementation
1670
- // ============================================================================
1671
-
1672
- function setupFetchFunction(
1673
- context: ivm.Context,
1674
- stateMap: Map<number, unknown>,
1675
- options?: FetchOptions
1676
- ): void {
1322
+ function setupFetchFunction(context, stateMap, options) {
1677
1323
  const global = context.global;
1678
-
1679
- // Create async fetch reference
1680
- // We use JSON serialization for complex data to avoid transfer issues
1681
- const fetchRef = new ivm.Reference(
1682
- async (
1683
- url: string,
1684
- method: string,
1685
- headersJson: string,
1686
- bodyJson: string | null,
1687
- signalAborted: boolean
1688
- ) => {
1689
- // Check if already aborted
1690
- if (signalAborted) {
1691
- throw new Error("[AbortError]The operation was aborted.");
1692
- }
1693
-
1694
- // Parse headers and body from JSON
1695
- const headers = JSON.parse(headersJson) as [string, string][];
1696
- const bodyBytes = bodyJson ? JSON.parse(bodyJson) as number[] : null;
1697
-
1698
- // Construct native Request
1699
- const body = bodyBytes ? new Uint8Array(bodyBytes) : null;
1700
- const nativeRequest = new Request(url, {
1701
- method,
1702
- headers,
1703
- body,
1704
- });
1705
-
1706
- // Call user's onFetch handler or default fetch
1707
- const onFetch = options?.onFetch ?? fetch;
1708
- const nativeResponse = await onFetch(nativeRequest);
1709
-
1710
- // Read response body
1711
- const responseBody = await nativeResponse.arrayBuffer();
1712
- const responseBodyArray = Array.from(new Uint8Array(responseBody));
1713
-
1714
- // Store the response in the state map and return just the ID + metadata
1715
- const instanceId = nextInstanceId++;
1716
- const state: ResponseState = {
1717
- status: nativeResponse.status,
1718
- statusText: nativeResponse.statusText,
1719
- headers: Array.from(nativeResponse.headers.entries()),
1720
- body: new Uint8Array(responseBodyArray),
1721
- bodyUsed: false,
1722
- type: "default",
1723
- url: nativeResponse.url,
1724
- redirected: nativeResponse.redirected,
1725
- streamId: null,
1726
- };
1727
- stateMap.set(instanceId, state);
1728
-
1729
- // Return only the instance ID - avoid complex object transfer
1730
- return instanceId;
1731
- }
1732
- );
1733
-
1324
+ const fetchRef = new ivm.Reference(async (url, method, headersJson, bodyJson, signalAborted) => {
1325
+ if (signalAborted) {
1326
+ throw new Error("[AbortError]The operation was aborted.");
1327
+ }
1328
+ const headers = JSON.parse(headersJson);
1329
+ const bodyBytes = bodyJson ? JSON.parse(bodyJson) : null;
1330
+ const body = bodyBytes ? new Uint8Array(bodyBytes) : null;
1331
+ const nativeRequest = new Request(url, {
1332
+ method,
1333
+ headers,
1334
+ body
1335
+ });
1336
+ const onFetch = options?.onFetch ?? fetch;
1337
+ const nativeResponse = await onFetch(nativeRequest);
1338
+ const responseBody = await nativeResponse.arrayBuffer();
1339
+ const responseBodyArray = Array.from(new Uint8Array(responseBody));
1340
+ const instanceId = nextInstanceId++;
1341
+ const state = {
1342
+ status: nativeResponse.status,
1343
+ statusText: nativeResponse.statusText,
1344
+ headers: Array.from(nativeResponse.headers.entries()),
1345
+ body: new Uint8Array(responseBodyArray),
1346
+ bodyUsed: false,
1347
+ type: "default",
1348
+ url: nativeResponse.url,
1349
+ redirected: nativeResponse.redirected,
1350
+ streamId: null
1351
+ };
1352
+ stateMap.set(instanceId, state);
1353
+ return instanceId;
1354
+ });
1734
1355
  global.setSync("__fetch_ref", fetchRef);
1735
-
1736
- // Inject fetch function
1737
1356
  const fetchCode = `
1738
1357
  (function() {
1739
1358
  function __decodeError(err) {
@@ -1780,35 +1399,17 @@ function setupFetchFunction(
1780
1399
  };
1781
1400
  })();
1782
1401
  `;
1783
-
1784
1402
  context.evalSync(fetchCode);
1785
1403
  }
1786
-
1787
- // ============================================================================
1788
- // Server Implementation (for serve())
1789
- // ============================================================================
1790
-
1791
- function setupServer(
1792
- context: ivm.Context,
1793
- serveState: ServeState
1794
- ): void {
1404
+ function setupServer(context, serveState) {
1795
1405
  const global = context.global;
1796
-
1797
- // Setup upgrade registry in isolate (data stays in isolate, never marshalled to host)
1798
1406
  context.evalSync(`
1799
1407
  globalThis.__upgradeRegistry__ = new Map();
1800
1408
  globalThis.__upgradeIdCounter__ = 0;
1801
1409
  `);
1802
-
1803
- // Host callback to notify about pending upgrade
1804
- global.setSync(
1805
- "__setPendingUpgrade__",
1806
- new ivm.Callback((connectionId: string) => {
1807
- serveState.pendingUpgrade = { requested: true, connectionId };
1808
- })
1809
- );
1810
-
1811
- // Pure JS Server class with upgrade method
1410
+ global.setSync("__setPendingUpgrade__", new ivm.Callback((connectionId) => {
1411
+ serveState.pendingUpgrade = { requested: true, connectionId };
1412
+ }));
1812
1413
  context.evalSync(`
1813
1414
  (function() {
1814
1415
  class Server {
@@ -1824,36 +1425,18 @@ function setupServer(
1824
1425
  })();
1825
1426
  `);
1826
1427
  }
1827
-
1828
- // ============================================================================
1829
- // ServerWebSocket Implementation (for serve())
1830
- // ============================================================================
1831
-
1832
- function setupServerWebSocket(
1833
- context: ivm.Context,
1834
- wsCommandCallbacks: Set<(cmd: WebSocketCommand) => void>
1835
- ): void {
1428
+ function setupServerWebSocket(context, wsCommandCallbacks) {
1836
1429
  const global = context.global;
1837
-
1838
- // Host callback for ws.send()
1839
- global.setSync(
1840
- "__ServerWebSocket_send",
1841
- new ivm.Callback((connectionId: string, data: string) => {
1842
- const cmd: WebSocketCommand = { type: "message", connectionId, data };
1843
- for (const cb of wsCommandCallbacks) cb(cmd);
1844
- })
1845
- );
1846
-
1847
- // Host callback for ws.close()
1848
- global.setSync(
1849
- "__ServerWebSocket_close",
1850
- new ivm.Callback((connectionId: string, code?: number, reason?: string) => {
1851
- const cmd: WebSocketCommand = { type: "close", connectionId, code, reason };
1852
- for (const cb of wsCommandCallbacks) cb(cmd);
1853
- })
1854
- );
1855
-
1856
- // Pure JS ServerWebSocket class
1430
+ global.setSync("__ServerWebSocket_send", new ivm.Callback((connectionId, data) => {
1431
+ const cmd = { type: "message", connectionId, data };
1432
+ for (const cb of wsCommandCallbacks)
1433
+ cb(cmd);
1434
+ }));
1435
+ global.setSync("__ServerWebSocket_close", new ivm.Callback((connectionId, code, reason) => {
1436
+ const cmd = { type: "close", connectionId, code, reason };
1437
+ for (const cb of wsCommandCallbacks)
1438
+ cb(cmd);
1439
+ }));
1857
1440
  context.evalSync(`
1858
1441
  (function() {
1859
1442
  const _wsInstanceData = new WeakMap();
@@ -1901,13 +1484,7 @@ function setupServerWebSocket(
1901
1484
  })();
1902
1485
  `);
1903
1486
  }
1904
-
1905
- // ============================================================================
1906
- // serve() Function Implementation
1907
- // ============================================================================
1908
-
1909
- function setupServe(context: ivm.Context): void {
1910
- // Pure JS serve() that stores options on __serveOptions__ global
1487
+ function setupServe(context) {
1911
1488
  context.evalSync(`
1912
1489
  (function() {
1913
1490
  globalThis.__serveOptions__ = null;
@@ -1920,136 +1497,58 @@ function setupServe(context: ivm.Context): void {
1920
1497
  })();
1921
1498
  `);
1922
1499
  }
1923
-
1924
- // ============================================================================
1925
- // Main Setup Function
1926
- // ============================================================================
1927
-
1928
- /**
1929
- * Setup Fetch API in an isolated-vm context
1930
- *
1931
- * Injects fetch, Request, Response, Headers, FormData
1932
- * Also sets up core APIs (Blob, File, AbortController, etc.) if not already present
1933
- *
1934
- * @example
1935
- * const handle = await setupFetch(context, {
1936
- * onFetch: async (request) => {
1937
- * // Proxy fetch requests to the host
1938
- * return fetch(request);
1939
- * }
1940
- * });
1941
- *
1942
- * await context.eval(`
1943
- * const response = await fetch("https://example.com");
1944
- * const text = await response.text();
1945
- * `);
1946
- */
1947
- export async function setupFetch(
1948
- context: ivm.Context,
1949
- options?: FetchOptions
1950
- ): Promise<FetchHandle> {
1951
- // Setup core APIs first (Blob, File, AbortController, Streams, etc.)
1500
+ async function setupFetch(context, options) {
1952
1501
  await setupCore(context);
1953
-
1954
1502
  const stateMap = getInstanceStateMapForContext(context);
1955
1503
  const streamRegistry = getStreamRegistryForContext(context);
1956
-
1957
- // Inject Headers (pure JS)
1958
1504
  context.evalSync(headersCode);
1959
-
1960
- // Inject FormData (pure JS)
1961
1505
  context.evalSync(formDataCode);
1962
-
1963
- // Inject multipart parsing/serialization (pure JS)
1964
1506
  context.evalSync(multipartCode);
1965
-
1966
- // Setup stream callbacks and inject HostBackedReadableStream
1967
1507
  setupStreamCallbacks(context, streamRegistry);
1968
1508
  context.evalSync(hostBackedStreamCode);
1969
-
1970
- // Setup Response (host state + isolate class)
1971
1509
  setupResponse(context, stateMap);
1972
-
1973
- // Setup Request (host state + isolate class)
1974
1510
  setupRequest(context, stateMap);
1975
-
1976
- // Setup fetch function
1977
1511
  setupFetchFunction(context, stateMap, options);
1978
-
1979
- // Setup serve state
1980
- const serveState: ServeState = {
1512
+ const serveState = {
1981
1513
  pendingUpgrade: null,
1982
- activeConnections: new Map(),
1514
+ activeConnections: new Map
1983
1515
  };
1984
-
1985
- // Setup WebSocket command callbacks
1986
- const wsCommandCallbacks = new Set<(cmd: WebSocketCommand) => void>();
1987
-
1988
- // Setup Server class
1516
+ const wsCommandCallbacks = new Set;
1989
1517
  setupServer(context, serveState);
1990
-
1991
- // Setup ServerWebSocket class
1992
1518
  setupServerWebSocket(context, wsCommandCallbacks);
1993
-
1994
- // Setup serve function
1995
1519
  setupServe(context);
1996
-
1997
1520
  return {
1998
1521
  dispose() {
1999
- // Clear state for this context
2000
1522
  stateMap.clear();
2001
- // Clear upgrade registry
2002
1523
  context.evalSync(`globalThis.__upgradeRegistry__.clear()`);
2003
- // Clear serve state
2004
1524
  serveState.activeConnections.clear();
2005
1525
  serveState.pendingUpgrade = null;
2006
1526
  },
2007
-
2008
- async dispatchRequest(
2009
- request: Request,
2010
- dispatchOptions?: DispatchRequestOptions
2011
- ): Promise<Response> {
1527
+ async dispatchRequest(request, dispatchOptions) {
2012
1528
  const tick = dispatchOptions?.tick;
2013
-
2014
- // Clean up previous pending upgrade if not consumed
2015
1529
  if (serveState.pendingUpgrade) {
2016
1530
  const oldConnectionId = serveState.pendingUpgrade.connectionId;
2017
1531
  context.evalSync(`globalThis.__upgradeRegistry__.delete("${oldConnectionId}")`);
2018
1532
  serveState.pendingUpgrade = null;
2019
1533
  }
2020
-
2021
- // Check if serve handler exists
2022
1534
  const hasHandler = context.evalSync(`!!globalThis.__serveOptions__?.fetch`);
2023
1535
  if (!hasHandler) {
2024
1536
  throw new Error("No serve() handler registered");
2025
1537
  }
2026
-
2027
- // Setup streaming for request body
2028
- let requestStreamId: number | null = null;
2029
- let streamCleanup: (() => Promise<void>) | null = null;
2030
-
1538
+ let requestStreamId = null;
1539
+ let streamCleanup = null;
2031
1540
  if (request.body) {
2032
- // Create a stream in the registry for the request body
2033
1541
  requestStreamId = streamRegistry.create();
2034
-
2035
- // Start background reader that pushes from native stream to host queue
2036
- streamCleanup = startNativeStreamReader(
2037
- request.body,
2038
- requestStreamId,
2039
- streamRegistry
2040
- );
1542
+ streamCleanup = startNativeStreamReader(request.body, requestStreamId, streamRegistry);
2041
1543
  }
2042
-
2043
1544
  try {
2044
1545
  const headersArray = Array.from(request.headers.entries());
2045
-
2046
- // Create Request instance in isolate
2047
1546
  const requestInstanceId = nextInstanceId++;
2048
- const requestState: RequestState = {
1547
+ const requestState = {
2049
1548
  url: request.url,
2050
1549
  method: request.method,
2051
1550
  headers: headersArray,
2052
- body: null, // No buffered body - using stream
1551
+ body: null,
2053
1552
  bodyUsed: false,
2054
1553
  streamId: requestStreamId,
2055
1554
  mode: request.mode,
@@ -2057,12 +1556,9 @@ export async function setupFetch(
2057
1556
  cache: request.cache,
2058
1557
  redirect: request.redirect,
2059
1558
  referrer: request.referrer,
2060
- integrity: request.integrity,
1559
+ integrity: request.integrity
2061
1560
  };
2062
1561
  stateMap.set(requestInstanceId, requestState);
2063
-
2064
- // Call the fetch handler and get response
2065
- // We use eval with promise: true to handle async handlers
2066
1562
  const responseInstanceId = await context.eval(`
2067
1563
  (async function() {
2068
1564
  const request = Request._fromInstanceId(${requestInstanceId});
@@ -2071,46 +1567,32 @@ export async function setupFetch(
2071
1567
  return response._getInstanceId();
2072
1568
  })()
2073
1569
  `, { promise: true });
2074
-
2075
- // Get ResponseState from the instance
2076
- const responseState = stateMap.get(responseInstanceId) as ResponseState | undefined;
1570
+ const responseState = stateMap.get(responseInstanceId);
2077
1571
  if (!responseState) {
2078
1572
  throw new Error("Response state not found");
2079
1573
  }
2080
-
2081
- // Check if response has streaming body
2082
1574
  if (responseState.streamId !== null) {
2083
1575
  const responseStreamId = responseState.streamId;
2084
1576
  let streamDone = false;
2085
-
2086
- // Create native stream that pumps isolate timers while waiting
2087
- const pumpedStream = new ReadableStream<Uint8Array>({
1577
+ const pumpedStream = new ReadableStream({
2088
1578
  async pull(controller) {
2089
- if (streamDone) return;
2090
-
2091
- // Pump isolate timers to allow stream pump to progress
1579
+ if (streamDone)
1580
+ return;
2092
1581
  while (!streamDone) {
2093
1582
  if (tick) {
2094
1583
  await tick();
2095
1584
  }
2096
-
2097
- // Check if data is available
2098
1585
  const state = streamRegistry.get(responseStreamId);
2099
1586
  if (!state) {
2100
1587
  controller.close();
2101
1588
  streamDone = true;
2102
1589
  return;
2103
1590
  }
2104
-
2105
- // If queue has data or stream is done, break and pull
2106
1591
  if (state.queue.length > 0 || state.closed || state.errored) {
2107
1592
  break;
2108
1593
  }
2109
-
2110
- // Small delay to avoid busy-waiting
2111
1594
  await new Promise((r) => setTimeout(r, 1));
2112
1595
  }
2113
-
2114
1596
  try {
2115
1597
  const result = await streamRegistry.pull(responseStreamId);
2116
1598
  if (result.done) {
@@ -2128,79 +1610,50 @@ export async function setupFetch(
2128
1610
  },
2129
1611
  cancel() {
2130
1612
  streamDone = true;
2131
- streamRegistry.error(
2132
- responseStreamId,
2133
- new Error("Stream cancelled")
2134
- );
1613
+ streamRegistry.error(responseStreamId, new Error("Stream cancelled"));
2135
1614
  streamRegistry.delete(responseStreamId);
2136
- },
1615
+ }
2137
1616
  });
2138
-
2139
- const responseHeaders = new Headers(responseState.headers);
2140
- const status =
2141
- responseState.status === 101 ? 200 : responseState.status;
2142
- const response = new Response(pumpedStream, {
2143
- status,
1617
+ const responseHeaders2 = new Headers(responseState.headers);
1618
+ const status2 = responseState.status === 101 ? 200 : responseState.status;
1619
+ const response2 = new Response(pumpedStream, {
1620
+ status: status2,
2144
1621
  statusText: responseState.statusText,
2145
- headers: responseHeaders,
1622
+ headers: responseHeaders2
2146
1623
  });
2147
-
2148
- // @ts-expect-error - adding custom property
2149
- response._originalStatus = responseState.status;
2150
-
2151
- return response;
1624
+ response2._originalStatus = responseState.status;
1625
+ return response2;
2152
1626
  }
2153
-
2154
- // Convert to native Response (non-streaming)
2155
1627
  const responseHeaders = new Headers(responseState.headers);
2156
1628
  const responseBody = responseState.body;
2157
-
2158
- // Note: Status 101 (Switching Protocols) is not valid for Response constructor
2159
- // We use 200 as the status but preserve the actual status in a custom header
2160
- // The caller should check getUpgradeRequest() for WebSocket upgrades
2161
1629
  const status = responseState.status === 101 ? 200 : responseState.status;
2162
- const response = new Response(responseBody as ConstructorParameters<typeof Response>[0], {
1630
+ const response = new Response(responseBody, {
2163
1631
  status,
2164
1632
  statusText: responseState.statusText,
2165
- headers: responseHeaders,
1633
+ headers: responseHeaders
2166
1634
  });
2167
-
2168
- // Expose the original status via a property for callers to check
2169
- // @ts-expect-error - adding custom property
2170
1635
  response._originalStatus = responseState.status;
2171
-
2172
1636
  return response;
2173
1637
  } finally {
2174
- // Cleanup: cancel stream reader if still running
2175
1638
  if (streamCleanup) {
2176
1639
  await streamCleanup();
2177
1640
  }
2178
- // Delete stream from registry
2179
1641
  if (requestStreamId !== null) {
2180
1642
  streamRegistry.delete(requestStreamId);
2181
1643
  }
2182
1644
  }
2183
1645
  },
2184
-
2185
- getUpgradeRequest(): UpgradeRequest | null {
1646
+ getUpgradeRequest() {
2186
1647
  const result = serveState.pendingUpgrade;
2187
- // Don't clear yet - it will be cleared on next dispatchRequest or consumed by dispatchWebSocketOpen
2188
1648
  return result;
2189
1649
  },
2190
-
2191
- dispatchWebSocketOpen(connectionId: string): void {
2192
- // Check if websocket.open handler exists - required for connection tracking
1650
+ dispatchWebSocketOpen(connectionId) {
2193
1651
  const hasOpenHandler = context.evalSync(`!!globalThis.__serveOptions__?.websocket?.open`);
2194
1652
  if (!hasOpenHandler) {
2195
- // Delete from registry and return - connection NOT tracked
2196
1653
  context.evalSync(`globalThis.__upgradeRegistry__.delete("${connectionId}")`);
2197
1654
  return;
2198
1655
  }
2199
-
2200
- // Store connection (data stays in isolate registry)
2201
1656
  serveState.activeConnections.set(connectionId, { connectionId });
2202
-
2203
- // Create ServerWebSocket and call open handler
2204
1657
  context.evalSync(`
2205
1658
  (function() {
2206
1659
  const ws = new __ServerWebSocket__("${connectionId}");
@@ -2208,35 +1661,26 @@ export async function setupFetch(
2208
1661
  __serveOptions__.websocket.open(ws);
2209
1662
  })()
2210
1663
  `);
2211
-
2212
- // Clear pending upgrade after successful open
2213
1664
  if (serveState.pendingUpgrade?.connectionId === connectionId) {
2214
1665
  serveState.pendingUpgrade = null;
2215
1666
  }
2216
1667
  },
2217
-
2218
- dispatchWebSocketMessage(connectionId: string, message: string | ArrayBuffer): void {
2219
- // Check if connection is tracked
1668
+ dispatchWebSocketMessage(connectionId, message) {
2220
1669
  if (!serveState.activeConnections.has(connectionId)) {
2221
- return; // Silently ignore for unknown connections
1670
+ return;
2222
1671
  }
2223
-
2224
- // Check if message handler exists
2225
1672
  const hasMessageHandler = context.evalSync(`!!globalThis.__serveOptions__?.websocket?.message`);
2226
1673
  if (!hasMessageHandler) {
2227
1674
  return;
2228
1675
  }
2229
-
2230
- // Marshal message and call handler
2231
1676
  if (typeof message === "string") {
2232
1677
  context.evalSync(`
2233
1678
  (function() {
2234
1679
  const ws = globalThis.__activeWs_${connectionId}__;
2235
- if (ws) __serveOptions__.websocket.message(ws, "${message.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n")}");
1680
+ if (ws) __serveOptions__.websocket.message(ws, "${message.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n")}");
2236
1681
  })()
2237
1682
  `);
2238
1683
  } else {
2239
- // ArrayBuffer - convert to base64 or pass as array
2240
1684
  const bytes = Array.from(new Uint8Array(message));
2241
1685
  context.evalSync(`
2242
1686
  (function() {
@@ -2249,25 +1693,19 @@ export async function setupFetch(
2249
1693
  `);
2250
1694
  }
2251
1695
  },
2252
-
2253
- dispatchWebSocketClose(connectionId: string, code: number, reason: string): void {
2254
- // Check if connection is tracked
1696
+ dispatchWebSocketClose(connectionId, code, reason) {
2255
1697
  if (!serveState.activeConnections.has(connectionId)) {
2256
1698
  return;
2257
1699
  }
2258
-
2259
- // Update readyState to CLOSED
2260
1700
  context.evalSync(`
2261
1701
  (function() {
2262
1702
  const ws = globalThis.__activeWs_${connectionId}__;
2263
1703
  if (ws) ws._setReadyState(3);
2264
1704
  })()
2265
1705
  `);
2266
-
2267
- // Check if close handler exists
2268
1706
  const hasCloseHandler = context.evalSync(`!!globalThis.__serveOptions__?.websocket?.close`);
2269
1707
  if (hasCloseHandler) {
2270
- const safeReason = reason.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
1708
+ const safeReason = reason.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
2271
1709
  context.evalSync(`
2272
1710
  (function() {
2273
1711
  const ws = globalThis.__activeWs_${connectionId}__;
@@ -2275,29 +1713,22 @@ export async function setupFetch(
2275
1713
  })()
2276
1714
  `);
2277
1715
  }
2278
-
2279
- // Cleanup
2280
1716
  context.evalSync(`
2281
1717
  delete globalThis.__activeWs_${connectionId}__;
2282
1718
  globalThis.__upgradeRegistry__.delete("${connectionId}");
2283
1719
  `);
2284
1720
  serveState.activeConnections.delete(connectionId);
2285
1721
  },
2286
-
2287
- dispatchWebSocketError(connectionId: string, error: Error): void {
2288
- // Check if connection is tracked
1722
+ dispatchWebSocketError(connectionId, error) {
2289
1723
  if (!serveState.activeConnections.has(connectionId)) {
2290
1724
  return;
2291
1725
  }
2292
-
2293
- // Check if error handler exists
2294
1726
  const hasErrorHandler = context.evalSync(`!!globalThis.__serveOptions__?.websocket?.error`);
2295
1727
  if (!hasErrorHandler) {
2296
1728
  return;
2297
1729
  }
2298
-
2299
- const safeName = error.name.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
2300
- const safeMessage = error.message.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
1730
+ const safeName = error.name.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
1731
+ const safeMessage = error.message.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
2301
1732
  context.evalSync(`
2302
1733
  (function() {
2303
1734
  const ws = globalThis.__activeWs_${connectionId}__;
@@ -2308,18 +1739,21 @@ export async function setupFetch(
2308
1739
  })()
2309
1740
  `);
2310
1741
  },
2311
-
2312
- onWebSocketCommand(callback: (cmd: WebSocketCommand) => void): () => void {
1742
+ onWebSocketCommand(callback) {
2313
1743
  wsCommandCallbacks.add(callback);
2314
1744
  return () => wsCommandCallbacks.delete(callback);
2315
1745
  },
2316
-
2317
- hasServeHandler(): boolean {
2318
- return context.evalSync(`!!globalThis.__serveOptions__?.fetch`) as boolean;
1746
+ hasServeHandler() {
1747
+ return context.evalSync(`!!globalThis.__serveOptions__?.fetch`);
2319
1748
  },
2320
-
2321
- hasActiveConnections(): boolean {
1749
+ hasActiveConnections() {
2322
1750
  return serveState.activeConnections.size > 0;
2323
- },
1751
+ }
2324
1752
  };
2325
1753
  }
1754
+ export {
1755
+ setupFetch,
1756
+ clearAllInstanceState
1757
+ };
1758
+
1759
+ //# debugId=30AA5852218E953D64756E2164756E21