Skip to content

reference/nodejs.mdx: JsBox table documents 4 of 12 members; documented JsBoxlite.rest() form throws at runtime #31

Description

@Mandalorian-Wang

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdocumentationImprovements or additions to documentation

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions