@nimbus-sh/core 0.4.0 → 0.6.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 (156) hide show
  1. package/README.md +35 -7
  2. package/dist/_shared/cache-stats.d.ts +23 -1
  3. package/dist/_shared/cache-stats.d.ts.map +1 -1
  4. package/dist/_shared/cache-stats.js +13 -1
  5. package/dist/_shared/error-text.d.ts +15 -0
  6. package/dist/_shared/error-text.d.ts.map +1 -0
  7. package/dist/_shared/error-text.js +21 -0
  8. package/dist/_shared/exports-resolver.d.ts +19 -7
  9. package/dist/_shared/exports-resolver.d.ts.map +1 -1
  10. package/dist/_shared/exports-resolver.js +1 -1
  11. package/dist/_shared/retry.d.ts.map +1 -1
  12. package/dist/_shared/retry.js +4 -2
  13. package/dist/constants.d.ts +5 -11
  14. package/dist/constants.d.ts.map +1 -1
  15. package/dist/constants.js +23 -49
  16. package/dist/runtime/assets-loader.js +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 +2 -2
  41. package/dist/runtime/virtual-socket-kernel.generated.d.ts.map +1 -1
  42. package/dist/runtime/virtual-socket-kernel.generated.js +3 -3
  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 +9 -3
  82. package/dist/vfs/sqlite-vfs.d.ts +63 -31
  83. package/dist/vfs/sqlite-vfs.d.ts.map +1 -1
  84. package/dist/vfs/sqlite-vfs.js +206 -41
  85. package/package.json +3 -2
  86. package/src/_shared/cache-stats.ts +27 -1
  87. package/src/_shared/error-text.ts +21 -0
  88. package/src/_shared/exports-resolver.ts +24 -4
  89. package/src/_shared/retry.ts +5 -3
  90. package/src/constants.ts +37 -53
  91. package/src/runtime/assets-loader.ts +1 -1
  92. package/src/runtime/esbuild-service.ts +16 -11
  93. package/src/runtime/installed-runtimes.ts +4 -3
  94. package/src/runtime/long-running-handle.ts +3 -3
  95. package/src/runtime/os-contracts.ts +7 -5
  96. package/src/runtime/port-registry.ts +97 -9
  97. package/src/runtime/process-logs.ts +12 -3
  98. package/src/runtime/project-detect.ts +7 -1
  99. package/src/runtime/require-resolver.ts +7 -7
  100. package/src/runtime/runtime-registry.ts +9 -7
  101. package/src/runtime/sqlite-runtime-fs-bridge.ts +9 -6
  102. package/src/runtime/virtual-socket-kernel.generated.ts +3 -3
  103. package/src/runtime/virtual-socket-kernel.ts +10 -5
  104. package/src/runtime/wasm-runner.ts +55 -29
  105. package/src/shell/features.ts +5 -4
  106. package/src/shell/unix-commands.ts +273 -128
  107. package/src/substrate/lifo/commands/net/ip.ts +15 -15
  108. package/src/substrate/lifo/commands/system/ps.ts +2 -1
  109. package/src/substrate/lifo/kernel/ServiceManager.ts +0 -2
  110. package/src/substrate/lifo/kernel/network/NetworkStack.ts +24 -17
  111. package/src/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.ts +37 -12
  112. package/src/substrate/lifo/kernel/vfs/VFS.ts +34 -0
  113. package/src/substrate/lifo/node-compat/esbuild.ts +16 -8
  114. package/src/substrate/lifo/node-compat/index.ts +3 -2
  115. package/src/substrate/lifo/node-compat/loose-equality.ts +81 -0
  116. package/src/substrate/lifo/node-compat/process.ts +5 -2
  117. package/src/substrate/lifo/shell/Shell.ts +92 -70
  118. package/src/substrate/lifo/shell/lexer.ts +38 -0
  119. package/src/vfs/seed-project.ts +13 -6
  120. package/src/vfs/sqlite-vfs.ts +233 -77
  121. package/dist/_shared/install-phase.d.ts +0 -47
  122. package/dist/_shared/install-phase.d.ts.map +0 -1
  123. package/dist/_shared/install-phase.js +0 -1
  124. package/dist/_shared/rpc-dispose.d.ts +0 -4
  125. package/dist/_shared/rpc-dispose.d.ts.map +0 -1
  126. package/dist/_shared/rpc-dispose.js +0 -30
  127. package/dist/_shared/w7-frame.d.ts +0 -79
  128. package/dist/_shared/w7-frame.d.ts.map +0 -1
  129. package/dist/_shared/w7-frame.js +0 -839
  130. package/dist/_shared/weighted-credit-pool.d.ts +0 -61
  131. package/dist/_shared/weighted-credit-pool.d.ts.map +0 -1
  132. package/dist/_shared/weighted-credit-pool.js +0 -196
  133. package/dist/observability/diag-counters.d.ts +0 -236
  134. package/dist/observability/diag-counters.d.ts.map +0 -1
  135. package/dist/observability/diag-counters.js +0 -195
  136. package/dist/observability/heap-estimate.d.ts +0 -225
  137. package/dist/observability/heap-estimate.d.ts.map +0 -1
  138. package/dist/observability/heap-estimate.js +0 -185
  139. package/dist/observability/heavy-alloc-coord.d.ts +0 -93
  140. package/dist/observability/heavy-alloc-coord.d.ts.map +0 -1
  141. package/dist/observability/heavy-alloc-coord.js +0 -181
  142. package/dist/observability/oom-classify.d.ts +0 -138
  143. package/dist/observability/oom-classify.d.ts.map +0 -1
  144. package/dist/observability/oom-classify.js +0 -261
  145. package/dist/observability/oom-discriminator.d.ts +0 -176
  146. package/dist/observability/oom-discriminator.d.ts.map +0 -1
  147. package/dist/observability/oom-discriminator.js +0 -272
  148. package/src/_shared/install-phase.ts +0 -56
  149. package/src/_shared/rpc-dispose.ts +0 -31
  150. package/src/_shared/w7-frame.ts +0 -1012
  151. package/src/_shared/weighted-credit-pool.ts +0 -244
  152. package/src/observability/diag-counters.ts +0 -343
  153. package/src/observability/heap-estimate.ts +0 -331
  154. package/src/observability/heavy-alloc-coord.ts +0 -241
  155. package/src/observability/oom-classify.ts +0 -253
  156. package/src/observability/oom-discriminator.ts +0 -405
@@ -6,8 +6,8 @@
6
6
  *
7
7
  * Self-contained IIFE that installs globalThis.__nimbusVirtualSockets.
8
8
  * Consumed by python-runner.ts and ruby-runner.ts: spliced into the
9
- * socket process worker module source passed to NimbusLoaderPool.
9
+ * socket process worker module source passed to NimbusIsolatePool.
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";
@@ -99,7 +99,9 @@ function encodeHttpRequest(request, body) {
99
99
  headers.set('Content-Length', String(body.byteLength));
100
100
  }
101
101
  const lines = [`${request.method} ${path} HTTP/1.1`];
102
- headers.forEach((value, key) => lines.push(`${key}: ${value}`));
102
+ headers.forEach((value, key) => {
103
+ lines.push(`${key}: ${value}`);
104
+ });
103
105
  lines.push('', '');
104
106
  return concatBytes([new TextEncoder().encode(lines.join('\r\n')), body]);
105
107
  }
@@ -115,7 +117,9 @@ function encodeHttpRequest(request, body) {
115
117
  */
116
118
  function encodeHttpResponseHead(status, statusText, headers, chunkedBody) {
117
119
  const lines = [`HTTP/1.1 ${status} ${statusText}`];
118
- headers.forEach((value, key) => lines.push(`${key}: ${value}`));
120
+ headers.forEach((value, key) => {
121
+ lines.push(`${key}: ${value}`);
122
+ });
119
123
  if (chunkedBody)
120
124
  lines.push('Transfer-Encoding: chunked');
121
125
  lines.push('Connection: close');
@@ -647,8 +651,8 @@ class LoopbackClientConnection {
647
651
  throw new Error('Nimbus loopback socket: read before a complete HTTP request was written ' +
648
652
  '(loopback sockets carry HTTP requests, not arbitrary byte streams)');
649
653
  }
650
- const waiter = (this.readable ??= new Deferred());
651
- await waiter.promise;
654
+ this.readable ??= new Deferred();
655
+ await this.readable.promise;
652
656
  }
653
657
  }
654
658
  close() {
@@ -775,7 +779,8 @@ class LoopbackClientConnection {
775
779
  awaitDrain() {
776
780
  if (this.inbound.pendingBytes <= LOOPBACK_READ_HIGH_WATER_BYTES)
777
781
  return Promise.resolve();
778
- return (this.drained ??= new Deferred()).promise;
782
+ this.drained ??= new Deferred();
783
+ return this.drained.promise;
779
784
  }
780
785
  withTimeout(promise) {
781
786
  let timer = null;
@@ -1 +1 @@
1
- {"version":3,"file":"wasm-runner.d.ts","sourceRoot":"","sources":["../../src/runtime/wasm-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC3F,OAAO,KAAK,EAAS,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAkDtD,eAAO,MAAM,mBAAmB,UAAU,CAAC;AAE3C,eAAO,MAAM,gBAAgB,QAoCwC,CAAC;AAEtE,wBAAgB,wBAAwB,IAAI,MAAM,CASjD;AAyCD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,GAAG,EAAE,SAAS,CAAC;IACf,MAAM,EAAE,SAAS,CAAC;IAClB,SAAS,EAAE,wBAAwB,CAAC;CACrC,IAEG,OAAO,MAAM,EACb,MAAM,cAAc,KACnB,OAAO,CAAC,gBAAgB,CAAC,CAslB7B;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,GAAG,EAAE,SAAS,CAAC;IACf,MAAM,EAAE,SAAS,CAAC;IAClB,SAAS,EAAE,wBAAwB,CAAC;CACrC,GAAG,WAAW,CAgBd"}
1
+ {"version":3,"file":"wasm-runner.d.ts","sourceRoot":"","sources":["../../src/runtime/wasm-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC3F,OAAO,KAAK,EAAS,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAmDtD,eAAO,MAAM,mBAAmB,UAAU,CAAC;AAE3C,eAAO,MAAM,gBAAgB,QAoCwC,CAAC;AAEtE,wBAAgB,wBAAwB,IAAI,MAAM,CASjD;AAyCD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,GAAG,EAAE,SAAS,CAAC;IACf,MAAM,EAAE,SAAS,CAAC;IAClB,SAAS,EAAE,wBAAwB,CAAC;CACrC,IAEG,OAAO,MAAM,EACb,MAAM,cAAc,KACnB,OAAO,CAAC,gBAAgB,CAAC,CA+mB7B;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,GAAG,EAAE,SAAS,CAAC;IACf,MAAM,EAAE,SAAS,CAAC;IAClB,SAAS,EAAE,wBAAwB,CAAC;CACrC,GAAG,WAAW,CAgBd"}
@@ -50,6 +50,7 @@ import { requireVfsCred, WASM32_WASI_NIMBUS_ABI } from './os-contracts.js';
50
50
  import { WASI_INSTANCE_PREAMBLE_SRC, WASI_IMPLEMENTED_FNS, WASI_ABI_NAMESPACE } from './wasi-instance.js';
51
51
  import { inspectWasmThreads, wasiThreadsLoadError } from './wasi-threads.js';
52
52
  import { withMemoryLimit, DEFAULT_WASM_PROCESS_LIMIT_BYTES } from './wasm-memory.js';
53
+ import { errorText } from '../_shared/error-text.js';
53
54
  export const WASM_RUNNER_VERSION = '0.3.0';
54
55
  export const WASM_RUNNER_HELP = 'Usage: wasm-runner [options] <file.wasm> [exportName] [int args...]\n' +
55
56
  ' wasm-runner --version\n' +
@@ -271,6 +272,12 @@ export function makeWasmRunner(deps) {
271
272
  // overly conservative; cast to ArrayBuffer is correct.
272
273
  const buf = limited.buffer.slice(limited.byteOffset, limited.byteOffset + limited.byteLength);
273
274
  const facetFn = async function wasmFacetCall(args, facetEnv) {
275
+ // This body is serialized into the facet isolate, where the supervisor's
276
+ // module graph — and so _shared/error-text.js — does not exist. Same
277
+ // fallback as errorText, spelled out locally.
278
+ const errText = (err) => typeof err === 'object' && err !== null && 'message' in err && err.message
279
+ ? String(err.message)
280
+ : String(err);
274
281
  const wasmTable = globalThis.__NIMBUS_WASM || {};
275
282
  const mod = wasmTable['user.wasm'];
276
283
  if (!mod) {
@@ -390,7 +397,7 @@ export function makeWasmRunner(deps) {
390
397
  mode: 'wasi',
391
398
  error: `wasi-threads: could not reserve the shared memory the module declares `
392
399
  + `(${args.threads.memory.initial}–${args.threads.memory.maximum} pages, `
393
- + `${(args.threads.memory.maximum * 64) / 1024} MiB): ${e?.message || e}. `
400
+ + `${(args.threads.memory.maximum * 64) / 1024} MiB): ${errText(e)}. `
394
401
  + 'A shared memory reserves its maximum immediately — lower --max-memory.',
395
402
  };
396
403
  }
@@ -414,11 +421,14 @@ export function makeWasmRunner(deps) {
414
421
  return {
415
422
  ok: false,
416
423
  mode: 'wasi',
417
- error: `instantiate failed: ${e?.message || e}`,
424
+ error: `instantiate failed: ${errText(e)}`,
418
425
  };
419
426
  }
420
- if (!memRef.mem)
421
- memRef.mem = inst.exports.memory;
427
+ if (!memRef.mem) {
428
+ const exported = inst.exports.memory;
429
+ if (exported instanceof WebAssembly.Memory)
430
+ memRef.mem = exported;
431
+ }
422
432
  if (!memRef.mem) {
423
433
  return {
424
434
  ok: false,
@@ -464,7 +474,7 @@ export function makeWasmRunner(deps) {
464
474
  return {
465
475
  ok: false,
466
476
  mode: 'direct',
467
- error: `instantiate failed: ${e?.message || e}`,
477
+ error: `instantiate failed: ${errText(e)}`,
468
478
  };
469
479
  }
470
480
  const exportNames = Object.keys(inst.exports);
@@ -487,7 +497,7 @@ export function makeWasmRunner(deps) {
487
497
  ok: false,
488
498
  mode: 'direct',
489
499
  exports: exportNames,
490
- error: `${args.exportName}(${(args.intArgs || []).join(', ')}) threw: ${e?.message || e}`,
500
+ error: `${args.exportName}(${(args.intArgs || []).join(', ')}) threw: ${errText(e)}`,
491
501
  };
492
502
  }
493
503
  // BigInt (i64) → string; everything else → as-is.
@@ -585,7 +595,7 @@ export function makeWasmRunner(deps) {
585
595
  }));
586
596
  }
587
597
  catch (e) {
588
- outcome = { ok: false, error: `dispatch failed: ${e?.message || e}` };
598
+ outcome = { ok: false, error: `dispatch failed: ${errorText(e)}` };
589
599
  }
590
600
  finally {
591
601
  facet?.dispose();
@@ -603,15 +613,13 @@ export function makeWasmRunner(deps) {
603
613
  // If runStart reported an `error` (wasm trapped, _start missing,
604
614
  // …), append it to stderr but still surface its exitCode (default
605
615
  // 1 from runStart on trap) so callers can distinguish.
606
- const wasiOut = outcome;
607
- // Either branch carries optional stdout/stderr/exitCode/error.
608
- stdout = wasiOut.stdout || '';
609
- stderr = wasiOut.stderr || '';
610
- if (wasiOut.error) {
616
+ stdout = outcome.stdout || '';
617
+ stderr = outcome.stderr || '';
618
+ if (outcome.error) {
611
619
  stderr = (stderr ? stderr : '') +
612
- `wasm-runner: wasi trap: ${wasiOut.error}\n`;
620
+ `wasm-runner: wasi trap: ${outcome.error}\n`;
613
621
  }
614
- exitCode = wasiOut.exitCode ?? (wasiOut.ok ? 0 : 1);
622
+ exitCode = outcome.exitCode ?? (outcome.ok ? 0 : 1);
615
623
  }
616
624
  else if (!outcome.ok) {
617
625
  // Direct-mode failure or pre-instantiate dispatch failure — shell
@@ -1 +1 @@
1
- {"version":3,"file":"features.d.ts","sourceRoot":"","sources":["../../src/shell/features.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"features.d.ts","sourceRoot":"","sources":["../../src/shell/features.ts"],"names":[],"mappings":"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"}
@@ -121,7 +121,7 @@ export class HeredocHandler {
121
121
  }
122
122
  return;
123
123
  }
124
- return this._handleOriginalInput(data);
124
+ this._handleOriginalInput(data);
125
125
  };
126
126
  this.shell.executeLine = async (line) => {
127
127
  if (this.active) {
@@ -144,7 +144,7 @@ export class HeredocHandler {
144
144
  this._accumulateLine('');
145
145
  return;
146
146
  }
147
- return this._printPrompt();
147
+ this._printPrompt();
148
148
  };
149
149
  }
150
150
  _printPrompt() {
@@ -353,7 +353,8 @@ export class LineEditorExtender {
353
353
  // shell-prompt input. Process-stdin input has its own model
354
354
  // (terminalStdin / stdinLineBuffer) handled by the shell.
355
355
  if (this.shell.running) {
356
- return this.originalHandleInput(data);
356
+ this.originalHandleInput(data);
357
+ return;
357
358
  }
358
359
  // Reverse-i-search sub-mode owns ALL input until exited.
359
360
  if (this.rsearchActive) {
@@ -368,7 +369,7 @@ export class LineEditorExtender {
368
369
  // Try each binding. First-match wins; on no match fall through.
369
370
  if (this._handleBinding(data))
370
371
  return;
371
- return this.originalHandleInput(data);
372
+ this.originalHandleInput(data);
372
373
  };
373
374
  }
374
375
  /**
@@ -11,5 +11,18 @@
11
11
  * touch, stat, file, xxd, base64, sha256sum, id, hostname, realpath
12
12
  */
13
13
  import type { SqliteVFS } from '../vfs/sqlite-vfs.js';
14
- export declare function registerUnixCommands(registry: any, sqliteVfs: SqliteVFS): void;
14
+ import type { Command } from '../substrate/lifo/commands/types.js';
15
+ /**
16
+ * The registry these commands dispatch through: registration, and name
17
+ * resolution for `which`, `type`, `command`, `find -exec` and `xargs`.
18
+ * `resolve` answers `unknown` because the registry holds whatever any module
19
+ * registered — and the worker's npm-bin fallback replaces the method outright
20
+ * — so what comes back is a command only once it has been checked.
21
+ */
22
+ type UnixCommandRegistry = {
23
+ register(name: string, handler: Command): void;
24
+ resolve(name: string): unknown;
25
+ };
26
+ export declare function registerUnixCommands(registry: UnixCommandRegistry, sqliteVfs: SqliteVFS): void;
27
+ export {};
15
28
  //# sourceMappingURL=unix-commands.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"unix-commands.d.ts","sourceRoot":"","sources":["../../src/shell/unix-commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAmB,SAAS,EAAE,MAAM,sBAAsB,CAAC;AA0jIvE,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,GAAG,EACb,SAAS,EAAE,SAAS,GACnB,IAAI,CAoKN"}
1
+ {"version":3,"file":"unix-commands.d.ts","sourceRoot":"","sources":["../../src/shell/unix-commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAmB,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAavE,OAAO,KAAK,EAAE,OAAO,EAAsB,MAAM,qCAAqC,CAAC;AAoFvF;;;;;;GAMG;AACH,KAAK,mBAAmB,GAAG;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/C,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CAChC,CAAC;AAgmIF,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,mBAAmB,EAC7B,SAAS,EAAE,SAAS,GACnB,IAAI,CAoKN"}