@nimbus-sh/worker 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/dist/_shared/crypto.d.ts +15 -0
- package/dist/_shared/crypto.d.ts.map +1 -0
- package/dist/_shared/crypto.js +122 -0
- package/dist/facets/manager.d.ts +24 -0
- package/dist/facets/manager.d.ts.map +1 -1
- package/dist/facets/manager.js +279 -70
- package/dist/facets/process.d.ts +11 -2
- package/dist/facets/process.d.ts.map +1 -1
- package/dist/facets/process.js +187 -21
- package/dist/facets/vite-dev-server.d.ts +2 -0
- package/dist/facets/vite-dev-server.d.ts.map +1 -1
- package/dist/facets/vite-dev-server.js +39 -12
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/loaders/child-process/spawn-facet.d.ts +1 -3
- package/dist/loaders/child-process/spawn-facet.d.ts.map +1 -1
- package/dist/loaders/child-process/spawn-facet.js +1 -12
- package/dist/loaders/child-process/spawn-pool.d.ts +1 -1
- package/dist/loaders/child-process/spawn-pool.d.ts.map +1 -1
- package/dist/loaders/child-process/spawn-pool.js +1 -6
- package/dist/npm/installer.d.ts.map +1 -1
- package/dist/npm/installer.js +47 -27
- package/dist/npm/pre-bundle-facet.d.ts.map +1 -1
- package/dist/npm/pre-bundle-facet.js +2 -1
- package/dist/npm/resolve-one-facet.d.ts.map +1 -1
- package/dist/npm/resolve-one-facet.js +41 -13
- package/dist/npm/resolver.d.ts.map +1 -1
- package/dist/npm/resolver.js +70 -35
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +45 -21
- package/dist/router/remote-api.d.ts.map +1 -1
- package/dist/router/remote-api.js +29 -8
- package/dist/runtime/barrel-synthesizer.d.ts +7 -0
- package/dist/runtime/barrel-synthesizer.d.ts.map +1 -1
- package/dist/runtime/barrel-synthesizer.js +11 -0
- package/dist/runtime/clang-runner.d.ts.map +1 -1
- package/dist/runtime/clang-runner.js +1 -8
- package/dist/runtime/esbuild-service.d.ts +12 -5
- package/dist/runtime/esbuild-service.d.ts.map +1 -1
- package/dist/runtime/esbuild-service.js +28 -14
- package/dist/runtime/node-runner.d.ts +2 -0
- package/dist/runtime/node-runner.d.ts.map +1 -1
- package/dist/runtime/node-shims.d.ts +4 -2
- package/dist/runtime/node-shims.d.ts.map +1 -1
- package/dist/runtime/node-shims.js +404 -55
- package/dist/runtime/os-contracts.d.ts +128 -0
- package/dist/runtime/os-contracts.d.ts.map +1 -0
- package/dist/runtime/os-contracts.js +1 -0
- package/dist/runtime/package-manager.d.ts.map +1 -1
- package/dist/runtime/package-manager.js +32 -7
- package/dist/runtime/port-registry.d.ts +5 -0
- package/dist/runtime/port-registry.d.ts.map +1 -1
- package/dist/runtime/port-registry.js +22 -1
- package/dist/runtime/pyodide-runtime-assets.d.ts +19 -0
- package/dist/runtime/pyodide-runtime-assets.d.ts.map +1 -0
- package/dist/runtime/pyodide-runtime-assets.js +44 -0
- package/dist/runtime/python-repl.d.ts +3 -1
- package/dist/runtime/python-repl.d.ts.map +1 -1
- package/dist/runtime/python-repl.js +11 -29
- package/dist/runtime/python-runner.d.ts +8 -9
- package/dist/runtime/python-runner.d.ts.map +1 -1
- package/dist/runtime/python-runner.js +897 -61
- package/dist/runtime/python-socket-shim.d.ts +11 -0
- package/dist/runtime/python-socket-shim.d.ts.map +1 -0
- package/dist/runtime/python-socket-shim.js +526 -0
- package/dist/runtime/require-resolver.d.ts.map +1 -1
- package/dist/runtime/require-resolver.js +2 -11
- package/dist/runtime/ruby-gems.d.ts +25 -0
- package/dist/runtime/ruby-gems.d.ts.map +1 -0
- package/dist/runtime/ruby-gems.js +582 -0
- package/dist/runtime/ruby-runner.d.ts +2 -4
- package/dist/runtime/ruby-runner.d.ts.map +1 -1
- package/dist/runtime/ruby-runner.js +335 -34
- package/dist/runtime/runtime-catalog.d.ts +16 -4
- package/dist/runtime/runtime-catalog.d.ts.map +1 -1
- package/dist/runtime/runtime-catalog.js +21 -10
- package/dist/runtime/runtime-registry.d.ts +3 -0
- package/dist/runtime/runtime-registry.d.ts.map +1 -1
- package/dist/runtime/runtime-registry.js +4 -0
- package/dist/runtime/sqlite-runtime-fs-bridge.d.ts +43 -0
- package/dist/runtime/sqlite-runtime-fs-bridge.d.ts.map +1 -0
- package/dist/runtime/sqlite-runtime-fs-bridge.js +224 -0
- package/dist/runtime/static-server.d.ts +3 -0
- package/dist/runtime/static-server.d.ts.map +1 -0
- package/dist/runtime/static-server.js +121 -0
- package/dist/runtime/vfs-snapshot.d.ts +57 -0
- package/dist/runtime/vfs-snapshot.d.ts.map +1 -0
- package/dist/runtime/vfs-snapshot.js +141 -0
- package/dist/runtime/virtual-socket-kernel.d.ts +16 -0
- package/dist/runtime/virtual-socket-kernel.d.ts.map +1 -0
- package/dist/runtime/virtual-socket-kernel.js +360 -0
- package/dist/runtime/wasi-instance.d.ts +6 -3
- package/dist/runtime/wasi-instance.d.ts.map +1 -1
- package/dist/runtime/wasm-runner.d.ts +1 -25
- package/dist/runtime/wasm-runner.d.ts.map +1 -1
- package/dist/runtime/wasm-runner.js +1 -160
- package/dist/session/agent.d.ts +43 -0
- package/dist/session/agent.d.ts.map +1 -0
- package/dist/session/agent.js +1252 -0
- package/dist/session/init.d.ts.map +1 -1
- package/dist/session/init.js +15 -16
- package/dist/session/nimbus-session.d.ts +13 -2
- package/dist/session/nimbus-session.d.ts.map +1 -1
- package/dist/session/nimbus-session.js +55 -7
- package/dist/session/programmatic.d.ts +10 -0
- package/dist/session/programmatic.d.ts.map +1 -1
- package/dist/session/programmatic.js +121 -0
- package/dist/session/routes.d.ts.map +1 -1
- package/dist/session/routes.js +19 -1
- package/dist/session/rpc.d.ts +11 -0
- package/dist/session/rpc.d.ts.map +1 -1
- package/dist/session/rpc.js +56 -43
- package/dist/session/supervisor-rpc.d.ts +15 -0
- package/dist/session/supervisor-rpc.d.ts.map +1 -1
- package/dist/session/supervisor-rpc.js +32 -0
- package/dist/session/ws.d.ts +4 -3
- package/dist/session/ws.d.ts.map +1 -1
- package/dist/session/ws.js +11 -1
- package/dist/shell/unix-commands.js +12 -16
- package/dist/vfs/path.d.ts +8 -4
- package/dist/vfs/path.d.ts.map +1 -1
- package/dist/vfs/path.js +16 -4
- package/dist/vfs/sqlite-vfs.d.ts +2 -0
- package/dist/vfs/sqlite-vfs.d.ts.map +1 -1
- package/dist/vfs/sqlite-vfs.js +11 -0
- package/dist/vfs/symlink-registry.d.ts +7 -1
- package/dist/vfs/symlink-registry.d.ts.map +1 -1
- package/dist/vfs/symlink-registry.js +16 -11
- package/package.json +4 -1
- package/public/index.html +240 -0
- package/public/s/index.html +1172 -146
- package/runtime-contracts/pyodide-workerd-adapter.json +18 -0
- package/scripts/bundle-runtime.mjs +99 -24
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
export function bytesToB64(bytes) {
|
|
2
|
+
let s = '';
|
|
3
|
+
for (let i = 0; i < bytes.length; i++)
|
|
4
|
+
s += String.fromCharCode(bytes[i]);
|
|
5
|
+
return btoa(s);
|
|
6
|
+
}
|
|
7
|
+
export function b64ToBytes(b64) {
|
|
8
|
+
const bin = atob(b64);
|
|
9
|
+
const out = new Uint8Array(bin.length);
|
|
10
|
+
for (let i = 0; i < bin.length; i++)
|
|
11
|
+
out[i] = bin.charCodeAt(i);
|
|
12
|
+
return out;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Snapshot a VFS subtree into a JSON-serializable WASI-shaped filesystem.
|
|
16
|
+
*
|
|
17
|
+
* The snapshot is intentionally bounded. Runtimes that need incremental or
|
|
18
|
+
* lazy file IO should add a streaming bridge; a single request payload is the
|
|
19
|
+
* right primitive only for normal source trees and small app state.
|
|
20
|
+
*/
|
|
21
|
+
export function snapshotVfs(vfs, vfsRoot, caps = {}) {
|
|
22
|
+
const maxBytes = caps.maxBytes ?? 32 * 1024 * 1024;
|
|
23
|
+
const maxFiles = caps.maxFiles ?? 5000;
|
|
24
|
+
const skipSubdirs = new Set(caps.skipSubdirs ?? ['.nimbus', 'node_modules', '.cache', '.npm']);
|
|
25
|
+
const root = vfsRoot.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
26
|
+
const roots = Array.from(new Set([
|
|
27
|
+
root,
|
|
28
|
+
...Array.from(caps.extraRoots ?? []).map((r) => r.replace(/^\/+/, '').replace(/\/+$/, '')).filter(Boolean),
|
|
29
|
+
].filter(Boolean)));
|
|
30
|
+
const files = {};
|
|
31
|
+
const dirsSet = new Set();
|
|
32
|
+
let totalBytes = 0;
|
|
33
|
+
let fileCount = 0;
|
|
34
|
+
const stack = [];
|
|
35
|
+
const addDirWithParents = (path) => {
|
|
36
|
+
const clean = path.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
37
|
+
if (!clean)
|
|
38
|
+
return;
|
|
39
|
+
const parts = clean.split('/').filter(Boolean);
|
|
40
|
+
for (let i = 1; i <= parts.length; i++) {
|
|
41
|
+
dirsSet.add(parts.slice(0, i).join('/'));
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
for (const start of roots) {
|
|
45
|
+
addDirWithParents(start);
|
|
46
|
+
if (vfs.exists(start))
|
|
47
|
+
stack.push(start);
|
|
48
|
+
}
|
|
49
|
+
while (stack.length > 0) {
|
|
50
|
+
const dir = stack.pop();
|
|
51
|
+
let entries;
|
|
52
|
+
try {
|
|
53
|
+
entries = vfs.readdir(dir);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
const childPath = `${dir}/${entry.name}`;
|
|
60
|
+
if (entry.type === 'directory') {
|
|
61
|
+
if (skipSubdirs.has(entry.name))
|
|
62
|
+
continue;
|
|
63
|
+
addDirWithParents(childPath);
|
|
64
|
+
stack.push(childPath);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
let bytes;
|
|
68
|
+
try {
|
|
69
|
+
bytes = vfs.readFile(childPath);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
totalBytes += bytes.length;
|
|
75
|
+
fileCount++;
|
|
76
|
+
if (totalBytes > maxBytes) {
|
|
77
|
+
return { error: `runtime filesystem snapshot exceeded ${(maxBytes / 1024 / 1024).toFixed(0)} MiB cap (current dir: ${dir})` };
|
|
78
|
+
}
|
|
79
|
+
if (fileCount > maxFiles) {
|
|
80
|
+
return { error: `runtime filesystem snapshot exceeded ${maxFiles} file cap` };
|
|
81
|
+
}
|
|
82
|
+
addDirWithParents(dir);
|
|
83
|
+
files[childPath] = bytesToB64(bytes);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { snapshot: { root, roots, preopens: [], files, dirs: Array.from(dirsSet).sort() }, bytes: totalBytes, files: fileCount };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Apply a runtime-produced filesystem diff back into the supervisor VFS.
|
|
90
|
+
* Operations are independent so one bad path never loses the rest of a run.
|
|
91
|
+
*/
|
|
92
|
+
export function flushVfsDiff(vfs, diff) {
|
|
93
|
+
let written = 0;
|
|
94
|
+
let deleted = 0;
|
|
95
|
+
let mkdirs = 0;
|
|
96
|
+
let rmdirs = 0;
|
|
97
|
+
let timesTouched = 0;
|
|
98
|
+
let symlinks = 0;
|
|
99
|
+
for (const path of diff.dirsCreated) {
|
|
100
|
+
try {
|
|
101
|
+
vfs.mkdir(path, { recursive: true });
|
|
102
|
+
mkdirs++;
|
|
103
|
+
}
|
|
104
|
+
catch { }
|
|
105
|
+
}
|
|
106
|
+
for (const [path, b64] of Object.entries(diff.filesWritten)) {
|
|
107
|
+
try {
|
|
108
|
+
const lastSlash = path.lastIndexOf('/');
|
|
109
|
+
if (lastSlash > 0) {
|
|
110
|
+
try {
|
|
111
|
+
vfs.mkdir(path.substring(0, lastSlash), { recursive: true });
|
|
112
|
+
}
|
|
113
|
+
catch { }
|
|
114
|
+
}
|
|
115
|
+
vfs.writeFile(path, b64ToBytes(b64));
|
|
116
|
+
written++;
|
|
117
|
+
}
|
|
118
|
+
catch { }
|
|
119
|
+
}
|
|
120
|
+
for (const path of diff.filesDeleted) {
|
|
121
|
+
try {
|
|
122
|
+
vfs.unlink(path);
|
|
123
|
+
deleted++;
|
|
124
|
+
}
|
|
125
|
+
catch { }
|
|
126
|
+
}
|
|
127
|
+
for (const path of diff.dirsDeleted) {
|
|
128
|
+
try {
|
|
129
|
+
vfs.rmdir(path);
|
|
130
|
+
rmdirs++;
|
|
131
|
+
}
|
|
132
|
+
catch { }
|
|
133
|
+
}
|
|
134
|
+
if (diff.timesChanged)
|
|
135
|
+
timesTouched = Object.keys(diff.timesChanged).length;
|
|
136
|
+
if (diff.symlinksCreated)
|
|
137
|
+
symlinks = Object.keys(diff.symlinksCreated).length;
|
|
138
|
+
if (diff.symlinksDeleted)
|
|
139
|
+
symlinks += diff.symlinksDeleted.length;
|
|
140
|
+
return { written, deleted, mkdirs, rmdirs, timesTouched, symlinks };
|
|
141
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* virtual-socket-kernel.ts - shared in-facet loopback socket substrate.
|
|
3
|
+
*
|
|
4
|
+
* The supervisor-facing surface stays the existing PortRegistry:
|
|
5
|
+
* /port/<n>/ and /preview/?port=<n> route a real Worker Request to a
|
|
6
|
+
* facet's handleHttpRequest(Request). Inside the facet this kernel
|
|
7
|
+
* converts that Request into an accepted HTTP/1.1 byte stream so guest
|
|
8
|
+
* runtimes can implement normal socket APIs without Cloudflare inbound
|
|
9
|
+
* TCP support.
|
|
10
|
+
*
|
|
11
|
+
* Runtime adapters (Python socket.py, Ruby socket.rb, future
|
|
12
|
+
* wasm32-wasi-nimbus syscalls) should call this shared JS kernel rather
|
|
13
|
+
* than each implementing their own preview bridge.
|
|
14
|
+
*/
|
|
15
|
+
export declare const VIRTUAL_SOCKET_KERNEL_SRC = "\nconst __NIMBUS_SOCKET_TIMEOUT_MS = 30_000;\n\nclass __NimbusDeferred {\n constructor() {\n this.promise = new Promise((resolve, reject) => {\n this.resolve = resolve;\n this.reject = reject;\n });\n }\n}\n\nfunction __nimbusSocketBytes(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 new Uint8Array(value);\n if (typeof value === \"string\") return new TextEncoder().encode(value);\n if (value && typeof value.toJs === \"function\") return __nimbusSocketBytes(value.toJs());\n return new Uint8Array(0);\n}\n\nfunction __nimbusConcatBytes(parts) {\n let len = 0;\n for (const p of parts) len += p.byteLength;\n const out = new Uint8Array(len);\n let off = 0;\n for (const p of parts) {\n out.set(p, off);\n off += p.byteLength;\n }\n return out;\n}\n\nfunction __nimbusFindHeaderEnd(bytes) {\n for (let i = 0; i + 3 < bytes.length; i++) {\n if (bytes[i] === 13 && bytes[i + 1] === 10 && bytes[i + 2] === 13 && bytes[i + 3] === 10) return i + 4;\n }\n return -1;\n}\n\nfunction __nimbusHeaderValue(headers, name) {\n const target = name.toLowerCase();\n for (const [k, v] of headers) {\n if (k.toLowerCase() === target) return v;\n }\n return null;\n}\n\nfunction __nimbusParseChunked(body) {\n const dec = new TextDecoder();\n const chunks = [];\n let off = 0;\n while (off < body.length) {\n let lineEnd = -1;\n for (let i = off; i + 1 < body.length; i++) {\n if (body[i] === 13 && body[i + 1] === 10) { lineEnd = i; break; }\n }\n if (lineEnd < 0) return null;\n const line = dec.decode(body.subarray(off, lineEnd)).trim();\n const sizeText = line.split(\";\", 1)[0];\n const size = parseInt(sizeText, 16);\n if (!Number.isFinite(size) || size < 0) return null;\n off = lineEnd + 2;\n if (body.length < off + size + 2) return null;\n if (size === 0) return __nimbusConcatBytes(chunks);\n chunks.push(body.subarray(off, off + size));\n off += size;\n if (body[off] !== 13 || body[off + 1] !== 10) return null;\n off += 2;\n }\n return null;\n}\n\nasync function __nimbusRequestBytes(request) {\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 const body = request.method === \"GET\" || request.method === \"HEAD\"\n ? new Uint8Array(0)\n : new Uint8Array(await request.arrayBuffer());\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 __nimbusConcatBytes([new TextEncoder().encode(lines.join(\"\\r\\n\")), body]);\n}\n\nclass __NimbusVirtualConnection {\n constructor(id, requestBytes) {\n this.id = id;\n this.requestBytes = requestBytes;\n this.requestOffset = 0;\n this.output = [];\n this.closed = false;\n this.waiters = [];\n }\n\n read(maxBytes) {\n if (this.requestOffset >= this.requestBytes.byteLength) return [];\n const end = Math.min(this.requestOffset + Math.max(1, maxBytes | 0), this.requestBytes.byteLength);\n const chunk = this.requestBytes.subarray(this.requestOffset, end);\n this.requestOffset = end;\n return Array.from(chunk);\n }\n\n write(bytesLike) {\n const bytes = __nimbusSocketBytes(bytesLike);\n if (bytes.byteLength > 0) this.output.push(bytes.slice());\n this._wake();\n return bytes.byteLength;\n }\n\n close() {\n this.closed = true;\n this._wake();\n }\n\n _wake() {\n const waiters = this.waiters.splice(0);\n for (const w of waiters) w.resolve();\n }\n\n _responseFromBytes() {\n const bytes = __nimbusConcatBytes(this.output);\n const headerEnd = __nimbusFindHeaderEnd(bytes);\n if (headerEnd < 0) return null;\n const headerText = new TextDecoder().decode(bytes.subarray(0, headerEnd));\n const lines = headerText.replace(/\\r\\n/g, \"\\n\").split(\"\\n\").filter((line) => line.length > 0);\n const statusLine = lines.shift() || \"HTTP/1.1 200 OK\";\n const m = /^HTTP\\/\\d(?:\\.\\d)?\\s+(\\d{3})(?:\\s+(.*))?$/.exec(statusLine);\n const status = m ? parseInt(m[1], 10) : 200;\n const statusText = m && m[2] ? m[2] : \"\";\n const headerPairs = [];\n for (const line of lines) {\n const idx = line.indexOf(\":\");\n if (idx <= 0) continue;\n headerPairs.push([line.slice(0, idx), line.slice(idx + 1).trimStart()]);\n }\n const body = bytes.subarray(headerEnd);\n const contentLength = __nimbusHeaderValue(headerPairs, \"Content-Length\");\n const transferEncoding = __nimbusHeaderValue(headerPairs, \"Transfer-Encoding\");\n let responseBody = body;\n if (transferEncoding && /chunked/i.test(transferEncoding)) {\n const parsed = __nimbusParseChunked(body);\n if (!parsed) return null;\n responseBody = parsed;\n } else if (contentLength != null) {\n const expected = parseInt(contentLength, 10);\n if (Number.isFinite(expected) && body.byteLength < expected) return null;\n if (Number.isFinite(expected)) responseBody = body.subarray(0, expected);\n } else if (!this.closed) {\n return null;\n }\n const headers = new Headers();\n for (const [k, v] of headerPairs) {\n if (/^transfer-encoding$/i.test(k)) continue;\n headers.append(k, v);\n }\n return new Response(responseBody, { status, statusText, headers });\n }\n\n async response(timeoutMs) {\n const deadline = Date.now() + Math.max(1, timeoutMs || __NIMBUS_SOCKET_TIMEOUT_MS);\n while (Date.now() < deadline) {\n const response = this._responseFromBytes();\n if (response) return response;\n const waiter = new __NimbusDeferred();\n this.waiters.push(waiter);\n const remaining = Math.max(1, deadline - Date.now());\n await Promise.race([\n waiter.promise,\n new Promise((resolve) => setTimeout(resolve, Math.min(remaining, 250))),\n ]);\n }\n return new Response(\"Nimbus virtual socket: timed out waiting for response\", { status: 504 });\n }\n}\n\nclass __NimbusVirtualListener {\n constructor(port) {\n this.port = port;\n this.queue = [];\n this.waiters = [];\n }\n\n push(conn) {\n const waiter = this.waiters.shift();\n if (waiter) waiter.resolve(conn);\n else this.queue.push(conn);\n }\n\n accept() {\n const queued = this.queue.shift();\n if (queued) return Promise.resolve(queued);\n const waiter = new __NimbusDeferred();\n this.waiters.push(waiter);\n return waiter.promise;\n }\n\n take() {\n return this.queue.shift() || null;\n }\n\n pending() {\n return this.queue.length;\n }\n}\n\nclass __NimbusVirtualSocketKernel {\n constructor() {\n this.listeners = new Map();\n this.connections = new Map();\n this.nextConnectionId = 1;\n this.nextEphemeralPort = 49152;\n this.listenWaiters = [];\n }\n\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 let listener = this.listeners.get(n);\n if (!listener) {\n listener = new __NimbusVirtualListener(n);\n this.listeners.set(n, listener);\n try { globalThis.__nimbusVirtualSocketDidListen?.(n); } catch {}\n const waiters = this.listenWaiters.splice(0);\n for (const waiter of waiters) waiter.resolve(n);\n }\n return n;\n }\n\n closeListener(port) {\n this.listeners.delete(Number(port));\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\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 recv(id, maxBytes) {\n const conn = this.connections.get(Number(id));\n if (!conn) return [];\n return conn.read(maxBytes);\n }\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\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\n pending(port) {\n return this.listeners.get(Number(port))?.pending() || 0;\n }\n\n firstListeningPort() {\n return this.listeners.size > 0 ? Array.from(this.listeners.keys())[0] : null;\n }\n\n waitReadable(ports, timeoutSeconds) {\n const normalized = (Array.isArray(ports) ? ports : []).map((p) => Number(p)).filter((p) => Number.isInteger(p));\n for (const port of normalized) {\n if (this.pending(port) > 0) return Promise.resolve([port]);\n }\n const timeoutMs = timeoutSeconds == null ? __NIMBUS_SOCKET_TIMEOUT_MS : Math.max(0, Number(timeoutSeconds) * 1000);\n const waiter = new __NimbusDeferred();\n const check = () => {\n const ready = normalized.filter((port) => this.pending(port) > 0);\n if (ready.length > 0) waiter.resolve(ready);\n };\n const oldWaiters = this.listenWaiters;\n const poll = setInterval(check, 25);\n const timer = setTimeout(() => {\n clearInterval(poll);\n waiter.resolve([]);\n }, timeoutMs);\n return waiter.promise.finally(() => {\n clearInterval(poll);\n clearTimeout(timer);\n this.listenWaiters = oldWaiters;\n });\n }\n\n async waitForListen(timeoutMs) {\n const existing = this.firstListeningPort();\n if (existing) return existing;\n const waiter = new __NimbusDeferred();\n this.listenWaiters.push(waiter);\n return await Promise.race([\n waiter.promise,\n new Promise((resolve) => setTimeout(() => resolve(null), Math.max(1, timeoutMs || 5_000))),\n ]);\n }\n\n async handleHttpRequest(port, request) {\n let listener = this.listeners.get(Number(port));\n if (!listener) {\n try { await globalThis.__nimbusVirtualSocketEnsureListener?.(Number(port)); } catch {}\n listener = this.listeners.get(Number(port));\n }\n if (!listener) return new Response(\"Nimbus virtual socket: no listener on port \" + port, { status: 502 });\n const id = this.nextConnectionId++;\n const conn = new __NimbusVirtualConnection(id, await __nimbusRequestBytes(request));\n this.connections.set(id, conn);\n listener.push(conn);\n try {\n try { await globalThis.__nimbusVirtualSocketRequestQueued?.(Number(port)); } catch {}\n return await conn.response(__NIMBUS_SOCKET_TIMEOUT_MS);\n } finally {\n conn.close();\n this.connections.delete(id);\n }\n }\n}\n\nglobalThis.__nimbusVirtualSockets = globalThis.__nimbusVirtualSockets || new __NimbusVirtualSocketKernel();\n";
|
|
16
|
+
//# sourceMappingURL=virtual-socket-kernel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"virtual-socket-kernel.d.ts","sourceRoot":"","sources":["../../src/runtime/virtual-socket-kernel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,eAAO,MAAM,yBAAyB,g8WAyVrC,CAAC"}
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* virtual-socket-kernel.ts - shared in-facet loopback socket substrate.
|
|
3
|
+
*
|
|
4
|
+
* The supervisor-facing surface stays the existing PortRegistry:
|
|
5
|
+
* /port/<n>/ and /preview/?port=<n> route a real Worker Request to a
|
|
6
|
+
* facet's handleHttpRequest(Request). Inside the facet this kernel
|
|
7
|
+
* converts that Request into an accepted HTTP/1.1 byte stream so guest
|
|
8
|
+
* runtimes can implement normal socket APIs without Cloudflare inbound
|
|
9
|
+
* TCP support.
|
|
10
|
+
*
|
|
11
|
+
* Runtime adapters (Python socket.py, Ruby socket.rb, future
|
|
12
|
+
* wasm32-wasi-nimbus syscalls) should call this shared JS kernel rather
|
|
13
|
+
* than each implementing their own preview bridge.
|
|
14
|
+
*/
|
|
15
|
+
export const VIRTUAL_SOCKET_KERNEL_SRC = `
|
|
16
|
+
const __NIMBUS_SOCKET_TIMEOUT_MS = 30_000;
|
|
17
|
+
|
|
18
|
+
class __NimbusDeferred {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.promise = new Promise((resolve, reject) => {
|
|
21
|
+
this.resolve = resolve;
|
|
22
|
+
this.reject = reject;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function __nimbusSocketBytes(value) {
|
|
28
|
+
if (value instanceof Uint8Array) return value;
|
|
29
|
+
if (value instanceof ArrayBuffer) return new Uint8Array(value);
|
|
30
|
+
if (ArrayBuffer.isView(value)) return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
31
|
+
if (Array.isArray(value)) return new Uint8Array(value);
|
|
32
|
+
if (typeof value === "string") return new TextEncoder().encode(value);
|
|
33
|
+
if (value && typeof value.toJs === "function") return __nimbusSocketBytes(value.toJs());
|
|
34
|
+
return new Uint8Array(0);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function __nimbusConcatBytes(parts) {
|
|
38
|
+
let len = 0;
|
|
39
|
+
for (const p of parts) len += p.byteLength;
|
|
40
|
+
const out = new Uint8Array(len);
|
|
41
|
+
let off = 0;
|
|
42
|
+
for (const p of parts) {
|
|
43
|
+
out.set(p, off);
|
|
44
|
+
off += p.byteLength;
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function __nimbusFindHeaderEnd(bytes) {
|
|
50
|
+
for (let i = 0; i + 3 < bytes.length; i++) {
|
|
51
|
+
if (bytes[i] === 13 && bytes[i + 1] === 10 && bytes[i + 2] === 13 && bytes[i + 3] === 10) return i + 4;
|
|
52
|
+
}
|
|
53
|
+
return -1;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function __nimbusHeaderValue(headers, name) {
|
|
57
|
+
const target = name.toLowerCase();
|
|
58
|
+
for (const [k, v] of headers) {
|
|
59
|
+
if (k.toLowerCase() === target) return v;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function __nimbusParseChunked(body) {
|
|
65
|
+
const dec = new TextDecoder();
|
|
66
|
+
const chunks = [];
|
|
67
|
+
let off = 0;
|
|
68
|
+
while (off < body.length) {
|
|
69
|
+
let lineEnd = -1;
|
|
70
|
+
for (let i = off; i + 1 < body.length; i++) {
|
|
71
|
+
if (body[i] === 13 && body[i + 1] === 10) { lineEnd = i; break; }
|
|
72
|
+
}
|
|
73
|
+
if (lineEnd < 0) return null;
|
|
74
|
+
const line = dec.decode(body.subarray(off, lineEnd)).trim();
|
|
75
|
+
const sizeText = line.split(";", 1)[0];
|
|
76
|
+
const size = parseInt(sizeText, 16);
|
|
77
|
+
if (!Number.isFinite(size) || size < 0) return null;
|
|
78
|
+
off = lineEnd + 2;
|
|
79
|
+
if (body.length < off + size + 2) return null;
|
|
80
|
+
if (size === 0) return __nimbusConcatBytes(chunks);
|
|
81
|
+
chunks.push(body.subarray(off, off + size));
|
|
82
|
+
off += size;
|
|
83
|
+
if (body[off] !== 13 || body[off + 1] !== 10) return null;
|
|
84
|
+
off += 2;
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function __nimbusRequestBytes(request) {
|
|
90
|
+
const url = new URL(request.url);
|
|
91
|
+
const path = (url.pathname || "/") + url.search;
|
|
92
|
+
const headers = new Headers(request.headers);
|
|
93
|
+
if (!headers.has("Host")) headers.set("Host", url.host || "nimbus.local");
|
|
94
|
+
const body = request.method === "GET" || request.method === "HEAD"
|
|
95
|
+
? new Uint8Array(0)
|
|
96
|
+
: new Uint8Array(await request.arrayBuffer());
|
|
97
|
+
if (body.byteLength > 0 && !headers.has("Content-Length")) {
|
|
98
|
+
headers.set("Content-Length", String(body.byteLength));
|
|
99
|
+
}
|
|
100
|
+
const lines = [request.method + " " + path + " HTTP/1.1"];
|
|
101
|
+
headers.forEach((value, key) => lines.push(key + ": " + value));
|
|
102
|
+
lines.push("", "");
|
|
103
|
+
return __nimbusConcatBytes([new TextEncoder().encode(lines.join("\\r\\n")), body]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
class __NimbusVirtualConnection {
|
|
107
|
+
constructor(id, requestBytes) {
|
|
108
|
+
this.id = id;
|
|
109
|
+
this.requestBytes = requestBytes;
|
|
110
|
+
this.requestOffset = 0;
|
|
111
|
+
this.output = [];
|
|
112
|
+
this.closed = false;
|
|
113
|
+
this.waiters = [];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
read(maxBytes) {
|
|
117
|
+
if (this.requestOffset >= this.requestBytes.byteLength) return [];
|
|
118
|
+
const end = Math.min(this.requestOffset + Math.max(1, maxBytes | 0), this.requestBytes.byteLength);
|
|
119
|
+
const chunk = this.requestBytes.subarray(this.requestOffset, end);
|
|
120
|
+
this.requestOffset = end;
|
|
121
|
+
return Array.from(chunk);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
write(bytesLike) {
|
|
125
|
+
const bytes = __nimbusSocketBytes(bytesLike);
|
|
126
|
+
if (bytes.byteLength > 0) this.output.push(bytes.slice());
|
|
127
|
+
this._wake();
|
|
128
|
+
return bytes.byteLength;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
close() {
|
|
132
|
+
this.closed = true;
|
|
133
|
+
this._wake();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
_wake() {
|
|
137
|
+
const waiters = this.waiters.splice(0);
|
|
138
|
+
for (const w of waiters) w.resolve();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
_responseFromBytes() {
|
|
142
|
+
const bytes = __nimbusConcatBytes(this.output);
|
|
143
|
+
const headerEnd = __nimbusFindHeaderEnd(bytes);
|
|
144
|
+
if (headerEnd < 0) return null;
|
|
145
|
+
const headerText = new TextDecoder().decode(bytes.subarray(0, headerEnd));
|
|
146
|
+
const lines = headerText.replace(/\\r\\n/g, "\\n").split("\\n").filter((line) => line.length > 0);
|
|
147
|
+
const statusLine = lines.shift() || "HTTP/1.1 200 OK";
|
|
148
|
+
const m = /^HTTP\\/\\d(?:\\.\\d)?\\s+(\\d{3})(?:\\s+(.*))?$/.exec(statusLine);
|
|
149
|
+
const status = m ? parseInt(m[1], 10) : 200;
|
|
150
|
+
const statusText = m && m[2] ? m[2] : "";
|
|
151
|
+
const headerPairs = [];
|
|
152
|
+
for (const line of lines) {
|
|
153
|
+
const idx = line.indexOf(":");
|
|
154
|
+
if (idx <= 0) continue;
|
|
155
|
+
headerPairs.push([line.slice(0, idx), line.slice(idx + 1).trimStart()]);
|
|
156
|
+
}
|
|
157
|
+
const body = bytes.subarray(headerEnd);
|
|
158
|
+
const contentLength = __nimbusHeaderValue(headerPairs, "Content-Length");
|
|
159
|
+
const transferEncoding = __nimbusHeaderValue(headerPairs, "Transfer-Encoding");
|
|
160
|
+
let responseBody = body;
|
|
161
|
+
if (transferEncoding && /chunked/i.test(transferEncoding)) {
|
|
162
|
+
const parsed = __nimbusParseChunked(body);
|
|
163
|
+
if (!parsed) return null;
|
|
164
|
+
responseBody = parsed;
|
|
165
|
+
} else if (contentLength != null) {
|
|
166
|
+
const expected = parseInt(contentLength, 10);
|
|
167
|
+
if (Number.isFinite(expected) && body.byteLength < expected) return null;
|
|
168
|
+
if (Number.isFinite(expected)) responseBody = body.subarray(0, expected);
|
|
169
|
+
} else if (!this.closed) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
const headers = new Headers();
|
|
173
|
+
for (const [k, v] of headerPairs) {
|
|
174
|
+
if (/^transfer-encoding$/i.test(k)) continue;
|
|
175
|
+
headers.append(k, v);
|
|
176
|
+
}
|
|
177
|
+
return new Response(responseBody, { status, statusText, headers });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async response(timeoutMs) {
|
|
181
|
+
const deadline = Date.now() + Math.max(1, timeoutMs || __NIMBUS_SOCKET_TIMEOUT_MS);
|
|
182
|
+
while (Date.now() < deadline) {
|
|
183
|
+
const response = this._responseFromBytes();
|
|
184
|
+
if (response) return response;
|
|
185
|
+
const waiter = new __NimbusDeferred();
|
|
186
|
+
this.waiters.push(waiter);
|
|
187
|
+
const remaining = Math.max(1, deadline - Date.now());
|
|
188
|
+
await Promise.race([
|
|
189
|
+
waiter.promise,
|
|
190
|
+
new Promise((resolve) => setTimeout(resolve, Math.min(remaining, 250))),
|
|
191
|
+
]);
|
|
192
|
+
}
|
|
193
|
+
return new Response("Nimbus virtual socket: timed out waiting for response", { status: 504 });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
class __NimbusVirtualListener {
|
|
198
|
+
constructor(port) {
|
|
199
|
+
this.port = port;
|
|
200
|
+
this.queue = [];
|
|
201
|
+
this.waiters = [];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
push(conn) {
|
|
205
|
+
const waiter = this.waiters.shift();
|
|
206
|
+
if (waiter) waiter.resolve(conn);
|
|
207
|
+
else this.queue.push(conn);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
accept() {
|
|
211
|
+
const queued = this.queue.shift();
|
|
212
|
+
if (queued) return Promise.resolve(queued);
|
|
213
|
+
const waiter = new __NimbusDeferred();
|
|
214
|
+
this.waiters.push(waiter);
|
|
215
|
+
return waiter.promise;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
take() {
|
|
219
|
+
return this.queue.shift() || null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
pending() {
|
|
223
|
+
return this.queue.length;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
class __NimbusVirtualSocketKernel {
|
|
228
|
+
constructor() {
|
|
229
|
+
this.listeners = new Map();
|
|
230
|
+
this.connections = new Map();
|
|
231
|
+
this.nextConnectionId = 1;
|
|
232
|
+
this.nextEphemeralPort = 49152;
|
|
233
|
+
this.listenWaiters = [];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
listen(port) {
|
|
237
|
+
let n = Number(port);
|
|
238
|
+
if (!Number.isInteger(n) || n < 0 || n >= 65536) throw new Error("invalid port: " + port);
|
|
239
|
+
if (n === 0) {
|
|
240
|
+
while (this.listeners.has(this.nextEphemeralPort)) {
|
|
241
|
+
this.nextEphemeralPort++;
|
|
242
|
+
if (this.nextEphemeralPort >= 65535) this.nextEphemeralPort = 49152;
|
|
243
|
+
}
|
|
244
|
+
n = this.nextEphemeralPort++;
|
|
245
|
+
if (this.nextEphemeralPort >= 65535) this.nextEphemeralPort = 49152;
|
|
246
|
+
}
|
|
247
|
+
let listener = this.listeners.get(n);
|
|
248
|
+
if (!listener) {
|
|
249
|
+
listener = new __NimbusVirtualListener(n);
|
|
250
|
+
this.listeners.set(n, listener);
|
|
251
|
+
try { globalThis.__nimbusVirtualSocketDidListen?.(n); } catch {}
|
|
252
|
+
const waiters = this.listenWaiters.splice(0);
|
|
253
|
+
for (const waiter of waiters) waiter.resolve(n);
|
|
254
|
+
}
|
|
255
|
+
return n;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
closeListener(port) {
|
|
259
|
+
this.listeners.delete(Number(port));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async accept(port) {
|
|
263
|
+
const listener = this.listeners.get(Number(port));
|
|
264
|
+
if (!listener) throw new Error("port is not listening: " + port);
|
|
265
|
+
const conn = await listener.accept();
|
|
266
|
+
return { id: conn.id, host: "127.0.0.1", port: 0 };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
acceptNow(port) {
|
|
270
|
+
const listener = this.listeners.get(Number(port));
|
|
271
|
+
if (!listener) throw new Error("port is not listening: " + port);
|
|
272
|
+
const conn = listener.take();
|
|
273
|
+
return conn ? { id: conn.id, host: "127.0.0.1", port: 0 } : null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
recv(id, maxBytes) {
|
|
277
|
+
const conn = this.connections.get(Number(id));
|
|
278
|
+
if (!conn) return [];
|
|
279
|
+
return conn.read(maxBytes);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
send(id, bytesLike) {
|
|
283
|
+
const conn = this.connections.get(Number(id));
|
|
284
|
+
if (!conn) throw new Error("connection is closed: " + id);
|
|
285
|
+
return conn.write(bytesLike);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
close(id) {
|
|
289
|
+
const conn = this.connections.get(Number(id));
|
|
290
|
+
if (!conn) return;
|
|
291
|
+
conn.close();
|
|
292
|
+
this.connections.delete(Number(id));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
pending(port) {
|
|
296
|
+
return this.listeners.get(Number(port))?.pending() || 0;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
firstListeningPort() {
|
|
300
|
+
return this.listeners.size > 0 ? Array.from(this.listeners.keys())[0] : null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
waitReadable(ports, timeoutSeconds) {
|
|
304
|
+
const normalized = (Array.isArray(ports) ? ports : []).map((p) => Number(p)).filter((p) => Number.isInteger(p));
|
|
305
|
+
for (const port of normalized) {
|
|
306
|
+
if (this.pending(port) > 0) return Promise.resolve([port]);
|
|
307
|
+
}
|
|
308
|
+
const timeoutMs = timeoutSeconds == null ? __NIMBUS_SOCKET_TIMEOUT_MS : Math.max(0, Number(timeoutSeconds) * 1000);
|
|
309
|
+
const waiter = new __NimbusDeferred();
|
|
310
|
+
const check = () => {
|
|
311
|
+
const ready = normalized.filter((port) => this.pending(port) > 0);
|
|
312
|
+
if (ready.length > 0) waiter.resolve(ready);
|
|
313
|
+
};
|
|
314
|
+
const oldWaiters = this.listenWaiters;
|
|
315
|
+
const poll = setInterval(check, 25);
|
|
316
|
+
const timer = setTimeout(() => {
|
|
317
|
+
clearInterval(poll);
|
|
318
|
+
waiter.resolve([]);
|
|
319
|
+
}, timeoutMs);
|
|
320
|
+
return waiter.promise.finally(() => {
|
|
321
|
+
clearInterval(poll);
|
|
322
|
+
clearTimeout(timer);
|
|
323
|
+
this.listenWaiters = oldWaiters;
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async waitForListen(timeoutMs) {
|
|
328
|
+
const existing = this.firstListeningPort();
|
|
329
|
+
if (existing) return existing;
|
|
330
|
+
const waiter = new __NimbusDeferred();
|
|
331
|
+
this.listenWaiters.push(waiter);
|
|
332
|
+
return await Promise.race([
|
|
333
|
+
waiter.promise,
|
|
334
|
+
new Promise((resolve) => setTimeout(() => resolve(null), Math.max(1, timeoutMs || 5_000))),
|
|
335
|
+
]);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async handleHttpRequest(port, request) {
|
|
339
|
+
let listener = this.listeners.get(Number(port));
|
|
340
|
+
if (!listener) {
|
|
341
|
+
try { await globalThis.__nimbusVirtualSocketEnsureListener?.(Number(port)); } catch {}
|
|
342
|
+
listener = this.listeners.get(Number(port));
|
|
343
|
+
}
|
|
344
|
+
if (!listener) return new Response("Nimbus virtual socket: no listener on port " + port, { status: 502 });
|
|
345
|
+
const id = this.nextConnectionId++;
|
|
346
|
+
const conn = new __NimbusVirtualConnection(id, await __nimbusRequestBytes(request));
|
|
347
|
+
this.connections.set(id, conn);
|
|
348
|
+
listener.push(conn);
|
|
349
|
+
try {
|
|
350
|
+
try { await globalThis.__nimbusVirtualSocketRequestQueued?.(Number(port)); } catch {}
|
|
351
|
+
return await conn.response(__NIMBUS_SOCKET_TIMEOUT_MS);
|
|
352
|
+
} finally {
|
|
353
|
+
conn.close();
|
|
354
|
+
this.connections.delete(id);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
globalThis.__nimbusVirtualSockets = globalThis.__nimbusVirtualSockets || new __NimbusVirtualSocketKernel();
|
|
360
|
+
`;
|
|
@@ -112,13 +112,16 @@ export declare const WASI_INSTANCE_PREAMBLE_SRC = "\n// \u2500\u2500 BEGIN: wasi
|
|
|
112
112
|
* invocation. Files are base64-encoded so the JSON-serializable
|
|
113
113
|
* loader-pool `context` field can carry them.
|
|
114
114
|
*
|
|
115
|
-
* WASI socket and polling support B1+B3: added optional `times` and `symlinks` fields.
|
|
116
|
-
*
|
|
117
|
-
*
|
|
115
|
+
* WASI socket and polling support B1+B3: added optional `times` and `symlinks` fields.
|
|
116
|
+
* `roots` is additive and lets language runtimes snapshot a cwd plus targeted
|
|
117
|
+
* persistent runtime state without widening every command to the whole home
|
|
118
|
+
* directory. Backward-compatible — callers that omit it use `root` only.
|
|
118
119
|
*/
|
|
119
120
|
export interface WasiFsSnapshot {
|
|
120
121
|
/** Canonical VFS root (no leading slash). E.g. `home/user/wasi-files`. */
|
|
121
122
|
root: string;
|
|
123
|
+
/** Canonical VFS roots covered by this snapshot. Defaults to `[root]`. */
|
|
124
|
+
roots?: string[];
|
|
122
125
|
/** Preopen list (order matters; preopens are assigned to fd 3, 4, …). */
|
|
123
126
|
preopens: Array<{
|
|
124
127
|
wasiPath: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wasi-instance.d.ts","sourceRoot":"","sources":["../../src/runtime/wasi-instance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqGG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,u0pFAk2DtC,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"wasi-instance.d.ts","sourceRoot":"","sources":["../../src/runtime/wasi-instance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqGG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,u0pFAk2DtC,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,WAAW,cAAc;IAC7B,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,yEAAyE;IACzE,QAAQ,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvD,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,yCAAyC;IACzC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxE;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB,mEAAmE;IACnE,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,mCAAmC;IACnC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,kCAAkC;IAClC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,kCAAkC;IAClC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/E,gCAAgC;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,wBAAwB;IACxB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,0DAA0D;AAC1D,eAAO,MAAM,oBAAoB,EAAE,SAAS,MAAM,EA2BhD,CAAC"}
|
|
@@ -49,33 +49,9 @@
|
|
|
49
49
|
* workerd CSP rejects that path.
|
|
50
50
|
*/
|
|
51
51
|
import type { RuntimeRunOpts, RuntimeRunResult } from './runtime-registry.js';
|
|
52
|
+
import { type VfsLike } from './vfs-snapshot.js';
|
|
52
53
|
export declare const WASM_RUNNER_VERSION = "0.3.0";
|
|
53
54
|
export declare const WASM_RUNNER_HELP: string;
|
|
54
|
-
/**
|
|
55
|
-
* Minimal VFS shape we depend on. Avoids importing the full
|
|
56
|
-
* SqliteVFS type tree from the supervisor module graph — this file
|
|
57
|
-
* is part of `src/runtime/`, importing supervisor-specific types
|
|
58
|
-
* would create a cycle.
|
|
59
|
-
*
|
|
60
|
-
* filesystem WASI: extended for WASI file-IO. The snapshot path uses readdir +
|
|
61
|
-
* isDirectory + stat to traverse the user's session subtree; the
|
|
62
|
-
* flush path uses writeFile + mkdir + unlink + rmdir.
|
|
63
|
-
*/
|
|
64
|
-
interface VfsLike {
|
|
65
|
-
exists(path: string): boolean;
|
|
66
|
-
isDirectory(path: string): boolean;
|
|
67
|
-
readFile(path: string): Uint8Array;
|
|
68
|
-
writeFile(path: string, content: Uint8Array | string): void;
|
|
69
|
-
readdir(path: string): {
|
|
70
|
-
name: string;
|
|
71
|
-
type: string;
|
|
72
|
-
}[];
|
|
73
|
-
mkdir(path: string, opts?: {
|
|
74
|
-
recursive?: boolean;
|
|
75
|
-
}): void;
|
|
76
|
-
unlink(path: string): void;
|
|
77
|
-
rmdir(path: string): void;
|
|
78
|
-
}
|
|
79
55
|
/**
|
|
80
56
|
* Minimal processTable shape — the parts wasm-runner needs to
|
|
81
57
|
* register a PID + mark exit so `ps` and `logs <pid>` see the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wasm-runner.d.ts","sourceRoot":"","sources":["../../src/runtime/wasm-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"wasm-runner.d.ts","sourceRoot":"","sources":["../../src/runtime/wasm-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9E,OAAO,EAA6B,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAoD5E,eAAO,MAAM,mBAAmB,UAAU,CAAC;AAE3C,eAAO,MAAM,gBAAgB,QA+BwC,CAAC;AA2CtE;;;;;GAKG;AACH,UAAU,gBAAgB;IACxB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACrE,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACvC;AAED,UAAU,mBAAmB;IAC3B,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAG,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACrE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,GAAG,CAAC;IACT,GAAG,EAAE,kBAAkB,CAAC;IACxB,YAAY,EAAE,gBAAgB,CAAC;IAC/B,WAAW,EAAE,mBAAmB,CAAC;CAClC,IAEG,WAAW,OAAO,EAClB,OAAO,MAAM,EACb,MAAM,cAAc,KACnB,OAAO,CAAC,gBAAgB,CAAC,CAgd7B"}
|