@nimbus-sh/core 0.4.0 → 0.5.0

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 (121) hide show
  1. package/README.md +35 -7
  2. package/dist/_shared/error-text.d.ts +15 -0
  3. package/dist/_shared/error-text.d.ts.map +1 -0
  4. package/dist/_shared/error-text.js +21 -0
  5. package/dist/_shared/exports-resolver.d.ts +19 -7
  6. package/dist/_shared/exports-resolver.d.ts.map +1 -1
  7. package/dist/_shared/exports-resolver.js +1 -1
  8. package/dist/_shared/retry.d.ts.map +1 -1
  9. package/dist/_shared/retry.js +3 -1
  10. package/dist/_shared/w7-frame.js +2 -1
  11. package/dist/constants.d.ts.map +1 -1
  12. package/dist/constants.js +30 -16
  13. package/dist/observability/oom-classify.d.ts +1 -1
  14. package/dist/observability/oom-classify.d.ts.map +1 -1
  15. package/dist/observability/oom-classify.js +7 -0
  16. package/dist/observability/oom-discriminator.d.ts +1 -1
  17. package/dist/runtime/esbuild-service.d.ts +4 -3
  18. package/dist/runtime/esbuild-service.d.ts.map +1 -1
  19. package/dist/runtime/esbuild-service.js +9 -4
  20. package/dist/runtime/installed-runtimes.d.ts +4 -3
  21. package/dist/runtime/installed-runtimes.d.ts.map +1 -1
  22. package/dist/runtime/long-running-handle.d.ts +3 -3
  23. package/dist/runtime/long-running-handle.d.ts.map +1 -1
  24. package/dist/runtime/os-contracts.d.ts +7 -5
  25. package/dist/runtime/os-contracts.d.ts.map +1 -1
  26. package/dist/runtime/port-registry.d.ts +28 -0
  27. package/dist/runtime/port-registry.d.ts.map +1 -1
  28. package/dist/runtime/port-registry.js +79 -9
  29. package/dist/runtime/process-logs.d.ts +9 -1
  30. package/dist/runtime/process-logs.d.ts.map +1 -1
  31. package/dist/runtime/project-detect.d.ts +6 -1
  32. package/dist/runtime/project-detect.d.ts.map +1 -1
  33. package/dist/runtime/require-resolver.d.ts.map +1 -1
  34. package/dist/runtime/require-resolver.js +3 -4
  35. package/dist/runtime/runtime-registry.d.ts +4 -3
  36. package/dist/runtime/runtime-registry.d.ts.map +1 -1
  37. package/dist/runtime/runtime-registry.js +2 -1
  38. package/dist/runtime/sqlite-runtime-fs-bridge.js +1 -5
  39. package/dist/runtime/virtual-socket-kernel.d.ts.map +1 -1
  40. package/dist/runtime/virtual-socket-kernel.generated.d.ts +1 -1
  41. package/dist/runtime/virtual-socket-kernel.generated.d.ts.map +1 -1
  42. package/dist/runtime/virtual-socket-kernel.generated.js +2 -2
  43. package/dist/runtime/virtual-socket-kernel.js +10 -5
  44. package/dist/runtime/wasm-runner.d.ts.map +1 -1
  45. package/dist/runtime/wasm-runner.js +22 -14
  46. package/dist/shell/features.d.ts.map +1 -1
  47. package/dist/shell/features.js +5 -4
  48. package/dist/shell/unix-commands.d.ts +14 -1
  49. package/dist/shell/unix-commands.d.ts.map +1 -1
  50. package/dist/shell/unix-commands.js +115 -69
  51. package/dist/substrate/lifo/commands/net/ip.d.ts.map +1 -1
  52. package/dist/substrate/lifo/commands/system/ps.d.ts +2 -1
  53. package/dist/substrate/lifo/commands/system/ps.d.ts.map +1 -1
  54. package/dist/substrate/lifo/kernel/ServiceManager.d.ts.map +1 -1
  55. package/dist/substrate/lifo/kernel/ServiceManager.js +0 -2
  56. package/dist/substrate/lifo/kernel/network/NetworkStack.d.ts +22 -11
  57. package/dist/substrate/lifo/kernel/network/NetworkStack.d.ts.map +1 -1
  58. package/dist/substrate/lifo/kernel/network/NetworkStack.js +0 -2
  59. package/dist/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.d.ts +2 -1
  60. package/dist/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.d.ts.map +1 -1
  61. package/dist/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.js +3 -4
  62. package/dist/substrate/lifo/kernel/vfs/VFS.d.ts +17 -0
  63. package/dist/substrate/lifo/kernel/vfs/VFS.d.ts.map +1 -1
  64. package/dist/substrate/lifo/kernel/vfs/VFS.js +33 -0
  65. package/dist/substrate/lifo/node-compat/esbuild.d.ts.map +1 -1
  66. package/dist/substrate/lifo/node-compat/esbuild.js +2 -4
  67. package/dist/substrate/lifo/node-compat/index.d.ts.map +1 -1
  68. package/dist/substrate/lifo/node-compat/index.js +3 -2
  69. package/dist/substrate/lifo/node-compat/loose-equality.d.ts +19 -0
  70. package/dist/substrate/lifo/node-compat/loose-equality.d.ts.map +1 -0
  71. package/dist/substrate/lifo/node-compat/loose-equality.js +91 -0
  72. package/dist/substrate/lifo/node-compat/process.d.ts.map +1 -1
  73. package/dist/substrate/lifo/node-compat/process.js +6 -3
  74. package/dist/substrate/lifo/shell/Shell.d.ts +19 -0
  75. package/dist/substrate/lifo/shell/Shell.d.ts.map +1 -1
  76. package/dist/substrate/lifo/shell/Shell.js +86 -75
  77. package/dist/substrate/lifo/shell/lexer.d.ts +9 -0
  78. package/dist/substrate/lifo/shell/lexer.d.ts.map +1 -1
  79. package/dist/substrate/lifo/shell/lexer.js +40 -0
  80. package/dist/vfs/seed-project.d.ts.map +1 -1
  81. package/dist/vfs/seed-project.js +8 -2
  82. package/dist/vfs/sqlite-vfs.d.ts +61 -1
  83. package/dist/vfs/sqlite-vfs.d.ts.map +1 -1
  84. package/dist/vfs/sqlite-vfs.js +199 -35
  85. package/package.json +1 -1
  86. package/src/_shared/error-text.ts +21 -0
  87. package/src/_shared/exports-resolver.ts +24 -4
  88. package/src/_shared/retry.ts +4 -2
  89. package/src/_shared/w7-frame.ts +2 -1
  90. package/src/constants.ts +30 -16
  91. package/src/observability/oom-classify.ts +7 -0
  92. package/src/observability/oom-discriminator.ts +1 -1
  93. package/src/runtime/esbuild-service.ts +16 -11
  94. package/src/runtime/installed-runtimes.ts +4 -3
  95. package/src/runtime/long-running-handle.ts +3 -3
  96. package/src/runtime/os-contracts.ts +7 -5
  97. package/src/runtime/port-registry.ts +97 -9
  98. package/src/runtime/process-logs.ts +12 -3
  99. package/src/runtime/project-detect.ts +7 -1
  100. package/src/runtime/require-resolver.ts +7 -7
  101. package/src/runtime/runtime-registry.ts +9 -7
  102. package/src/runtime/sqlite-runtime-fs-bridge.ts +9 -6
  103. package/src/runtime/virtual-socket-kernel.generated.ts +2 -2
  104. package/src/runtime/virtual-socket-kernel.ts +10 -5
  105. package/src/runtime/wasm-runner.ts +55 -29
  106. package/src/shell/features.ts +5 -4
  107. package/src/shell/unix-commands.ts +273 -128
  108. package/src/substrate/lifo/commands/net/ip.ts +15 -15
  109. package/src/substrate/lifo/commands/system/ps.ts +2 -1
  110. package/src/substrate/lifo/kernel/ServiceManager.ts +0 -2
  111. package/src/substrate/lifo/kernel/network/NetworkStack.ts +24 -17
  112. package/src/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.ts +37 -12
  113. package/src/substrate/lifo/kernel/vfs/VFS.ts +34 -0
  114. package/src/substrate/lifo/node-compat/esbuild.ts +16 -8
  115. package/src/substrate/lifo/node-compat/index.ts +3 -2
  116. package/src/substrate/lifo/node-compat/loose-equality.ts +81 -0
  117. package/src/substrate/lifo/node-compat/process.ts +5 -2
  118. package/src/substrate/lifo/shell/Shell.ts +92 -70
  119. package/src/substrate/lifo/shell/lexer.ts +38 -0
  120. package/src/vfs/seed-project.ts +10 -3
  121. package/src/vfs/sqlite-vfs.ts +209 -36
@@ -37,6 +37,12 @@ export interface PortEntry {
37
37
  */
38
38
  facetStub: RouteableFacetTarget | null;
39
39
  registeredAt: number;
40
+ /**
41
+ * Unguessable token for the lifetime of THIS registration, so an embedder
42
+ * can hand out one port's traffic without handing out the session. A fresh
43
+ * one per `register`, which is what makes an unexposed port stay unexposed.
44
+ */
45
+ capability: string;
40
46
  }
41
47
  export declare class PortRegistry {
42
48
  private ports;
@@ -65,6 +71,15 @@ export declare class PortRegistry {
65
71
  has(port: number): boolean;
66
72
  /** Get all registered ports. */
67
73
  getAll(): PortEntry[];
74
+ /** Answer only whether this capability matches, never what is registered. */
75
+ hasCapability(port: number, capability: string): boolean;
76
+ /**
77
+ * Re-adopt a capability the embedder was already handed, after the supervisor
78
+ * was rebuilt. A restored dev server is a NEW registration with a new token,
79
+ * which would silently invalidate every preview URL already in circulation
80
+ * across an eviction — so the durable value wins over the fresh one.
81
+ */
82
+ restoreCapability(port: number, capability: string): boolean;
68
83
  /**
69
84
  * Forward an HTTP request to the facet owning a port.
70
85
  *
@@ -87,6 +102,19 @@ export declare class PortRegistry {
87
102
  * client receives.
88
103
  */
89
104
  routeRequest(port: number, request: Request, pathname: string): Promise<Response | null>;
105
+ /**
106
+ * Route a request a trusted embedder has already authenticated against the
107
+ * port's capability and stripped its own credentials from.
108
+ *
109
+ * Unlike the generic route, this one PRESERVES `Authorization`. The generic
110
+ * route strips it because the header it sees is Nimbus's own, and handing a
111
+ * session credential to untrusted code is the thing that must never happen.
112
+ * Here the embedder has removed that credential already and what remains
113
+ * belongs to the guest application, which needs it to authenticate its own
114
+ * users.
115
+ */
116
+ routeCapabilityRequest(port: number, capability: string, request: Request, pathname: string): Promise<Response | null>;
117
+ private routeRequestInternal;
90
118
  get stats(): {
91
119
  activePorts: number;
92
120
  ports: {
@@ -1 +1 @@
1
- {"version":3,"file":"port-registry.d.ts","sourceRoot":"","sources":["../../src/runtime/port-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAE9D,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,SAAS,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACvC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,KAAK,CAAgC;IAC7C,OAAO,CAAC,eAAe,CAA2C;IAClE,OAAO,CAAC,gBAAgB,CAAsC;IAE9D,uEAAuE;IACvE,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI;IAQpD;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAMzC,yBAAyB;IACzB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIjC,oDAAoD;IACpD,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAYpC,4BAA4B;IAC5B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAIxC,8EAA8E;IAC9E,OAAO,CAAC,oBAAoB;IAW5B,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE;IAMvC,0BAA0B,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAOnF,qCAAqC;IACrC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B,gCAAgC;IAChC,MAAM,IAAI,SAAS,EAAE;IAIrB;;;;;;;;;;;;;;;;;;;;OAoBG;IACG,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAoG9F,IAAI,KAAK;;;;;;MAKR;IAED,OAAO,CAAC,oBAAoB;IAsB5B,OAAO,CAAC,iBAAiB;CAM1B"}
1
+ {"version":3,"file":"port-registry.d.ts","sourceRoot":"","sources":["../../src/runtime/port-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAE9D,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,SAAS,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACvC,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;CACpB;AAOD,qBAAa,YAAY;IACvB,OAAO,CAAC,KAAK,CAAgC;IAC7C,OAAO,CAAC,eAAe,CAA2C;IAClE,OAAO,CAAC,gBAAgB,CAAsC;IAE9D,uEAAuE;IACvE,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI;IAQpD;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAYzC,yBAAyB;IACzB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIjC,oDAAoD;IACpD,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAYpC,4BAA4B;IAC5B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAIxC,8EAA8E;IAC9E,OAAO,CAAC,oBAAoB;IAW5B,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE;IAMvC,0BAA0B,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAOnF,qCAAqC;IACrC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B,gCAAgC;IAChC,MAAM,IAAI,SAAS,EAAE;IAIrB,6EAA6E;IAC7E,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO;IAIxD;;;;;OAKG;IACH,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO;IAO5D;;;;;;;;;;;;;;;;;;;;OAoBG;IACG,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAI9F;;;;;;;;;;OAUG;IACG,sBAAsB,CAC1B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;YAKb,oBAAoB;IAsHlC,IAAI,KAAK;;;;;;MAKR;IAED,OAAO,CAAC,oBAAoB;IAsB5B,OAAO,CAAC,iBAAiB;CAM1B"}
@@ -27,6 +27,10 @@
27
27
  * to the outer fetch as-is; its body is streamed directly.
28
28
  */
29
29
  import { sanitizeUntrustedHeaders } from '../_shared/untrusted-request.js';
30
+ function createPortCapability() {
31
+ const bytes = crypto.getRandomValues(new Uint8Array(12));
32
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
33
+ }
30
34
  export class PortRegistry {
31
35
  ports = new Map();
32
36
  facetStubsByPid = new Map();
@@ -48,7 +52,13 @@ export class PortRegistry {
48
52
  */
49
53
  register(port, pid) {
50
54
  const target = this.facetStubsByPid.get(pid) ?? null;
51
- this.ports.set(port, { port, pid, facetStub: target, registeredAt: Date.now() });
55
+ this.ports.set(port, {
56
+ port,
57
+ pid,
58
+ facetStub: target,
59
+ registeredAt: Date.now(),
60
+ capability: createPortCapability(),
61
+ });
52
62
  this.notifyPortWaiters(pid);
53
63
  }
54
64
  /** Unregister a port. */
@@ -104,6 +114,23 @@ export class PortRegistry {
104
114
  getAll() {
105
115
  return [...this.ports.values()];
106
116
  }
117
+ /** Answer only whether this capability matches, never what is registered. */
118
+ hasCapability(port, capability) {
119
+ return this.ports.get(port)?.capability === capability;
120
+ }
121
+ /**
122
+ * Re-adopt a capability the embedder was already handed, after the supervisor
123
+ * was rebuilt. A restored dev server is a NEW registration with a new token,
124
+ * which would silently invalidate every preview URL already in circulation
125
+ * across an eviction — so the durable value wins over the fresh one.
126
+ */
127
+ restoreCapability(port, capability) {
128
+ const entry = this.ports.get(port);
129
+ if (!entry || !/^[a-f0-9]{24}$/.test(capability))
130
+ return false;
131
+ entry.capability = capability;
132
+ return true;
133
+ }
107
134
  /**
108
135
  * Forward an HTTP request to the facet owning a port.
109
136
  *
@@ -126,6 +153,25 @@ export class PortRegistry {
126
153
  * client receives.
127
154
  */
128
155
  async routeRequest(port, request, pathname) {
156
+ return this.routeRequestInternal(port, request, pathname, false);
157
+ }
158
+ /**
159
+ * Route a request a trusted embedder has already authenticated against the
160
+ * port's capability and stripped its own credentials from.
161
+ *
162
+ * Unlike the generic route, this one PRESERVES `Authorization`. The generic
163
+ * route strips it because the header it sees is Nimbus's own, and handing a
164
+ * session credential to untrusted code is the thing that must never happen.
165
+ * Here the embedder has removed that credential already and what remains
166
+ * belongs to the guest application, which needs it to authenticate its own
167
+ * users.
168
+ */
169
+ async routeCapabilityRequest(port, capability, request, pathname) {
170
+ if (!this.hasCapability(port, capability))
171
+ return null;
172
+ return this.routeRequestInternal(port, request, pathname, true);
173
+ }
174
+ async routeRequestInternal(port, request, pathname, preserveAuthorization) {
129
175
  const entry = this.ports.get(port);
130
176
  // Honour the documented contract: no entry at all → null, so callers
131
177
  // report "no process listening". (Pre-fix this fell into the 501 below,
@@ -163,7 +209,13 @@ export class PortRegistry {
163
209
  // if a body is supplied on those methods.
164
210
  const hasBody = request.method !== 'GET' && request.method !== 'HEAD';
165
211
  const headers = new Headers(request.headers);
212
+ // The strip below removes Nimbus's own credentials. On a capability
213
+ // route the embedder has already removed those, so what is left belongs
214
+ // to the guest application and has to survive the hop.
215
+ const authorization = preserveAuthorization ? headers.get('authorization') : null;
166
216
  sanitizeUntrustedHeaders(headers);
217
+ if (authorization)
218
+ headers.set('authorization', authorization);
167
219
  headers.set('X-Nimbus-Port', String(port));
168
220
  // `duplex: 'half'` is required by workerd when body is a
169
221
  // ReadableStream — otherwise `new Request(…)` throws. It's not
@@ -178,11 +230,19 @@ export class PortRegistry {
178
230
  if (hasBody)
179
231
  init.duplex = 'half';
180
232
  const forwarded = new Request(innerUrl.toString(), init);
181
- // RPC: the facet receives the Request and returns a Response.
182
- // Both cross the isolate boundary via Workers RPC's native
183
- // Request/Response transport bytes are streamed with
184
- // flow-control, never materialised.
185
- const response = await entry.facetStub.handleHttpRequest(forwarded);
233
+ // HTTP crosses Workers RPC as Request/Response values, streamed with
234
+ // flow control and never materialised. A WebSocket upgrade cannot: its
235
+ // 101 owns a live socket, so it takes the fetch-semantic entrypoint,
236
+ // which every hop preserves. A target with no such entrypoint serves
237
+ // HTTP only, and says so rather than dropping the socket.
238
+ const isWebSocket = request.headers.get('upgrade')?.toLowerCase() === 'websocket';
239
+ const handler = isWebSocket
240
+ ? entry.facetStub.handleWebSocketRequest
241
+ : entry.facetStub.handleHttpRequest;
242
+ if (!handler) {
243
+ return new Response('Port target does not expose a WebSocket fetch route', { status: 501 });
244
+ }
245
+ const response = await handler(forwarded);
186
246
  if (!(response instanceof Response)) {
187
247
  // Defensive: if a facet ever returns something else (JSON
188
248
  // envelope, string, etc.), treat it as a 502 so the client
@@ -253,11 +313,21 @@ export class PortRegistry {
253
313
  function routeableFacetTarget(value) {
254
314
  if ((typeof value !== 'object' && typeof value !== 'function') || value === null)
255
315
  return null;
256
- const method = Reflect.get(value, 'fetch') || Reflect.get(value, 'handleHttpRequest');
257
- if (typeof method !== 'function')
316
+ // A `fetch` target already has fetch semantics, so it serves both.
317
+ const fetchMethod = Reflect.get(value, 'fetch');
318
+ if (typeof fetchMethod === 'function') {
319
+ const fetch = fetchMethod.bind(value);
320
+ return { handleHttpRequest: fetch, handleWebSocketRequest: fetch };
321
+ }
322
+ const httpMethod = Reflect.get(value, 'handleHttpRequest');
323
+ if (typeof httpMethod !== 'function')
258
324
  return null;
325
+ const webSocketMethod = Reflect.get(value, 'handleWebSocketRequest');
259
326
  return {
260
- handleHttpRequest: method.bind(value),
327
+ handleHttpRequest: httpMethod.bind(value),
328
+ ...(typeof webSocketMethod === 'function'
329
+ ? { handleWebSocketRequest: webSocketMethod.bind(value) }
330
+ : {}),
261
331
  };
262
332
  }
263
333
  function errorMessage(error) {
@@ -57,6 +57,14 @@ export interface LogChunk {
57
57
  export interface SequencedLogChunk extends LogChunk {
58
58
  seq: number;
59
59
  }
60
+ /**
61
+ * A chunk as a persist adapter hands it back on load. `seq` is the sequence
62
+ * number the adapter stored alongside it; hydration falls back to array
63
+ * position for a row that carries none.
64
+ */
65
+ export interface PersistedLogChunk extends LogChunk {
66
+ seq?: number;
67
+ }
60
68
  export interface ProcessLogReadOptions {
61
69
  cursor?: number;
62
70
  lines?: number;
@@ -92,7 +100,7 @@ export interface ProcessExitInfo {
92
100
  */
93
101
  export interface PersistAdapter {
94
102
  load(pid: number): {
95
- chunks: LogChunk[];
103
+ chunks: PersistedLogChunk[];
96
104
  exit: ProcessExitInfo | null;
97
105
  } | null;
98
106
  persistChunks(pid: number, rows: {
@@ -1 +1 @@
1
- {"version":3,"file":"process-logs.d.ts","sourceRoot":"","sources":["../../src/runtime/process-logs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"process-logs.d.ts","sourceRoot":"","sources":["../../src/runtime/process-logs.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,11 @@
9
9
  * canonical "is this a Cloudflare Workers project?" check. Future waves
10
10
  * can add detectVite, detectNext, etc. here.
11
11
  */
12
+ /** The filesystem reads a project probe needs. */
13
+ export interface ProjectProbeFs {
14
+ exists(path: string): boolean;
15
+ readFileString(path: string): string;
16
+ }
12
17
  /**
13
18
  * W10: detect whether the project at `<root>` is a Cloudflare Workers
14
19
  * project. Returns true if any of the standard markers are present:
@@ -17,5 +22,5 @@
17
22
  * - <root>/wrangler.toml
18
23
  * - <root>/package.json with `wrangler` in deps or devDeps
19
24
  */
20
- export declare function detectCloudflareWorkersProject(vfs: any, root: string): boolean;
25
+ export declare function detectCloudflareWorkersProject(vfs: ProjectProbeFs, root: string): boolean;
21
26
  //# sourceMappingURL=project-detect.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"project-detect.d.ts","sourceRoot":"","sources":["../../src/runtime/project-detect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAqB9E"}
1
+ {"version":3,"file":"project-detect.d.ts","sourceRoot":"","sources":["../../src/runtime/project-detect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,kDAAkD;AAClD,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CACtC;AAED;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAqBzF"}
@@ -1 +1 @@
1
- {"version":3,"file":"require-resolver.d.ts","sourceRoot":"","sources":["../../src/runtime/require-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AA6d5D;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,sEAAsE;AACtE,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,eAAe,EACpB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,SAAS,CAAC,EAAE,MAAM,GACjB,cAAc,CAkNhB"}
1
+ {"version":3,"file":"require-resolver.d.ts","sourceRoot":"","sources":["../../src/runtime/require-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AA8d5D;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,sEAAsE;AACtE,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,eAAe,EACpB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,SAAS,CAAC,EAAE,MAAM,GACjB,cAAc,CAiNhB"}
@@ -548,8 +548,7 @@ export function prefetchForRequire(vfs, entryCode, cwd, entryFile) {
548
548
  // See `comment-strip.ts` header + the wave's verdict.md.
549
549
  const stripped = stripCommentsForImports(code);
550
550
  REQUIRE_RE.lastIndex = 0;
551
- let match;
552
- while ((match = REQUIRE_RE.exec(stripped)) !== null) {
551
+ for (let match = REQUIRE_RE.exec(stripped); match !== null; match = REQUIRE_RE.exec(stripped)) {
553
552
  const specifier = match[2];
554
553
  if (isFacetProvided(specifier))
555
554
  continue;
@@ -567,7 +566,7 @@ export function prefetchForRequire(vfs, entryCode, cwd, entryFile) {
567
566
  // runtime W3.5 Fix B's CJS rewrite calls require('./x') which then
568
567
  // fails because `x` was never added.
569
568
  IMPORT_RE.lastIndex = 0;
570
- while ((match = IMPORT_RE.exec(stripped)) !== null) {
569
+ for (let match = IMPORT_RE.exec(stripped); match !== null; match = IMPORT_RE.exec(stripped)) {
571
570
  const specifier = match[2];
572
571
  if (isFacetProvided(specifier))
573
572
  continue;
@@ -581,7 +580,7 @@ export function prefetchForRequire(vfs, entryCode, cwd, entryFile) {
581
580
  // Follow static-string dynamic imports so a CLI entry that defers to
582
581
  // import('./dist/index.js') has that subtree's content prefetched.
583
582
  DYNIMPORT_RE.lastIndex = 0;
584
- while ((match = DYNIMPORT_RE.exec(stripped)) !== null) {
583
+ for (let match = DYNIMPORT_RE.exec(stripped); match !== null; match = DYNIMPORT_RE.exec(stripped)) {
585
584
  const specifier = match[2];
586
585
  if (isFacetProvided(specifier))
587
586
  continue;
@@ -40,6 +40,7 @@ import type { SqliteVFS } from '../vfs/sqlite-vfs.js';
40
40
  import { type VfsCred } from './os-contracts.js';
41
41
  import type { EsbuildService } from './esbuild-service.js';
42
42
  import { type FacetBundleProfile } from './bundle-profile.js';
43
+ import type { Command, CommandContext } from '../substrate/lifo/commands/types.js';
43
44
  /**
44
45
  * Result shape that runtime-registry expects from a runner. Mirrors
45
46
  * the existing RunFreshResult / RunBunResult shapes — kept narrow so
@@ -96,7 +97,7 @@ export declare function resolveRuntimeScriptPath(fs: ScriptResolutionFs, cwd: st
96
97
  * to the very same execution path `bun <file>` takes, rather than growing
97
98
  * a second one.
98
99
  */
99
- export type RuntimeSubcommand = (ctx: any, registry: ShellRegistry, runAsRuntime: (args: string[]) => Promise<number>) => Promise<number>;
100
+ export type RuntimeSubcommand = (ctx: CommandContext, registry: ShellRegistry, runAsRuntime: (args: string[]) => Promise<number>) => Promise<number>;
100
101
  export interface RuntimeSpec {
101
102
  /** Shell-command name: 'node' / 'bun' / 'wasm-runner' / 'python'. */
102
103
  name: string;
@@ -142,7 +143,7 @@ export interface RuntimeSpec {
142
143
  * shell registry type tree when the runtime path only needs resolve().
143
144
  */
144
145
  export interface ShellRegistry {
145
- resolve(name: string): Promise<any> | any;
146
+ resolve(name: string): Promise<Command | null | undefined> | Command | null | undefined;
146
147
  }
147
148
  /**
148
149
  * Build a shell-handler function for a runtime. The returned function
@@ -158,5 +159,5 @@ export declare function buildRuntimeHandler(spec: RuntimeSpec, ctx0: {
158
159
  * the module is loaded eagerly or on this call. */
159
160
  getEsbuild(): EsbuildService | Promise<EsbuildService>;
160
161
  registry: ShellRegistry;
161
- }): (ctx: any) => Promise<number>;
162
+ }): Command;
162
163
  //# sourceMappingURL=runtime-registry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-registry.d.ts","sourceRoot":"","sources":["../../src/runtime/runtime-registry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runtime-registry.d.ts","sourceRoot":"","sources":["../../src/runtime/runtime-registry.ts"],"names":[],"mappings":"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"}
@@ -40,6 +40,7 @@ import { normalizeVfsPath, resolveVfsPath, vfsPathExtension } from '../vfs/path.
40
40
  import { CRED_KERNEL } from './os-contracts.js';
41
41
  import { parseFacetBundleProfile } from './bundle-profile.js';
42
42
  import { bindImportMetaResolve, importMetaDefines } from './import-meta-transform.js';
43
+ import { errorText } from '../_shared/error-text.js';
43
44
  /** Extensions probed when a target names no exact file, in Node's order. */
44
45
  const SCRIPT_RESOLUTION_CANDIDATES = ['.js', '.ts', '.tsx', '.mjs', '.jsx', '/index.js', '/index.ts'];
45
46
  /**
@@ -311,7 +312,7 @@ export function buildRuntimeHandler(spec, ctx0) {
311
312
  code = bindImportMetaResolve(transformed.code, absUrl);
312
313
  }
313
314
  catch (e) {
314
- ctx.stderr.write(`${name}: transform error for ${scriptPath}: ${e?.message}\n`);
315
+ ctx.stderr.write(`${name}: transform error for ${scriptPath}: ${errorText(e)}\n`);
315
316
  return 1;
316
317
  }
317
318
  }
@@ -414,11 +414,7 @@ function normalizeOpenFlags(flags) {
414
414
  };
415
415
  }
416
416
  function fsError(code, syscall, path) {
417
- const err = new Error(`${code}: ${syscall} '${path}'`);
418
- err.code = code;
419
- err.syscall = syscall;
420
- err.path = path;
421
- return err;
417
+ return Object.assign(new Error(`${code}: ${syscall} '${path}'`), { code, syscall, path });
422
418
  }
423
419
  function hasErrorCode(error, code) {
424
420
  return typeof error === 'object' && error !== null && 'code' in error && error.code === code;
@@ -1 +1 @@
1
- {"version":3,"file":"virtual-socket-kernel.d.ts","sourceRoot":"","sources":["../../src/runtime/virtual-socket-kernel.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"virtual-socket-kernel.d.ts","sourceRoot":"","sources":["../../src/runtime/virtual-socket-kernel.ts"],"names":[],"mappings":"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"}
@@ -8,7 +8,7 @@
8
8
  * Consumed by python-runner.ts and ruby-runner.ts: spliced into the
9
9
  * socket process worker module source passed to NimbusLoaderPool.
10
10
  *
11
- * Size: 33.72 KiB
11
+ * Size: 33.76 KiB
12
12
  */
13
13
  export declare const VIRTUAL_SOCKET_KERNEL_SRC: string;
14
14
  //# sourceMappingURL=virtual-socket-kernel.generated.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"virtual-socket-kernel.generated.d.ts","sourceRoot":"","sources":["../../src/runtime/virtual-socket-kernel.generated.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,yBAAyB,EAAE,MAAu5lC,CAAC"}
1
+ {"version":3,"file":"virtual-socket-kernel.generated.d.ts","sourceRoot":"","sources":["../../src/runtime/virtual-socket-kernel.generated.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,yBAAyB,EAAE,MAAo8lC,CAAC"}
@@ -8,6 +8,6 @@
8
8
  * Consumed by python-runner.ts and ruby-runner.ts: spliced into the
9
9
  * socket process worker module source passed to NimbusLoaderPool.
10
10
  *
11
- * Size: 33.72 KiB
11
+ * Size: 33.76 KiB
12
12
  */
13
- export const VIRTUAL_SOCKET_KERNEL_SRC = "(() => {\n // ../core/src/runtime/virtual-socket-kernel.ts\n var DEFAULT_LIMITS = {\n responseTimeoutMs: 3e4,\n maxRequestBodyBytes: 32 * 1024 * 1024,\n maxResponseBufferBytes: 64 * 1024 * 1024\n };\n var EMPTY_BYTES = new Uint8Array(0);\n var Deferred = class {\n promise;\n resolve;\n reject;\n constructor() {\n let resolve = () => {\n };\n let reject = () => {\n };\n this.promise = new Promise((res, rej) => {\n resolve = res;\n reject = rej;\n });\n this.resolve = resolve;\n this.reject = reject;\n }\n };\n function isPyodideProxyLike(value) {\n return value !== null && typeof value === \"object\" && typeof value.toJs === \"function\";\n }\n function toBytes(value) {\n if (value instanceof Uint8Array) return value;\n if (value instanceof ArrayBuffer) return new Uint8Array(value);\n if (ArrayBuffer.isView(value)) return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);\n if (Array.isArray(value)) return Uint8Array.from(value);\n if (typeof value === \"string\") return new TextEncoder().encode(value);\n if (isPyodideProxyLike(value)) return toBytes(value.toJs());\n return EMPTY_BYTES;\n }\n function concatBytes(parts) {\n let length = 0;\n for (const part of parts) length += part.byteLength;\n const out = new Uint8Array(length);\n let offset = 0;\n for (const part of parts) {\n out.set(part, offset);\n offset += part.byteLength;\n }\n return out;\n }\n function responseCanHaveBody(requestMethod, status) {\n if (requestMethod.toUpperCase() === \"HEAD\") return false;\n return status !== 204 && status !== 205 && status !== 304;\n }\n function encodeHttpRequest(request, body) {\n const url = new URL(request.url);\n const path = (url.pathname || \"/\") + url.search;\n const headers = new Headers(request.headers);\n if (!headers.has(\"Host\")) headers.set(\"Host\", url.host || \"nimbus.local\");\n if (body.byteLength > 0 && !headers.has(\"Content-Length\")) {\n headers.set(\"Content-Length\", String(body.byteLength));\n }\n const lines = [`${request.method} ${path} HTTP/1.1`];\n headers.forEach((value, key) => lines.push(`${key}: ${value}`));\n lines.push(\"\", \"\");\n return concatBytes([new TextEncoder().encode(lines.join(\"\\r\\n\")), body]);\n }\n function encodeHttpResponseHead(status, statusText, headers, chunkedBody) {\n const lines = [`HTTP/1.1 ${status} ${statusText}`];\n headers.forEach((value, key) => lines.push(`${key}: ${value}`));\n if (chunkedBody) lines.push(\"Transfer-Encoding: chunked\");\n lines.push(\"Connection: close\");\n lines.push(\"\", \"\");\n return new TextEncoder().encode(lines.join(\"\\r\\n\"));\n }\n function encodeChunkedFrame(bytes) {\n return concatBytes([\n new TextEncoder().encode(`${bytes.byteLength.toString(16)}\\r\n`),\n bytes,\n new TextEncoder().encode(\"\\r\\n\")\n ]);\n }\n var CHUNKED_TERMINATOR = new TextEncoder().encode(\"0\\r\\n\\r\\n\");\n var LOOPBACK_READ_HIGH_WATER_BYTES = 1024 * 1024;\n var LOOPBACK_STREAM_CHUNK_BYTES = 64 * 1024;\n var ByteChunkQueue = class {\n constructor(limitTotalBytes, overflowLabel) {\n this.limitTotalBytes = limitTotalBytes;\n this.overflowLabel = overflowLabel;\n }\n limitTotalBytes;\n overflowLabel;\n chunks = [];\n headOffset = 0;\n enqueuedTotalBytes = 0;\n undrainedBytes = 0;\n /** Bytes enqueued but not yet read. Drives flow control on streamed queues. */\n get pendingBytes() {\n return this.undrainedBytes;\n }\n enqueue(bytes) {\n if (bytes.byteLength === 0) return;\n if (this.enqueuedTotalBytes + bytes.byteLength > this.limitTotalBytes) {\n throw new Error(\n `Nimbus virtual socket: ${this.overflowLabel} exceeds ${this.limitTotalBytes} bytes`\n );\n }\n this.enqueuedTotalBytes += bytes.byteLength;\n this.undrainedBytes += bytes.byteLength;\n this.chunks.push(bytes);\n }\n /** Drain up to maxBytes from the head chunk; empty result means no data is queued. */\n readUpTo(maxBytes) {\n const head = this.chunks[0];\n if (!head) return EMPTY_BYTES;\n const available = head.byteLength - this.headOffset;\n const take = Math.min(available, Math.max(1, maxBytes));\n const out = head.subarray(this.headOffset, this.headOffset + take);\n this.headOffset += take;\n this.undrainedBytes -= take;\n if (this.headOffset >= head.byteLength) {\n this.chunks.shift();\n this.headOffset = 0;\n }\n return out;\n }\n };\n var HttpMessageParser = class {\n phase = \"headers\";\n settled = null;\n headerBuffer = EMPTY_BYTES;\n headerScanOffset = 0;\n headerPairs = [];\n framing = \"none\";\n expectedBodyBytes = 0;\n bodyChunks = [];\n bodyByteCount = 0;\n chunkState = \"size\";\n chunkSizeLine = [];\n chunkDataRemaining = 0;\n get outcome() {\n return this.settled;\n }\n feed(chunk) {\n if (this.phase === \"done\" || chunk.byteLength === 0) return;\n if (this.phase === \"headers\") {\n this.feedHeaders(chunk);\n return;\n }\n this.feedBody(chunk);\n }\n /** EOF from the peer closing the connection (or the request being torn down). */\n finish() {\n if (this.phase === \"done\") return;\n if (this.phase === \"headers\") {\n this.fail(`connection closed before ${this.messageLabel} headers`);\n return;\n }\n if (this.framing === \"until-close\") {\n this.completeWithBody(concatBytes(this.bodyChunks));\n return;\n }\n this.fail(`connection closed before the ${this.messageLabel} completed`);\n }\n feedHeaders(chunk) {\n this.headerBuffer = concatBytes([this.headerBuffer, chunk]);\n const headerEnd = this.findHeaderEnd();\n if (headerEnd < 0) {\n this.headerScanOffset = Math.max(0, this.headerBuffer.byteLength - 3);\n return;\n }\n const parsed = this.parseHeaderBlock(this.headerBuffer.subarray(0, headerEnd));\n const leftover = this.headerBuffer.subarray(headerEnd);\n this.headerBuffer = EMPTY_BYTES;\n if (!parsed) return;\n if (!this.bodyAllowed()) {\n this.completeWithBody(null);\n return;\n }\n const transferEncoding = this.headerValue(\"Transfer-Encoding\");\n if (transferEncoding !== null && /chunked/i.test(transferEncoding)) {\n this.framing = \"chunked\";\n } else {\n const contentLength = this.headerValue(\"Content-Length\");\n const expected = contentLength === null ? Number.NaN : parseInt(contentLength, 10);\n if (Number.isFinite(expected) && expected >= 0) {\n this.framing = \"content-length\";\n this.expectedBodyBytes = expected;\n if (expected === 0) {\n this.completeWithBody(EMPTY_BYTES);\n return;\n }\n } else {\n this.framing = this.defaultFramingWhenUnframed();\n if (this.framing === \"none\") {\n this.completeWithBody(null);\n return;\n }\n }\n }\n this.phase = \"body\";\n this.feedBody(leftover);\n }\n feedBody(chunk) {\n if (this.framing === \"chunked\") {\n this.feedChunkedBody(chunk);\n return;\n }\n if (chunk.byteLength === 0) return;\n if (this.framing === \"content-length\") {\n const needed = this.expectedBodyBytes - this.bodyByteCount;\n this.appendBody(chunk.subarray(0, Math.min(needed, chunk.byteLength)));\n if (this.bodyByteCount >= this.expectedBodyBytes) {\n this.completeWithBody(concatBytes(this.bodyChunks));\n }\n return;\n }\n this.appendBody(chunk);\n }\n feedChunkedBody(chunk) {\n let i = 0;\n while (i < chunk.byteLength && this.phase === \"body\") {\n if (this.chunkState === \"size\") {\n const byte = chunk[i++];\n if (byte === 10) {\n const line = new TextDecoder().decode(Uint8Array.from(this.chunkSizeLine)).trim();\n this.chunkSizeLine.length = 0;\n const size = parseInt(line.split(\";\", 1)[0], 16);\n if (!Number.isFinite(size) || size < 0) {\n this.fail(\"malformed chunked transfer encoding\");\n return;\n }\n if (size === 0) {\n this.completeWithBody(concatBytes(this.bodyChunks));\n return;\n }\n this.chunkState = \"data\";\n this.chunkDataRemaining = size;\n } else if (byte !== 13) {\n this.chunkSizeLine.push(byte);\n }\n } else if (this.chunkState === \"data\") {\n const take = Math.min(this.chunkDataRemaining, chunk.byteLength - i);\n this.appendBody(chunk.subarray(i, i + take));\n i += take;\n this.chunkDataRemaining -= take;\n if (this.chunkDataRemaining === 0) this.chunkState = \"data-end\";\n } else {\n const byte = chunk[i++];\n if (byte === 10) {\n this.chunkState = \"size\";\n } else if (byte !== 13) {\n this.fail(\"malformed chunked transfer encoding\");\n return;\n }\n }\n }\n }\n appendBody(bytes) {\n if (bytes.byteLength === 0) return;\n this.bodyChunks.push(bytes);\n this.bodyByteCount += bytes.byteLength;\n }\n findHeaderEnd() {\n const bytes = this.headerBuffer;\n for (let i = this.headerScanOffset; i + 3 < bytes.byteLength; i++) {\n if (bytes[i] === 13 && bytes[i + 1] === 10 && bytes[i + 2] === 13 && bytes[i + 3] === 10) {\n return i + 4;\n }\n }\n return -1;\n }\n /** Returns false when the start line is malformed (the parser has already failed). */\n parseHeaderBlock(headerBytes) {\n const headerText = new TextDecoder().decode(headerBytes);\n const lines = headerText.replace(/\\r\\n/g, \"\\n\").split(\"\\n\").filter((line) => line.length > 0);\n const startLine = lines.shift() ?? \"\";\n if (!this.parseStartLine(startLine)) return false;\n for (const line of lines) {\n const separator = line.indexOf(\":\");\n if (separator <= 0) continue;\n this.headerPairs.push([line.slice(0, separator), line.slice(separator + 1).trimStart()]);\n }\n return true;\n }\n headerValue(name) {\n const target = name.toLowerCase();\n for (const [key, value] of this.headerPairs) {\n if (key.toLowerCase() === target) return value;\n }\n return null;\n }\n completeWithBody(body) {\n this.phase = \"done\";\n this.settled = { kind: \"message\", message: this.buildMessage(body) };\n }\n fail(message) {\n this.phase = \"done\";\n this.settled = { kind: \"failed\", message };\n }\n };\n var HttpResponseParser = class extends HttpMessageParser {\n constructor(requestMethod) {\n super();\n this.requestMethod = requestMethod;\n }\n requestMethod;\n status = 200;\n statusText = \"\";\n get messageLabel() {\n return \"response\";\n }\n parseStartLine(line) {\n const match = /^HTTP\\/\\d(?:\\.\\d)?\\s+(\\d{3})(?:\\s+(.*))?$/.exec(line || \"HTTP/1.1 200 OK\");\n this.status = match ? parseInt(match[1], 10) : 200;\n this.statusText = match?.[2] ?? \"\";\n return true;\n }\n bodyAllowed() {\n return responseCanHaveBody(this.requestMethod, this.status);\n }\n defaultFramingWhenUnframed() {\n return \"until-close\";\n }\n buildMessage(body) {\n return { status: this.status, statusText: this.statusText, headerPairs: this.headerPairs, body };\n }\n };\n var HttpRequestParser = class extends HttpMessageParser {\n method = \"GET\";\n target = \"/\";\n get messageLabel() {\n return \"request\";\n }\n parseStartLine(line) {\n const match = /^([A-Za-z]+)\\s+(\\S+)\\s+HTTP\\/\\d(?:\\.\\d)?$/.exec(line);\n if (!match) {\n this.fail(`malformed HTTP request line: ${JSON.stringify(line.slice(0, 120))}`);\n return false;\n }\n this.method = match[1].toUpperCase();\n this.target = match[2];\n return true;\n }\n bodyAllowed() {\n return true;\n }\n defaultFramingWhenUnframed() {\n return \"none\";\n }\n buildMessage(body) {\n return { method: this.method, target: this.target, headerPairs: this.headerPairs, body };\n }\n };\n function errorResponse(status, message) {\n return {\n status,\n statusText: \"\",\n headerPairs: [[\"content-type\", \"text/plain; charset=utf-8\"]],\n body: new TextEncoder().encode(`Nimbus virtual socket: ${message}`)\n };\n }\n function toResponse(settled) {\n const headers = new Headers();\n for (const [key, value] of settled.headerPairs) headers.append(key, value);\n return new Response(settled.body, {\n status: settled.status,\n statusText: settled.statusText,\n headers\n });\n }\n var VirtualConnection = class {\n constructor(id, requestMethod, requestBytes, limits) {\n this.id = id;\n this.inbound = new ByteChunkQueue(requestBytes.byteLength, \"request buffer\");\n this.inbound.enqueue(requestBytes);\n this.outbound = new ByteChunkQueue(limits.maxResponseBufferBytes, \"response buffer\");\n this.parser = new HttpResponseParser(requestMethod);\n }\n id;\n /** Request bytes the guest server reads; filled in one shot in stage 1. */\n inbound;\n /** Response bytes the guest server writes; drained into the parser. */\n outbound;\n parser;\n responseReady = new Deferred();\n settled = false;\n closed = false;\n read(maxBytes) {\n return Array.from(this.inbound.readUpTo(Math.max(1, maxBytes | 0)));\n }\n /** The whole request is buffered before the connection is accepted, so nothing is ever awaited. */\n readAsync(maxBytes) {\n return Promise.resolve(this.read(maxBytes));\n }\n readBytesAsync(maxBytes) {\n return Promise.resolve(this.inbound.readUpTo(Math.max(1, maxBytes | 0)));\n }\n /** Same reason: an empty read on an accepted connection is always genuine EOF. */\n atEof() {\n return true;\n }\n write(bytesLike) {\n const bytes = toBytes(bytesLike);\n if (bytes.byteLength === 0 || this.settled || this.closed) return bytes.byteLength;\n this.outbound.enqueue(bytes.slice());\n this.pumpParser();\n return bytes.byteLength;\n }\n /** EOF from the server side (or request teardown); completes until-close bodies. */\n close() {\n if (this.closed) return;\n this.closed = true;\n if (this.settled) return;\n this.parser.finish();\n this.pumpParser();\n }\n /** Abort propagation: settle the pending preview request with a terminal status. */\n abort(message, status) {\n this.closed = true;\n this.settle(errorResponse(status, message));\n }\n async response(timeoutMs) {\n const timer = setTimeout(() => {\n this.settle(errorResponse(504, \"timed out waiting for response\"));\n }, Math.max(1, timeoutMs));\n try {\n return toResponse(await this.responseReady.promise);\n } finally {\n clearTimeout(timer);\n }\n }\n pumpParser() {\n for (; ; ) {\n const chunk = this.outbound.readUpTo(Number.MAX_SAFE_INTEGER);\n if (chunk.byteLength === 0) break;\n this.parser.feed(chunk);\n }\n const outcome = this.parser.outcome;\n if (!outcome) return;\n if (outcome.kind === \"failed\") {\n this.settle(errorResponse(502, outcome.message));\n return;\n }\n const { status, statusText, headerPairs, body } = outcome.message;\n this.settle({\n status,\n statusText,\n // The framing is consumed here; the Worker Response re-frames the body itself.\n headerPairs: headerPairs.filter(([key]) => !/^transfer-encoding$/i.test(key)),\n body\n });\n }\n settle(response) {\n if (this.settled) return;\n this.settled = true;\n this.responseReady.resolve(response);\n }\n };\n var LoopbackClientConnection = class {\n constructor(id, port, route, limits) {\n this.id = id;\n this.port = port;\n this.route = route;\n this.limits = limits;\n }\n id;\n port;\n route;\n limits;\n parser = new HttpRequestParser();\n /** Response bytes the guest client reads. */\n inbound = new ByteChunkQueue(Number.MAX_SAFE_INTEGER, \"loopback response buffer\");\n readable = null;\n drained = null;\n dispatched = false;\n eof = false;\n closed = false;\n write(bytesLike) {\n const bytes = toBytes(bytesLike);\n if (bytes.byteLength === 0 || this.closed) return bytes.byteLength;\n if (this.dispatched) {\n throw new Error(\n `Nimbus loopback socket: port ${this.port} answered with \"Connection: close\"; open a new connection for the next request`\n );\n }\n this.parser.feed(bytes.slice());\n const outcome = this.parser.outcome;\n if (!outcome) return bytes.byteLength;\n this.dispatched = true;\n if (outcome.kind === \"failed\") this.failWith(400, outcome.message);\n else void this.dispatch(outcome.message);\n return bytes.byteLength;\n }\n read(maxBytes) {\n return Array.from(this.readBytes(maxBytes));\n }\n /** Byte-array read. The fd-backed path uses this so a stream never round-trips through number[]. */\n readBytes(maxBytes) {\n const out = this.inbound.readUpTo(Math.max(1, maxBytes | 0));\n if (this.drained && this.inbound.pendingBytes <= LOOPBACK_READ_HIGH_WATER_BYTES) {\n const waiter = this.drained;\n this.drained = null;\n waiter.resolve();\n }\n return out;\n }\n atEof() {\n return (this.eof || this.closed) && this.inbound.pendingBytes === 0;\n }\n /** Blocks the guest until response bytes arrive; an empty result is EOF. */\n async readAsync(maxBytes) {\n return Array.from(await this.readBytesAsync(maxBytes));\n }\n async readBytesAsync(maxBytes) {\n for (; ; ) {\n const chunk = this.readBytes(maxBytes);\n if (chunk.byteLength > 0) return chunk;\n if (this.eof || this.closed) return EMPTY_BYTES;\n if (!this.dispatched) {\n throw new Error(\n \"Nimbus loopback socket: read before a complete HTTP request was written (loopback sockets carry HTTP requests, not arbitrary byte streams)\"\n );\n }\n const waiter = this.readable ??= new Deferred();\n await waiter.promise;\n }\n }\n close() {\n if (this.closed) return;\n this.closed = true;\n this.wakeReader();\n if (this.drained) {\n const waiter = this.drained;\n this.drained = null;\n waiter.resolve();\n }\n }\n async dispatch(parsed) {\n let request;\n try {\n request = this.buildRequest(parsed);\n } catch (error) {\n this.failWith(400, describeError(error));\n return;\n }\n let response;\n try {\n response = await this.withTimeout(this.route(this.port, request));\n } catch (error) {\n this.failWith(502, describeError(error));\n return;\n }\n await this.streamResponse(request.method, response);\n }\n buildRequest(parsed) {\n const headers = new Headers();\n let authority = `127.0.0.1:${this.port}`;\n for (const [key, value] of parsed.headerPairs) {\n if (/^host$/i.test(key)) {\n authority = value;\n continue;\n }\n if (/^(transfer-encoding|connection|content-length|keep-alive)$/i.test(key)) continue;\n headers.append(key, value);\n }\n const target = parsed.target.startsWith(\"/\") ? parsed.target : `/${parsed.target}`;\n const url = /^https?:\\/\\//i.test(parsed.target) ? parsed.target : `http://${authority}${target}`;\n const body = parsed.body && parsed.body.byteLength > 0 ? parsed.body : null;\n const methodTakesBody = parsed.method !== \"GET\" && parsed.method !== \"HEAD\";\n return new Request(url, {\n method: parsed.method,\n headers,\n body: methodTakesBody ? body : null\n });\n }\n async streamResponse(method, response) {\n const withBody = responseCanHaveBody(method, response.status) && response.body !== null;\n const headers = new Headers(response.headers);\n for (const hopByHop of [\"transfer-encoding\", \"content-length\", \"connection\", \"keep-alive\"]) {\n headers.delete(hopByHop);\n }\n this.pushBytes(encodeHttpResponseHead(response.status, response.statusText, headers, withBody));\n if (!withBody || !response.body) {\n this.finishStream();\n return;\n }\n const reader = response.body.getReader();\n try {\n for (; ; ) {\n const { value, done } = await reader.read();\n if (done) break;\n if (this.closed) return;\n const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);\n if (bytes.byteLength === 0) continue;\n this.pushBytes(encodeChunkedFrame(bytes));\n await this.awaitDrain();\n }\n this.pushBytes(CHUNKED_TERMINATOR);\n } catch {\n } finally {\n try {\n await reader.cancel();\n } catch {\n }\n this.finishStream();\n }\n }\n /** Synthesize a complete response so the guest sees an HTTP error, never a hang. */\n failWith(status, detail) {\n const body = new TextEncoder().encode(`Nimbus loopback socket: ${detail}`);\n const headers = new Headers({\n \"content-type\": \"text/plain; charset=utf-8\",\n \"content-length\": String(body.byteLength)\n });\n this.pushBytes(encodeHttpResponseHead(status, \"\", headers, false));\n this.pushBytes(body);\n this.finishStream();\n }\n pushBytes(bytes) {\n if (this.closed) return;\n this.inbound.enqueue(bytes);\n this.wakeReader();\n }\n finishStream() {\n this.eof = true;\n this.wakeReader();\n }\n wakeReader() {\n const waiter = this.readable;\n if (!waiter) return;\n this.readable = null;\n waiter.resolve();\n }\n /** Pause the body pump while the guest is behind, so a big stream cannot grow unbounded. */\n awaitDrain() {\n if (this.inbound.pendingBytes <= LOOPBACK_READ_HIGH_WATER_BYTES) return Promise.resolve();\n return (this.drained ??= new Deferred()).promise;\n }\n withTimeout(promise) {\n let timer = null;\n const timeout = new Promise((_resolve, reject) => {\n timer = setTimeout(\n () => reject(new Error(`port ${this.port} did not respond within ${this.limits.responseTimeoutMs}ms`)),\n Math.max(1, this.limits.responseTimeoutMs)\n );\n });\n return Promise.race([promise, timeout]).finally(() => {\n if (timer !== null) clearTimeout(timer);\n });\n }\n };\n function describeError(error) {\n return error instanceof Error ? error.message : String(error);\n }\n function socketStreamFor(conn) {\n return {\n // Neither direction has a handshake: an accepted connection already holds\n // the request, and a dialed one starts its exchange on the first write.\n opened: Promise.resolve(),\n readable: new ReadableStream(\n {\n async pull(controller) {\n const bytes = await conn.readBytesAsync(LOOPBACK_STREAM_CHUNK_BYTES);\n if (bytes.byteLength === 0) controller.close();\n else controller.enqueue(bytes);\n },\n cancel() {\n conn.close();\n }\n },\n // highWaterMark 0 so the stream never pulls speculatively. A default\n // strategy pulls once at construction, which on a client connection\n // means reading before the guest has written its request.\n { highWaterMark: 0 }\n ),\n writable: new WritableStream({\n write(chunk) {\n conn.write(chunk);\n },\n abort() {\n conn.close();\n }\n }),\n close() {\n conn.close();\n return Promise.resolve();\n }\n };\n }\n var VirtualListener = class {\n constructor(port) {\n this.port = port;\n }\n port;\n queue = [];\n acceptWaiters = [];\n push(conn) {\n const waiter = this.acceptWaiters.shift();\n if (waiter) waiter.resolve(conn);\n else this.queue.push(conn);\n }\n accept() {\n const queued = this.queue.shift();\n if (queued) return Promise.resolve(queued);\n const waiter = new Deferred();\n this.acceptWaiters.push(waiter);\n return waiter.promise;\n }\n take() {\n return this.queue.shift() ?? null;\n }\n pending() {\n return this.queue.length;\n }\n drainQueued() {\n return this.queue.splice(0);\n }\n rejectPendingAccepts(error) {\n for (const waiter of this.acceptWaiters.splice(0)) waiter.reject(error);\n }\n };\n var VirtualSocketKernel = class {\n constructor(host, limits) {\n this.host = host;\n this.limits = { ...DEFAULT_LIMITS, ...limits };\n }\n host;\n /** Public: runner glue inspects listeners.keys() for the default preview port. */\n listeners = /* @__PURE__ */ new Map();\n connections = /* @__PURE__ */ new Map();\n limits;\n nextConnectionId = 1;\n nextEphemeralPort = 49152;\n listenWaiters = [];\n readableWaiters = /* @__PURE__ */ new Set();\n listen(port) {\n let n = Number(port);\n if (!Number.isInteger(n) || n < 0 || n >= 65536) throw new Error(`invalid port: ${port}`);\n if (n === 0) {\n while (this.listeners.has(this.nextEphemeralPort)) {\n this.nextEphemeralPort++;\n if (this.nextEphemeralPort >= 65535) this.nextEphemeralPort = 49152;\n }\n n = this.nextEphemeralPort++;\n if (this.nextEphemeralPort >= 65535) this.nextEphemeralPort = 49152;\n }\n if (!this.listeners.has(n)) {\n this.listeners.set(n, new VirtualListener(n));\n try {\n this.host.__nimbusVirtualSocketDidListen?.(n);\n } catch {\n }\n for (const waiter of this.listenWaiters.splice(0)) waiter.resolve(n);\n }\n return n;\n }\n closeListener(port) {\n const n = Number(port);\n const listener = this.listeners.get(n);\n if (!listener) return;\n this.listeners.delete(n);\n for (const conn of listener.drainQueued()) {\n this.connections.delete(conn.id);\n conn.abort(`listener closed on port ${n}`, 502);\n }\n listener.rejectPendingAccepts(new Error(`port is not listening: ${n}`));\n }\n async accept(port) {\n const listener = this.listeners.get(Number(port));\n if (!listener) throw new Error(`port is not listening: ${port}`);\n const conn = await listener.accept();\n return { id: conn.id, host: \"127.0.0.1\", port: 0 };\n }\n acceptNow(port) {\n const listener = this.listeners.get(Number(port));\n if (!listener) throw new Error(`port is not listening: ${port}`);\n const conn = listener.take();\n return conn ? { id: conn.id, host: \"127.0.0.1\", port: 0 } : null;\n }\n /**\n * Open a client connection to `port` on the session's loopback.\n *\n * The returned id is an ordinary connection id: send() writes the\n * request, recv()/recvAsync() read the response, close() releases it.\n * Only HTTP/1.1 crosses a loopback connection - see\n * LoopbackClientConnection for why - and each one carries a single\n * request/response exchange.\n */\n connect(port) {\n const conn = this.openLoopbackClient(port);\n this.connections.set(conn.id, conn);\n return conn.id;\n }\n /**\n * The same client connection as `connect`, handed back in Cloudflare's\n * `Socket` shape.\n *\n * Guests whose sockets are real WASI file descriptors (ruby.wasm, and any\n * future wasm32-wasi program) reach loopback through this: the WASI shim\n * stores it in exactly the fd slot a `cloudflare:sockets` connection would\n * occupy, so `fd_read` on an in-session port is the same suspending read as\n * `fd_read` on a remote host.\n */\n connectStream(port) {\n return socketStreamFor(this.openLoopbackClient(port));\n }\n /**\n * An already-accepted connection in the same `Socket` shape, so a server's\n * accepted socket is the same kind of file descriptor as a client's dialed\n * one. `accept`/`acceptNow` still hand out the connection id, because accept\n * itself stays on the cooperative pump - this only binds the result.\n */\n streamFor(id) {\n const conn = this.connections.get(Number(id));\n if (!conn) throw new Error(`connection is closed: ${id}`);\n return socketStreamFor(conn);\n }\n openLoopbackClient(port) {\n const n = Number(port);\n if (!Number.isInteger(n) || n <= 0 || n >= 65536) throw new Error(`invalid port: ${port}`);\n const route = this.host.__nimbusVirtualSocketRouteLoopback;\n if (typeof route !== \"function\") {\n throw new Error(\"Nimbus loopback sockets are unavailable in this runtime\");\n }\n return new LoopbackClientConnection(this.nextConnectionId++, n, route, this.limits);\n }\n /** Plain number array: Pyodide bytes() and the ruby.wasm base64 bridge both consume it. */\n recv(id, maxBytes) {\n const conn = this.connections.get(Number(id));\n if (!conn) return [];\n return conn.read(Number(maxBytes));\n }\n /**\n * Suspending read. A client connection has nothing to hand back until\n * the loopback response arrives, so guests that can suspend (Pyodide\n * `run_sync`, a JSPI-suspending WASI call) await this instead of\n * spinning on recv(). An empty result is EOF.\n */\n async recvAsync(id, maxBytes) {\n const conn = this.connections.get(Number(id));\n if (!conn) return [];\n return conn.readAsync(Number(maxBytes));\n }\n /**\n * Whether an empty recv() means end-of-response rather than \"not yet\".\n * Guests that poll instead of suspending (ruby.wasm, whose JS bridge is\n * synchronous) need the two apart to avoid truncating a response.\n */\n atEof(id) {\n const conn = this.connections.get(Number(id));\n return conn ? conn.atEof() : true;\n }\n send(id, bytesLike) {\n const conn = this.connections.get(Number(id));\n if (!conn) throw new Error(`connection is closed: ${id}`);\n return conn.write(bytesLike);\n }\n close(id) {\n const conn = this.connections.get(Number(id));\n if (!conn) return;\n conn.close();\n this.connections.delete(Number(id));\n }\n pending(port) {\n return this.listeners.get(Number(port))?.pending() ?? 0;\n }\n firstListeningPort() {\n for (const port of this.listeners.keys()) return port;\n return null;\n }\n /** select()-style readiness: resolves ports with queued connections, [] on timeout. */\n waitReadable(ports, timeoutSeconds) {\n const normalized = (Array.isArray(ports) ? ports : []).map((p) => Number(p)).filter((p) => Number.isInteger(p));\n const readyNow = normalized.filter((port) => this.pending(port) > 0);\n if (readyNow.length > 0) return Promise.resolve(readyNow);\n const timeoutMs = timeoutSeconds == null ? this.limits.responseTimeoutMs : Math.max(0, Number(timeoutSeconds) * 1e3);\n const deferred = new Deferred();\n const waiter = {\n ports: normalized,\n deferred,\n timer: setTimeout(() => {\n this.readableWaiters.delete(waiter);\n deferred.resolve([]);\n }, timeoutMs)\n };\n this.readableWaiters.add(waiter);\n return deferred.promise;\n }\n async waitForListen(timeoutMs) {\n const existing = this.firstListeningPort();\n if (existing) return existing;\n const waiter = new Deferred();\n this.listenWaiters.push(waiter);\n let timer = null;\n const timeout = new Promise((resolve) => {\n timer = setTimeout(() => resolve(null), Math.max(1, timeoutMs ?? 5e3));\n });\n try {\n return await Promise.race([waiter.promise, timeout]);\n } finally {\n if (timer !== null) clearTimeout(timer);\n }\n }\n async handleHttpRequest(port, request) {\n const n = Number(port);\n let listener = this.listeners.get(n);\n if (!listener) {\n try {\n await this.host.__nimbusVirtualSocketEnsureListener?.(n);\n } catch {\n }\n listener = this.listeners.get(n);\n }\n if (!listener) {\n return new Response(`Nimbus virtual socket: no listener on port ${port}`, { status: 502 });\n }\n const body = request.method === \"GET\" || request.method === \"HEAD\" ? EMPTY_BYTES : new Uint8Array(await request.arrayBuffer());\n if (body.byteLength > this.limits.maxRequestBodyBytes) {\n return new Response(\n `Nimbus virtual socket: request body exceeds ${this.limits.maxRequestBodyBytes} bytes`,\n { status: 413 }\n );\n }\n const id = this.nextConnectionId++;\n const conn = new VirtualConnection(id, request.method, encodeHttpRequest(request, body), this.limits);\n this.connections.set(id, conn);\n listener.push(conn);\n this.notifyReadable(n);\n const signal = request.signal;\n const onAbort = () => conn.abort(\"client aborted the request\", 499);\n if (signal?.aborted) onAbort();\n else signal?.addEventListener(\"abort\", onAbort);\n try {\n try {\n const accepted = await this.host.__nimbusVirtualSocketRequestQueued?.(n);\n if (accepted === false) {\n const detail = typeof this.host.__nimbusVirtualSocketLastError === \"string\" ? this.host.__nimbusVirtualSocketLastError.trim() : \"\";\n const suffix = detail ? `: ${detail}` : \"\";\n return new Response(\n `Nimbus virtual socket: runtime handler did not accept the request${suffix}`,\n { status: 502 }\n );\n }\n } catch {\n }\n return await conn.response(this.limits.responseTimeoutMs);\n } finally {\n signal?.removeEventListener(\"abort\", onAbort);\n conn.close();\n this.connections.delete(id);\n }\n }\n notifyReadable(port) {\n for (const waiter of Array.from(this.readableWaiters)) {\n if (!waiter.ports.includes(port)) continue;\n const ready = waiter.ports.filter((p) => this.pending(p) > 0);\n if (ready.length === 0) continue;\n clearTimeout(waiter.timer);\n this.readableWaiters.delete(waiter);\n waiter.deferred.resolve(ready);\n }\n }\n };\n function installVirtualSocketKernel(scope = globalThis) {\n if (!scope.__nimbusVirtualSockets) {\n scope.__nimbusVirtualSockets = new VirtualSocketKernel(scope);\n }\n return scope.__nimbusVirtualSockets;\n }\n\n // <stdin>\n installVirtualSocketKernel();\n})();\n";
13
+ export const VIRTUAL_SOCKET_KERNEL_SRC = "(() => {\n // ../core/src/runtime/virtual-socket-kernel.ts\n var DEFAULT_LIMITS = {\n responseTimeoutMs: 3e4,\n maxRequestBodyBytes: 32 * 1024 * 1024,\n maxResponseBufferBytes: 64 * 1024 * 1024\n };\n var EMPTY_BYTES = new Uint8Array(0);\n var Deferred = class {\n promise;\n resolve;\n reject;\n constructor() {\n let resolve = () => {\n };\n let reject = () => {\n };\n this.promise = new Promise((res, rej) => {\n resolve = res;\n reject = rej;\n });\n this.resolve = resolve;\n this.reject = reject;\n }\n };\n function isPyodideProxyLike(value) {\n return value !== null && typeof value === \"object\" && typeof value.toJs === \"function\";\n }\n function toBytes(value) {\n if (value instanceof Uint8Array) return value;\n if (value instanceof ArrayBuffer) return new Uint8Array(value);\n if (ArrayBuffer.isView(value)) return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);\n if (Array.isArray(value)) return Uint8Array.from(value);\n if (typeof value === \"string\") return new TextEncoder().encode(value);\n if (isPyodideProxyLike(value)) return toBytes(value.toJs());\n return EMPTY_BYTES;\n }\n function concatBytes(parts) {\n let length = 0;\n for (const part of parts) length += part.byteLength;\n const out = new Uint8Array(length);\n let offset = 0;\n for (const part of parts) {\n out.set(part, offset);\n offset += part.byteLength;\n }\n return out;\n }\n function responseCanHaveBody(requestMethod, status) {\n if (requestMethod.toUpperCase() === \"HEAD\") return false;\n return status !== 204 && status !== 205 && status !== 304;\n }\n function encodeHttpRequest(request, body) {\n const url = new URL(request.url);\n const path = (url.pathname || \"/\") + url.search;\n const headers = new Headers(request.headers);\n if (!headers.has(\"Host\")) headers.set(\"Host\", url.host || \"nimbus.local\");\n if (body.byteLength > 0 && !headers.has(\"Content-Length\")) {\n headers.set(\"Content-Length\", String(body.byteLength));\n }\n const lines = [`${request.method} ${path} HTTP/1.1`];\n headers.forEach((value, key) => {\n lines.push(`${key}: ${value}`);\n });\n lines.push(\"\", \"\");\n return concatBytes([new TextEncoder().encode(lines.join(\"\\r\\n\")), body]);\n }\n function encodeHttpResponseHead(status, statusText, headers, chunkedBody) {\n const lines = [`HTTP/1.1 ${status} ${statusText}`];\n headers.forEach((value, key) => {\n lines.push(`${key}: ${value}`);\n });\n if (chunkedBody) lines.push(\"Transfer-Encoding: chunked\");\n lines.push(\"Connection: close\");\n lines.push(\"\", \"\");\n return new TextEncoder().encode(lines.join(\"\\r\\n\"));\n }\n function encodeChunkedFrame(bytes) {\n return concatBytes([\n new TextEncoder().encode(`${bytes.byteLength.toString(16)}\\r\n`),\n bytes,\n new TextEncoder().encode(\"\\r\\n\")\n ]);\n }\n var CHUNKED_TERMINATOR = new TextEncoder().encode(\"0\\r\\n\\r\\n\");\n var LOOPBACK_READ_HIGH_WATER_BYTES = 1024 * 1024;\n var LOOPBACK_STREAM_CHUNK_BYTES = 64 * 1024;\n var ByteChunkQueue = class {\n constructor(limitTotalBytes, overflowLabel) {\n this.limitTotalBytes = limitTotalBytes;\n this.overflowLabel = overflowLabel;\n }\n limitTotalBytes;\n overflowLabel;\n chunks = [];\n headOffset = 0;\n enqueuedTotalBytes = 0;\n undrainedBytes = 0;\n /** Bytes enqueued but not yet read. Drives flow control on streamed queues. */\n get pendingBytes() {\n return this.undrainedBytes;\n }\n enqueue(bytes) {\n if (bytes.byteLength === 0) return;\n if (this.enqueuedTotalBytes + bytes.byteLength > this.limitTotalBytes) {\n throw new Error(\n `Nimbus virtual socket: ${this.overflowLabel} exceeds ${this.limitTotalBytes} bytes`\n );\n }\n this.enqueuedTotalBytes += bytes.byteLength;\n this.undrainedBytes += bytes.byteLength;\n this.chunks.push(bytes);\n }\n /** Drain up to maxBytes from the head chunk; empty result means no data is queued. */\n readUpTo(maxBytes) {\n const head = this.chunks[0];\n if (!head) return EMPTY_BYTES;\n const available = head.byteLength - this.headOffset;\n const take = Math.min(available, Math.max(1, maxBytes));\n const out = head.subarray(this.headOffset, this.headOffset + take);\n this.headOffset += take;\n this.undrainedBytes -= take;\n if (this.headOffset >= head.byteLength) {\n this.chunks.shift();\n this.headOffset = 0;\n }\n return out;\n }\n };\n var HttpMessageParser = class {\n phase = \"headers\";\n settled = null;\n headerBuffer = EMPTY_BYTES;\n headerScanOffset = 0;\n headerPairs = [];\n framing = \"none\";\n expectedBodyBytes = 0;\n bodyChunks = [];\n bodyByteCount = 0;\n chunkState = \"size\";\n chunkSizeLine = [];\n chunkDataRemaining = 0;\n get outcome() {\n return this.settled;\n }\n feed(chunk) {\n if (this.phase === \"done\" || chunk.byteLength === 0) return;\n if (this.phase === \"headers\") {\n this.feedHeaders(chunk);\n return;\n }\n this.feedBody(chunk);\n }\n /** EOF from the peer closing the connection (or the request being torn down). */\n finish() {\n if (this.phase === \"done\") return;\n if (this.phase === \"headers\") {\n this.fail(`connection closed before ${this.messageLabel} headers`);\n return;\n }\n if (this.framing === \"until-close\") {\n this.completeWithBody(concatBytes(this.bodyChunks));\n return;\n }\n this.fail(`connection closed before the ${this.messageLabel} completed`);\n }\n feedHeaders(chunk) {\n this.headerBuffer = concatBytes([this.headerBuffer, chunk]);\n const headerEnd = this.findHeaderEnd();\n if (headerEnd < 0) {\n this.headerScanOffset = Math.max(0, this.headerBuffer.byteLength - 3);\n return;\n }\n const parsed = this.parseHeaderBlock(this.headerBuffer.subarray(0, headerEnd));\n const leftover = this.headerBuffer.subarray(headerEnd);\n this.headerBuffer = EMPTY_BYTES;\n if (!parsed) return;\n if (!this.bodyAllowed()) {\n this.completeWithBody(null);\n return;\n }\n const transferEncoding = this.headerValue(\"Transfer-Encoding\");\n if (transferEncoding !== null && /chunked/i.test(transferEncoding)) {\n this.framing = \"chunked\";\n } else {\n const contentLength = this.headerValue(\"Content-Length\");\n const expected = contentLength === null ? Number.NaN : parseInt(contentLength, 10);\n if (Number.isFinite(expected) && expected >= 0) {\n this.framing = \"content-length\";\n this.expectedBodyBytes = expected;\n if (expected === 0) {\n this.completeWithBody(EMPTY_BYTES);\n return;\n }\n } else {\n this.framing = this.defaultFramingWhenUnframed();\n if (this.framing === \"none\") {\n this.completeWithBody(null);\n return;\n }\n }\n }\n this.phase = \"body\";\n this.feedBody(leftover);\n }\n feedBody(chunk) {\n if (this.framing === \"chunked\") {\n this.feedChunkedBody(chunk);\n return;\n }\n if (chunk.byteLength === 0) return;\n if (this.framing === \"content-length\") {\n const needed = this.expectedBodyBytes - this.bodyByteCount;\n this.appendBody(chunk.subarray(0, Math.min(needed, chunk.byteLength)));\n if (this.bodyByteCount >= this.expectedBodyBytes) {\n this.completeWithBody(concatBytes(this.bodyChunks));\n }\n return;\n }\n this.appendBody(chunk);\n }\n feedChunkedBody(chunk) {\n let i = 0;\n while (i < chunk.byteLength && this.phase === \"body\") {\n if (this.chunkState === \"size\") {\n const byte = chunk[i++];\n if (byte === 10) {\n const line = new TextDecoder().decode(Uint8Array.from(this.chunkSizeLine)).trim();\n this.chunkSizeLine.length = 0;\n const size = parseInt(line.split(\";\", 1)[0], 16);\n if (!Number.isFinite(size) || size < 0) {\n this.fail(\"malformed chunked transfer encoding\");\n return;\n }\n if (size === 0) {\n this.completeWithBody(concatBytes(this.bodyChunks));\n return;\n }\n this.chunkState = \"data\";\n this.chunkDataRemaining = size;\n } else if (byte !== 13) {\n this.chunkSizeLine.push(byte);\n }\n } else if (this.chunkState === \"data\") {\n const take = Math.min(this.chunkDataRemaining, chunk.byteLength - i);\n this.appendBody(chunk.subarray(i, i + take));\n i += take;\n this.chunkDataRemaining -= take;\n if (this.chunkDataRemaining === 0) this.chunkState = \"data-end\";\n } else {\n const byte = chunk[i++];\n if (byte === 10) {\n this.chunkState = \"size\";\n } else if (byte !== 13) {\n this.fail(\"malformed chunked transfer encoding\");\n return;\n }\n }\n }\n }\n appendBody(bytes) {\n if (bytes.byteLength === 0) return;\n this.bodyChunks.push(bytes);\n this.bodyByteCount += bytes.byteLength;\n }\n findHeaderEnd() {\n const bytes = this.headerBuffer;\n for (let i = this.headerScanOffset; i + 3 < bytes.byteLength; i++) {\n if (bytes[i] === 13 && bytes[i + 1] === 10 && bytes[i + 2] === 13 && bytes[i + 3] === 10) {\n return i + 4;\n }\n }\n return -1;\n }\n /** Returns false when the start line is malformed (the parser has already failed). */\n parseHeaderBlock(headerBytes) {\n const headerText = new TextDecoder().decode(headerBytes);\n const lines = headerText.replace(/\\r\\n/g, \"\\n\").split(\"\\n\").filter((line) => line.length > 0);\n const startLine = lines.shift() ?? \"\";\n if (!this.parseStartLine(startLine)) return false;\n for (const line of lines) {\n const separator = line.indexOf(\":\");\n if (separator <= 0) continue;\n this.headerPairs.push([line.slice(0, separator), line.slice(separator + 1).trimStart()]);\n }\n return true;\n }\n headerValue(name) {\n const target = name.toLowerCase();\n for (const [key, value] of this.headerPairs) {\n if (key.toLowerCase() === target) return value;\n }\n return null;\n }\n completeWithBody(body) {\n this.phase = \"done\";\n this.settled = { kind: \"message\", message: this.buildMessage(body) };\n }\n fail(message) {\n this.phase = \"done\";\n this.settled = { kind: \"failed\", message };\n }\n };\n var HttpResponseParser = class extends HttpMessageParser {\n constructor(requestMethod) {\n super();\n this.requestMethod = requestMethod;\n }\n requestMethod;\n status = 200;\n statusText = \"\";\n get messageLabel() {\n return \"response\";\n }\n parseStartLine(line) {\n const match = /^HTTP\\/\\d(?:\\.\\d)?\\s+(\\d{3})(?:\\s+(.*))?$/.exec(line || \"HTTP/1.1 200 OK\");\n this.status = match ? parseInt(match[1], 10) : 200;\n this.statusText = match?.[2] ?? \"\";\n return true;\n }\n bodyAllowed() {\n return responseCanHaveBody(this.requestMethod, this.status);\n }\n defaultFramingWhenUnframed() {\n return \"until-close\";\n }\n buildMessage(body) {\n return { status: this.status, statusText: this.statusText, headerPairs: this.headerPairs, body };\n }\n };\n var HttpRequestParser = class extends HttpMessageParser {\n method = \"GET\";\n target = \"/\";\n get messageLabel() {\n return \"request\";\n }\n parseStartLine(line) {\n const match = /^([A-Za-z]+)\\s+(\\S+)\\s+HTTP\\/\\d(?:\\.\\d)?$/.exec(line);\n if (!match) {\n this.fail(`malformed HTTP request line: ${JSON.stringify(line.slice(0, 120))}`);\n return false;\n }\n this.method = match[1].toUpperCase();\n this.target = match[2];\n return true;\n }\n bodyAllowed() {\n return true;\n }\n defaultFramingWhenUnframed() {\n return \"none\";\n }\n buildMessage(body) {\n return { method: this.method, target: this.target, headerPairs: this.headerPairs, body };\n }\n };\n function errorResponse(status, message) {\n return {\n status,\n statusText: \"\",\n headerPairs: [[\"content-type\", \"text/plain; charset=utf-8\"]],\n body: new TextEncoder().encode(`Nimbus virtual socket: ${message}`)\n };\n }\n function toResponse(settled) {\n const headers = new Headers();\n for (const [key, value] of settled.headerPairs) headers.append(key, value);\n return new Response(settled.body, {\n status: settled.status,\n statusText: settled.statusText,\n headers\n });\n }\n var VirtualConnection = class {\n constructor(id, requestMethod, requestBytes, limits) {\n this.id = id;\n this.inbound = new ByteChunkQueue(requestBytes.byteLength, \"request buffer\");\n this.inbound.enqueue(requestBytes);\n this.outbound = new ByteChunkQueue(limits.maxResponseBufferBytes, \"response buffer\");\n this.parser = new HttpResponseParser(requestMethod);\n }\n id;\n /** Request bytes the guest server reads; filled in one shot in stage 1. */\n inbound;\n /** Response bytes the guest server writes; drained into the parser. */\n outbound;\n parser;\n responseReady = new Deferred();\n settled = false;\n closed = false;\n read(maxBytes) {\n return Array.from(this.inbound.readUpTo(Math.max(1, maxBytes | 0)));\n }\n /** The whole request is buffered before the connection is accepted, so nothing is ever awaited. */\n readAsync(maxBytes) {\n return Promise.resolve(this.read(maxBytes));\n }\n readBytesAsync(maxBytes) {\n return Promise.resolve(this.inbound.readUpTo(Math.max(1, maxBytes | 0)));\n }\n /** Same reason: an empty read on an accepted connection is always genuine EOF. */\n atEof() {\n return true;\n }\n write(bytesLike) {\n const bytes = toBytes(bytesLike);\n if (bytes.byteLength === 0 || this.settled || this.closed) return bytes.byteLength;\n this.outbound.enqueue(bytes.slice());\n this.pumpParser();\n return bytes.byteLength;\n }\n /** EOF from the server side (or request teardown); completes until-close bodies. */\n close() {\n if (this.closed) return;\n this.closed = true;\n if (this.settled) return;\n this.parser.finish();\n this.pumpParser();\n }\n /** Abort propagation: settle the pending preview request with a terminal status. */\n abort(message, status) {\n this.closed = true;\n this.settle(errorResponse(status, message));\n }\n async response(timeoutMs) {\n const timer = setTimeout(() => {\n this.settle(errorResponse(504, \"timed out waiting for response\"));\n }, Math.max(1, timeoutMs));\n try {\n return toResponse(await this.responseReady.promise);\n } finally {\n clearTimeout(timer);\n }\n }\n pumpParser() {\n for (; ; ) {\n const chunk = this.outbound.readUpTo(Number.MAX_SAFE_INTEGER);\n if (chunk.byteLength === 0) break;\n this.parser.feed(chunk);\n }\n const outcome = this.parser.outcome;\n if (!outcome) return;\n if (outcome.kind === \"failed\") {\n this.settle(errorResponse(502, outcome.message));\n return;\n }\n const { status, statusText, headerPairs, body } = outcome.message;\n this.settle({\n status,\n statusText,\n // The framing is consumed here; the Worker Response re-frames the body itself.\n headerPairs: headerPairs.filter(([key]) => !/^transfer-encoding$/i.test(key)),\n body\n });\n }\n settle(response) {\n if (this.settled) return;\n this.settled = true;\n this.responseReady.resolve(response);\n }\n };\n var LoopbackClientConnection = class {\n constructor(id, port, route, limits) {\n this.id = id;\n this.port = port;\n this.route = route;\n this.limits = limits;\n }\n id;\n port;\n route;\n limits;\n parser = new HttpRequestParser();\n /** Response bytes the guest client reads. */\n inbound = new ByteChunkQueue(Number.MAX_SAFE_INTEGER, \"loopback response buffer\");\n readable = null;\n drained = null;\n dispatched = false;\n eof = false;\n closed = false;\n write(bytesLike) {\n const bytes = toBytes(bytesLike);\n if (bytes.byteLength === 0 || this.closed) return bytes.byteLength;\n if (this.dispatched) {\n throw new Error(\n `Nimbus loopback socket: port ${this.port} answered with \"Connection: close\"; open a new connection for the next request`\n );\n }\n this.parser.feed(bytes.slice());\n const outcome = this.parser.outcome;\n if (!outcome) return bytes.byteLength;\n this.dispatched = true;\n if (outcome.kind === \"failed\") this.failWith(400, outcome.message);\n else void this.dispatch(outcome.message);\n return bytes.byteLength;\n }\n read(maxBytes) {\n return Array.from(this.readBytes(maxBytes));\n }\n /** Byte-array read. The fd-backed path uses this so a stream never round-trips through number[]. */\n readBytes(maxBytes) {\n const out = this.inbound.readUpTo(Math.max(1, maxBytes | 0));\n if (this.drained && this.inbound.pendingBytes <= LOOPBACK_READ_HIGH_WATER_BYTES) {\n const waiter = this.drained;\n this.drained = null;\n waiter.resolve();\n }\n return out;\n }\n atEof() {\n return (this.eof || this.closed) && this.inbound.pendingBytes === 0;\n }\n /** Blocks the guest until response bytes arrive; an empty result is EOF. */\n async readAsync(maxBytes) {\n return Array.from(await this.readBytesAsync(maxBytes));\n }\n async readBytesAsync(maxBytes) {\n for (; ; ) {\n const chunk = this.readBytes(maxBytes);\n if (chunk.byteLength > 0) return chunk;\n if (this.eof || this.closed) return EMPTY_BYTES;\n if (!this.dispatched) {\n throw new Error(\n \"Nimbus loopback socket: read before a complete HTTP request was written (loopback sockets carry HTTP requests, not arbitrary byte streams)\"\n );\n }\n this.readable ??= new Deferred();\n await this.readable.promise;\n }\n }\n close() {\n if (this.closed) return;\n this.closed = true;\n this.wakeReader();\n if (this.drained) {\n const waiter = this.drained;\n this.drained = null;\n waiter.resolve();\n }\n }\n async dispatch(parsed) {\n let request;\n try {\n request = this.buildRequest(parsed);\n } catch (error) {\n this.failWith(400, describeError(error));\n return;\n }\n let response;\n try {\n response = await this.withTimeout(this.route(this.port, request));\n } catch (error) {\n this.failWith(502, describeError(error));\n return;\n }\n await this.streamResponse(request.method, response);\n }\n buildRequest(parsed) {\n const headers = new Headers();\n let authority = `127.0.0.1:${this.port}`;\n for (const [key, value] of parsed.headerPairs) {\n if (/^host$/i.test(key)) {\n authority = value;\n continue;\n }\n if (/^(transfer-encoding|connection|content-length|keep-alive)$/i.test(key)) continue;\n headers.append(key, value);\n }\n const target = parsed.target.startsWith(\"/\") ? parsed.target : `/${parsed.target}`;\n const url = /^https?:\\/\\//i.test(parsed.target) ? parsed.target : `http://${authority}${target}`;\n const body = parsed.body && parsed.body.byteLength > 0 ? parsed.body : null;\n const methodTakesBody = parsed.method !== \"GET\" && parsed.method !== \"HEAD\";\n return new Request(url, {\n method: parsed.method,\n headers,\n body: methodTakesBody ? body : null\n });\n }\n async streamResponse(method, response) {\n const withBody = responseCanHaveBody(method, response.status) && response.body !== null;\n const headers = new Headers(response.headers);\n for (const hopByHop of [\"transfer-encoding\", \"content-length\", \"connection\", \"keep-alive\"]) {\n headers.delete(hopByHop);\n }\n this.pushBytes(encodeHttpResponseHead(response.status, response.statusText, headers, withBody));\n if (!withBody || !response.body) {\n this.finishStream();\n return;\n }\n const reader = response.body.getReader();\n try {\n for (; ; ) {\n const { value, done } = await reader.read();\n if (done) break;\n if (this.closed) return;\n const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);\n if (bytes.byteLength === 0) continue;\n this.pushBytes(encodeChunkedFrame(bytes));\n await this.awaitDrain();\n }\n this.pushBytes(CHUNKED_TERMINATOR);\n } catch {\n } finally {\n try {\n await reader.cancel();\n } catch {\n }\n this.finishStream();\n }\n }\n /** Synthesize a complete response so the guest sees an HTTP error, never a hang. */\n failWith(status, detail) {\n const body = new TextEncoder().encode(`Nimbus loopback socket: ${detail}`);\n const headers = new Headers({\n \"content-type\": \"text/plain; charset=utf-8\",\n \"content-length\": String(body.byteLength)\n });\n this.pushBytes(encodeHttpResponseHead(status, \"\", headers, false));\n this.pushBytes(body);\n this.finishStream();\n }\n pushBytes(bytes) {\n if (this.closed) return;\n this.inbound.enqueue(bytes);\n this.wakeReader();\n }\n finishStream() {\n this.eof = true;\n this.wakeReader();\n }\n wakeReader() {\n const waiter = this.readable;\n if (!waiter) return;\n this.readable = null;\n waiter.resolve();\n }\n /** Pause the body pump while the guest is behind, so a big stream cannot grow unbounded. */\n awaitDrain() {\n if (this.inbound.pendingBytes <= LOOPBACK_READ_HIGH_WATER_BYTES) return Promise.resolve();\n this.drained ??= new Deferred();\n return this.drained.promise;\n }\n withTimeout(promise) {\n let timer = null;\n const timeout = new Promise((_resolve, reject) => {\n timer = setTimeout(\n () => reject(new Error(`port ${this.port} did not respond within ${this.limits.responseTimeoutMs}ms`)),\n Math.max(1, this.limits.responseTimeoutMs)\n );\n });\n return Promise.race([promise, timeout]).finally(() => {\n if (timer !== null) clearTimeout(timer);\n });\n }\n };\n function describeError(error) {\n return error instanceof Error ? error.message : String(error);\n }\n function socketStreamFor(conn) {\n return {\n // Neither direction has a handshake: an accepted connection already holds\n // the request, and a dialed one starts its exchange on the first write.\n opened: Promise.resolve(),\n readable: new ReadableStream(\n {\n async pull(controller) {\n const bytes = await conn.readBytesAsync(LOOPBACK_STREAM_CHUNK_BYTES);\n if (bytes.byteLength === 0) controller.close();\n else controller.enqueue(bytes);\n },\n cancel() {\n conn.close();\n }\n },\n // highWaterMark 0 so the stream never pulls speculatively. A default\n // strategy pulls once at construction, which on a client connection\n // means reading before the guest has written its request.\n { highWaterMark: 0 }\n ),\n writable: new WritableStream({\n write(chunk) {\n conn.write(chunk);\n },\n abort() {\n conn.close();\n }\n }),\n close() {\n conn.close();\n return Promise.resolve();\n }\n };\n }\n var VirtualListener = class {\n constructor(port) {\n this.port = port;\n }\n port;\n queue = [];\n acceptWaiters = [];\n push(conn) {\n const waiter = this.acceptWaiters.shift();\n if (waiter) waiter.resolve(conn);\n else this.queue.push(conn);\n }\n accept() {\n const queued = this.queue.shift();\n if (queued) return Promise.resolve(queued);\n const waiter = new Deferred();\n this.acceptWaiters.push(waiter);\n return waiter.promise;\n }\n take() {\n return this.queue.shift() ?? null;\n }\n pending() {\n return this.queue.length;\n }\n drainQueued() {\n return this.queue.splice(0);\n }\n rejectPendingAccepts(error) {\n for (const waiter of this.acceptWaiters.splice(0)) waiter.reject(error);\n }\n };\n var VirtualSocketKernel = class {\n constructor(host, limits) {\n this.host = host;\n this.limits = { ...DEFAULT_LIMITS, ...limits };\n }\n host;\n /** Public: runner glue inspects listeners.keys() for the default preview port. */\n listeners = /* @__PURE__ */ new Map();\n connections = /* @__PURE__ */ new Map();\n limits;\n nextConnectionId = 1;\n nextEphemeralPort = 49152;\n listenWaiters = [];\n readableWaiters = /* @__PURE__ */ new Set();\n listen(port) {\n let n = Number(port);\n if (!Number.isInteger(n) || n < 0 || n >= 65536) throw new Error(`invalid port: ${port}`);\n if (n === 0) {\n while (this.listeners.has(this.nextEphemeralPort)) {\n this.nextEphemeralPort++;\n if (this.nextEphemeralPort >= 65535) this.nextEphemeralPort = 49152;\n }\n n = this.nextEphemeralPort++;\n if (this.nextEphemeralPort >= 65535) this.nextEphemeralPort = 49152;\n }\n if (!this.listeners.has(n)) {\n this.listeners.set(n, new VirtualListener(n));\n try {\n this.host.__nimbusVirtualSocketDidListen?.(n);\n } catch {\n }\n for (const waiter of this.listenWaiters.splice(0)) waiter.resolve(n);\n }\n return n;\n }\n closeListener(port) {\n const n = Number(port);\n const listener = this.listeners.get(n);\n if (!listener) return;\n this.listeners.delete(n);\n for (const conn of listener.drainQueued()) {\n this.connections.delete(conn.id);\n conn.abort(`listener closed on port ${n}`, 502);\n }\n listener.rejectPendingAccepts(new Error(`port is not listening: ${n}`));\n }\n async accept(port) {\n const listener = this.listeners.get(Number(port));\n if (!listener) throw new Error(`port is not listening: ${port}`);\n const conn = await listener.accept();\n return { id: conn.id, host: \"127.0.0.1\", port: 0 };\n }\n acceptNow(port) {\n const listener = this.listeners.get(Number(port));\n if (!listener) throw new Error(`port is not listening: ${port}`);\n const conn = listener.take();\n return conn ? { id: conn.id, host: \"127.0.0.1\", port: 0 } : null;\n }\n /**\n * Open a client connection to `port` on the session's loopback.\n *\n * The returned id is an ordinary connection id: send() writes the\n * request, recv()/recvAsync() read the response, close() releases it.\n * Only HTTP/1.1 crosses a loopback connection - see\n * LoopbackClientConnection for why - and each one carries a single\n * request/response exchange.\n */\n connect(port) {\n const conn = this.openLoopbackClient(port);\n this.connections.set(conn.id, conn);\n return conn.id;\n }\n /**\n * The same client connection as `connect`, handed back in Cloudflare's\n * `Socket` shape.\n *\n * Guests whose sockets are real WASI file descriptors (ruby.wasm, and any\n * future wasm32-wasi program) reach loopback through this: the WASI shim\n * stores it in exactly the fd slot a `cloudflare:sockets` connection would\n * occupy, so `fd_read` on an in-session port is the same suspending read as\n * `fd_read` on a remote host.\n */\n connectStream(port) {\n return socketStreamFor(this.openLoopbackClient(port));\n }\n /**\n * An already-accepted connection in the same `Socket` shape, so a server's\n * accepted socket is the same kind of file descriptor as a client's dialed\n * one. `accept`/`acceptNow` still hand out the connection id, because accept\n * itself stays on the cooperative pump - this only binds the result.\n */\n streamFor(id) {\n const conn = this.connections.get(Number(id));\n if (!conn) throw new Error(`connection is closed: ${id}`);\n return socketStreamFor(conn);\n }\n openLoopbackClient(port) {\n const n = Number(port);\n if (!Number.isInteger(n) || n <= 0 || n >= 65536) throw new Error(`invalid port: ${port}`);\n const route = this.host.__nimbusVirtualSocketRouteLoopback;\n if (typeof route !== \"function\") {\n throw new Error(\"Nimbus loopback sockets are unavailable in this runtime\");\n }\n return new LoopbackClientConnection(this.nextConnectionId++, n, route, this.limits);\n }\n /** Plain number array: Pyodide bytes() and the ruby.wasm base64 bridge both consume it. */\n recv(id, maxBytes) {\n const conn = this.connections.get(Number(id));\n if (!conn) return [];\n return conn.read(Number(maxBytes));\n }\n /**\n * Suspending read. A client connection has nothing to hand back until\n * the loopback response arrives, so guests that can suspend (Pyodide\n * `run_sync`, a JSPI-suspending WASI call) await this instead of\n * spinning on recv(). An empty result is EOF.\n */\n async recvAsync(id, maxBytes) {\n const conn = this.connections.get(Number(id));\n if (!conn) return [];\n return conn.readAsync(Number(maxBytes));\n }\n /**\n * Whether an empty recv() means end-of-response rather than \"not yet\".\n * Guests that poll instead of suspending (ruby.wasm, whose JS bridge is\n * synchronous) need the two apart to avoid truncating a response.\n */\n atEof(id) {\n const conn = this.connections.get(Number(id));\n return conn ? conn.atEof() : true;\n }\n send(id, bytesLike) {\n const conn = this.connections.get(Number(id));\n if (!conn) throw new Error(`connection is closed: ${id}`);\n return conn.write(bytesLike);\n }\n close(id) {\n const conn = this.connections.get(Number(id));\n if (!conn) return;\n conn.close();\n this.connections.delete(Number(id));\n }\n pending(port) {\n return this.listeners.get(Number(port))?.pending() ?? 0;\n }\n firstListeningPort() {\n for (const port of this.listeners.keys()) return port;\n return null;\n }\n /** select()-style readiness: resolves ports with queued connections, [] on timeout. */\n waitReadable(ports, timeoutSeconds) {\n const normalized = (Array.isArray(ports) ? ports : []).map((p) => Number(p)).filter((p) => Number.isInteger(p));\n const readyNow = normalized.filter((port) => this.pending(port) > 0);\n if (readyNow.length > 0) return Promise.resolve(readyNow);\n const timeoutMs = timeoutSeconds == null ? this.limits.responseTimeoutMs : Math.max(0, Number(timeoutSeconds) * 1e3);\n const deferred = new Deferred();\n const waiter = {\n ports: normalized,\n deferred,\n timer: setTimeout(() => {\n this.readableWaiters.delete(waiter);\n deferred.resolve([]);\n }, timeoutMs)\n };\n this.readableWaiters.add(waiter);\n return deferred.promise;\n }\n async waitForListen(timeoutMs) {\n const existing = this.firstListeningPort();\n if (existing) return existing;\n const waiter = new Deferred();\n this.listenWaiters.push(waiter);\n let timer = null;\n const timeout = new Promise((resolve) => {\n timer = setTimeout(() => resolve(null), Math.max(1, timeoutMs ?? 5e3));\n });\n try {\n return await Promise.race([waiter.promise, timeout]);\n } finally {\n if (timer !== null) clearTimeout(timer);\n }\n }\n async handleHttpRequest(port, request) {\n const n = Number(port);\n let listener = this.listeners.get(n);\n if (!listener) {\n try {\n await this.host.__nimbusVirtualSocketEnsureListener?.(n);\n } catch {\n }\n listener = this.listeners.get(n);\n }\n if (!listener) {\n return new Response(`Nimbus virtual socket: no listener on port ${port}`, { status: 502 });\n }\n const body = request.method === \"GET\" || request.method === \"HEAD\" ? EMPTY_BYTES : new Uint8Array(await request.arrayBuffer());\n if (body.byteLength > this.limits.maxRequestBodyBytes) {\n return new Response(\n `Nimbus virtual socket: request body exceeds ${this.limits.maxRequestBodyBytes} bytes`,\n { status: 413 }\n );\n }\n const id = this.nextConnectionId++;\n const conn = new VirtualConnection(id, request.method, encodeHttpRequest(request, body), this.limits);\n this.connections.set(id, conn);\n listener.push(conn);\n this.notifyReadable(n);\n const signal = request.signal;\n const onAbort = () => conn.abort(\"client aborted the request\", 499);\n if (signal?.aborted) onAbort();\n else signal?.addEventListener(\"abort\", onAbort);\n try {\n try {\n const accepted = await this.host.__nimbusVirtualSocketRequestQueued?.(n);\n if (accepted === false) {\n const detail = typeof this.host.__nimbusVirtualSocketLastError === \"string\" ? this.host.__nimbusVirtualSocketLastError.trim() : \"\";\n const suffix = detail ? `: ${detail}` : \"\";\n return new Response(\n `Nimbus virtual socket: runtime handler did not accept the request${suffix}`,\n { status: 502 }\n );\n }\n } catch {\n }\n return await conn.response(this.limits.responseTimeoutMs);\n } finally {\n signal?.removeEventListener(\"abort\", onAbort);\n conn.close();\n this.connections.delete(id);\n }\n }\n notifyReadable(port) {\n for (const waiter of Array.from(this.readableWaiters)) {\n if (!waiter.ports.includes(port)) continue;\n const ready = waiter.ports.filter((p) => this.pending(p) > 0);\n if (ready.length === 0) continue;\n clearTimeout(waiter.timer);\n this.readableWaiters.delete(waiter);\n waiter.deferred.resolve(ready);\n }\n }\n };\n function installVirtualSocketKernel(scope = globalThis) {\n if (!scope.__nimbusVirtualSockets) {\n scope.__nimbusVirtualSockets = new VirtualSocketKernel(scope);\n }\n return scope.__nimbusVirtualSockets;\n }\n\n // <stdin>\n installVirtualSocketKernel();\n})();\n";