Summary
reference/nodejs.mdx under-documents the low-level JsBox handle and, in the same file, documents a JsBoxlite.rest() call form that throws at runtime. Verified against the published npm package @boxlite-ai/boxlite@0.9.7 by both reading dist/native-contracts.d.ts and introspecting the loaded native binding on macOS (Apple Silicon).
Nothing has been changed yet — this issue records the symptom and the reproduction so a fix can be reviewed against evidence rather than memory.
Impact (already caused real harm)
The JsBox methods table at reference/nodejs.mdx:399-406 lists exactly four rows: info, exec, stop, metrics. A writer working on the BoxLite Cloud docs read that table as the complete interface, concluded the Node box handle had no way to start a box, and omitted the Node code sample from a quickstart page as a result. start() exists and works.
The rest() problem is worse than an omission: a reader who copies the documented snippet gets an exception, and the same page shows the correct form 20 lines later, so the page contradicts itself.
Reproduction
cd "$(mktemp -d)"
npm init -y >/dev/null
npm install @boxlite-ai/boxlite # resolved to 0.9.7
sed -n '217,230p' node_modules/@boxlite-ai/boxlite/dist/native-contracts.d.ts
Static contract (dist/native-contracts.d.ts:217-230):
export interface JsBox {
readonly id: string;
readonly name: string | null;
info(): JsBoxInfo;
exec(command: string, args?: string[] | null, env?: Array<[string, string]> | null, tty?: boolean | null, user?: string | null, timeoutSecs?: number | null, workingDir?: string | null): Promise<JsExecution>;
readonly snapshot: JsSnapshotHandle;
cloneBox(options?: JsCloneOptions | null, name?: string | null): Promise<JsBox>;
export(dest: string, options?: JsExportOptions | null): Promise<string>;
start(): Promise<void>;
stop(): Promise<void>;
metrics(): Promise<JsBoxMetrics>;
copyIn(hostPath: string, containerDest: string, options?: CopyOptions | null): Promise<void>;
copyOut(containerSrc: string, hostDest: string, options?: CopyOptions | null): Promise<void>;
}
Runtime confirmation — the .d.ts is not aspirational, these members are really on the prototype:
// probe.mjs — run: node probe.mjs
import { JsBoxlite, ApiKeyCredential, BoxliteRestOptions, getJsBoxlite } from "@boxlite-ai/boxlite";
const rt = JsBoxlite.withDefaultConfig();
const box = await rt.create({ image: "alpine:latest" });
console.log("JsBox:", Object.getOwnPropertyNames(Object.getPrototypeOf(box)).sort().join(", "));
const ex = await box.exec("echo", ["hi"]);
console.log("JsExecution:", Object.getOwnPropertyNames(Object.getPrototypeOf(ex)).sort().join(", "));
console.log("JsBoxlite statics:", Object.getOwnPropertyNames(getJsBoxlite())
.filter(n => !["length", "name", "prototype", "arguments", "caller"].includes(n)).sort().join(", "));
// the form documented at reference/nodejs.mdx:534 and :539-547
try {
JsBoxlite.rest("http://localhost:8100", new ApiKeyCredential("blk_test_123"));
console.log("POSITIONAL rest() OK");
} catch (e) { console.log("POSITIONAL rest() THREW ->", e.message); }
// the form documented at reference/nodejs.mdx:557-561
JsBoxlite.rest(new BoxliteRestOptions({ url: "http://localhost:8100", credential: new ApiKeyCredential("blk_test_123") }));
console.log("OPTIONS-BAG rest() OK");
await box.stop();
await rt.remove(box.id, true);
rt.close();
Actual output:
JsBox: cloneBox, constructor, copyIn, copyOut, exec, export, id, info, metrics, name, snapshot, start, stop
JsExecution: constructor, id, kill, resizeTty, signal, stderr, stdin, stdout, wait
JsBoxlite statics: initDefault, rest, withDefaultConfig
POSITIONAL rest() THREW -> Failed to convert JavaScript value `Undefined` into rust type `String`
OPTIONS-BAG rest() OK
Findings
1. JsBox table is missing 8 of 12 members — reference/nodejs.mdx:399-406
| Member |
Real signature |
Documented? |
id |
readonly string |
missing |
name |
readonly string | null |
missing |
info |
() => JsBoxInfo |
yes |
exec |
(cmd, args?, env?, tty?, user?, timeoutSecs?, workingDir?) => Promise<JsExecution> |
signature truncated at tty? |
snapshot |
readonly JsSnapshotHandle |
missing |
cloneBox |
(options?, name?) => Promise<JsBox> |
missing |
export |
(dest, options?) => Promise<string> |
missing |
start |
() => Promise<void> |
missing — this is the one that caused the Cloud quickstart gap |
stop |
() => Promise<void> |
yes |
metrics |
() => Promise<JsBoxMetrics> |
yes |
copyIn |
(hostPath, containerDest, options?) => Promise<void> |
missing |
copyOut |
(containerSrc, hostDest, options?) => Promise<void> |
missing |
JsSnapshotHandle (native-contracts.d.ts:208-214) has create(name, options?), list(), get(name), remove(name), restore(name) and has no table on the page at all. The options? argument of cloneBox/export/snapshot.create is Record<string, never> today (native-contracts.d.ts:207,215,216) — an accepted-but-empty bag, worth stating so readers do not go looking for fields. CopyOptions (dist/copy.d.ts:4-13) is recursive / overwrite / followSymlinks / includeParent, the first, second and fourth defaulting to true.
2. Documented JsBoxlite.rest(url, credential?) throws — reference/nodejs.mdx:380, :534, :539-547
The page states "pass it positionally: JsBoxlite.rest(url, credential)" and shows two positional snippets. dist/index.js:24-30 shows why that cannot work — rest takes one options bag and reads properties off it:
class BoxliteWithBagRest extends nativeBoxlite {
static rest(options) {
return nativeBoxlite.rest(new NativeBoxliteRestOptions(options.url, options.credential ?? null,
options.pathPrefix ?? null));
}
}
With a string argument, options.url is undefined, and napi rejects the conversion. The public type in dist/index.d.ts:25-28 agrees: rest(options: BoxliteRestOptions). The snippet at :557-561 is the correct form, so the fix is to delete the positional prose and both positional snippets, and to fold the ApiKeyCredential.fromEnv() example into the options-bag form.
3. JsBoxlite table is missing the initDefault static — reference/nodejs.mdx:376-391
initDefault(options: JsOptions): void is present on the native constructor (native-contracts.d.ts:251, and in the runtime output above) and is not in the table.
What is already correct (spot-checked, no change needed)
JsExecution table (reference/nodejs.mdx:410-419) — all 8 members match native-contracts.d.ts:189-198 exactly, including signal and resizeTty. No omissions.
JsBoxlite instance members (reference/nodejs.mdx:381-391) — all 11 of create, getOrCreate, get, getInfo, listInfo, metrics, remove, importBox, shutdown, close, images are documented and match native-contracts.d.ts:235-247. Only the initDefault static is missing.
JsExecStdin / JsExecStdout / JsExecStderr / JsExecResult tables match native-contracts.d.ts:174-188.
Suggested fix
Extend the JsBox table to all 12 members in the existing table format, correct the exec signature to its full 7 parameters, add a JsSnapshotHandle table, drop the positional rest() prose and snippets in favour of the options-bag form already on the page, and add initDefault to the JsBoxlite table. Repo conventions apply: no soft promises (Record<string, never> options are "accepted but not enforced", not "not yet supported"), and python3 scripts/lint-docs.py . plus mint broken-links before the PR.
Summary
reference/nodejs.mdxunder-documents the low-levelJsBoxhandle and, in the same file, documents aJsBoxlite.rest()call form that throws at runtime. Verified against the published npm package@boxlite-ai/boxlite@0.9.7by both readingdist/native-contracts.d.tsand introspecting the loaded native binding on macOS (Apple Silicon).Nothing has been changed yet — this issue records the symptom and the reproduction so a fix can be reviewed against evidence rather than memory.
Impact (already caused real harm)
The
JsBoxmethods table atreference/nodejs.mdx:399-406lists exactly four rows:info,exec,stop,metrics. A writer working on the BoxLite Cloud docs read that table as the complete interface, concluded the Node box handle had no way to start a box, and omitted the Node code sample from a quickstart page as a result.start()exists and works.The
rest()problem is worse than an omission: a reader who copies the documented snippet gets an exception, and the same page shows the correct form 20 lines later, so the page contradicts itself.Reproduction
Static contract (
dist/native-contracts.d.ts:217-230):Runtime confirmation — the
.d.tsis not aspirational, these members are really on the prototype:Actual output:
Findings
1.
JsBoxtable is missing 8 of 12 members —reference/nodejs.mdx:399-406idreadonly stringnamereadonly string | nullinfo() => JsBoxInfoexec(cmd, args?, env?, tty?, user?, timeoutSecs?, workingDir?) => Promise<JsExecution>tty?snapshotreadonly JsSnapshotHandlecloneBox(options?, name?) => Promise<JsBox>export(dest, options?) => Promise<string>start() => Promise<void>stop() => Promise<void>metrics() => Promise<JsBoxMetrics>copyIn(hostPath, containerDest, options?) => Promise<void>copyOut(containerSrc, hostDest, options?) => Promise<void>JsSnapshotHandle(native-contracts.d.ts:208-214) hascreate(name, options?),list(),get(name),remove(name),restore(name)and has no table on the page at all. Theoptions?argument ofcloneBox/export/snapshot.createisRecord<string, never>today (native-contracts.d.ts:207,215,216) — an accepted-but-empty bag, worth stating so readers do not go looking for fields.CopyOptions(dist/copy.d.ts:4-13) isrecursive/overwrite/followSymlinks/includeParent, the first, second and fourth defaulting totrue.2. Documented
JsBoxlite.rest(url, credential?)throws —reference/nodejs.mdx:380,:534,:539-547The page states "pass it positionally:
JsBoxlite.rest(url, credential)" and shows two positional snippets.dist/index.js:24-30shows why that cannot work —resttakes one options bag and reads properties off it:With a string argument,
options.urlisundefined, and napi rejects the conversion. The public type indist/index.d.ts:25-28agrees:rest(options: BoxliteRestOptions). The snippet at:557-561is the correct form, so the fix is to delete the positional prose and both positional snippets, and to fold theApiKeyCredential.fromEnv()example into the options-bag form.3.
JsBoxlitetable is missing theinitDefaultstatic —reference/nodejs.mdx:376-391initDefault(options: JsOptions): voidis present on the native constructor (native-contracts.d.ts:251, and in the runtime output above) and is not in the table.What is already correct (spot-checked, no change needed)
JsExecutiontable (reference/nodejs.mdx:410-419) — all 8 members matchnative-contracts.d.ts:189-198exactly, includingsignalandresizeTty. No omissions.JsBoxliteinstance members (reference/nodejs.mdx:381-391) — all 11 ofcreate,getOrCreate,get,getInfo,listInfo,metrics,remove,importBox,shutdown,close,imagesare documented and matchnative-contracts.d.ts:235-247. Only theinitDefaultstatic is missing.JsExecStdin/JsExecStdout/JsExecStderr/JsExecResulttables matchnative-contracts.d.ts:174-188.Suggested fix
Extend the
JsBoxtable to all 12 members in the existing table format, correct theexecsignature to its full 7 parameters, add aJsSnapshotHandletable, drop the positionalrest()prose and snippets in favour of the options-bag form already on the page, and addinitDefaultto theJsBoxlitetable. Repo conventions apply: no soft promises (Record<string, never>options are "accepted but not enforced", not "not yet supported"), andpython3 scripts/lint-docs.py .plusmint broken-linksbefore the PR.