#978

fix(docs): JavaScript examples use `await using` without saying it needs Node 24, though the package supports Node 20 and 22

Opencoygeekopened 24d ago

Summary

@pydantic/monty declares support for Node 20 and later, and CI tests it on Node 20, 22 and 24. Every JavaScript example in the docs that creates a pool declares the pool and session with await using, starting with the quickstart's "First run" example. That syntax only parses on Node 24 and later, and the docs never state a Node version requirement.

On Node 22, running the verbatim "First run" TypeScript block with Node's built-in type stripping fails at parse time with SyntaxError: Unexpected identifier 'pool'. On Node 20 and 22, readers who turn the example into plain JavaScript get the same error. The package itself works on those versions: the same program with explicit close() calls prints the expected result. The docs mention the close() alternative, but they don't say it is required on Node versions the package supports.

Runtime or environment

  • @pydantic/monty 1.1.0 from npm (latest), the same release as tag v1.1.0 and main at commit 591527397445bf734873252d1ee20c1b6308fe7f.
  • Node v20.20.2, v22.23.3, v23.11.1 and v24.21.0 from the official node:20, node:22, node:23 and node:24 Docker images on linux arm64. All runs share one npm install (@pydantic/monty-linux-arm64-gnu).
  • Node v22.23.2 on macOS arm64.
  • TypeScript 5.9.3 for the transpiled variant.

Steps to reproduce

  1. In an empty directory, create package.json containing { "type": "module" } and run npm install @pydantic/monty@1.1.0.
  2. Save the "First run" TypeScript block from docs/quickstart/javascript.md (lines 21–30) verbatim as first.ts, then run node first.ts on Node 22. Node 22 strips the types by default.
  3. Save the same example as plain JavaScript in first.mjs. Removing its one type annotation is the only change:
import { Monty } from '@pydantic/monty'
await using pool = await Monty.create()
await using session = await pool.checkout({ limits: { maxMemory: 10_000_000, maxFeedDurationSecs: 1 } })
const result = await session.feedRun('double(x) + y', { inputs: { x: 5, y: 1 }, externalLookup: { double: (x) => x * 2 } })
console.log(result)
  1. Run node first.mjs on Node 20 and on Node 22.
  2. For comparison, save the same program without await using as first2.mjs, and run it on the same Node versions:
import { Monty } from '@pydantic/monty'
const pool = await Monty.create()
const session = await pool.checkout({ limits: { maxMemory: 10_000_000, maxFeedDurationSecs: 1 } })
const result = await session.feedRun('double(x) + y', { inputs: { x: 5, y: 1 }, externalLookup: { double: (x) => x * 2 } })
console.log(result)
await session.close(); await pool.close()

Expected behavior

The package's contract covers Node 20 and later:

  • crates/monty-js/package.json declares "engines": { "node": ">= 20" }.
  • CI's test-js job runs the test suite on Node 20 and 22.
  • CI's test-js-packages job tests the packed package on Node 20 and 24.

The quickstart's "First run" section is the first runnable example. A reader on a supported Node version should be able to run that example as written. Failing that, the docs should tell them that await using needs Node 24 or later (or a transpiler), and that otherwise they must call close() themselves.

Actual behavior

On Node v22.23.3, node first.ts (the verbatim TypeScript block) exits with status 1 and prints the SyntaxError shown below. The same command on Node v23.11.1 fails the same way, and on Node v24.21.0 it prints 11. Node 20 has no built-in type stripping and rejects .ts files with ERR_UNKNOWN_FILE_EXTENSION.

On Node v20.20.2, v22.23.3 and v23.11.1, node first.mjs exits with status 1 before any Monty code runs:

file:///w/first.mjs:2
await using pool = await Monty.create()
            ^^^^

SyntaxError: Unexpected identifier 'pool'

Node v22.23.2 on macOS arm64 fails with the same SyntaxError. On Node v24.21.0, node first.mjs prints 11 and exits 0.

node first2.mjs prints 11 and exits 0 on Node v20.20.2, v22.23.3 and v24.21.0. The failure comes from the example's syntax, not from the package.

The V8 option --js-explicit-resource-management doesn't help. On Node 22.23.3, first.mjs still fails with the same SyntaxError when the option is passed. On Node 20.20.2, Node rejects the option with bad option.

The quickstart never states a Node version requirement, and neither do the other pages under docs/ or crates/monty-js/README.md (the npm package page). The published quickstart page matches the source: it contains 19 await using lines and no Node version. The only related sentence comes right after the "First run" example: "await using closes the session and the pool at the end of scope. Without it, call session.close() and pool.close() yourself." It doesn't say that this alternative is required on Node 20 through 23.

Evidence

  • Expected source: crates/monty-js/package.json ("engines": { "node": ">= 20" }); in .github/workflows/ci.yml, the test-js job's node matrix ('20', '22') and the test-js-packages job's matrix (node: ['20', '24']); and the "First run" section of docs/quickstart/javascript.md (lines 18–39).
  • Failure source: the local runs described under Actual behavior.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

await using also appears in docs/index.md, crates/monty-js/README.md, docs/host-functions.md, docs/snapshots.md, docs/host-objects.md, docs/security.md, docs/filesystem.md, docs/type-checking.md and docs/resource-limits.md, and none of them states a Node version. A grep of docs/, README.md and crates/monty-js/README.md found no Node version requirement and no mention of explicit resource management.

Restoration check

Failing: on Node 22, the quickstart's "First run" example, run with node first.ts or as the plain JavaScript in first.mjs, gives SyntaxError: Unexpected identifier 'pool', and nothing in the docs explains why.

Passing requires one of the following:

  • The docs state the Node version that await using needs where readers meet the examples (at least the quickstart and the npm README), and give a form that runs on the older supported versions.
  • The examples run as written on every Node version the package declares.

Control: the explicit-close() program above still prints 11 on Node 20 and 22. This shows the package's runtime support for those versions is unchanged.

Additional context

TypeScript users who transpile are not affected at runtime. tsc 5.9.3 with target: "ES2022" compiles the verbatim "First run" block into helper code, and the output prints 11 on Node 20.20.2, 22.23.3 and 24.21.0, because those Node versions define Symbol.asyncDispose. With the default lib for that target, however, tsc reports TS2318: Cannot find global type 'AsyncDisposable' and 'Disposable'. Adding "ESNext.Disposable" to lib makes the block type-check cleanly.

Related: https://github.com/pydantic/monty/issues/663 tracks that the TypeScript blocks in docs/ are not executed or type-checked in CI. Running them only on Node 24 would not catch this version gap.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Node 20 and 22 are both versions the package declares and CI tests. On Node 22.23.3 the verbatim first runnable JavaScript example fails at parse time. Its plain-JavaScript form fails the same way on Node 20.20.2 and 22.23.3. The failure is bounded: it is visible immediately, it affects only readers on Node versions older than 24, and the package works on those versions with explicit close() calls.
  • Workaround status: available
  • Workaround basis: Calling session.close() and pool.close() explicitly printed 11 on Node 20.20.2 and 22.23.3, and the quickstart mentions that alternative in prose. Transpiling with tsc also worked on both versions, and on Node 24 the examples run as written.

Activity

0

No activity yet.