NoSQLBooster 11.0 Breaking Changes: Migration Guide

NoSQLBooster 11.0 introduces a completely rewritten MongoDB Shell engine and several architectural changes that may affect your existing scripts and configurations. This guide walks you through each breaking change and provides migration examples.

MongoDB Shell Engine: Fibers → Mongosh Async-Rewriter

What Changed

NoSQLBooster versions prior to 11.0 used a Fibers-based execution engine. Fibers is a native Node.js add-on that allows writing asynchronous code in a synchronous style. This approach required the fibers and deasync native add-ons, and used a custom qh-asyncawait wrapper to bridge async MongoDB operations.

Version 11.0 replaces this with @mongosh/async-rewriter2 — the same technology used by the official MongoDB Shell (mongosh). This means your scripts now run inside the actual mongosh engine (CLI version 2.8.2), with full top-level await support.

Good News: Most Shell-API Scripts Work Without Changes

Mongosh's async-rewriter automatically resolves shell-api promises at the top level. Methods like find(), toArray(), countDocuments(), insertOne(), aggregate(), bulkWrite() etc. work without explicit await, just as they did in the old Fibers-based engine:

1
2
3
4
5
6
7
8
9
// ✅ v11.0 — shell-api methods work without await, same as v10.x
var cursor = db.users.find({ status: "active" });
var count = db.users.countDocuments({ status: "active" });
var docs = cursor.toArray();

print("Active users: " + count);
docs.forEach(function(doc) {
print(doc.name);
});

This is identical to how you would write the same code in v10.x with Fibers. The async-rewriter transparently handles the promise resolution behind the scenes.

What Actually Breaks: Real Behavioral Differences

1. .forEach(async () => {}) Must Be Converted to for...of (Critical)

This is the most common and impactful migration issue. The async-rewriter cannot transform async callbacks inside .forEach(), .map(), or other array iteration methods. The Fibers engine could block at any call stack depth, but the async-rewriter only transforms the top-level script and properly structured control flow.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ❌ v10.x — async callback in forEach worked via Fibers, WILL FAIL in v11.0
db.users.find({ status: "active" }).forEach(async function(user) {
await db.logs.insertOne({ userId: user._id, action: "audit" });
});

// ❌ This also fails — async arrow function in forEach
var docs = db.users.find({ status: "active" }).toArray();
docs.forEach(async (user) => {
await db.logs.insertOne({ userId: user._id, action: "audit" });
});

// ✅ v11.0 — use for...of so the async-rewriter can handle it properly
for (const user of db.users.find({ status: "active" }).toArray()) {
db.logs.insertOne({ userId: user._id, action: "audit" });
}

// ✅ Or use a plain for loop
var docs = db.users.find({ status: "active" }).toArray();
for (var i = 0; i < docs.length; i++) {
db.logs.insertOne({ userId: docs[i]._id, action: "audit" });
}

Why does this happen? The async-rewriter transforms your script by inserting hidden await calls at the top level. When you pass an async function as a callback, the rewriter cannot see into that function's body — the await inside it is handled by JavaScript's native async machinery, which returns a Promise that .forEach() silently discards. The result is that your database operations fire-and-forget without completing.

Rule of thumb: If you have any loop that performs database operations, always use for...of or a plain for loop instead of .forEach().

2. Deprecated Methods

cursor.count() is deprecated since MongoDB 4.0. Use collection.countDocuments() or collection.estimatedDocumentCount() instead:

1
2
3
4
5
6
7
8
// ❌ v10.x — cursor.count() (deprecated since MongoDB 4.0)
var count = db.users.find({ status: "active" }).count();

// ✅ v11.0 — use countDocuments()
var count = db.users.countDocuments({ status: "active" });

// ✅ or estimatedDocumentCount() for fast approximate count
var total = db.users.estimatedDocumentCount();

3. cursor.map() Returns Cursor Instead of Array

1
2
3
4
5
6
7
// ❌ v10.x — map() returned an Array
var names = db.users.find().map(function(u) { return u.name; });
names.length; // worked

// ✅ v11.0 — map() returns a Cursor, call .toArray() first
var names = db.users.find().map(function(u) { return u.name; }).toArray();
names.length; // works

4. User-Defined Async Code Requires Explicit await

Shell-api methods are auto-resolved, but your own async code (custom Promises, fetch(), setTimeout, etc.) requires explicit await:

1
2
3
4
5
6
7
// ❌ This will NOT work without await
const response = fetch("https://api.example.com/data");
const json = response.json(); // TypeError: response.json is not a function

// ✅ User-defined async code needs explicit await
const response = await fetch("https://api.example.com/data");
const json = await response.json();

5. getQueryPlan() Removed

The cursor.getQueryPlan() extension method has been completely removed. Use the standard explain() method instead:

1
2
3
4
5
6
// ❌ v10.x — getQueryPlan() (removed)
cursor.getQueryPlan();

// ✅ v11.0 — use explain()
db.collection.find(query).explain();
db.collection.find(query).explain("executionStats");

The following extension methods remain available on multiple interfaces and require no changes to existing scripts:

  • getShellScript() — available on IAggregateCursor and internal query cursors
  • getAggregationPipeline() — available on IAggregateCursor and internal query cursors
  • runSQLQuery() — available on IAggregateCursor and mb namespace
  • saveAsView() — available on both ICursor (find cursors) and IAggregateCursor

Side-by-Side Comparison

Pattern v10.x (Fibers) v11.0 (Mongosh)
Loop with db ops docs.forEach(async fn) for (const doc of docs) { fn(doc) } — must change
Find + iterate db.col.find().forEach(fn) db.col.find().forEach(fn) — same (non-async callback)
Find + toArray db.col.find().toArray() db.col.find().toArray() — same
Count db.col.find(q).count() db.col.countDocuments(q) — method changed
Aggregate db.col.aggregate(p).toArray() db.col.aggregate(p).toArray() — same
Bulk write db.col.bulkWrite(ops) db.col.bulkWrite(ops) — same
Insert many db.col.insertMany(docs) db.col.insertMany(docs) — same
Cursor map db.col.find().map(fn) returns Array db.col.find().map(fn) returns Cursor, add .toArray()
Custom async auto-resolved by Fibers requires explicit await
getShellScript on ICursor (find) on IAggregateCursor + internal query cursors — still available
getAggregationPipeline on ICursor (find) on IAggregateCursor + internal query cursors — still available
runSQLQuery on ICursor (find) on IAggregateCursor + mb namespace — still available
saveAsView on ICursor (find) on both ICursor and IAggregateCursor — same
getQueryPlan on ICursor (find) removed — use .explain()

Shell API Type Definition Changes (mongo-shell.d.ts)

The TypeScript type definitions for the MongoDB Shell API have been significantly updated (445 insertions, 225 deletions) to align with the official mongosh shell-api.

Extension Method Status

The following table summarizes the availability of NoSQLBooster extension methods in v11.0. Methods marked "still available" are accessible on multiple interfaces and require no script changes:

Method v10.x Location v11.0 Location
getShellScript() ICursor (find) IAggregateCursor + internal query cursors — still available
getAggregationPipeline() ICursor (find) IAggregateCursor + internal query cursors — still available
runSQLQuery(sql) ICursor (find) IAggregateCursor + mb namespace — still available
saveAsView(name, options) ICursor (find) Both ICursor and IAggregateCursor — no change
getQueryPlan() ICursor (find) Removed — use db.col.find(q).explain()
returnKey() (no param) ICursor Use cursor.returnKey(true) with boolean parameter

BSON Constructor Changes

Several BSON type constructors have been updated to align with mongosh conventions:

UUID / BinData / HexData

1
2
3
4
5
6
7
8
9
10
// ❌ v10.x — "new" keyword was accepted
var id = new BinData(4, "base64string");
var hex = new HexData(4, "hexstring");

// ✅ v11.0 — use as function call (no "new" keyword)
var id = BinData(4, "base64string");
var hex = HexData(4, "hexstring");

// UUID constructors now have proper interface definitions
var uuid = UUID("550e8400-e29b-41d4-a716-446655440000");

MinKey / MaxKey

1
2
3
4
// The MinKey/MaxKey constructors are now properly typed
// with both function-call and new-constructor patterns
var min = MinKey(); // ✅ works
var max = new MaxKey(); // ✅ also works

Code / BSONSymbol

1
2
3
4
5
6
// Code constructor interface updated
var code = Code("function() { return 1; }", { x: 1 });

// objectIdFromDate return type updated from IObjectId to ObjectId
var oid = objectIdFromDate(new Date("2024-01-01"));
var date = dateFromObjectId(oid);

New Methods and Types Added

The type definitions now include methods that are part of the official mongosh API:

  • cursor.oplogReplay() — marks cursor to replay oplog entries (MongoDB 3.2+)
  • cursor.comment() — adds a comment to the query
  • history() — declared for type completeness (throws descriptive error in GUI)
  • mb.runSQLQuery() now accepts verbose option to print equivalent MongoDB script
  • Proper UUIDConstructor, LUUIDConstructor, JUUIDConstructor, CSUUIDConstructor, PYUUIDConstructor, MD5Constructor interfaces

Deprecated Methods (Still Available)

The following methods are still available but now carry explicit @deprecated annotations matching the official MongoDB documentation:

  • cursor.count() — deprecated since MongoDB 4.0; use collection.countDocuments() or collection.estimatedDocumentCount()
  • db.setSlaveOk() — deprecated; use readPref() instead

In-Use Encryption (CSFLE / Queryable Encryption) Breaking Changes

Configuration Storage Format Change

What Changed

In v10.x, the autoEncryptionOptions were stored as a plain JSON object in the connection configuration. This caused a critical issue: when the configuration was serialized to JSON and deserialized back, BSON type prototypes (such as UUID and BinData) were lost. This triggered libmongocrypt error 51088 ("keyId array elements must be BinData") when using UUID-typed key IDs.

New Storage Format

Version 11.0 introduces a new field autoEncryptionOptionsJs that stores the configuration as a JavaScript expression string. The original autoEncryptionOptions field is preserved for backward compatibility.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// v11.0 — autoEncryptionOptionsJs stores the config as a JS expression
// BSON prototypes like UUID() and BinData() survive serialization
({
keyVaultNamespace: "encryption.__keyVault",
kmsProviders: {
local: {
key: process.env.LOCAL_KMS_KEY // env vars supported
}
},
schemaMap: {
"mydb.users": {
bsonType: "object",
properties: {
ssn: {
encrypt: {
keyId: [UUID("550e8400-e29b-41d4-a716-446655440000")],
bsonType: "string",
algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
}
}
}
}
}
})

Migration Steps

  1. Open your connection in the Connection Editor
  2. Navigate to the In-Use Encryption tab
  3. If your existing configuration contains UUID() or BinData() values in keyId arrays, re-save the connection — the new format will be applied automatically
  4. The old autoEncryptionOptions field remains as a fallback; no manual migration is required for simple configurations

Environment Variable Support

The new JS expression format supports process.env.XXX references, allowing you to avoid storing KMS keys directly in the connection configuration:

1
2
3
4
5
6
7
8
9
// Use environment variables for sensitive KMS credentials
({
kmsProviders: {
aws: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
}
}
})

Stricter Validation

Version 11.0 enforces validation rules that were previously advisory:

  • keyVaultNamespace is required — connections with empty or missing keyVaultNamespace will fail with a clear error message
  • kmsProviders must be non-empty — at least one KMS provider must be configured
  • These validations apply to all connection paths: GUI, CLI, script, and IPC

Named KMS Providers

Version 11.0 supports named KMS providers (introduced in MongoDB driver 6.x), such as "aws:account1" or "local:keyA". The validation logic now correctly handles the "provider:name" format when checking KMS provider configurations.

1
2
3
4
5
6
7
8
9
10
11
12
13
// Named KMS providers are now supported
({
kmsProviders: {
"aws:production": {
accessKeyId: process.env.PROD_AWS_KEY,
secretAccessKey: process.env.PROD_AWS_SECRET
},
"aws:staging": {
accessKeyId: process.env.STAGING_AWS_KEY,
secretAccessKey: process.env.STAGING_AWS_SECRET
}
}
})

Crypt Shared Library Path

If you use the cryptSharedLibPath option, note that MongoDB driver 7.x enforces strict filepath validation. The library file must retain its default name:

  • Windows: mongo_crypt_v1.dll
  • macOS: mongo_crypt_v1.dylib
  • Linux: mongo_crypt_v1.so

System Requirements Changes

Requirement v10.x v11.0
Windows Windows 7+ Windows 10+ (64-bit only)
macOS macOS 10.13+ macOS 10.15+ (Catalina)
Linux Ubuntu 18.04+ Ubuntu 20.04+ / Debian 11+ / Fedora 36+
Node.js (embedded) 12.x 22.x

The minimum system requirements have been raised due to the Electron 40 upgrade. Users on older operating systems will need to remain on NoSQLBooster 10.x.

Compatibility Mode

NoSQLBooster 11.0 loads the mongocompat mongosh-snippets by default, which provides legacy mongo shell compatibility APIs. This helps bridge the gap for scripts that use older API patterns.

For a complete list of compatibility changes between the legacy mongo shell and mongosh, see the official MongoDB compatibility documentation.

Thank you!

Please visit our feedback page or click the “Feedback” button in the app. Feel free to suggest improvements to our product or service. Users can discuss your suggestion and vote for and against it. We’ll look at it too.