Skip to content

@ruvector/[email protected] is unloadable: pkg/rvf_wasm.js mixes ESM syntax (import.meta, export default) into CJS-classified file #415

@rtenggario

Description

@rtenggario

Summary

@ruvector/[email protected] (published to npm) is unloadable in any Node.js consumer. Every entry point — CJS require(), ESM await import(), and the import condition pointing to pkg/rvf_wasm.mjs — transitively loads pkg/rvf_wasm.js, which contains both import.meta.url and a top-level export default while the package has no "type": "module" set. Node parses it as CJS and throws a SyntaxError before any runtime guard can execute.

This breaks every downstream agentdb / @claude-flow/memory / ruflo operation that touches the RVF backend (memory init, hooks pretrain, status, etc.). Encountered via [email protected]@claude-flow/memory[email protected]@ruvector/[email protected].

Reproduction

npm install -g ruflo@latest          # 3.6.12, ships @ruvector/[email protected]
mkdir /tmp/repro && cd /tmp/repro
ruflo init --full
ruflo memory init

Result:

/.../@ruvector/rvf-wasm/pkg/rvf_wasm.js:47
      var thisDir = path.default.dirname(url.default.fileURLToPath(import.meta.url));
                                                                          ^^^^
SyntaxError: Cannot use 'import.meta' outside a module
    at wrapSafe (node:internal/modules/cjs/loader:1378:20)
    at Module._compile (node:internal/modules/cjs/loader:1428:41)
    ...
    at async detectBackends (.../agentdb/dist/src/backends/factory.js:86:13)

ruflo status, ruflo hooks pretrain, and any other call path through agentdb's detectBackends() fails identically.

Root cause analysis

Three independent issues compound in the published artifact:

1. pkg/rvf_wasm.js mixes ESM-only syntax into a CJS-classified file

package.json has no "type" field, so Node treats .js files as CJS by default. But pkg/rvf_wasm.js contains:

  • Line 47: import.meta.url (inside a _isNode else branch)
  • Line 53: import.meta.url (inside the browser branch)
  • Line 72: top-level export default init;

These are all ESM-only. The author guards line 47 behind typeof __dirname !== 'undefined' — but that's runtime, not parse time. The file fails to parse before any guard runs. Same for line 53 (only relevant in browsers, but parser doesn't know that). Line 72 is unconditional ESM syntax in a CJS file.

2. pkg/rvf_wasm.mjs re-imports the broken .js

// pkg/rvf_wasm.mjs
import init from './rvf_wasm.js';
export default init;

So the import condition (which routes ESM consumers to .mjs) doesn't actually escape the bug — it just reaches the same broken file via a re-export, and Node still loads .js through the CJS translator because the package isn't marked as a module.

3. Two distinct kinds of consumers all hit the same crash

In ruflo's installed tree:

  • await import('@ruvector/rvf-wasm') from ESM (agentdb): picks the import condition → .mjs → re-imports .js → CJS parse → crash.
  • __importStar(require('@ruvector/rvf-wasm')) from CJS (@ruvector/rvf/dist/backend.js): picks the require condition → .js directly → CJS parse → crash.

There is no working code path.

Suggested fix (recommended)

Add "type": "module" to package.json and route require through a thin CJS shim, or drop CJS support entirely.

Option A — ESM-only (simplest, matches the file's actual syntax):

   "name": "@ruvector/rvf-wasm",
   "version": "0.1.7",
+  "type": "module",
   "main": "pkg/rvf_wasm.js",
   "exports": {
     ".": {
       "types": "./pkg/rvf_wasm.d.ts",
-      "import": "./pkg/rvf_wasm.mjs",
-      "require": "./pkg/rvf_wasm.js",
-      "default": "./pkg/rvf_wasm.js"
+      "default": "./pkg/rvf_wasm.mjs"
     }
   },

Tradeoff: CJS callers (currently only @ruvector/rvf/dist/backend.js in your stack) would get ERR_REQUIRE_ESM. They already crash today, so this is strictly better — and @ruvector/rvf should switch to dynamic import() anyway.

Option B — true dual package: keep .js as a real CJS file (replace import.meta.url with __dirname-only logic, replace export default with module.exports, replace await import('node:fs/promises') with require('node:fs/promises')), and have .mjs re-export from a separate ESM source that uses import.meta.url. More work but doesn't break require() consumers.

Option C — minimal patch keeping everything else the same: add "type": "module" and let the existing .js parse as ESM. The if (typeof module !== 'undefined') module.exports = init; line on row 71 silently no-ops in ESM, the import.meta.url and export default then parse correctly. CJS require() still breaks.

Pre-publish CI suggestion: add node -e "import('@ruvector/rvf-wasm').then(m => m.default())" and node -e "require('@ruvector/rvf-wasm')()" smoke tests on the packed tarball before publishing.

Workaround for end users (verified)

Single-line patch on the installed copy:

NODE_ROOT=$(npm root -g)
sed -i 's/"main": "pkg\/rvf_wasm.js",/"type": "module",\n  "main": "pkg\/rvf_wasm.js",/' \
  $NODE_ROOT/ruflo/node_modules/@ruvector/rvf-wasm/package.json

After patch, ruflo memory init --force and ruflo hooks pretrain complete successfully (727 files analyzed, 69 patterns extracted in my repro on [email protected]).

Environment

  • ruflo: 3.6.12
  • @ruvector/rvf-wasm: 0.1.6
  • agentdb: 3.0.0-alpha.11
  • Node: v20.18.0
  • npm: 10.8.2
  • OS: Linux 6.12.74 (Debian 13)

Impact

This blocks:

  • ruflo memory init (any backend that probes RVF)
  • ruflo hooks pretrain (fails at EMBED/HYPERBOLIC stage)
  • ruflo status (fails before output)
  • Any consumer of [email protected] doing detectBackends()
  • Any consumer of @ruvector/rvf that probes the WASM backend

Since the published tarball is unusable in any Node version, a prompt patch release (0.1.7) would be valuable.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions