7. npm packages and Node builtins

Use packages the way you always do: install them, import them.

npm install ms @types/ms
// ucm: namespace time
import ms from "ms";

/** A duration as words: 90000 → "2m". */
export const short = (millis: number): string => ms(millis);

short(90_000);
$ ucm check
scratch.ts → time
  + new       time.short : (millis: number) => string
installing npm__ms__2.1.3

    7 │ short(90_000)
      ⧩ '2m'

7.1 Versions are pinned

When you add a definition that imports a package, ucm records the exact version it was checked against, and that's part of the definition's hash:

$ ucm deps time.short
npm:ms@2.1.3#default

So a definition always runs with the version it was written for. Upgrading a package doesn't silently change saved code: after npm install ms@3, the definitions you update (or write anew) use 3; the rest keep 2.1.3, and the two versions live side by side. ucm installs each pinned version into its own cache (installing npm__ms__2.1.3), separately from your node_modules.

Only the package you import is pinned, not the packages it depends on.

7.2 Node builtins

node:fs, node:path and the rest import as usual and aren't versioned. Using one marks code as server-only in a web app.

7.3 Types

If a package has no types of its own, install its @types package, as you would anyway: ucm typechecks everything you add. @types/node comes with ucm, so Node's builtins are typed out of the box.

7.4 Packages in web apps

Browser code can use packages too; Vite bundles them. A package that only works on Node (no browser build, and it imports Node builtins) makes the code using it run on the server.

7.5 Running things

ucm run <name> [args...] runs a definition: functions are called with the arguments (as strings, as on any command line), the result is awaited and printed.

built from 5890e54f