Skip to Content

From Deno

Deno users will find 3va’s model familiar: everything is denied until you grant it with --allow-* flags. The module system is different, though. 3va follows Node (package.json + node_modules), not Deno’s URL and deno.json imports.

What doesn’t carry over

We ran a Deno 2.9 project unchanged:

main.ts
import { join } from "@std/path"; // from deno.json "imports" import ms from "ms"; // "ms": "npm:ms@^2.1.3"
$ 3va run main.ts Error: Cannot find module '@std/path'
  • deno.json / deno.jsonc (imports, tasks, compilerOptions) are not read.
  • npm: and jsr: specifiers in source (import ms from "npm:ms@2.1.3") aren’t supported: Cannot find module 'npm:ms@2.1.3'.
  • https:// imports aren’t supported.
  • The Deno global doesn’t exist: Deno.serve, Deno.readTextFile, Deno.env, Deno.Command all need a Node equivalent.

Step by step

  1. Create a package.json with "type": "module".

  2. Install each dependency from its registry. 3va picks the registry from --allow-net, so run one command per registry:

    3va install @std/path --allow-net=jsr.io # JSR 3va install ms --allow-net=registry.npmjs.org # npm

    Don’t combine hosts in one command. With --allow-net=jsr.io,registry.npmjs.org, npm packages are looked up on JSR and fail (the install exits with code 1 and doesn’t add them to package.json).

  3. Rewrite imports to bare specifiers: "npm:ms@^2.1.3" → "ms", "jsr:@std/path@^1" → "@std/path".

  4. Replace Deno.* APIs:

    Deno3va (Node API)
    Deno.serve(handler)http.createServer()
    Deno.readTextFile(p)fs.promises.readFile(p, "utf-8")
    Deno.env.get("X")process.env.X
    Deno.argsprocess.argv.slice(2)
    new Deno.Command(cmd)child_process.spawn(cmd)
    deno task xpackage.json scripts
    deno test + @std/assert3va test + expect
  5. Carry your permission flags over. They map almost one to one:

    Deno3va
    --allow-read, --allow-write, --allow-net, --allow-envsame names
    --allow-run--allow-child-process
    --allow-ffi--allow-ffi
    --allow-sys, --allow-hrtimeno equivalent
    -A / --allow-allno single flag; grant each capability

    One difference: importing local files and packages needs --allow-read in 3va. Deno allows module loading without it.

Things to keep in mind

  • JSR packages are installed without their JSR dependencies. @std/path installs, but loading it fails with Cannot find module '@jsr/std__internal/os'. Until that’s fixed, prefer the npm version of a package when one exists.
  • Top-level await doesn’t work correctly yet: at module top level it gives you the Promise itself, and a file with no import/export fails to parse. This is very common in Deno code, so wrap such code in an async function main().
  • Web APIs are there: fetch, Request/Response, WebSocket, URLPattern, web streams, crypto.randomUUID(), structuredClone, import.meta.dirname and import.meta.main all exist.
  • Type-checking: deno check has no counterpart. 3va strips types without checking them, so run tsc --noEmit (on Node) in CI.