Skip to content

Commit 2ee8bd1

Browse files
committed
computer, docs: document the process shim and cover the stdin ceiling
Add a section to the JavaScript runtime doc describing the process shim: caller-supplied env exposed through process.env with the host environment hidden, caller-supplied stdin as a non-interactive async-iterable bounded by maxStdinBytes, stdout and stderr capture with console routing, and inert argv, cwd, and platform. List maxStdinBytes among the configurable byte limits. Add a test asserting an oversized stdin fails the run before the loader is invoked.
1 parent 4150136 commit 2ee8bd1

2 files changed

Lines changed: 43 additions & 1 deletion

File tree

docs/17_isolate_javascript.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,35 @@ Cancellation stops new host capability calls, disposes the Dynamic Worker, and w
9191

9292
Host calls have a caller-visible deadline, controlled by `maxHostCallMs` and defaulting to `maxTimeoutMs`. Missing the deadline fails the capability call and marks the execution failed, even if caller code catches that error. Execution still waits for the accepted host operation itself before publishing a terminal event because many host APIs cannot roll back an external side effect after dispatch. Trusted modules receive an optional `{ signal, deadline }` context and must stop promptly when the signal aborts. A trusted module that ignores cancellation and never settles will keep execution in its finalizing state. `compatibilityDate` and `compatibilityFlags` control the Dynamic Worker runtime and default to the package-tested settings.
9393

94+
## Environment, standard input, and the `process` shim
95+
96+
Each execution installs a small `node:process` shim so ordinary module code can read its environment and standard streams. The shim exposes only what the caller supplies for that execution; the host environment is never visible.
97+
98+
`process.env` is a snapshot of the `env` record passed on the exec options. Values the caller does not pass are absent, and the Durable Object's own environment is never merged in, so a module cannot read host bindings or secrets through `process.env`.
99+
100+
`process.stdin` is a non-interactive async-iterable over the caller-supplied `stdin` bytes. The caller passes `stdin` as a `Uint8Array` or string on the exec options; `for await` yields the bytes once and then ends, and there is no blocking read for further input because an evaluate-once execution has no session to wait on. `isTTY` is `false`. The supplied input is bounded by `maxStdinBytes`; exceeding it fails the run with a clear error.
101+
102+
`process.stdout` and `process.stderr` are writable streams whose writes are captured as standard output and standard error. `console.log` and `console.info` route to standard output, `console.warn` and `console.error` route to standard error, and the captured output is bounded. `process.argv`, `process.cwd()`, and `process.platform` return inert values: `cwd()` reflects the execution's working directory, while `argv` and `platform` carry fixed placeholders rather than describing the host process.
103+
104+
```ts
105+
const handle = await workspace.runtime.exec(
106+
`
107+
export default async function main() {
108+
let piped = "";
109+
for await (const chunk of process.stdin) piped += new TextDecoder().decode(chunk);
110+
console.log("received", piped.length, "bytes");
111+
return { who: process.env.WHO, piped };
112+
}
113+
`,
114+
{
115+
backend: "worker-javascript",
116+
env: { WHO: "demo" },
117+
stdin: "hello",
118+
encoding: "utf8",
119+
},
120+
);
121+
```
122+
94123
## Configured modules
95124

96125
Bare imports are installed at backend construction, not passed on individual executions:
@@ -156,7 +185,7 @@ Each execution receives a fresh Dynamic Worker with:
156185
- a host wall-clock deadline;
157186
- `globalOutbound: null` by default;
158187
- finite, acyclic JSON-compatible input and structured result validation;
159-
- configurable source/module graph, input, result, captured-log, file/capability request, and response byte limits (`maxSourceBytes`, `maxInputBytes`, `maxResultBytes`, `maxLogBytes`, and `maxCapabilityBytes`);
188+
- configurable source/module graph, input, result, stdin, captured-log, file/capability request, and response byte limits (`maxSourceBytes`, `maxInputBytes`, `maxResultBytes`, `maxStdinBytes`, `maxLogBytes`, and `maxCapabilityBytes`);
160189
- explicit entrypoint and Worker disposal;
161190
- host-owned cancellation;
162191
- retained events and result rows in the Workspace database.

packages/computer/src/backends/worker-javascript/worker-javascript.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,19 @@ describe("WorkerJavaScriptBackend", () => {
221221
await expect(execution).rejects.toMatchObject({ code: "ECLOSED" });
222222
});
223223

224+
it("rejects stdin larger than the configured ceiling", async () => {
225+
const load = vi.fn();
226+
const workspace = new Workspace({
227+
storage: new SQLiteTestStorage(),
228+
backends: [new WorkerJavaScriptBackend({ loader: { load }, maxStdinBytes: 8 })],
229+
});
230+
await workspace.fs.mkdir("/workspace", { recursive: true });
231+
await expect(
232+
workspace.runtime.exec("export default 1", { stdin: "x".repeat(64) }),
233+
).rejects.toThrow(/stdin exceeds 8 bytes/);
234+
expect(load).not.toHaveBeenCalled();
235+
});
236+
224237
it("checks limits against the complete loader map including the runtime runner", async () => {
225238
const load = vi.fn();
226239
const workspace = new Workspace({

0 commit comments

Comments
 (0)