npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

depvault

v0.1.2

Published

DepVault — a global content-addressable dependency vault for Node. Eliminates per-project node_modules and resolves modules at runtime via Node's loader hooks.

Readme

DepVault

A global vault for Node dependencies. Projects keep nothing locally — packages live once in a content-addressable store and resolve at runtime through Node's loader hooks.

project/
├── package.json
├── depvault-lock.json    ← exact-version + integrity manifest
├── app.js
└── (no node_modules anywhere)

~/.depvault/store/        ← shared across every project on the machine
└── cas/sha512/<2>/<rest>/{package.json, lib/, ...}

When app.js does require('react'), DepVault intercepts the call, walks depvault-lock.json to find the right version for this importer, and points Node directly at ~/.depvault/store/cas/sha512/.../react/. No copying. No hardlinks per project. No nested node_modules trees. The vault is the single source of truth.


Table of contents


Why

| Concern | Traditional node_modules | pnpm | DepVault | |---|---|---|---| | Disk per project | full copy of every dep | hardlinks to global store | zero per-project files | | Cold install | minutes | tens of seconds | seconds (no per-project copy step) | | Cross-project dedupe | none | hardlinks (per-file) | direct shared paths | | Editor "go to definition" | works | works | works (resolves into the vault path) | | Native modules | work | work | flagged in MVP, rebuild flow on roadmap | | Lockfile portability | yes (package-lock.json) | yes (pnpm-lock.yaml) | yes (depvault-lock.json) | | Lifecycle scripts | run automatically | run automatically | disabled by default (security stance) |

The headline number from scripts/bench.mjs on a lodash + chalk project:

project disk:  3.3 KiB (DepVault)  vs  1.4 MiB (npm)   — 99.8% smaller per project
warm install:    90 ms (DepVault)  vs   489 ms (npm)   — 5× faster

For a developer machine with 100 Node projects sharing common deps, this is the difference between a 50 GB and a 5 GB working set.


Install

DepVault ships as a regular npm package. Pick one path:

From the npm registry (recommended)

npm install -g depvault

After install you'll have two commands on your PATH: depvault (canonical) and dv (shorthand). They're aliases for the same binary.

depvault --version
dv --version

From a tarball (no clone, no build)

# from a local tarball produced by `npm pack`
npm install -g ./depvault-0.1.0.tgz

# or from any URL hosting it
npm install -g https://your-host.example.com/depvault-0.1.0.tgz

From a git URL (no registry, no tarball)

npm install -g git+https://github.com/estd20xx/ndve.git
# or pin to a tag
npm install -g git+https://github.com/estd20xx/ndve.git#v0.1.0

npm runs the package's prepare script after a git install, so the TypeScript build happens automatically.

Update / uninstall

npm install -g depvault@latest
npm uninstall -g depvault

Quick start

Once depvault is on your PATH:

cd ~/my-app                  # has package.json, no node_modules
depvault install             # populates the global vault, writes depvault-lock.json
depvault run app.js          # runs without node_modules

Or use the dv shorthand:

dv install
dv run app.js

Try the bundled examples:

# minimal CJS + ESM demo
cd examples/hello-app
dv install
dv run app.js                # CJS
dv run app.mjs               # ESM
# real HTTP server with 72 transitive deps
cd examples/express-app
dv install
dv run server.js

Or build a 30-second demo from scratch:

mkdir my-test && cd my-test
echo '{"name":"t","version":"1.0.0","dependencies":{"chalk":"^4"}}' > package.json
echo 'console.log(require("chalk").green("hello from DepVault"));' > app.js
dv install
dv run app.js
ls                           # only package.json, app.js, depvault-lock.json — no node_modules

Architecture

Component diagram

                                ┌──────────────────────────────────┐
                                │         npm registry             │
                                │  (packuments + .tgz tarballs)    │
                                └──────────────┬───────────────────┘
                                               │  HTTPS
        ┌──────────────────────────────────────┴───────────────────────────────────┐
        │                              depvault install                            │
        │                                                                          │
        │   package.json                                                           │
        │       │                                                                  │
        │       ▼                                                                  │
        │  ┌─────────────────┐    packument    ┌────────────────────────┐         │
        │  │ RegistryClient  │ ───────────────►│ resolveDependencyGraph │         │
        │  │ (cache, fetch)  │                 │  • semver max-satisfy  │         │
        │  └─────────────────┘                 │  • version reuse       │         │
        │           ▲                          │  • optional/peer aware │         │
        │           │ tarballs                 │  • os/cpu gating       │         │
        │           │                          └───────────┬────────────┘         │
        │           │                                      │ ResolutionResult     │
        │           │                                      ▼                      │
        │           │                          ┌────────────────────────┐         │
        │           └─────────────────────────►│   StoreManager         │         │
        │                                      │ • verifyTarball (SRI)  │         │
        │                                      │ • atomic extract       │         │
        │                                      │ • idempotent add       │         │
        │                                      │ • gc / disk usage      │         │
        │                                      └───────────┬────────────┘         │
        │                                                  │                      │
        │                                                  ▼                      │
        │                                  ┌────────────────────────────────┐    │
        │                                  │  ~/.depvault/store/            │    │
        │                                  │   cas/<algo>/<2>/<rest>/...    │◄───┼─── canonical, read-only
        │                                  │   packages/<name>/<version>/   │    │
        │                                  │   meta/   tmp/                 │    │
        │                                  └────────────────────────────────┘    │
        │                                                                          │
        │                                  ┌────────────────────────────────┐    │
        │                                  │ project/depvault-lock.json     │◄───┼─── lockfileFromResolution
        │                                  │ • full graph (all transitive)  │    │
        │                                  │ • exact versions per importer  │    │
        │                                  │ • SRI integrity per package    │    │
        │                                  │ • CAS keys                     │    │
        │                                  └────────────────────────────────┘    │
        └──────────────────────────────────────────────────────────────────────────┘

        ┌──────────────────────────────────────────────────────────────────────────┐
        │                            depvault run app.js                          │
        │                                                                          │
        │   spawns: node --require cjs-hook.cjs --import bootstrap.js app.js       │
        │           with env DEPVAULT_PROJECT_DIR=<project>                        │
        │                                                                          │
        │       ┌────────────────────────┐         ┌────────────────────────┐    │
        │       │  CJS resolution path   │         │  ESM resolution path   │    │
        │       │                        │         │                        │    │
        │       │  Module._resolve…      │         │  module.register()     │    │
        │       │   (patched)            │         │  loader.resolve/load   │    │
        │       └───────────┬────────────┘         └───────────┬────────────┘    │
        │                   │                                  │                  │
        │                   └────────────┬─────────────────────┘                  │
        │                                ▼                                        │
        │                  ┌───────────────────────────────┐                      │
        │                  │   resolver-core.cjs (shared)  │                      │
        │                  │                               │                      │
        │                  │   1. parse depvault-lock.json │                      │
        │                  │   2. build CAS-path → key map │                      │
        │                  │   3. on each request:         │                      │
        │                  │       a. id importer          │                      │
        │                  │       b. lookup dep version   │                      │
        │                  │       c. resolve subpath      │                      │
        │                  │          (exports/main/probe) │                      │
        │                  │       d. memoize (LRU)        │                      │
        │                  └───────────────────────────────┘                      │
        │                                │                                        │
        │                                ▼                                        │
        │                       absolute vault path                               │
        │                                                                          │
        │                       (Node loads it as if it were any other file)      │
        └──────────────────────────────────────────────────────────────────────────┘

Install pipeline (sequence)

user           CLI            Installer       RegistryClient    DepGraph       StoreManager   FS (vault)
 │              │                 │                 │              │                │           │
 │ dv install   │                 │                 │              │                │           │
 ├─────────────►│                 │                 │              │                │           │
 │              │  install({...}) │                 │              │                │           │
 │              ├────────────────►│                 │              │                │           │
 │              │                 │  read pkg.json  │              │                │           │
 │              │                 ├──────────────────────────────────────────────────────────────┤
 │              │                 │  fast-path? if lockfile up-to-date and CAS complete → return │
 │              │                 │                 │              │                │           │
 │              │                 │  resolveGraph(directDeps)      │                │           │
 │              │                 ├────────────────►│              │                │           │
 │              │                 │                 │ fetchPackument(name) [cached] │           │
 │              │                 │                 ├─────────────►│                │           │
 │              │                 │                 │              │ pickVersion    │           │
 │              │                 │                 │              │ (semver max)   │           │
 │              │                 │                 │              │                │           │
 │              │                 │                 │   (recurse for each transitive)│          │
 │              │                 │                 │              │                │           │
 │              │                 │   ResolutionResult{packages, rootDeps}          │           │
 │              │                 │◄──────────────────────────────────────────────────          │
 │              │                 │                 │              │                │           │
 │              │                 │ for each missing pkg in parallel (concurrency=8):           │
 │              │                 │   fetchTarball  │              │                │           │
 │              │                 ├────────────────►│              │                │           │
 │              │                 │   addFromTarball                                │           │
 │              │                 ├────────────────────────────────────────────────►│           │
 │              │                 │              verify SRI ──► extract to tmp ──► atomic rename│
 │              │                 │                 │              │                ├──────────►│
 │              │                 │                 │              │                │           │
 │              │                 │ writeLockfile(depvault-lock.json)               │           │
 │              │                 ├──────────────────────────────────────────────────────────────►
 │              │  InstallReport  │                 │              │                │           │
 │              │◄────────────────│                 │              │                │           │
 │   summary    │                 │                 │              │                │           │
 │◄─────────────│                 │                 │              │                │           │

Runtime resolution (sequence)

Node process              cjs-hook.cjs         resolver-core.cjs        FS (vault)
     │                         │                       │                    │
     │ require('react')        │                       │                    │
     ├────────────────────────►│                       │                    │
     │                         │ Module._resolveFilename('react', parent)   │
     │                         │                                            │
     │              early-out for builtins / relative paths                 │
     │                         │                       │                    │
     │                         │ core.resolve('react', parent.filename, 'require')
     │                         ├──────────────────────►│                    │
     │                         │                       │ identifyImporter   │
     │                         │                       │  (prefix-match     │
     │                         │                       │   parent against   │
     │                         │                       │   sortedCasPaths)  │
     │                         │                       │                    │
     │                         │                       │ resolveDepVersion  │
     │                         │                       │  (importer's deps  │
     │                         │                       │   in lockfile)     │
     │                         │                       │                    │
     │                         │                       │ resolveSubpath     │
     │                         │                       │  (exports / main / │
     │                         │                       │   file probe)      │
     │                         │                       ├───────────────────►│
     │                         │                       │      stat()        │
     │                         │                       │◄───────────────────┤
     │                         │ absolute path or null │                    │
     │                         │◄──────────────────────│                    │
     │ original Module._load with the resolved path                         │
     │◄────────────────────────│                       │                    │
     │                         │                       │                    │
     │  (subsequent calls hit the LRU cache; ~50ns each)                    │

Storage layout

~/.depvault/                               ← override with $DEPVAULT_HOME
└── store/
    ├── cas/
    │   └── sha512/                        ← algorithm folder
    │       └── ab/                        ← 2-char fan-out (avoid 100k entries in one dir)
    │           └── cdef0123…/             ← rest of the SRI digest (hex)
    │               ├── package.json       ← unpacked tarball contents
    │               ├── lib/
    │               └── …
    ├── packages/                          ← human-readable index
    │   ├── react/
    │   │   └── 18.2.0/
    │   │       └── meta.json              ← { name, version, integrity, casKey, installedAt }
    │   └── @scope/
    │       └── pkg/
    │           └── 1.0.0/meta.json
    ├── meta/                              ← reserved (registry-level metadata cache)
    └── tmp/                               ← staging dirs for atomic extraction

Why content-addressed? The CAS key is derived from the tarball's SRI hash. Two byte-identical tarballs collide and dedupe automatically — this protects against the rare case where a registry republishes a version, and naturally dedupes across registry mirrors.

Lockfile shape

{
  "lockfileVersion": 1,
  "name": "my-app",
  "version": "1.0.0",
  "dependencies": {                       // direct deps only
    "react": "18.2.0"
  },
  "packages": {                           // FULL graph, every transitive node
    "[email protected]": {
      "integrity": "sha512-FCSh4nwlAyt0jA2g…",
      "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
      "casKey": "sha512/14/2bfa…",
      "dependencies": { "loose-envify": "1.4.0" },
      "type": "commonjs"
    },
    "[email protected]": { … }
  }
}

The richness of the packages map is the trick that makes runtime resolution O(1) — every nested dep is pre-resolved at install time, so the runtime resolver never re-runs semver math.


CLI reference

depvault install [options]              # alias: i
depvault add <pkg...> [options]
depvault remove <pkg...> [options]      # alias: rm
depvault run <entry|script> [-- ...args]
depvault clean [options]
depvault doctor

The shorthand dv works everywhere depvault does.

depvault install

Resolves dependencies, populates the global vault, writes depvault-lock.json.

-C, --cwd <dir>           project directory (default: cwd)
-c, --concurrency <n>     tarball download concurrency (default: 8)
    --no-fast             always re-resolve from registry, even if lockfile is current

depvault add <pkg...>

Adds packages to package.json and runs install. Accepts:

dv add react                        # → "^<latest>"
dv add react@^18.2.0                # explicit range
dv add @scope/foo@latest            # scoped
dv add lodash [email protected]           # multiple

depvault remove <pkg...>

Removes from package.json and re-installs. The vault itself is untouched — use depvault clean to reclaim space.

depvault run <target> [-- ...args]

Spawns Node with DepVault hooks active. <target> is either a JS file path or a key in package.json#scripts (only node … style scripts).

dv run app.js
dv run app.mjs
dv run start                        # runs scripts.start if it's a node script
dv run app.js -- --port 3000        # args after `--` go to your program

depvault clean

Garbage-collects the vault. A CAS entry is live iff some lockfile references it.

-C, --cwd <dir>           project whose lockfile counts as live
    --scan <dirs...>      directories to recursively scan for depvault-lock.json (depth 4)
    --dry-run             show what would be removed

Without --scan, only the current project's lockfile is in the live set. On a developer machine, run with --scan ~/code to protect every project.

depvault doctor

Diagnostic. Reports vault size, package count, lockfile/vault consistency, and packages with install scripts (likely native modules).

exit 0 → vault is consistent with lockfile
exit 1 → missing CAS entries; run `depvault install` to repair

Configuration

| env var | default | purpose | |---|---|---| | DEPVAULT_HOME | ~/.depvault | root of the global vault. Override for CI / shared caches. | | DEPVAULT_REGISTRY | https://registry.npmjs.org/ | registry URL. Mirrors and private registries supported (no auth in MVP). | | DEPVAULT_DEBUG | unset | set to 1 for verbose logging. | | DEPVAULT_PROJECT_DIR | (auto) | set by depvault run for the spawned child; rarely set manually. |

There is no .depvaultrc file in the MVP. All config is via env vars or CLI flags.


Framework integration

DepVault works in two layers, and tools fall into one of two camps:

Layer 1 — Node's resolver. When something does require('foo') or import 'foo' at Node runtime, our hooks intercept. This covers all plain Node apps, server code, tsx/tsc, and the Node-side of any build tool.

Layer 2 — bundlers' own resolvers. Vite, Next.js, webpack, esbuild, Rollup all walk the filesystem with their own resolver code. They look for node_modules/<pkg> directly. They don't go through Node's require(). So they need something to find packages on disk.

DepVault handles this in two ways, summarized here and detailed below:

| status | mechanism | |---|---| | ✅ works today | Layer 1 only — pure Node apps, server code, scripts | | 🛠 use materialize fallback | Layer 2 tools — bundlers, type checkers, anything that walks disk |

The --materialize install mode (planned, see Roadmap) generates a thin node_modules/ tree of symlinks/junctions into the vault. It costs ~zero disk and unblocks every tool that expects a traditional layout. Until it ships, the integrations below describe manual workarounds.

Plain Node services

Works today, no extra config.

dv install
dv run server.js

Express, Koa, Fastify, NestJS (when run as node main.js rather than through nest's CLI), and anything else that does runtime require/ import resolves cleanly through DepVault's hooks. The examples/express-app demo proves this with a 72-package transitive tree.

Vite

🛠 Two paths:

a) Vite as a build tool (vite build, vite preview):

Vite's bundler uses its own filesystem-based resolver and assumes node_modules/ exists. Until materialize ships, the workable approach is hybrid:

# Use npm/pnpm just to populate node_modules for build-time tools
npm install --no-audit --ignore-scripts
# Use DepVault for runtime
dv install
# Build with Vite (reads node_modules)
dv run vite build
# Preview / serve with DepVault-only resolution
dv run dist/server.js

b) Vite dev server with DepVault plugin (planned):

A vite-plugin-depvault is on the roadmap. It registers a resolveId hook that consults depvault-lock.json directly, so dev-server imports go through the global vault with no node_modules. Rough sketch of the plugin contract:

// vite-plugin-depvault (planned)
import depvault from 'vite-plugin-depvault';
export default { plugins: [depvault({ projectDir: process.cwd() })] };

Next.js

🛠 Same shape as Vite. Next bundles via webpack/Turbopack, which need node_modules. Until materialize ships:

# Install for both worlds
npm install --no-audit --ignore-scripts   # for build-time
dv install                                # for runtime

# Build
dv run node_modules/next/dist/bin/next build

# Run the production server through DepVault (zero node_modules in the runtime path)
dv run .next/standalone/server.js

The standalone output (output: 'standalone' in next.config.js) narrows what the runtime needs, which makes it a great fit for DepVault once materialize lands — you can ship the .next/standalone directory plus a single depvault-lock.json and zero node_modules.

Webpack

🛠 Same constraint as Vite — webpack's enhanced-resolve walks node_modules. The roadmap includes a ResolverPlugin for webpack 5 that forwards bare specifiers to DepVault's resolver-core. Until then, use a real node_modules for the build, then dv run the bundle.

TypeScript

🛠 Type checking with tsc: Reads from disk. Needs node_modules or types fed via paths in tsconfig.json.

Workarounds:

  1. Hybrid (recommended today): install with npm install --ignore-scripts for tooling, and dv install for runtime. The two lockfiles stay in sync as long as you only manage deps via dv add/remove (which write package.json first).

  2. tsc --noResolve + manual paths: for advanced setups, point tsconfig.json#compilerOptions.paths at the CAS dirs from depvault-lock.json. Works for small projects, gets unwieldy fast.

Running compiled code (node dist/index.js): ✅ works today via dv run dist/index.js.

ts-node / tsx: ✅ works as long as you launch them through DepVault:

dv run tsx src/index.ts
# or
dv run ts-node src/index.ts

tsx itself uses Node's loader API; both DepVault's loader and tsx's loader register cleanly with module.register.

esbuild / Rollup / Rspack

🛠 Same pattern as Vite/Webpack — bundler walks disk. Materialize fallback or hybrid install. DepVault-aware plugins are roadmap items.

For esbuild specifically, the resolve callback API makes the plugin straightforward; expect this to be the first bundler integration to land.

Test runners

| runner | status | notes | |---|---|---| | node --test | ✅ | run with dv run and Node's built-in test runner. | | Jest | 🛠 | uses its own resolver (jest-resolve). Plugin needed; for now use the hybrid install approach. | | Vitest | 🛠 | inherits Vite's resolver. Same path as Vite. | | Mocha | ✅ | dv run mocha test/**/*.test.js — Mocha uses Node's require. | | Playwright | ✅ | runtime resolution; works directly. |


Performance

See PERFORMANCE.md for full benchmark methodology and discussion. Headline:

| metric | DepVault | npm | |---|---|---| | cold install | 2113 ms | 3860 ms | | warm install | 90 ms | 489 ms | | cold start | 187 ms | 54 ms (DepVault pays loader overhead) | | project disk | 3.3 KiB | 1.4 MiB |

The cold-start gap (~133 ms) is the cost of the CJS hook plus the ESM loader registration. It's amortized to zero for long-running processes (servers, dev servers) and noticeable only for very short scripts that do many invocations (e.g. node per test file).


Security

DepVault deliberately makes a stricter security stance than npm:

  • Integrity-first. Every tarball is verified against its SRI hash (crypto.timingSafeEqual) before extraction. Mismatches abort the install with an IntegrityError.
  • No code execution at install. preinstall, install, postinstall scripts are not run. Packages declaring them are flagged by dv doctor so you can audit and rebuild manually. This trades some convenience (typed packages with prepare scripts) for closing npm's largest supply-chain vector.
  • Read-only vault. Runtime hooks never write into the CAS. The vault is owned by dv install alone.
  • Sandbox-friendly. The run command spawns Node with the user's permissions. For hardened runs, layer Node's permission model:
    node --permission --allow-fs-read=$DEPVAULT_HOME --allow-fs-read=. \
         --require=cjs-hook.cjs --import=bootstrap.js app.js
  • No transitive auth. No .npmrc token support in MVP. Public registries only (private registries via DEPVAULT_REGISTRY work if they don't require auth).

Limitations

  • Lifecycle scripts not run (security stance — see above).
  • Native modules: detection only. dv doctor flags packages with install scripts. Per-platform CAS variants and a dv rebuild command are roadmap items. Pure-JS packages work fully.
  • Layer-2 tools need fallback. Bundlers (Vite, Next, webpack) need a node_modules until --materialize or per-tool plugins land. See Framework integration.
  • No imports field, no package self-reference. The resolver implements the exports field — including conditional + wildcard subpaths — but not the full Node spec surface.
  • No workspaces / monorepo wiring in MVP.
  • No .npmrc auth / private registry tokens.
  • Designed for Node ≥ 20.6.0 (when module.register became stable).

FAQ

Q: How is this different from pnpm? A: pnpm has a global store, but each project still gets a node_modules directory full of hardlinks. DepVault eliminates the project directory entirely — Node loads modules directly out of the vault. The trade-off is that DepVault needs Node's loader hooks at runtime (small cold-start overhead), while pnpm projects are indistinguishable from npm projects from Node's perspective.

Q: How is this different from Yarn PnP? A: PnP is closer in spirit — it also bypasses node_modules. The differences are: DepVault uses the modern module.register API instead of patching internals; the vault layout is content-addressed (so byte-identical tarballs dedupe across versions and registries); and DepVault deliberately disables lifecycle scripts.

Q: What happens if I delete ~/.depvault? A: Every project breaks until you re-run dv install somewhere. The lockfile in each project remains valid — it can repopulate the vault.

Q: Can two projects use different versions of the same package? A: Yes. Each project's depvault-lock.json records the versions it uses. The CAS key is per-version, so all versions coexist in the vault.

Q: Can a single project have two versions of the same package (typical npm "nested" case)? A: Yes. The lockfile stores the full graph, so package A can see lodash@3 while package B sees lodash@4 — both are in the vault, and the resolver routes each requesting package to the right version.

Q: What about ESM-only packages? A: Fully supported. The ESM loader inspects the package's type field (persisted in the lockfile) to pick the right format. CJS-only and ESM-only and dual-format packages all work.

Q: Does this work with Bun / Deno? A: No. DepVault plugs into Node's loader hooks specifically. Bun has its own bundled resolver; Deno has a different model entirely.

Q: Why is the package called depvault but the repo / module is in ndve/? A: Historical. The project's working name was NDVE (Node Dependency Virtualization Engine) during development. We renamed for the public release. The repository directory may be renamed in a future commit.


Roadmap

Ordered by user impact:

  1. dv install --materialize — generate a node_modules/ directory of symlinks (or junctions on Windows) into the vault. Unblocks every Layer-2 tool (Vite, Next.js, webpack, tsc, jest) without changing their config. Disk cost remains near zero.
  2. Native module rebuild flowdv rebuild runs node-gyp in a per-platform CAS variant tagged with <platform>-<arch>-<abi>. Resolver picks the matching variant.
  3. Bundler pluginsvite-plugin-depvault, webpack-plugin-depvault, esbuild plugin. Lets bundlers resolve through the vault without a materialized node_modules.
  4. Workspaces / monorepo support — root + child lockfiles, shared resolution across packages.
  5. Remote shared cache — pull from S3/GCS bucket; dv install fetches CAS entries from a team mirror before falling back to the public registry. Protocol already maps onto CAS layout.
  6. dv audit — CVE checks via the npm advisories endpoint.
  7. FUSE / projfs virtual filesystem — most ambitious; gives the illusion of a real node_modules without the disk cost. Bypasses the need for plugin-per-tool but requires per-OS native code.
  8. V8 startup snapshots — bake the resolver-core init into a snapshot to remove cold-start overhead.

Publishing (for maintainers)

The package is configured so a clean release is one command. From a fresh checkout:

npm run release:patch    # 0.1.0 → 0.1.1, builds, publishes, pushes tags
npm run release:minor    # 0.1.0 → 0.2.0
npm run release:major    # 0.1.0 → 1.0.0

Each release:* script does:

  1. npm version <bump> — bumps package.json, makes a git commit, tags it.
  2. npm publish — runs prepare → clean + build, then uploads.
  3. git push --follow-tags — pushes the version commit and tag.

To produce a tarball without publishing — useful for sharing internally, hosting on S3, or installing in air-gapped environments:

npm pack                          # → depvault-<version>.tgz

Smoke test the tarball before releasing:

npm install -g ./depvault-<version>.tgz
dv --version
cd /tmp && mkdir dv-smoke && cd dv-smoke
echo '{"dependencies":{"chalk":"^4"}}' > package.json
echo 'console.log(require("chalk").green("ok"));' > app.js
dv install && dv run app.js

To unpublish a bad release, follow npm's standard procedure — npm unpublish is restricted to the first 72 hours and only removes versions; users are better served by publishing a 0.1.1 with a fix.


Contributing

The codebase is laid out to make contributions easy to scope:

src/store/        ← isolated CAS layer; testable without network
src/registry/     ← all HTTP; mock the fetch for tests
src/installer/    ← orchestration only; pure logic in dep-graph.ts
src/resolver/     ← the crown jewel; resolver-core.cjs is plain JS for clarity
src/cli/          ← thin layer over the above

Tests live next to the code (*.test.ts) and run via node --test dist/**/*.test.js after npm run build. The bench is at scripts/bench.mjs — please include before/after numbers when changing anything in the install or resolver hot paths.

When extending the resolver, keep resolver-core.cjs in plain CommonJS — it's deliberately not TypeScript so both the CJS hook and the ESM loader can require it without a compile step. The trade-off (no static types in the hottest code path) is intentional; treat the public functions as a stable contract and document with JSDoc.


License

MIT.