npm package: @jongodb/memory-server
Description: Node.js test runtime adapter for jongodb standalone launcher (MongoDB-compatible integration tests).
Author: jongodb contributors
Latest version: 0.1.6

# @jongodb/memory-server

Node test runtime adapter for [`jongodb`](https://github.com/midagedev/jongodb).

It starts a local `jongodb` launcher process, provides a MongoDB URI, and manages lifecycle for test runners.

## Why This Package Exists

`mongodb-memory-server` is useful, but it still depends on `mongod` bootstrap behavior.

`@jongodb/memory-server` focuses on a different tradeoff:
- faster integration-test startup loops
- deterministic local process lifecycle control
- simple test runner integration (Jest, Vitest, Nest Jest)

This package is for test runtime usage only. It is not a production MongoDB server.

## Value for Integration Test Suites

Observed benchmark summary on one NestJS integration suite shape:
- historical suite-scoped lifecycle: about `55.9%` faster than `mongodb-memory-server`
- current single-boot lifecycle: about `64.2%` faster than `mongodb-memory-server`

Actual deltas vary by test shape, dataset size, and CI machine characteristics.

## Scope

- integration-test runtime helper
- launcher process start/stop/detach lifecycle
- framework-agnostic runtime API
- convenience hooks for Jest, Vitest, and Nest Jest

## Requirements

- Node.js 20+

## CI Coverage

Node adapter compatibility smoke runs in CI across:
- OS: `ubuntu-latest`, `macos-latest`
- Node: `20`, `22`

Cold-start performance gate:
- workflow: `.github/workflows/node-cold-start-benchmark.yml`
- benchmark docs: [`docs/NODE_COLD_START_BENCHMARK.md`](../../docs/NODE_COLD_START_BENCHMARK.md)

## Versioning Policy

SemVer/compatibility policy for Node adapter releases:
- [`docs/NODE_SEMVER_POLICY.md`](../../docs/NODE_SEMVER_POLICY.md)

## Install

```bash
npm i -D @jongodb/memory-server
```

Canary channel:

```bash
npm i -D @jongodb/memory-server@canary
```

## Module Format

This package supports both:
- ESM (`import`)
- CommonJS (`require`)

Dual-export contract coverage:
- test suite runs both `scripts/esm-smoke.mjs` and `scripts/cjs-smoke.cjs` to guard import/require entrypoint parity.

## Quick Start

Recommended pattern: one runtime per test process.

```ts
import { beforeAll, afterAll } from "@jest/globals";
import { createJongodbEnvRuntime } from "@jongodb/memory-server/runtime";

const runtime = createJongodbEnvRuntime({
  databaseName: "test",
  databaseNameStrategy: "worker",
  envVarNames: ["MONGODB_URI", "DATABASE_URL"],
});

beforeAll(async () => {
  await runtime.setup();
});

afterAll(async () => {
  await runtime.teardown();
});
```

Runtime behavior:
- `setup()` starts launcher and writes URI to configured env key(s)
- `teardown()` stops launcher and restores previous env values
- overlapping runtimes that share env keys now restore without clobbering each other

CommonJS example:

```js
const { createJongodbEnvRuntime } = require("@jongodb/memory-server/runtime");
const runtime = createJongodbEnvRuntime({ databaseName: "test" });
```

## Migration from `mongodb-memory-server`

Full migration guide:
- [`docs/NODE_MIGRATION_FROM_MONGODB_MEMORY_SERVER.md`](../../docs/NODE_MIGRATION_FROM_MONGODB_MEMORY_SERVER.md)

Basic Jest migration:

Before:

```ts
import { MongoMemoryServer } from "mongodb-memory-server";

let mongod: MongoMemoryServer;

beforeAll(async () => {
  mongod = await MongoMemoryServer.create();
  process.env.MONGODB_URI = mongod.getUri();
});

afterAll(async () => {
  await mongod.stop();
});
```

After:

```ts
import { beforeAll, afterAll } from "@jest/globals";
import { createJongodbEnvRuntime } from "@jongodb/memory-server/runtime";

const runtime = createJongodbEnvRuntime({ databaseName: "test" });

beforeAll(async () => {
  await runtime.setup();
});

afterAll(async () => {
  await runtime.teardown();
});
```

Option mapping:
- `MongoMemoryServer.create({ instance: { port } })` -> `createJongodbEnvRuntime({ port })`
- `MongoMemoryServer.create({ instance: { dbName } })` -> `createJongodbEnvRuntime({ databaseName })`
- `mongod.getUri()` -> `runtime.uri` (after `setup`) or configured env var
- `mongod.stop()` -> `runtime.teardown()`

Parallel test tip:
- set `databaseNameStrategy: "worker"` to isolate worker data by database name

## Runner Integrations

Jest (per-file hooks):

```ts
import { beforeAll, afterAll } from "@jest/globals";
import { registerJongodbForJest } from "@jongodb/memory-server/jest";

registerJongodbForJest({ beforeAll, afterAll });
```

Jest (global setup/teardown):

`jest.global-setup.ts`

```ts
import { createJestGlobalSetup } from "@jongodb/memory-server/jest";

export default createJestGlobalSetup();
```

`jest.global-teardown.ts`

```ts
import { createJestGlobalTeardown } from "@jongodb/memory-server/jest";

export default createJestGlobalTeardown();
```

Global setup/teardown stabilization notes:
- repeated global setup calls with the same state file now reuse the existing detached launcher (idempotent)
- stale state files (PID no longer alive) are auto-healed by starting a fresh launcher

Vitest:

```ts
import { beforeAll, afterAll } from "vitest";
import { registerJongodbForVitest } from "@jongodb/memory-server/vitest";

registerJongodbForVitest({ beforeAll, afterAll });
```

Vitest workspace (project isolation):

```ts
import { beforeAll, afterAll } from "vitest";
import { registerJongodbForVitestWorkspace } from "@jongodb/memory-server/vitest";

registerJongodbForVitestWorkspace({ beforeAll, afterAll }, {
  projectName: "catalog-api",
  isolationMode: "project",
  databaseName: "test",
});
```

Workspace helper behavior:
- `isolationMode: "project"` (default): appends project token to database name and writes project-scoped URI env key
- `isolationMode: "shared"`: keeps provided database name without project suffix

NestJS (Jest E2E):

```ts
import { beforeAll, afterAll } from "@jest/globals";
import { registerJongodbForNestJest } from "@jongodb/memory-server/nestjs";

registerJongodbForNestJest({ beforeAll, afterAll });
```

Nest adapter defaults:
- binds both `MONGODB_URI` and `DATABASE_URL` unless env keys are explicitly overridden
- defaults to `databaseNameStrategy: "worker"` for Jest parallel isolation

Common patterns:
- NestJS + Mongoose: use `process.env.MONGODB_URI` in `forRootAsync`
- Mongoose transaction/session tests: run `startSession`/`withTransaction` against runtime URI in integration suites
- Prisma (Mongo): set runtime `envVarNames` to include `DATABASE_URL` or inject URI via `PrismaClient` datasource override
- TypeORM (Mongo): pass runtime URI into `url` and validate `MongoRepository` CRUD in smoke suites
- Express: inject `process.env.MONGODB_URI` into a shared MongoClient bootstrap and keep app routes DB-agnostic
- Koa: inject `process.env.MONGODB_URI` into app context/bootstrap and keep middleware layers DB-agnostic

## Runtime Resolution

`launchMode` values:
- `auto` (default): binary first, Java fallback
- `binary`: binary only
- `java`: Java only

Port collision retry (fixed `port > 0`):
- when launcher failures include port-in-use signatures, runtime retries on the next port number
- defaults: `portRetryAttempts=3`, `portRetryBackoffMs=100` (linear backoff by retry index)

Binary resolution order:
1. `options.binaryPath`
2. `JONGODB_BINARY_PATH`
3. bundled platform binary package

Binary integrity verification:
- bundled platform binary packages are verified against embedded `jongodb.sha256` metadata before launch
- explicit binary paths can opt into verification with `binaryChecksum` or `JONGODB_BINARY_CHECKSUM`

Bundled targets:
- `@jongodb/memory-server-bin-darwin-arm64`
- `@jongodb/memory-server-bin-linux-x64-gnu`
- `@jongodb/memory-server-bin-win32-x64`

Java fallback:
- set `classpath` option or `JONGODB_CLASSPATH`
- if both are missing, classpath auto-discovery probes:
  - command: `options.classpathDiscoveryCommand` -> `JONGODB_CLASSPATH_DISCOVERY_CMD` -> repo-local Gradle -> `gradle`
  - args: `--no-daemon -q printLauncherClasspath`
  - working directory: `options.classpathDiscoveryWorkingDirectory` -> `JONGODB_CLASSPATH_DISCOVERY_CWD` -> `process.cwd()`
  - disable probe: `classpathDiscovery: "off"` or `JONGODB_CLASSPATH_DISCOVERY=off`
  - probe results are cached under `.jongodb/cache` with default pruning (`maxEntries=32`, `maxBytes=5MiB`, `ttl=7d`)

Launcher contract:
- stdout ready line: `JONGODB_URI=mongodb://...`
- optional stderr failure line: `JONGODB_START_FAILURE=...`
- process stays alive until termination signal
- runtime validates URI topology sync (requested `topologyProfile` vs emitted `replicaSet` query)

## Troubleshooting

Signature-based troubleshooting playbook:
- [`docs/NODE_TROUBLESHOOTING_PLAYBOOK.md`](../../docs/NODE_TROUBLESHOOTING_PLAYBOOK.md)

## API Surface

Main export (`@jongodb/memory-server`):
- `startJongodbMemoryServer(options?)`

Runtime export (`@jongodb/memory-server/runtime`):
- `createJongodbEnvRuntime(options?)`

Jest export (`@jongodb/memory-server/jest`):
- `registerJongodbForJest(hooks, options?)`
- `createJestGlobalSetup(options?)`
- `createJestGlobalTeardown(options?)`
- `readJestGlobalState(options?)`
- `readJestGlobalUri(options?)`

Nest export (`@jongodb/memory-server/nestjs`):
- `registerJongodbForNestJest(hooks, options?)`

Vitest export (`@jongodb/memory-server/vitest`):
- `registerJongodbForVitest(hooks, options?)`
- `registerJongodbForVitestWorkspace(hooks, workspaceOptions)`

## Options Reference

Core:
- `launchMode`: `auto` | `binary` | `java` (default: `auto`)
- `binaryPath`: binary executable override path
- `binaryChecksum`: expected SHA-256 checksum for explicit binary verification
- `classpath`: Java classpath string or string array
- `classpathDiscovery`: `auto` | `off` (default: `auto`)
- `classpathDiscoveryCommand`: override command used for classpath auto-discovery probe
- `classpathDiscoveryWorkingDirectory`: cwd for classpath auto-discovery probe
- `artifactCacheDir`: artifact cache directory for classpath auto-discovery metadata (default: `.jongodb/cache`)
- `artifactCacheMaxEntries`: max cache entries retained after prune (default: `32`)
- `artifactCacheMaxBytes`: max cache size retained after prune (default: `5_242_880` bytes)
- `artifactCacheTtlMs`: cache TTL for entries before expiration prune (default: `604_800_000`)
- `javaPath`: Java executable path (default: `java`)
- `launcherClass`: Java launcher class (default: `org.jongodb.server.TcpMongoServerLauncher`)
- `topologyProfile`: `standalone` | `singleNodeReplicaSet` (default: `standalone`)
- `replicaSetName`: replica-set name for `singleNodeReplicaSet` profile (default: `jongodb-rs0`)
- `databaseName`: base DB name (default: `test`)
- `databaseNameSuffix`: suffix appended to `databaseName` (example: `_ci`)
- `databaseNameStrategy`: `static` | `worker` (default: `static`)
- `host`: bind host (default: `127.0.0.1`)
- `port`: bind port (`0` means ephemeral)
- `portRetryAttempts`: retry count for port-collision failures when `port > 0` (default: `3`)
- `portRetryBackoffMs`: base backoff per retry for port collisions (default: `100`)
- `startupTimeoutMs`: startup timeout (default: `15000`)
- `stopTimeoutMs`: stop timeout before forced kill (default: `5000`)
- `cleanupOnProcessExit`: send `SIGTERM` to non-detached launcher on parent process exit (default: `true`)
- `env`: extra child process environment variables
- `logLevel`: `silent` | `error` | `warn` | `info` | `debug` (default: `silent`)
- `logFormat`: `plain` | `json` console output format (default: `plain`)
- `onLog`: structured runtime log hook (`timestamp`, `level`, `event`, `message`, `attempt`, `mode`, `source`, `port`, `stream`)
- `onStartupTelemetry`: optional hook for startup attempt telemetry (`attempt`, `mode`, `source`, `startupDurationMs`, `success`, `errorMessage`)

Runtime helper options:
- `envVarName`: single target env key (default: `MONGODB_URI`)
- `envVarNames`: multiple target env keys (example: `["MONGODB_URI", "DATABASE_URL"]`)
- `envTarget`: optional scoped env object (default: `process.env`)

Replica-set profile example:

```ts
const runtime = createJongodbEnvRuntime({
  topologyProfile: "singleNodeReplicaSet",
  replicaSetName: "rs-test",
});
```

Replica-set contract test coverage:
- URI must include `?replicaSet=<name>` when `topologyProfile=singleNodeReplicaSet`
- `hello` response must expose `setName`, `hosts`, `primary`, `isWritablePrimary`, `topologyVersion`

Run Node adapter contract tests:

```bash
npm run --workspace @jongodb/memory-server test
```

Run strict API type coverage report:

```bash
npm run node:type:report
```

Report output:
- `packages/memory-server/reports/type-api-coverage.md`

Scoped env example:

```ts
const scopedEnv: Record<string, string | undefined> = {};
const runtime = createJongodbEnvRuntime({
  envVarName: "MONGODB_URI",
  envTarget: scopedEnv,
});
```

## Troubleshooting

- runtime startup logs and failure tails apply secret redaction (`<redacted>`) for password/token-style values
- `No launcher runtime configured`: set `binaryPath` / `JONGODB_BINARY_PATH` / `classpath` / `JONGODB_CLASSPATH`
- `Binary launch mode requested but no binary was found`: provide `binaryPath` or `JONGODB_BINARY_PATH`
- `Binary checksum verification failed`: validate `binaryChecksum` / `JONGODB_BINARY_CHECKSUM` and binary file provenance
- `EADDRINUSE` / `address already in use`: increase base `port`, tune `portRetryAttempts`, or use `port: 0`
- `Java launch mode requested but Java classpath is not configured`: provide `classpath` or `JONGODB_CLASSPATH`
- `Classpath auto-discovery probe failed`: set explicit `classpath` / `JONGODB_CLASSPATH`, or fix Gradle probe command/cwd
- stale cache concerns: tune `artifactCache*` options or remove `.jongodb/cache` to force fresh probe
- `spawn ... ENOENT`: missing runtime executable path
- startup timeout: launcher did not emit `JONGODB_URI=...`
- `Launcher URI topology options are out of sync`: emitted URI query does not match requested `topologyProfile`/`replicaSetName`

Parallel test tip:
- use `databaseNameStrategy: "worker"` or per-worker suffixes to prevent data collisions
- if multiple runtimes share a Node process, prefer `envTarget` for per-runtime scoped bindings

Security policy reference:
- [`docs/NODE_LOG_REDACTION_POLICY.md`](../../docs/NODE_LOG_REDACTION_POLICY.md)

Support/debug bundle:
- generate issue bundle: `npm run node:debug:bundle -- --outputDir build/reports/node-debug-bundle`
- collector guide: [`docs/NODE_DEBUG_BUNDLE.md`](../../docs/NODE_DEBUG_BUNDLE.md)
- preflight doctor: `npm run node:doctor -- --outputDir build/reports/node-doctor`
- doctor guide: [`docs/NODE_DOCTOR.md`](../../docs/NODE_DOCTOR.md)