Socket
Socket
Sign inDemoInstall

mongodb

Package Overview
Dependencies
Maintainers
0
Versions
550
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mongodb - npm Package Compare versions

Comparing version 6.7.0 to 6.8.0

69

lib/client-side-encryption/auto_encrypter.js

@@ -6,2 +6,3 @@ "use strict";

const bson_1 = require("../bson");
const constants_1 = require("../constants");
const deps_1 = require("../deps");

@@ -24,10 +25,2 @@ const error_1 = require("../error");

});
// Typescript errors if we index objects with `Symbol.for(...)`, so
// to avoid TS errors we pull them out into variables. Then we can type
// the objects (and class) that we expect to see them on and prevent TS
// errors.
/** @internal */
const kDecorateResult = Symbol.for('@@mdb.decorateDecryptionResult');
/** @internal */
const kDecoratedKeys = Symbol.for('@@mdb.decryptedKeys');
/**

@@ -221,3 +214,6 @@ * @internal An internal class to be used by the driver for auto encryption

});
return await stateMachine.execute(this, context);
return (0, bson_1.deserialize)(await stateMachine.execute(this, context), {
promoteValues: false,
promoteLongs: false
});
}

@@ -228,4 +224,3 @@ /**

async decrypt(response, options = {}) {
const buffer = Buffer.isBuffer(response) ? response : (0, bson_1.serialize)(response, options);
const context = this._mongocrypt.makeDecryptionContext(buffer);
const context = this._mongocrypt.makeDecryptionContext(response);
context.id = this._contextCounter++;

@@ -237,8 +232,3 @@ const stateMachine = new state_machine_1.StateMachine({

});
const decorateResult = this[kDecorateResult];
const result = await stateMachine.execute(this, context);
if (decorateResult) {
decorateDecryptionResult(result, response);
}
return result;
return await stateMachine.execute(this, context);
}

@@ -268,46 +258,3 @@ /**

exports.AutoEncrypter = AutoEncrypter;
_a = kDecorateResult;
/**
* Recurse through the (identically-shaped) `decrypted` and `original`
* objects and attach a `decryptedKeys` property on each sub-object that
* contained encrypted fields. Because we only call this on BSON responses,
* we do not need to worry about circular references.
*
* @internal
*/
function decorateDecryptionResult(decrypted, original, isTopLevelDecorateCall = true) {
if (isTopLevelDecorateCall) {
// The original value could have been either a JS object or a BSON buffer
if (Buffer.isBuffer(original)) {
original = (0, bson_1.deserialize)(original);
}
if (Buffer.isBuffer(decrypted)) {
throw new error_1.MongoRuntimeError('Expected result of decryption to be deserialized BSON object');
}
}
if (!decrypted || typeof decrypted !== 'object')
return;
for (const k of Object.keys(decrypted)) {
const originalValue = original[k];
// An object was decrypted by libmongocrypt if and only if it was
// a BSON Binary object with subtype 6.
if (originalValue && originalValue._bsontype === 'Binary' && originalValue.sub_type === 6) {
if (!decrypted[kDecoratedKeys]) {
Object.defineProperty(decrypted, kDecoratedKeys, {
value: [],
configurable: true,
enumerable: false,
writable: false
});
}
// this is defined in the preceding if-statement
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
decrypted[kDecoratedKeys].push(k);
// Do not recurse into this decrypted value. It could be a sub-document/array,
// in which case there is no original value associated with its subfields.
continue;
}
decorateDecryptionResult(decrypted[k], originalValue, false);
}
}
_a = constants_1.kDecorateResult;
//# sourceMappingURL=auto_encrypter.js.map

10

lib/client-side-encryption/client_encryption.js

@@ -133,3 +133,3 @@ "use strict";

});
const dataKey = await stateMachine.execute(this, context);
const dataKey = (0, bson_1.deserialize)(await stateMachine.execute(this, context));
const { db: dbName, collection: collectionName } = utils_1.MongoDBCollectionNamespace.fromString(this._keyVaultNamespace);

@@ -181,3 +181,3 @@ const { insertedId } = await this._keyVaultClient

});
const { v: dataKeys } = await stateMachine.execute(this, context);
const { v: dataKeys } = (0, bson_1.deserialize)(await stateMachine.execute(this, context));
if (dataKeys.length === 0) {

@@ -489,3 +489,3 @@ return {};

});
const { v } = await stateMachine.execute(this, context);
const { v } = (0, bson_1.deserialize)(await stateMachine.execute(this, context));
return v;

@@ -553,4 +553,4 @@ }

const context = this._mongoCrypt.makeExplicitEncryptionContext(valueBuffer, contextOptions);
const result = await stateMachine.execute(this, context);
return result.v;
const { v } = (0, bson_1.deserialize)(await stateMachine.execute(this, context));
return v;
}

@@ -557,0 +557,0 @@ }

@@ -62,2 +62,14 @@ "use strict";

/**
* This is kind of a hack. For `rewrapManyDataKey`, we have tests that
* guarantee that when there are no matching keys, `rewrapManyDataKey` returns
* nothing. We also have tests for auto encryption that guarantee for `encrypt`
* we return an error when there are no matching keys. This error is generated in
* subsequent iterations of the state machine.
* Some apis (`encrypt`) throw if there are no filter matches and others (`rewrapManyDataKey`)
* do not. We set the result manually here, and let the state machine continue. `libmongocrypt`
* will inform us if we need to error by setting the state to `MONGOCRYPT_CTX_ERROR` but
* otherwise we'll return `{ v: [] }`.
*/
let EMPTY_V;
/**
* @internal

@@ -114,12 +126,4 @@ * An internal class that executes across a MongoCryptContext until either

if (keys.length === 0) {
// This is kind of a hack. For `rewrapManyDataKey`, we have tests that
// guarantee that when there are no matching keys, `rewrapManyDataKey` returns
// nothing. We also have tests for auto encryption that guarantee for `encrypt`
// we return an error when there are no matching keys. This error is generated in
// subsequent iterations of the state machine.
// Some apis (`encrypt`) throw if there are no filter matches and others (`rewrapManyDataKey`)
// do not. We set the result manually here, and let the state machine continue. `libmongocrypt`
// will inform us if we need to error by setting the state to `MONGOCRYPT_CTX_ERROR` but
// otherwise we'll return `{ v: [] }`.
result = { v: [] };
// See docs on EMPTY_V
result = EMPTY_V ??= (0, bson_1.serialize)({ v: [] });
}

@@ -150,3 +154,3 @@ for await (const key of keys) {

}
result = (0, bson_1.deserialize)(finalizedContext, this.options);
result = finalizedContext;
break;

@@ -153,0 +157,0 @@ }

@@ -6,2 +6,3 @@ "use strict";

const timers_1 = require("timers");
const bson_1 = require("../bson");
const constants_1 = require("../constants");

@@ -243,7 +244,3 @@ const error_1 = require("../error");

const bson = response.parse();
const document = responseType == null
? new responses_1.MongoDBResponse(bson)
: (0, responses_1.isErrorResponse)(bson)
? new responses_1.MongoDBResponse(bson)
: new responseType(bson);
const document = (responseType ?? responses_1.MongoDBResponse).make(bson);
yield document;

@@ -294,7 +291,3 @@ this.throwIfAborted();

}
if (document.has('writeConcernError')) {
object ??= document.toObject(bsonOptions);
throw new error_1.MongoWriteConcernError(object.writeConcernError, object);
}
if (document.isError) {
if (document.ok === 0) {
throw new error_1.MongoServerError((object ??= document.toObject(bsonOptions)));

@@ -316,8 +309,3 @@ }

if (this.shouldEmitAndLogCommand) {
if (error.name === 'MongoWriteConcernError') {
this.emitAndLogCommand(this.monitorCommands, Connection.COMMAND_SUCCEEDED, message.databaseName, this.established, new command_monitoring_events_1.CommandSucceededEvent(this, message, options.noResponse ? undefined : (object ??= document?.toObject(bsonOptions)), started, this.description.serverConnectionId));
}
else {
this.emitAndLogCommand(this.monitorCommands, Connection.COMMAND_FAILED, message.databaseName, this.established, new command_monitoring_events_1.CommandFailedEvent(this, message, error, started, this.description.serverConnectionId));
}
this.emitAndLogCommand(this.monitorCommands, Connection.COMMAND_FAILED, message.databaseName, this.established, new command_monitoring_events_1.CommandFailedEvent(this, message, error, started, this.description.serverConnectionId));
}

@@ -443,3 +431,3 @@ throw error;

}
async command(ns, cmd, options, _responseType) {
async command(ns, cmd, options, responseType) {
const { autoEncrypter } = this;

@@ -456,3 +444,3 @@ if (!autoEncrypter) {

// This means the initial handshake hasn't happened yet
return await super.command(ns, cmd, options, undefined);
return await super.command(ns, cmd, options, responseType);
}

@@ -483,4 +471,18 @@ if (serverWireVersion < 8) {

}
const response = await super.command(ns, encrypted, options, undefined);
return await autoEncrypter.decrypt(response, options);
const encryptedResponse = await super.command(ns, encrypted, options,
// Eventually we want to require `responseType` which means we would satisfy `T` as the return type.
// In the meantime, we want encryptedResponse to always be _at least_ a MongoDBResponse if not a more specific subclass
// So that we can ensure we have access to the on-demand APIs for decorate response
responseType ?? responses_1.MongoDBResponse);
const result = await autoEncrypter.decrypt(encryptedResponse.toBytes(), options);
const decryptedResponse = responseType?.make(result) ?? (0, bson_1.deserialize)(result, options);
if (autoEncrypter[constants_1.kDecorateResult]) {
if (responseType == null) {
(0, utils_1.decorateDecryptionResult)(decryptedResponse, encryptedResponse.toObject(), true);
}
else if (decryptedResponse instanceof responses_1.CursorResponse) {
decryptedResponse.encryptedResponse = encryptedResponse;
}
}
return decryptedResponse;
}

@@ -487,0 +489,0 @@ }

@@ -13,3 +13,5 @@ "use strict";

/** If this is an embedded document, indicates if this was a BSON array */
isArray = false) {
isArray = false,
/** If elements was already calculated */
elements) {
this.bson = bson;

@@ -28,3 +30,3 @@ this.offset = offset;

this.indexFound = Object.create(null);
this.elements = (0, bson_1.parseToElementsToArray)(this.bson, offset);
this.elements = elements ?? (0, bson_1.parseToElementsToArray)(this.bson, offset);
}

@@ -37,4 +39,5 @@ /** Only supports basic latin strings */

return false;
for (let i = 0; i < name.length; i++) {
if (this.bson[nameOffset + i] !== name.charCodeAt(i))
const nameEnd = nameOffset + nameLength;
for (let byteIndex = nameOffset, charIndex = 0; charIndex < name.length && byteIndex < nameEnd; charIndex++, byteIndex++) {
if (this.bson[byteIndex] !== name.charCodeAt(charIndex))
return false;

@@ -83,3 +86,3 @@ }

// skip this element if it has already been associated with a name
if (!this.indexFound[index] && this.isElementName(name, element)) {
if (!(index in this.indexFound) && this.isElementName(name, element)) {
const cachedElement = { element, value: undefined };

@@ -86,0 +89,0 @@ this.cache[name] = cachedElement;

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CursorResponse = exports.MongoDBResponse = exports.isErrorResponse = void 0;
exports.ExplainedCursorResponse = exports.CursorResponse = exports.MongoDBResponse = exports.isErrorResponse = void 0;
const bson_1 = require("../../bson");

@@ -19,4 +19,3 @@ const error_1 = require("../../error");

*/
function isErrorResponse(bson) {
const elements = (0, bson_1.parseToElementsToArray)(bson, 0);
function isErrorResponse(bson, elements) {
for (let eIdx = 0; eIdx < elements.length; eIdx++) {

@@ -45,12 +44,19 @@ const element = elements[eIdx];

class MongoDBResponse extends document_1.OnDemandDocument {
get(name, as, required) {
try {
return super.get(name, as, required);
}
catch (cause) {
throw new error_1.MongoUnexpectedServerResponseError(cause.message, { cause });
}
}
static is(value) {
return value instanceof MongoDBResponse;
}
/** Indicates this document is a server error */
get isError() {
let isError = this.ok === 0;
isError ||= this.has('errmsg');
isError ||= this.has('code');
isError ||= this.has('$err'); // The '$err' field is used in OP_REPLY responses
return isError;
static make(bson) {
const elements = (0, bson_1.parseToElementsToArray)(bson, 0);
const isError = isErrorResponse(bson, elements);
return isError
? new MongoDBResponse(bson, 0, false, elements)
: new this(bson, 0, false, elements);
}

@@ -80,2 +86,3 @@ /**

}
/** Normalizes whatever BSON value is "ok" to a JS number 1 or 0. */
get ok() {

@@ -109,9 +116,3 @@ return this.getNumber('ok') ? 1 : 0;

const exactBSONOptions = {
useBigInt64: options?.useBigInt64,
promoteLongs: options?.promoteLongs,
promoteValues: options?.promoteValues,
promoteBuffers: options?.promoteBuffers,
bsonRegExp: options?.bsonRegExp,
raw: options?.raw ?? false,
fieldsAsRaw: options?.fieldsAsRaw ?? {},
...(0, bson_1.pluckBSONSerializeOptions)(options ?? {}),
validation: this.parseBsonSerializationOptions(options)

@@ -134,27 +135,67 @@ };

class CursorResponse extends MongoDBResponse {
constructor() {
super(...arguments);
this._batch = null;
this.iterated = 0;
this._encryptedBatch = null;
}
static is(value) {
return value instanceof CursorResponse || value === CursorResponse.emptyGetMore;
}
constructor(bytes, offset, isArray) {
super(bytes, offset, isArray);
this.ns = null;
this.batchSize = 0;
this.iterated = 0;
const cursor = this.get('cursor', bson_1.BSONType.object, true);
const id = cursor.get('id', bson_1.BSONType.long, true);
this.id = new bson_1.Long(Number(id & 0xffffffffn), Number((id >> 32n) & 0xffffffffn));
const namespace = cursor.get('ns', bson_1.BSONType.string);
get cursor() {
return this.get('cursor', bson_1.BSONType.object, true);
}
get id() {
try {
return bson_1.Long.fromBigInt(this.cursor.get('id', bson_1.BSONType.long, true));
}
catch (cause) {
throw new error_1.MongoUnexpectedServerResponseError(cause.message, { cause });
}
}
get ns() {
const namespace = this.cursor.get('ns', bson_1.BSONType.string);
if (namespace != null)
this.ns = (0, utils_1.ns)(namespace);
return (0, utils_1.ns)(namespace);
return null;
}
get length() {
return Math.max(this.batchSize - this.iterated, 0);
}
get encryptedBatch() {
if (this.encryptedResponse == null)
return null;
if (this._encryptedBatch != null)
return this._encryptedBatch;
const cursor = this.encryptedResponse?.get('cursor', bson_1.BSONType.object);
if (cursor?.has('firstBatch'))
this._encryptedBatch = cursor.get('firstBatch', bson_1.BSONType.array, true);
else if (cursor?.has('nextBatch'))
this._encryptedBatch = cursor.get('nextBatch', bson_1.BSONType.array, true);
else
throw new error_1.MongoUnexpectedServerResponseError('Cursor document did not contain a batch');
return this._encryptedBatch;
}
get batch() {
if (this._batch != null)
return this._batch;
const cursor = this.cursor;
if (cursor.has('firstBatch'))
this.batch = cursor.get('firstBatch', bson_1.BSONType.array, true);
this._batch = cursor.get('firstBatch', bson_1.BSONType.array, true);
else if (cursor.has('nextBatch'))
this.batch = cursor.get('nextBatch', bson_1.BSONType.array, true);
this._batch = cursor.get('nextBatch', bson_1.BSONType.array, true);
else
throw new error_1.MongoUnexpectedServerResponseError('Cursor document did not contain a batch');
this.batchSize = this.batch.size();
return this._batch;
}
get length() {
return Math.max(this.batchSize - this.iterated, 0);
get batchSize() {
return this.batch?.size();
}
get postBatchResumeToken() {
return (this.cursor.get('postBatchResumeToken', bson_1.BSONType.object)?.toObject({
promoteValues: false,
promoteLongs: false,
promoteBuffers: false
}) ?? null);
}
shift(options) {

@@ -165,2 +206,3 @@ if (this.iterated >= this.batchSize) {

const result = this.batch.get(this.iterated, bson_1.BSONType.object, true) ?? null;
const encryptedResult = this.encryptedBatch?.get(this.iterated, bson_1.BSONType.object, true) ?? null;
this.iterated += 1;

@@ -171,3 +213,7 @@ if (options?.raw) {

else {
return result.toObject(options);
const object = result.toObject(options);
if (encryptedResult) {
(0, utils_1.decorateDecryptionResult)(object, encryptedResult.toObject(options), true);
}
return object;
}

@@ -178,8 +224,2 @@ }

}
pushMany() {
throw new Error('pushMany Unsupported method');
}
push() {
throw new Error('push Unsupported method');
}
}

@@ -190,4 +230,40 @@ /**

*/
CursorResponse.emptyGetMore = { id: new bson_1.Long(0), length: 0, shift: () => null };
CursorResponse.emptyGetMore = {
id: new bson_1.Long(0),
length: 0,
shift: () => null
};
exports.CursorResponse = CursorResponse;
/**
* Explain responses have nothing to do with cursor responses
* This class serves to temporarily avoid refactoring how cursors handle
* explain responses which is to detect that the response is not cursor-like and return the explain
* result as the "first and only" document in the "batch" and end the "cursor"
*/
class ExplainedCursorResponse extends CursorResponse {
constructor() {
super(...arguments);
this.isExplain = true;
this._length = 1;
}
get id() {
return bson_1.Long.fromBigInt(0n);
}
get batchSize() {
return 0;
}
get ns() {
return null;
}
get length() {
return this._length;
}
shift(options) {
if (this._length === 0)
return null;
this._length -= 1;
return this.toObject(options);
}
}
exports.ExplainedCursorResponse = ExplainedCursorResponse;
//# sourceMappingURL=responses.js.map

@@ -15,3 +15,2 @@ "use strict";

const count_1 = require("./operations/count");
const count_documents_1 = require("./operations/count_documents");
const delete_1 = require("./operations/delete");

@@ -460,3 +459,15 @@ const distinct_1 = require("./operations/distinct");

async countDocuments(filter = {}, options = {}) {
return await (0, execute_operation_1.executeOperation)(this.client, new count_documents_1.CountDocumentsOperation(this, filter, (0, utils_1.resolveOptions)(this, options)));
const pipeline = [];
pipeline.push({ $match: filter });
if (typeof options.skip === 'number') {
pipeline.push({ $skip: options.skip });
}
if (typeof options.limit === 'number') {
pipeline.push({ $limit: options.limit });
}
pipeline.push({ $group: { _id: 1, n: { $sum: 1 } } });
const cursor = this.aggregate(pipeline, options);
const doc = await cursor.next();
await cursor.close();
return doc?.n ?? 0;
}

@@ -463,0 +474,0 @@ async distinct(key, filter = {}, options = {}) {

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.END = exports.CHANGE = exports.INIT = exports.MORE = exports.RESPONSE = exports.SERVER_HEARTBEAT_FAILED = exports.SERVER_HEARTBEAT_SUCCEEDED = exports.SERVER_HEARTBEAT_STARTED = exports.COMMAND_FAILED = exports.COMMAND_SUCCEEDED = exports.COMMAND_STARTED = exports.CLUSTER_TIME_RECEIVED = exports.CONNECTION_CHECKED_IN = exports.CONNECTION_CHECKED_OUT = exports.CONNECTION_CHECK_OUT_FAILED = exports.CONNECTION_CHECK_OUT_STARTED = exports.CONNECTION_CLOSED = exports.CONNECTION_READY = exports.CONNECTION_CREATED = exports.CONNECTION_POOL_READY = exports.CONNECTION_POOL_CLEARED = exports.CONNECTION_POOL_CLOSED = exports.CONNECTION_POOL_CREATED = exports.WAITING_FOR_SUITABLE_SERVER = exports.SERVER_SELECTION_SUCCEEDED = exports.SERVER_SELECTION_FAILED = exports.SERVER_SELECTION_STARTED = exports.TOPOLOGY_DESCRIPTION_CHANGED = exports.TOPOLOGY_CLOSED = exports.TOPOLOGY_OPENING = exports.SERVER_DESCRIPTION_CHANGED = exports.SERVER_CLOSED = exports.SERVER_OPENING = exports.DESCRIPTION_RECEIVED = exports.UNPINNED = exports.PINNED = exports.MESSAGE = exports.ENDED = exports.CLOSED = exports.CONNECT = exports.OPEN = exports.CLOSE = exports.TIMEOUT = exports.ERROR = exports.SYSTEM_JS_COLLECTION = exports.SYSTEM_COMMAND_COLLECTION = exports.SYSTEM_USER_COLLECTION = exports.SYSTEM_PROFILE_COLLECTION = exports.SYSTEM_INDEX_COLLECTION = exports.SYSTEM_NAMESPACE_COLLECTION = void 0;
exports.LEGACY_HELLO_COMMAND_CAMEL_CASE = exports.LEGACY_HELLO_COMMAND = exports.MONGO_CLIENT_EVENTS = exports.LOCAL_SERVER_EVENTS = exports.SERVER_RELAY_EVENTS = exports.APM_EVENTS = exports.TOPOLOGY_EVENTS = exports.CMAP_EVENTS = exports.HEARTBEAT_EVENTS = exports.RESUME_TOKEN_CHANGED = void 0;
exports.kDecoratedKeys = exports.kDecorateResult = exports.LEGACY_HELLO_COMMAND_CAMEL_CASE = exports.LEGACY_HELLO_COMMAND = exports.MONGO_CLIENT_EVENTS = exports.LOCAL_SERVER_EVENTS = exports.SERVER_RELAY_EVENTS = exports.APM_EVENTS = exports.TOPOLOGY_EVENTS = exports.CMAP_EVENTS = exports.HEARTBEAT_EVENTS = exports.RESUME_TOKEN_CHANGED = void 0;
exports.SYSTEM_NAMESPACE_COLLECTION = 'system.namespaces';

@@ -162,2 +162,10 @@ exports.SYSTEM_INDEX_COLLECTION = 'system.indexes';

exports.LEGACY_HELLO_COMMAND_CAMEL_CASE = 'isMaster';
// Typescript errors if we index objects with `Symbol.for(...)`, so
// to avoid TS errors we pull them out into variables. Then we can type
// the objects (and class) that we expect to see them on and prevent TS
// errors.
/** @internal */
exports.kDecorateResult = Symbol.for('@@mdb.decorateDecryptionResult');
/** @internal */
exports.kDecoratedKeys = Symbol.for('@@mdb.decryptedKeys');
//# sourceMappingURL=constants.js.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.assertUninitialized = exports.AbstractCursor = exports.CURSOR_FLAGS = void 0;
exports.AbstractCursor = exports.CURSOR_FLAGS = void 0;
const stream_1 = require("stream");
const bson_1 = require("../bson");
const responses_1 = require("../cmap/wire_protocol/responses");
const error_1 = require("../error");

@@ -16,26 +15,2 @@ const mongo_types_1 = require("../mongo_types");

const utils_1 = require("../utils");
/** @internal */
const kId = Symbol('id');
/** @internal */
const kDocuments = Symbol('documents');
/** @internal */
const kServer = Symbol('server');
/** @internal */
const kNamespace = Symbol('namespace');
/** @internal */
const kClient = Symbol('client');
/** @internal */
const kSession = Symbol('session');
/** @internal */
const kOptions = Symbol('options');
/** @internal */
const kTransform = Symbol('transform');
/** @internal */
const kInitialized = Symbol('initialized');
/** @internal */
const kClosed = Symbol('closed');
/** @internal */
const kKilled = Symbol('killed');
/** @internal */
const kInit = Symbol('kInit');
/** @public */

@@ -55,13 +30,16 @@ exports.CURSOR_FLAGS = [

super();
/** @internal */
this.documents = null;
/** @internal */
this.hasEmittedClose = false;
if (!client.s.isMongoClient) {
throw new error_1.MongoRuntimeError('Cursor must be constructed with MongoClient');
}
this[kClient] = client;
this[kNamespace] = namespace;
this[kId] = null;
this[kDocuments] = new utils_1.List();
this[kInitialized] = false;
this[kClosed] = false;
this[kKilled] = false;
this[kOptions] = {
this.cursorClient = client;
this.cursorNamespace = namespace;
this.cursorId = null;
this.initialized = false;
this.isClosed = false;
this.isKilled = false;
this.cursorOptions = {
readPreference: options.readPreference && options.readPreference instanceof read_preference_1.ReadPreference

@@ -72,9 +50,9 @@ ? options.readPreference

};
this[kOptions].timeoutMS = options.timeoutMS;
this.cursorOptions.timeoutMS = options.timeoutMS;
const readConcern = read_concern_1.ReadConcern.fromOptions(options);
if (readConcern) {
this[kOptions].readConcern = readConcern;
this.cursorOptions.readConcern = readConcern;
}
if (typeof options.batchSize === 'number') {
this[kOptions].batchSize = options.batchSize;
this.cursorOptions.batchSize = options.batchSize;
}

@@ -84,64 +62,74 @@ // we check for undefined specifically here to allow falsy values

if (options.comment !== undefined) {
this[kOptions].comment = options.comment;
this.cursorOptions.comment = options.comment;
}
if (typeof options.maxTimeMS === 'number') {
this[kOptions].maxTimeMS = options.maxTimeMS;
this.cursorOptions.maxTimeMS = options.maxTimeMS;
}
if (typeof options.maxAwaitTimeMS === 'number') {
this[kOptions].maxAwaitTimeMS = options.maxAwaitTimeMS;
this.cursorOptions.maxAwaitTimeMS = options.maxAwaitTimeMS;
}
if (options.session instanceof sessions_1.ClientSession) {
this[kSession] = options.session;
this.cursorSession = options.session;
}
else {
this[kSession] = this[kClient].startSession({ owner: this, explicit: false });
this.cursorSession = this.cursorClient.startSession({ owner: this, explicit: false });
}
}
/**
* The cursor has no id until it receives a response from the initial cursor creating command.
*
* It is non-zero for as long as the database has an open cursor.
*
* The initiating command may receive a zero id if the entire result is in the `firstBatch`.
*/
get id() {
return this[kId] ?? undefined;
return this.cursorId ?? undefined;
}
/** @internal */
get isDead() {
return (this[kId]?.isZero() ?? false) || this[kClosed] || this[kKilled];
return (this.cursorId?.isZero() ?? false) || this.isClosed || this.isKilled;
}
/** @internal */
get client() {
return this[kClient];
return this.cursorClient;
}
/** @internal */
get server() {
return this[kServer];
return this.selectedServer;
}
get namespace() {
return this[kNamespace];
return this.cursorNamespace;
}
get readPreference() {
return this[kOptions].readPreference;
return this.cursorOptions.readPreference;
}
get readConcern() {
return this[kOptions].readConcern;
return this.cursorOptions.readConcern;
}
/** @internal */
get session() {
return this[kSession];
return this.cursorSession;
}
set session(clientSession) {
this[kSession] = clientSession;
this.cursorSession = clientSession;
}
/** @internal */
get cursorOptions() {
return this[kOptions];
}
/**
* The cursor is closed and all remaining locally buffered documents have been iterated.
*/
get closed() {
return this[kClosed];
return this.isClosed && (this.documents?.length ?? 0) === 0;
}
/**
* A `killCursors` command was attempted on this cursor.
* This is performed if the cursor id is non zero.
*/
get killed() {
return this[kKilled];
return this.isKilled;
}
get loadBalanced() {
return !!this[kClient].topology?.loadBalanced;
return !!this.cursorClient.topology?.loadBalanced;
}
/** Returns current buffered documents length */
bufferedCount() {
return this[kDocuments].length;
return this.documents?.length ?? 0;
}

@@ -151,5 +139,5 @@ /** Returns current buffered documents */

const bufferedDocs = [];
const documentsToRead = Math.min(number ?? this[kDocuments].length, this[kDocuments].length);
const documentsToRead = Math.min(number ?? this.documents?.length ?? 0, this.documents?.length ?? 0);
for (let count = 0; count < documentsToRead; count++) {
const document = this[kDocuments].shift(this[kOptions]);
const document = this.documents?.shift(this.cursorOptions);
if (document != null) {

@@ -162,3 +150,3 @@ bufferedDocs.push(document);

async *[Symbol.asyncIterator]() {
if (this.closed) {
if (this.isClosed) {
return;

@@ -168,24 +156,17 @@ }

while (true) {
if (this.isKilled) {
return;
}
if (this.closed) {
return;
}
if (this.cursorId != null && this.isDead && (this.documents?.length ?? 0) === 0) {
return;
}
const document = await this.next();
// Intentional strict null check, because users can map cursors to falsey values.
// We allow mapping to all values except for null.
// eslint-disable-next-line no-restricted-syntax
if (document === null) {
if (!this.closed) {
const message = 'Cursor returned a `null` document, but the cursor is not exhausted. Mapping documents to `null` is not supported in the cursor transform.';
try {
await cleanupCursor(this, { needsToEmitClosed: true });
}
catch (error) {
(0, utils_1.squashError)(error);
}
throw new error_1.MongoAPIError(message);
}
break;
return;
}
yield document;
if (this[kId] === bson_1.Long.ZERO) {
// Cursor exhausted
break;
}
}

@@ -196,3 +177,3 @@ }

// the case when a user would break out of a for await of loop early.
if (!this.closed) {
if (!this.isClosed) {
try {

@@ -232,16 +213,28 @@ await this.close();

async hasNext() {
if (this[kId] === bson_1.Long.ZERO) {
if (this.cursorId === bson_1.Long.ZERO) {
return false;
}
if (this[kDocuments].length !== 0) {
return true;
}
return await next(this, { blocking: true, transform: false, shift: false });
do {
if ((this.documents?.length ?? 0) !== 0) {
return true;
}
await this.fetchBatch();
} while (!this.isDead || (this.documents?.length ?? 0) !== 0);
return false;
}
/** Get the next available document from the cursor, returns null if no more documents are available. */
async next() {
if (this[kId] === bson_1.Long.ZERO) {
if (this.cursorId === bson_1.Long.ZERO) {
throw new error_1.MongoCursorExhaustedError();
}
return await next(this, { blocking: true, transform: true, shift: true });
do {
const doc = this.documents?.shift(this.cursorOptions);
if (doc != null) {
if (this.transform != null)
return await this.transformDocument(doc);
return doc;
}
await this.fetchBatch();
} while (!this.isDead || (this.documents?.length ?? 0) !== 0);
return null;
}

@@ -252,6 +245,19 @@ /**

async tryNext() {
if (this[kId] === bson_1.Long.ZERO) {
if (this.cursorId === bson_1.Long.ZERO) {
throw new error_1.MongoCursorExhaustedError();
}
return await next(this, { blocking: false, transform: true, shift: true });
let doc = this.documents?.shift(this.cursorOptions);
if (doc != null) {
if (this.transform != null)
return await this.transformDocument(doc);
return doc;
}
await this.fetchBatch();
doc = this.documents?.shift(this.cursorOptions);
if (doc != null) {
if (this.transform != null)
return await this.transformDocument(doc);
return doc;
}
return null;
}

@@ -278,5 +284,3 @@ /**

async close() {
const needsToEmitClosed = !this[kClosed];
this[kClosed] = true;
await cleanupCursor(this, { needsToEmitClosed });
await this.cleanup();
}

@@ -303,3 +307,3 @@ /**

addCursorFlag(flag, value) {
assertUninitialized(this);
this.throwIfInitialized();
if (!exports.CURSOR_FLAGS.includes(flag)) {

@@ -311,3 +315,3 @@ throw new error_1.MongoInvalidArgumentError(`Flag ${flag} is not one of ${exports.CURSOR_FLAGS}`);

}
this[kOptions][flag] = value;
this.cursorOptions[flag] = value;
return this;

@@ -358,6 +362,6 @@ }

map(transform) {
assertUninitialized(this);
const oldTransform = this[kTransform]; // TODO(NODE-3283): Improve transform typing
this.throwIfInitialized();
const oldTransform = this.transform;
if (oldTransform) {
this[kTransform] = doc => {
this.transform = doc => {
return transform(oldTransform(doc));

@@ -367,3 +371,3 @@ };

else {
this[kTransform] = transform;
this.transform = transform;
}

@@ -378,8 +382,8 @@ return this;

withReadPreference(readPreference) {
assertUninitialized(this);
this.throwIfInitialized();
if (readPreference instanceof read_preference_1.ReadPreference) {
this[kOptions].readPreference = readPreference;
this.cursorOptions.readPreference = readPreference;
}
else if (typeof readPreference === 'string') {
this[kOptions].readPreference = read_preference_1.ReadPreference.fromString(readPreference);
this.cursorOptions.readPreference = read_preference_1.ReadPreference.fromString(readPreference);
}

@@ -397,6 +401,6 @@ else {

withReadConcern(readConcern) {
assertUninitialized(this);
this.throwIfInitialized();
const resolvedReadConcern = read_concern_1.ReadConcern.fromOptions({ readConcern });
if (resolvedReadConcern) {
this[kOptions].readConcern = resolvedReadConcern;
this.cursorOptions.readConcern = resolvedReadConcern;
}

@@ -411,7 +415,7 @@ return this;

maxTimeMS(value) {
assertUninitialized(this);
this.throwIfInitialized();
if (typeof value !== 'number') {
throw new error_1.MongoInvalidArgumentError('Argument for maxTimeMS must be a number');
}
this[kOptions].maxTimeMS = value;
this.cursorOptions.maxTimeMS = value;
return this;

@@ -425,4 +429,4 @@ }

batchSize(value) {
assertUninitialized(this);
if (this[kOptions].tailable) {
this.throwIfInitialized();
if (this.cursorOptions.tailable) {
throw new error_1.MongoTailableCursorError('Tailable cursor does not support batchSize');

@@ -433,3 +437,3 @@ }

}
this[kOptions].batchSize = value;
this.cursorOptions.batchSize = value;
return this;

@@ -443,11 +447,11 @@ }

rewind() {
if (!this[kInitialized]) {
if (!this.initialized) {
return;
}
this[kId] = null;
this[kDocuments].clear();
this[kClosed] = false;
this[kKilled] = false;
this[kInitialized] = false;
const session = this[kSession];
this.cursorId = null;
this.documents?.clear();
this.isClosed = false;
this.isKilled = false;
this.initialized = false;
const session = this.cursorSession;
if (session) {

@@ -460,3 +464,3 @@ // We only want to end this session if we created it, and it hasn't ended yet

}
this[kSession] = this.client.startSession({ owner: this, explicit: false });
this.cursorSession = this.cursorClient.startSession({ owner: this, explicit: false });
}

@@ -466,11 +470,15 @@ }

/** @internal */
async getMore(batchSize, useCursorResponse = false) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const getMoreOperation = new get_more_1.GetMoreOperation(this[kNamespace], this[kId], this[kServer], {
...this[kOptions],
session: this[kSession],
batchSize,
useCursorResponse
async getMore(batchSize) {
if (this.cursorId == null) {
throw new error_1.MongoRuntimeError('Unexpected null cursor id. A cursor creating command should have set this');
}
if (this.selectedServer == null) {
throw new error_1.MongoRuntimeError('Unexpected null selectedServer. A cursor creating command should have set this');
}
const getMoreOperation = new get_more_1.GetMoreOperation(this.cursorNamespace, this.cursorId, this.selectedServer, {
...this.cursorOptions,
session: this.cursorSession,
batchSize
});
return await (0, execute_operation_1.executeOperation)(this[kClient], getMoreOperation);
return await (0, execute_operation_1.executeOperation)(this.cursorClient, getMoreOperation);
}

@@ -484,114 +492,52 @@ /**

*/
async [kInit]() {
async cursorInit() {
try {
const state = await this._initialize(this[kSession]);
const state = await this._initialize(this.cursorSession);
const response = state.response;
this[kServer] = state.server;
if (responses_1.CursorResponse.is(response)) {
this[kId] = response.id;
if (response.ns)
this[kNamespace] = response.ns;
this[kDocuments] = response;
}
else if (response.cursor) {
// TODO(NODE-2674): Preserve int64 sent from MongoDB
this[kId] =
typeof response.cursor.id === 'number'
? bson_1.Long.fromNumber(response.cursor.id)
: typeof response.cursor.id === 'bigint'
? bson_1.Long.fromBigInt(response.cursor.id)
: response.cursor.id;
if (response.cursor.ns) {
this[kNamespace] = (0, utils_1.ns)(response.cursor.ns);
}
this[kDocuments].pushMany(response.cursor.firstBatch);
}
// When server responses return without a cursor document, we close this cursor
// and return the raw server response. This is often the case for explain commands
// for example
if (this[kId] == null) {
this[kId] = bson_1.Long.ZERO;
// TODO(NODE-3286): ExecutionResult needs to accept a generic parameter
this[kDocuments].push(state.response);
}
// the cursor is now initialized, even if it is dead
this[kInitialized] = true;
this.selectedServer = state.server;
this.cursorId = response.id;
this.cursorNamespace = response.ns ?? this.namespace;
this.documents = response;
this.initialized = true; // the cursor is now initialized, even if it is dead
}
catch (error) {
// the cursor is now initialized, even if an error occurred
this[kInitialized] = true;
await cleanupCursor(this, { error });
this.initialized = true;
await this.cleanup(error);
throw error;
}
if (this.isDead) {
await cleanupCursor(this, undefined);
await this.cleanup();
}
return;
}
}
/** @event */
AbstractCursor.CLOSE = 'close';
exports.AbstractCursor = AbstractCursor;
async function next(cursor, { blocking, transform, shift }) {
if (cursor.closed) {
if (!shift)
return false;
return null;
}
do {
if (cursor[kId] == null) {
// All cursors must operate within a session, one must be made implicitly if not explicitly provided
await cursor[kInit]();
/** @internal Attempt to obtain more documents */
async fetchBatch() {
if (this.isClosed) {
return;
}
if (cursor[kDocuments].length !== 0) {
if (!shift)
return true;
const doc = cursor[kDocuments].shift(cursor[kOptions]);
if (doc != null && transform && cursor[kTransform]) {
try {
return cursor[kTransform](doc);
}
catch (error) {
try {
await cleanupCursor(cursor, { error, needsToEmitClosed: true });
}
catch (error) {
// `cleanupCursor` should never throw, squash and throw the original error
(0, utils_1.squashError)(error);
}
throw error;
}
}
return doc;
}
if (cursor.isDead) {
if (this.isDead) {
// if the cursor is dead, we clean it up
// cleanupCursor should never throw, but if it does it indicates a bug in the driver
// and we should surface the error
await cleanupCursor(cursor, {});
if (!shift)
return false;
return null;
await this.cleanup();
return;
}
if (this.cursorId == null) {
await this.cursorInit();
// If the cursor died or returned documents, return
if ((this.documents?.length ?? 0) !== 0 || this.isDead)
return;
// Otherwise, run a getMore
}
// otherwise need to call getMore
const batchSize = cursor[kOptions].batchSize || 1000;
const batchSize = this.cursorOptions.batchSize || 1000;
try {
const response = await cursor.getMore(batchSize);
if (responses_1.CursorResponse.is(response)) {
cursor[kId] = response.id;
cursor[kDocuments] = response;
}
else if (response) {
const cursorId = typeof response.cursor.id === 'number'
? bson_1.Long.fromNumber(response.cursor.id)
: typeof response.cursor.id === 'bigint'
? bson_1.Long.fromBigInt(response.cursor.id)
: response.cursor.id;
cursor[kDocuments].pushMany(response.cursor.nextBatch);
cursor[kId] = cursorId;
}
const response = await this.getMore(batchSize);
this.cursorId = response.id;
this.documents = response;
}
catch (error) {
try {
await cleanupCursor(cursor, { error, needsToEmitClosed: true });
await this.cleanup(error);
}

@@ -604,3 +550,3 @@ catch (error) {

}
if (cursor.isDead) {
if (this.isDead) {
// If we successfully received a response from a cursor BUT the cursor indicates that it is exhausted,

@@ -613,85 +559,81 @@ // we intentionally clean up the cursor to release its session back into the pool before the cursor

// and we should surface the error
await cleanupCursor(cursor, {});
await this.cleanup();
}
if (cursor[kDocuments].length === 0 && blocking === false) {
if (!shift)
return false;
return null;
}
/** @internal */
async cleanup(error) {
this.isClosed = true;
const session = this.cursorSession;
try {
if (!this.isKilled &&
this.cursorId &&
!this.cursorId.isZero() &&
this.cursorNamespace &&
this.selectedServer &&
!session.hasEnded) {
this.isKilled = true;
const cursorId = this.cursorId;
this.cursorId = bson_1.Long.ZERO;
await (0, execute_operation_1.executeOperation)(this.cursorClient, new kill_cursors_1.KillCursorsOperation(cursorId, this.cursorNamespace, this.selectedServer, {
session
}));
}
}
} while (!cursor.isDead || cursor[kDocuments].length !== 0);
if (!shift)
return false;
return null;
}
async function cleanupCursor(cursor, options) {
const cursorId = cursor[kId];
const cursorNs = cursor[kNamespace];
const server = cursor[kServer];
const session = cursor[kSession];
const error = options?.error;
// Cursors only emit closed events once the client-side cursor has been exhausted fully or there
// was an error. Notably, when the server returns a cursor id of 0 and a non-empty batch, we
// cleanup the cursor but don't emit a `close` event.
const needsToEmitClosed = options?.needsToEmitClosed ?? cursor[kDocuments].length === 0;
if (error) {
if (cursor.loadBalanced && error instanceof error_1.MongoNetworkError) {
return await completeCleanup();
catch (error) {
(0, utils_1.squashError)(error);
}
}
if (cursorId == null || server == null || cursorId.isZero() || cursorNs == null) {
if (needsToEmitClosed) {
cursor[kClosed] = true;
cursor[kId] = bson_1.Long.ZERO;
cursor.emit(AbstractCursor.CLOSE);
}
if (session) {
if (session.owner === cursor) {
finally {
if (session?.owner === this) {
await session.endSession({ error });
return;
}
if (!session.inTransaction()) {
if (!session?.inTransaction()) {
(0, sessions_1.maybeClearPinnedConnection)(session, { error });
}
this.emitClose();
}
return;
}
async function completeCleanup() {
if (session) {
if (session.owner === cursor) {
try {
await session.endSession({ error });
}
finally {
cursor.emit(AbstractCursor.CLOSE);
}
return;
/** @internal */
emitClose() {
try {
if (!this.hasEmittedClose && ((this.documents?.length ?? 0) === 0 || this.isClosed)) {
// @ts-expect-error: CursorEvents is generic so Parameters<CursorEvents["close"]> may not be assignable to `[]`. Not sure how to require extenders do not add parameters.
this.emit('close');
}
if (!session.inTransaction()) {
(0, sessions_1.maybeClearPinnedConnection)(session, { error });
}
finally {
this.hasEmittedClose = true;
}
}
/** @internal */
async transformDocument(document) {
if (this.transform == null)
return document;
try {
const transformedDocument = this.transform(document);
// eslint-disable-next-line no-restricted-syntax
if (transformedDocument === null) {
const TRANSFORM_TO_NULL_ERROR = 'Cursor returned a `null` document, but the cursor is not exhausted. Mapping documents to `null` is not supported in the cursor transform.';
throw new error_1.MongoAPIError(TRANSFORM_TO_NULL_ERROR);
}
return transformedDocument;
}
cursor.emit(AbstractCursor.CLOSE);
return;
catch (transformError) {
try {
await this.close();
}
catch (closeError) {
(0, utils_1.squashError)(closeError);
}
throw transformError;
}
}
cursor[kKilled] = true;
if (session.hasEnded) {
return await completeCleanup();
/** @internal */
throwIfInitialized() {
if (this.initialized)
throw new error_1.MongoCursorInUseError();
}
try {
await (0, execute_operation_1.executeOperation)(cursor[kClient], new kill_cursors_1.KillCursorsOperation(cursorId, cursorNs, server, { session }));
}
catch (error) {
(0, utils_1.squashError)(error);
}
finally {
await completeCleanup();
}
}
/** @internal */
function assertUninitialized(cursor) {
if (cursor[kInitialized]) {
throw new error_1.MongoCursorInUseError();
}
}
exports.assertUninitialized = assertUninitialized;
/** @event */
AbstractCursor.CLOSE = 'close';
exports.AbstractCursor = AbstractCursor;
class ReadableCursorStream extends stream_1.Readable {

@@ -719,4 +661,8 @@ constructor(cursor) {

_readNext() {
if (this._cursor.id === bson_1.Long.ZERO) {
this.push(null);
return;
}
// eslint-disable-next-line github/no-then
next(this._cursor, { blocking: true, transform: true, shift: true }).then(result => {
this._cursor.next().then(result => {
if (result == null) {

@@ -723,0 +669,0 @@ this.push(null);

@@ -8,6 +8,2 @@ "use strict";

const abstract_cursor_1 = require("./abstract_cursor");
/** @internal */
const kPipeline = Symbol('pipeline');
/** @internal */
const kOptions = Symbol('options');
/**

@@ -24,12 +20,9 @@ * The **AggregationCursor** class is an internal class that embodies an aggregation cursor on MongoDB

super(client, namespace, options);
this[kPipeline] = pipeline;
this[kOptions] = options;
this.pipeline = pipeline;
this.aggregateOptions = options;
}
get pipeline() {
return this[kPipeline];
}
clone() {
const clonedOptions = (0, utils_1.mergeOptions)({}, this[kOptions]);
const clonedOptions = (0, utils_1.mergeOptions)({}, this.aggregateOptions);
delete clonedOptions.session;
return new AggregationCursor(this.client, this.namespace, this[kPipeline], {
return new AggregationCursor(this.client, this.namespace, this.pipeline, {
...clonedOptions

@@ -43,4 +36,4 @@ });

async _initialize(session) {
const aggregateOperation = new aggregate_1.AggregateOperation(this.namespace, this[kPipeline], {
...this[kOptions],
const aggregateOperation = new aggregate_1.AggregateOperation(this.namespace, this.pipeline, {
...this.aggregateOptions,
...this.cursorOptions,

@@ -50,3 +43,2 @@ session

const response = await (0, execute_operation_1.executeOperation)(this.client, aggregateOperation);
// TODO: NODE-2882
return { server: aggregateOperation.server, session, response };

@@ -56,11 +48,11 @@ }

async explain(verbosity) {
return await (0, execute_operation_1.executeOperation)(this.client, new aggregate_1.AggregateOperation(this.namespace, this[kPipeline], {
...this[kOptions],
return (await (0, execute_operation_1.executeOperation)(this.client, new aggregate_1.AggregateOperation(this.namespace, this.pipeline, {
...this.aggregateOptions,
...this.cursorOptions,
explain: verbosity ?? true
}));
}))).shift(this.aggregateOptions);
}
addStage(stage) {
(0, abstract_cursor_1.assertUninitialized)(this);
this[kPipeline].push(stage);
this.throwIfInitialized();
this.pipeline.push(stage);
return this;

@@ -67,0 +59,0 @@ }

@@ -15,5 +15,5 @@ "use strict";

this.pipeline = pipeline;
this.options = options;
this.changeStreamCursorOptions = options;
this._resumeToken = null;
this.startAtOperationTime = options.startAtOperationTime;
this.startAtOperationTime = options.startAtOperationTime ?? null;
if (options.startAfter) {

@@ -35,3 +35,3 @@ this.resumeToken = options.startAfter;

const options = {
...this.options
...this.changeStreamCursorOptions
};

@@ -42,3 +42,3 @@ for (const key of ['resumeAfter', 'startAfter', 'startAtOperationTime']) {

if (this.resumeToken != null) {
if (this.options.startAfter && !this.hasReceived) {
if (this.changeStreamCursorOptions.startAfter && !this.hasReceived) {
options.startAfter = this.resumeToken;

@@ -65,8 +65,7 @@ }

_processBatch(response) {
const cursor = response.cursor;
if (cursor.postBatchResumeToken) {
this.postBatchResumeToken = response.cursor.postBatchResumeToken;
const batch = 'firstBatch' in response.cursor ? response.cursor.firstBatch : response.cursor.nextBatch;
if (batch.length === 0) {
this.resumeToken = cursor.postBatchResumeToken;
const { postBatchResumeToken } = response;
if (postBatchResumeToken) {
this.postBatchResumeToken = postBatchResumeToken;
if (response.batchSize === 0) {
this.resumeToken = postBatchResumeToken;
}

@@ -83,3 +82,3 @@ }

...this.cursorOptions,
...this.options,
...this.changeStreamCursorOptions,
session

@@ -91,4 +90,4 @@ });

if (this.startAtOperationTime == null &&
this.resumeAfter == null &&
this.startAfter == null &&
this.changeStreamCursorOptions.resumeAfter == null &&
this.changeStreamCursorOptions.startAfter == null &&
this.maxWireVersion >= 7) {

@@ -100,3 +99,2 @@ this.startAtOperationTime = response.operationTime;

this.emit(constants_1.RESPONSE);
// TODO: NODE-2882
return { server, session, response };

@@ -103,0 +101,0 @@ }

"use strict";
var _a;
Object.defineProperty(exports, "__esModule", { value: true });

@@ -13,8 +12,2 @@ exports.FindCursor = exports.FLAGS = void 0;

const abstract_cursor_1 = require("./abstract_cursor");
/** @internal */
const kFilter = Symbol('filter');
/** @internal */
const kNumReturned = Symbol('numReturned');
/** @internal */
const kBuiltOptions = Symbol('builtOptions');
/** @public Flags allowed for cursor */

@@ -35,13 +28,13 @@ exports.FLAGS = [

/** @internal */
this[_a] = 0;
this[kFilter] = filter;
this[kBuiltOptions] = options;
this.numReturned = 0;
this.cursorFilter = filter;
this.findOptions = options;
if (options.sort != null) {
this[kBuiltOptions].sort = (0, sort_1.formatSort)(options.sort);
this.findOptions.sort = (0, sort_1.formatSort)(options.sort);
}
}
clone() {
const clonedOptions = (0, utils_1.mergeOptions)({}, this[kBuiltOptions]);
const clonedOptions = (0, utils_1.mergeOptions)({}, this.findOptions);
delete clonedOptions.session;
return new FindCursor(this.client, this.namespace, this[kFilter], {
return new FindCursor(this.client, this.namespace, this.cursorFilter, {
...clonedOptions

@@ -55,4 +48,4 @@ });

async _initialize(session) {
const findOperation = new find_1.FindOperation(this.namespace, this[kFilter], {
...this[kBuiltOptions],
const findOperation = new find_1.FindOperation(this.namespace, this.cursorFilter, {
...this.findOptions,
...this.cursorOptions,

@@ -63,10 +56,3 @@ session

// the response is not a cursor when `explain` is enabled
if (responses_1.CursorResponse.is(response)) {
this[kNumReturned] = response.batchSize;
}
else {
// Can be an explain response, hence the ?. on everything
this[kNumReturned] = this[kNumReturned] + (response?.cursor?.firstBatch?.length ?? 0);
}
// TODO: NODE-2882
this.numReturned = response.batchSize;
return { server: findOperation.server, session, response };

@@ -76,6 +62,6 @@ }

async getMore(batchSize) {
const numReturned = this[kNumReturned];
const numReturned = this.numReturned;
if (numReturned) {
// TODO(DRIVERS-1448): Remove logic to enforce `limit` in the driver
const limit = this[kBuiltOptions].limit;
const limit = this.findOptions.limit;
batchSize =

@@ -101,10 +87,5 @@ limit && limit > 0 && numReturned + batchSize > limit ? limit - numReturned : batchSize;

}
const response = await super.getMore(batchSize, false);
const response = await super.getMore(batchSize);
// TODO: wrap this in some logic to prevent it from happening if we don't need this support
if (responses_1.CursorResponse.is(response)) {
this[kNumReturned] = this[kNumReturned] + response.batchSize;
}
else {
this[kNumReturned] = this[kNumReturned] + (response?.cursor?.nextBatch?.length ?? 0);
}
this.numReturned = this.numReturned + response.batchSize;
return response;

@@ -121,4 +102,4 @@ }

}
return await (0, execute_operation_1.executeOperation)(this.client, new count_1.CountOperation(this.namespace, this[kFilter], {
...this[kBuiltOptions],
return await (0, execute_operation_1.executeOperation)(this.client, new count_1.CountOperation(this.namespace, this.cursorFilter, {
...this.findOptions,
...this.cursorOptions,

@@ -130,12 +111,12 @@ ...options

async explain(verbosity) {
return await (0, execute_operation_1.executeOperation)(this.client, new find_1.FindOperation(this.namespace, this[kFilter], {
...this[kBuiltOptions],
return (await (0, execute_operation_1.executeOperation)(this.client, new find_1.FindOperation(this.namespace, this.cursorFilter, {
...this.findOptions,
...this.cursorOptions,
explain: verbosity ?? true
}));
}))).shift(this.findOptions);
}
/** Set the cursor query */
filter(filter) {
(0, abstract_cursor_1.assertUninitialized)(this);
this[kFilter] = filter;
this.throwIfInitialized();
this.cursorFilter = filter;
return this;

@@ -149,4 +130,4 @@ }

hint(hint) {
(0, abstract_cursor_1.assertUninitialized)(this);
this[kBuiltOptions].hint = hint;
this.throwIfInitialized();
this.findOptions.hint = hint;
return this;

@@ -160,4 +141,4 @@ }

min(min) {
(0, abstract_cursor_1.assertUninitialized)(this);
this[kBuiltOptions].min = min;
this.throwIfInitialized();
this.findOptions.min = min;
return this;

@@ -171,4 +152,4 @@ }

max(max) {
(0, abstract_cursor_1.assertUninitialized)(this);
this[kBuiltOptions].max = max;
this.throwIfInitialized();
this.findOptions.max = max;
return this;

@@ -184,4 +165,4 @@ }

returnKey(value) {
(0, abstract_cursor_1.assertUninitialized)(this);
this[kBuiltOptions].returnKey = value;
this.throwIfInitialized();
this.findOptions.returnKey = value;
return this;

@@ -195,4 +176,4 @@ }

showRecordId(value) {
(0, abstract_cursor_1.assertUninitialized)(this);
this[kBuiltOptions].showRecordId = value;
this.throwIfInitialized();
this.findOptions.showRecordId = value;
return this;

@@ -207,3 +188,3 @@ }

addQueryModifier(name, value) {
(0, abstract_cursor_1.assertUninitialized)(this);
this.throwIfInitialized();
if (name[0] !== '$') {

@@ -217,30 +198,30 @@ throw new error_1.MongoInvalidArgumentError(`${name} is not a valid query modifier`);

case 'comment':
this[kBuiltOptions].comment = value;
this.findOptions.comment = value;
break;
case 'explain':
this[kBuiltOptions].explain = value;
this.findOptions.explain = value;
break;
case 'hint':
this[kBuiltOptions].hint = value;
this.findOptions.hint = value;
break;
case 'max':
this[kBuiltOptions].max = value;
this.findOptions.max = value;
break;
case 'maxTimeMS':
this[kBuiltOptions].maxTimeMS = value;
this.findOptions.maxTimeMS = value;
break;
case 'min':
this[kBuiltOptions].min = value;
this.findOptions.min = value;
break;
case 'orderby':
this[kBuiltOptions].sort = (0, sort_1.formatSort)(value);
this.findOptions.sort = (0, sort_1.formatSort)(value);
break;
case 'query':
this[kFilter] = value;
this.cursorFilter = value;
break;
case 'returnKey':
this[kBuiltOptions].returnKey = value;
this.findOptions.returnKey = value;
break;
case 'showDiskLoc':
this[kBuiltOptions].showRecordId = value;
this.findOptions.showRecordId = value;
break;

@@ -258,4 +239,4 @@ default:

comment(value) {
(0, abstract_cursor_1.assertUninitialized)(this);
this[kBuiltOptions].comment = value;
this.throwIfInitialized();
this.findOptions.comment = value;
return this;

@@ -269,7 +250,7 @@ }

maxAwaitTimeMS(value) {
(0, abstract_cursor_1.assertUninitialized)(this);
this.throwIfInitialized();
if (typeof value !== 'number') {
throw new error_1.MongoInvalidArgumentError('Argument for maxAwaitTimeMS must be a number');
}
this[kBuiltOptions].maxAwaitTimeMS = value;
this.findOptions.maxAwaitTimeMS = value;
return this;

@@ -283,7 +264,7 @@ }

maxTimeMS(value) {
(0, abstract_cursor_1.assertUninitialized)(this);
this.throwIfInitialized();
if (typeof value !== 'number') {
throw new error_1.MongoInvalidArgumentError('Argument for maxTimeMS must be a number');
}
this[kBuiltOptions].maxTimeMS = value;
this.findOptions.maxTimeMS = value;
return this;

@@ -332,4 +313,4 @@ }

project(value) {
(0, abstract_cursor_1.assertUninitialized)(this);
this[kBuiltOptions].projection = value;
this.throwIfInitialized();
this.findOptions.projection = value;
return this;

@@ -344,7 +325,7 @@ }

sort(sort, direction) {
(0, abstract_cursor_1.assertUninitialized)(this);
if (this[kBuiltOptions].tailable) {
this.throwIfInitialized();
if (this.findOptions.tailable) {
throw new error_1.MongoTailableCursorError('Tailable cursor does not support sorting');
}
this[kBuiltOptions].sort = (0, sort_1.formatSort)(sort, direction);
this.findOptions.sort = (0, sort_1.formatSort)(sort, direction);
return this;

@@ -359,4 +340,4 @@ }

allowDiskUse(allow = true) {
(0, abstract_cursor_1.assertUninitialized)(this);
if (!this[kBuiltOptions].sort) {
this.throwIfInitialized();
if (!this.findOptions.sort) {
throw new error_1.MongoInvalidArgumentError('Option "allowDiskUse" requires a sort specification');

@@ -366,6 +347,6 @@ }

if (!allow) {
this[kBuiltOptions].allowDiskUse = false;
this.findOptions.allowDiskUse = false;
return this;
}
this[kBuiltOptions].allowDiskUse = true;
this.findOptions.allowDiskUse = true;
return this;

@@ -379,4 +360,4 @@ }

collation(value) {
(0, abstract_cursor_1.assertUninitialized)(this);
this[kBuiltOptions].collation = value;
this.throwIfInitialized();
this.findOptions.collation = value;
return this;

@@ -390,4 +371,4 @@ }

limit(value) {
(0, abstract_cursor_1.assertUninitialized)(this);
if (this[kBuiltOptions].tailable) {
this.throwIfInitialized();
if (this.findOptions.tailable) {
throw new error_1.MongoTailableCursorError('Tailable cursor does not support limit');

@@ -398,3 +379,3 @@ }

}
this[kBuiltOptions].limit = value;
this.findOptions.limit = value;
return this;

@@ -408,4 +389,4 @@ }

skip(value) {
(0, abstract_cursor_1.assertUninitialized)(this);
if (this[kBuiltOptions].tailable) {
this.throwIfInitialized();
if (this.findOptions.tailable) {
throw new error_1.MongoTailableCursorError('Tailable cursor does not support skip');

@@ -416,3 +397,3 @@ }

}
this[kBuiltOptions].skip = value;
this.findOptions.skip = value;
return this;

@@ -422,3 +403,2 @@ }

exports.FindCursor = FindCursor;
_a = kNumReturned;
//# sourceMappingURL=find_cursor.js.map

@@ -29,3 +29,2 @@ "use strict";

const response = await (0, execute_operation_1.executeOperation)(this.parent.client, operation);
// TODO: NODE-2882
return { server: operation.server, session, response };

@@ -32,0 +31,0 @@ }

@@ -28,3 +28,2 @@ "use strict";

const response = await (0, execute_operation_1.executeOperation)(this.parent.client, operation);
// TODO: NODE-2882
return { server: operation.server, session, response };

@@ -31,0 +30,0 @@ }

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RunCommandCursor = void 0;
const responses_1 = require("../cmap/wire_protocol/responses");
const error_1 = require("../error");

@@ -68,8 +69,6 @@ const execute_operation_1 = require("../operations/execute_operation");

session: session,
readPreference: this.cursorOptions.readPreference
readPreference: this.cursorOptions.readPreference,
responseType: responses_1.CursorResponse
});
const response = await (0, execute_operation_1.executeOperation)(this.client, operation);
if (response.cursor == null) {
throw new error_1.MongoUnexpectedServerResponseError('Expected server to respond with cursor');
}
return {

@@ -87,4 +86,3 @@ server: operation.server,

session: this.session,
...this.getMoreOptions,
useCursorResponse: false
...this.getMoreOptions
});

@@ -91,0 +89,0 @@ return await (0, execute_operation_1.executeOperation)(this.client, getMoreOperation);

@@ -52,3 +52,4 @@ "use strict";

UnsatisfiableWriteConcern: 100,
Reauthenticate: 391
Reauthenticate: 391,
ReadConcernMajorityNotAvailableYet: 134
});

@@ -677,4 +678,4 @@ // From spec@https://github.com/mongodb/specifications/blob/f93d78191f3db2898a59013a7ed5650352ef6da8/source/change-streams/change-streams.rst#resumable-error

**/
constructor(message) {
super(message);
constructor(message, options) {
super(message, options);
}

@@ -1052,12 +1053,2 @@ get name() {

exports.MongoServerSelectionError = MongoServerSelectionError;
function makeWriteConcernResultObject(input) {
const output = Object.assign({}, input);
if (output.ok === 0) {
output.ok = 1;
delete output.errmsg;
delete output.code;
delete output.codeName;
}
return output;
}
/**

@@ -1080,11 +1071,6 @@ * An error thrown when the server reports a writeConcernError

**/
constructor(message, result) {
if (result && Array.isArray(result.errorLabels)) {
message.errorLabels = result.errorLabels;
}
super(message);
this.errInfo = message.errInfo;
if (result != null) {
this.result = makeWriteConcernResultObject(result);
}
constructor(result) {
super({ ...result, ...result.writeConcernError });
this.errInfo = result.writeConcernError.errInfo;
this.result = result;
}

@@ -1109,3 +1095,4 @@ get name() {

exports.MONGODB_ERROR_CODES.NotPrimaryOrSecondary,
exports.MONGODB_ERROR_CODES.ExceededTimeLimit
exports.MONGODB_ERROR_CODES.ExceededTimeLimit,
exports.MONGODB_ERROR_CODES.ReadConcernMajorityNotAvailableYet
]);

@@ -1112,0 +1099,0 @@ // see: https://github.com/mongodb/specifications/blob/master/source/retryable-writes/retryable-writes.rst#terms

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AggregateOperation = exports.DB_AGGREGATE_COLLECTION = void 0;
const responses_1 = require("../cmap/wire_protocol/responses");
const error_1 = require("../error");

@@ -85,4 +86,3 @@ const utils_1 = require("../utils");

}
const res = await super.executeCommand(server, session, command);
return res;
return await super.executeCommand(server, session, command, this.explain ? responses_1.ExplainedCursorResponse : responses_1.CursorResponse);
}

@@ -89,0 +89,0 @@ }

@@ -29,4 +29,3 @@ "use strict";

// Execute the bulk
const result = await bulk.execute({ ...options, session });
return result;
return await bulk.execute({ ...options, session });
}

@@ -33,0 +32,0 @@ }

@@ -43,4 +43,3 @@ "use strict";

}
async executeCommand(server, session, cmd) {
// TODO: consider making this a non-enumerable property
async executeCommand(server, session, cmd, responseType) {
this.server = server;

@@ -75,3 +74,3 @@ const options = {

}
return await server.command(this.ns, cmd, options);
return await server.command(this.ns, cmd, options, responseType);
}

@@ -78,0 +77,0 @@ }

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FindOperation = void 0;
const responses_1 = require("../cmap/wire_protocol/responses");
const error_1 = require("../error");

@@ -38,3 +39,3 @@ const read_concern_1 = require("../read_concern");

session
}, undefined);
}, this.explain ? responses_1.ExplainedCursorResponse : responses_1.CursorResponse);
}

@@ -41,0 +42,0 @@ }

@@ -57,3 +57,3 @@ "use strict";

};
return await server.command(this.ns, getMoreCmd, commandOptions, this.options.useCursorResponse ? responses_1.CursorResponse : undefined);
return await server.command(this.ns, getMoreCmd, commandOptions, responses_1.CursorResponse);
}

@@ -60,0 +60,0 @@ }

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ListIndexesOperation = exports.DropIndexOperation = exports.CreateIndexesOperation = void 0;
const responses_1 = require("../cmap/wire_protocol/responses");
const error_1 = require("../error");

@@ -164,3 +165,3 @@ const utils_1 = require("../utils");

}
return await super.executeCommand(server, session, command);
return await super.executeCommand(server, session, command, responses_1.CursorResponse);
}

@@ -167,0 +168,0 @@ }

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ListCollectionsOperation = void 0;
const responses_1 = require("../cmap/wire_protocol/responses");
const utils_1 = require("../utils");

@@ -25,3 +26,3 @@ const command_1 = require("./command");

async execute(server, session) {
return await super.executeCommand(server, session, this.generateCommand((0, utils_1.maxWireVersion)(server)));
return await super.executeCommand(server, session, this.generateCommand((0, utils_1.maxWireVersion)(server)), responses_1.CursorResponse);
}

@@ -28,0 +29,0 @@ /* This is here for the purpose of unit testing the final command that gets sent. */

@@ -23,3 +23,3 @@ "use strict";

session
});
}, this.options.responseType);
return res;

@@ -26,0 +26,0 @@ }

@@ -54,3 +54,4 @@ "use strict";

}
return await super.executeCommand(server, session, command);
const res = await super.executeCommand(server, session, command);
return res;
}

@@ -57,0 +58,0 @@ }

@@ -12,2 +12,3 @@ "use strict";

const utils_1 = require("../utils");
const write_concern_1 = require("../write_concern");
const common_1 = require("./common");

@@ -167,3 +168,5 @@ const monitor_1 = require("./monitor");

try {
return await conn.command(ns, cmd, finalOptions, responseType);
const res = await conn.command(ns, cmd, finalOptions, responseType);
(0, write_concern_1.throwIfWriteConcernError)(res);
return res;
}

@@ -179,3 +182,5 @@ catch (commandError) {

try {
return await conn.command(ns, cmd, finalOptions, responseType);
const res = await conn.command(ns, cmd, finalOptions, responseType);
(0, write_concern_1.throwIfWriteConcernError)(res);
return res;
}

@@ -182,0 +187,0 @@ catch (commandError) {

@@ -68,3 +68,3 @@ "use strict";

this.clientOptions = clientOptions;
this.timeoutMS = options.defaultTimeoutMS ?? client.options?.timeoutMS;
this.timeoutMS = options.defaultTimeoutMS ?? client.s.options?.timeoutMS;
this.explicit = !!options.explicit;

@@ -71,0 +71,0 @@ this[kServerSession] = this.explicit ? this.sessionPool.acquire() : null;

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.COSMOS_DB_CHECK = exports.DOCUMENT_DB_CHECK = exports.request = exports.get = exports.matchesParentDomain = exports.parseUnsignedInteger = exports.parseInteger = exports.compareObjectId = exports.commandSupportsReadConcern = exports.shuffle = exports.supportsRetryableWrites = exports.enumToString = exports.emitWarningOnce = exports.emitWarning = exports.MONGODB_WARNING_CODE = exports.DEFAULT_PK_FACTORY = exports.HostAddress = exports.BufferPool = exports.List = exports.deepCopy = exports.isRecord = exports.setDifference = exports.isHello = exports.isSuperset = exports.resolveOptions = exports.hasAtomicOperators = exports.calculateDurationInMs = exports.now = exports.makeStateMachine = exports.errorStrictEqual = exports.arrayStrictEqual = exports.maxWireVersion = exports.uuidV4 = exports.makeCounter = exports.MongoDBCollectionNamespace = exports.MongoDBNamespace = exports.ns = exports.getTopology = exports.decorateWithExplain = exports.decorateWithReadConcern = exports.decorateWithCollation = exports.isPromiseLike = exports.applyRetryableWrites = exports.filterOptions = exports.mergeOptions = exports.isObject = exports.normalizeHintField = exports.hostMatchesWildcards = exports.isUint8Array = exports.ByteUtils = void 0;
exports.noop = exports.fileIsAccessible = exports.maybeAddIdToDocuments = exports.once = exports.randomBytes = exports.squashError = exports.promiseWithResolvers = exports.isHostMatch = exports.COSMOS_DB_MSG = exports.DOCUMENT_DB_MSG = void 0;
exports.decorateDecryptionResult = exports.noop = exports.fileIsAccessible = exports.maybeAddIdToDocuments = exports.once = exports.randomBytes = exports.squashError = exports.promiseWithResolvers = exports.isHostMatch = exports.COSMOS_DB_MSG = exports.DOCUMENT_DB_MSG = void 0;
const crypto = require("crypto");

@@ -1099,2 +1099,46 @@ const fs_1 = require("fs");

exports.noop = noop;
/**
* Recurse through the (identically-shaped) `decrypted` and `original`
* objects and attach a `decryptedKeys` property on each sub-object that
* contained encrypted fields. Because we only call this on BSON responses,
* we do not need to worry about circular references.
*
* @internal
*/
function decorateDecryptionResult(decrypted, original, isTopLevelDecorateCall = true) {
if (isTopLevelDecorateCall) {
// The original value could have been either a JS object or a BSON buffer
if (Buffer.isBuffer(original)) {
original = (0, bson_1.deserialize)(original);
}
if (Buffer.isBuffer(decrypted)) {
throw new error_1.MongoRuntimeError('Expected result of decryption to be deserialized BSON object');
}
}
if (!decrypted || typeof decrypted !== 'object')
return;
for (const k of Object.keys(decrypted)) {
const originalValue = original[k];
// An object was decrypted by libmongocrypt if and only if it was
// a BSON Binary object with subtype 6.
if (originalValue && originalValue._bsontype === 'Binary' && originalValue.sub_type === 6) {
if (!decrypted[constants_2.kDecoratedKeys]) {
Object.defineProperty(decrypted, constants_2.kDecoratedKeys, {
value: [],
configurable: true,
enumerable: false,
writable: false
});
}
// this is defined in the preceding if-statement
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
decrypted[constants_2.kDecoratedKeys].push(k);
// Do not recurse into this decrypted value. It could be a sub-document/array,
// in which case there is no original value associated with its subfields.
continue;
}
decorateDecryptionResult(decrypted[k], originalValue, false);
}
}
exports.decorateDecryptionResult = decorateDecryptionResult;
//# sourceMappingURL=utils.js.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WriteConcern = exports.WRITE_CONCERN_KEYS = void 0;
exports.throwIfWriteConcernError = exports.WriteConcern = exports.WRITE_CONCERN_KEYS = void 0;
const responses_1 = require("./cmap/wire_protocol/responses");
const error_1 = require("./error");
exports.WRITE_CONCERN_KEYS = ['w', 'wtimeout', 'j', 'journal', 'fsync'];

@@ -86,2 +88,16 @@ /**

exports.WriteConcern = WriteConcern;
/** Called with either a plain object or MongoDBResponse */
function throwIfWriteConcernError(response) {
if (typeof response === 'object' && response != null) {
const writeConcernError = responses_1.MongoDBResponse.is(response) && response.has('writeConcernError')
? response.toObject()
: !responses_1.MongoDBResponse.is(response) && 'writeConcernError' in response
? response
: null;
if (writeConcernError != null) {
throw new error_1.MongoWriteConcernError(writeConcernError);
}
}
}
exports.throwIfWriteConcernError = throwIfWriteConcernError;
//# sourceMappingURL=write_concern.js.map
{
"name": "mongodb",
"version": "6.7.0",
"version": "6.8.0",
"description": "The official MongoDB driver for Node.js",

@@ -101,3 +101,3 @@ "main": "lib/index.js",

"mocha-sinon": "^2.1.2",
"mongodb-client-encryption": "^6.0.0",
"mongodb-client-encryption": "^6.0.1",
"mongodb-legacy": "^6.0.1",

@@ -104,0 +104,0 @@ "nyc": "^15.1.0",

@@ -23,2 +23,21 @@ # MongoDB Node.js Driver

### Release Integrity
The GitHub release contains a detached signature file for the NPM package (named
`mongodb-X.Y.Z.tgz.sig`).
The following command returns the link npm package.
```shell
npm view mongodb@vX.Y.Z dist.tarball
```
Using the result of the above command, a `curl` command can return the official npm package for the release.
To verify the integrity of the downloaded package, run the following command:
```shell
gpg --verify mongodb-X.Y.Z.tgz.sig mongodb-X.Y.Z.tgz
```
### Bugs / Feature Requests

@@ -66,3 +85,3 @@

We recommend using the latest version of typescript, however we currently ensure the driver's public types compile against `typescript@4.1.6`.
We recommend using the latest version of typescript, however we currently ensure the driver's public types compile against `typescript@4.4.0`.
This is the lowest typescript version guaranteed to work with our driver: older versions may or may not work - use at your own risk.

@@ -69,0 +88,0 @@ Since typescript [does not restrict breaking changes to major versions](https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes), we consider this support best effort.

@@ -30,2 +30,3 @@ import type { DeserializeOptions, SerializeOptions } from 'bson';

/** @internal */
export type BSONElement = BSON.OnDemand['BSONElement'];

@@ -32,0 +33,0 @@

@@ -9,2 +9,3 @@ import {

import { type CommandOptions, type ProxyOptions } from '../cmap/connection';
import { kDecorateResult } from '../constants';
import { getMongoDBClientEncryption } from '../deps';

@@ -216,11 +217,2 @@ import { MongoRuntimeError } from '../error';

// Typescript errors if we index objects with `Symbol.for(...)`, so
// to avoid TS errors we pull them out into variables. Then we can type
// the objects (and class) that we expect to see them on and prevent TS
// errors.
/** @internal */
const kDecorateResult = Symbol.for('@@mdb.decorateDecryptionResult');
/** @internal */
const kDecoratedKeys = Symbol.for('@@mdb.decryptedKeys');
/**

@@ -472,3 +464,7 @@ * @internal An internal class to be used by the driver for auto encryption

});
return await stateMachine.execute<Document>(this, context);
return deserialize(await stateMachine.execute(this, context), {
promoteValues: false,
promoteLongs: false
});
}

@@ -479,7 +475,5 @@

*/
async decrypt(response: Uint8Array | Document, options: CommandOptions = {}): Promise<Document> {
const buffer = Buffer.isBuffer(response) ? response : serialize(response, options);
async decrypt(response: Uint8Array, options: CommandOptions = {}): Promise<Uint8Array> {
const context = this._mongocrypt.makeDecryptionContext(response);
const context = this._mongocrypt.makeDecryptionContext(buffer);
context.id = this._contextCounter++;

@@ -493,8 +487,3 @@

const decorateResult = this[kDecorateResult];
const result = await stateMachine.execute<Document>(this, context);
if (decorateResult) {
decorateDecryptionResult(result, response);
}
return result;
return await stateMachine.execute(this, context);
}

@@ -526,51 +515,1 @@

}
/**
* Recurse through the (identically-shaped) `decrypted` and `original`
* objects and attach a `decryptedKeys` property on each sub-object that
* contained encrypted fields. Because we only call this on BSON responses,
* we do not need to worry about circular references.
*
* @internal
*/
function decorateDecryptionResult(
decrypted: Document & { [kDecoratedKeys]?: Array<string> },
original: Document,
isTopLevelDecorateCall = true
): void {
if (isTopLevelDecorateCall) {
// The original value could have been either a JS object or a BSON buffer
if (Buffer.isBuffer(original)) {
original = deserialize(original);
}
if (Buffer.isBuffer(decrypted)) {
throw new MongoRuntimeError('Expected result of decryption to be deserialized BSON object');
}
}
if (!decrypted || typeof decrypted !== 'object') return;
for (const k of Object.keys(decrypted)) {
const originalValue = original[k];
// An object was decrypted by libmongocrypt if and only if it was
// a BSON Binary object with subtype 6.
if (originalValue && originalValue._bsontype === 'Binary' && originalValue.sub_type === 6) {
if (!decrypted[kDecoratedKeys]) {
Object.defineProperty(decrypted, kDecoratedKeys, {
value: [],
configurable: true,
enumerable: false,
writable: false
});
}
// this is defined in the preceding if-statement
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
decrypted[kDecoratedKeys]!.push(k);
// Do not recurse into this decrypted value. It could be a sub-document/array,
// in which case there is no original value associated with its subfields.
continue;
}
decorateDecryptionResult(decrypted[k], originalValue, false);
}
}

@@ -8,3 +8,3 @@ import type {

import { type Binary, type Document, type Long, serialize, type UUID } from '../bson';
import { type Binary, deserialize, type Document, type Long, serialize, type UUID } from '../bson';
import { type AnyBulkWriteOperation, type BulkWriteResult } from '../bulk/common';

@@ -206,3 +206,3 @@ import { type ProxyOptions } from '../cmap/connection';

const dataKey = await stateMachine.execute<DataKey>(this, context);
const dataKey = deserialize(await stateMachine.execute(this, context)) as DataKey;

@@ -264,3 +264,3 @@ const { db: dbName, collection: collectionName } = MongoDBCollectionNamespace.fromString(

const { v: dataKeys } = await stateMachine.execute<{ v: DataKey[] }>(this, context);
const { v: dataKeys } = deserialize(await stateMachine.execute(this, context));
if (dataKeys.length === 0) {

@@ -646,3 +646,3 @@ return {};

const { v } = await stateMachine.execute<{ v: T }>(this, context);
const { v } = deserialize(await stateMachine.execute(this, context));

@@ -726,4 +726,4 @@ return v;

const result = await stateMachine.execute<{ v: Binary }>(this, context);
return result.v;
const { v } = deserialize(await stateMachine.execute(this, context));
return v;
}

@@ -781,2 +781,3 @@ }

| GCPEncryptionKeyOptions
| KMIPEncryptionKeyOptions
| undefined;

@@ -896,2 +897,27 @@ }

* @public
* Configuration options for making a KMIP encryption key
*/
export interface KMIPEncryptionKeyOptions {
/**
* keyId is the KMIP Unique Identifier to a 96 byte KMIP Secret Data managed object.
*
* If keyId is omitted, a random 96 byte KMIP Secret Data managed object will be created.
*/
keyId?: string;
/**
* Host with optional port.
*/
endpoint?: string;
/**
* If true, this key should be decrypted by the KMIP server.
*
* Requires `mongodb-client-encryption>=6.0.1`.
*/
delegated?: boolean;
}
/**
* @public
* Options to provide when creating a new data key.

@@ -907,2 +933,3 @@ */

| GCPEncryptionKeyOptions
| KMIPEncryptionKeyOptions
| undefined;

@@ -924,15 +951,2 @@

*/
export interface ClientEncryptionRewrapManyDataKeyProviderOptions {
provider: ClientEncryptionDataKeyProvider;
masterKey?:
| AWSEncryptionKeyOptions
| AzureEncryptionKeyOptions
| GCPEncryptionKeyOptions
| undefined;
}
/**
* @public
* @experimental
*/
export interface ClientEncryptionRewrapManyDataKeyResult {

@@ -939,0 +953,0 @@ /** The result of rewrapping data keys. If unset, no keys matched the filter. */

@@ -0,1 +1,2 @@

import type { Binary } from '../../bson';
import { loadAWSCredentials } from './aws';

@@ -7,8 +8,114 @@ import { loadAzureCredentials } from './azure';

* @public
*
* A data key provider. Allowed values:
*
* - aws, gcp, local, kmip or azure
* - (`mongodb-client-encryption>=6.0.1` only) a named key, in the form of:
* `aws:<name>`, `gcp:<name>`, `local:<name>`, `kmip:<name>`, `azure:<name>`
* where `name` is an alphanumeric string, underscores allowed.
*/
export type ClientEncryptionDataKeyProvider = 'aws' | 'azure' | 'gcp' | 'local' | 'kmip';
export type ClientEncryptionDataKeyProvider = keyof KMSProviders;
/** @public */
export interface AWSKMSProviderConfiguration {
/**
* The access key used for the AWS KMS provider
*/
accessKeyId: string;
/**
* The secret access key used for the AWS KMS provider
*/
secretAccessKey: string;
/**
* An optional AWS session token that will be used as the
* X-Amz-Security-Token header for AWS requests.
*/
sessionToken?: string;
}
/** @public */
export interface LocalKMSProviderConfiguration {
/**
* The master key used to encrypt/decrypt data keys.
* A 96-byte long Buffer or base64 encoded string.
*/
key: Binary | Uint8Array | string;
}
/** @public */
export interface KMIPKMSProviderConfiguration {
/**
* The output endpoint string.
* The endpoint consists of a hostname and port separated by a colon.
* E.g. "example.com:123". A port is always present.
*/
endpoint?: string;
}
/** @public */
export type AzureKMSProviderConfiguration =
| {
/**
* The tenant ID identifies the organization for the account
*/
tenantId: string;
/**
* The client ID to authenticate a registered application
*/
clientId: string;
/**
* The client secret to authenticate a registered application
*/
clientSecret: string;
/**
* If present, a host with optional port. E.g. "example.com" or "example.com:443".
* This is optional, and only needed if customer is using a non-commercial Azure instance
* (e.g. a government or China account, which use different URLs).
* Defaults to "login.microsoftonline.com"
*/
identityPlatformEndpoint?: string | undefined;
}
| {
/**
* If present, an access token to authenticate with Azure.
*/
accessToken: string;
};
/** @public */
export type GCPKMSProviderConfiguration =
| {
/**
* The service account email to authenticate
*/
email: string;
/**
* A PKCS#8 encrypted key. This can either be a base64 string or a binary representation
*/
privateKey: string | Buffer;
/**
* If present, a host with optional port. E.g. "example.com" or "example.com:443".
* Defaults to "oauth2.googleapis.com"
*/
endpoint?: string | undefined;
}
| {
/**
* If present, an access token to authenticate with GCP.
*/
accessToken: string;
};
/**
* @public
* Configuration options that are used by specific KMS providers during key generation, encryption, and decryption.
*
* Named KMS providers _are not supported_ for automatic KMS credential fetching.
*/

@@ -19,32 +126,10 @@ export interface KMSProviders {

*/
aws?:
| {
/**
* The access key used for the AWS KMS provider
*/
accessKeyId: string;
aws?: AWSKMSProviderConfiguration | Record<string, never>;
[key: `aws:${string}`]: AWSKMSProviderConfiguration;
/**
* The secret access key used for the AWS KMS provider
*/
secretAccessKey: string;
/**
* An optional AWS session token that will be used as the
* X-Amz-Security-Token header for AWS requests.
*/
sessionToken?: string;
}
| Record<string, never>;
/**
* Configuration options for using 'local' as your KMS provider
*/
local?: {
/**
* The master key used to encrypt/decrypt data keys.
* A 96-byte long Buffer or base64 encoded string.
*/
key: Buffer | string;
};
local?: LocalKMSProviderConfiguration;
[key: `local:${string}`]: LocalKMSProviderConfiguration;

@@ -54,10 +139,4 @@ /**

*/
kmip?: {
/**
* The output endpoint string.
* The endpoint consists of a hostname and port separated by a colon.
* E.g. "example.com:123". A port is always present.
*/
endpoint?: string;
};
kmip?: KMIPKMSProviderConfiguration;
[key: `kmip:${string}`]: KMIPKMSProviderConfiguration;

@@ -67,63 +146,10 @@ /**

*/
azure?:
| {
/**
* The tenant ID identifies the organization for the account
*/
tenantId: string;
azure?: AzureKMSProviderConfiguration | Record<string, never>;
[key: `azure:${string}`]: AzureKMSProviderConfiguration;
/**
* The client ID to authenticate a registered application
*/
clientId: string;
/**
* The client secret to authenticate a registered application
*/
clientSecret: string;
/**
* If present, a host with optional port. E.g. "example.com" or "example.com:443".
* This is optional, and only needed if customer is using a non-commercial Azure instance
* (e.g. a government or China account, which use different URLs).
* Defaults to "login.microsoftonline.com"
*/
identityPlatformEndpoint?: string | undefined;
}
| {
/**
* If present, an access token to authenticate with Azure.
*/
accessToken: string;
}
| Record<string, never>;
/**
* Configuration options for using 'gcp' as your KMS provider
*/
gcp?:
| {
/**
* The service account email to authenticate
*/
email: string;
/**
* A PKCS#8 encrypted key. This can either be a base64 string or a binary representation
*/
privateKey: string | Buffer;
/**
* If present, a host with optional port. E.g. "example.com" or "example.com:443".
* Defaults to "oauth2.googleapis.com"
*/
endpoint?: string | undefined;
}
| {
/**
* If present, an access token to authenticate with GCP.
*/
accessToken: string;
}
| Record<string, never>;
gcp?: GCPKMSProviderConfiguration | Record<string, never>;
[key: `gcp:${string}`]: GCPKMSProviderConfiguration;
}

@@ -130,0 +156,0 @@

@@ -20,3 +20,3 @@ import * as fs from 'fs/promises';

import { type MongocryptdManager } from './mongocryptd_manager';
import { type ClientEncryptionDataKeyProvider, type KMSProviders } from './providers';
import { type KMSProviders } from './providers';

@@ -114,5 +114,20 @@ let socks: SocksLib | null = null;

azure?: ClientEncryptionTlsOptions;
[key: string]: ClientEncryptionTlsOptions | undefined;
};
/**
* This is kind of a hack. For `rewrapManyDataKey`, we have tests that
* guarantee that when there are no matching keys, `rewrapManyDataKey` returns
* nothing. We also have tests for auto encryption that guarantee for `encrypt`
* we return an error when there are no matching keys. This error is generated in
* subsequent iterations of the state machine.
* Some apis (`encrypt`) throw if there are no filter matches and others (`rewrapManyDataKey`)
* do not. We set the result manually here, and let the state machine continue. `libmongocrypt`
* will inform us if we need to error by setting the state to `MONGOCRYPT_CTX_ERROR` but
* otherwise we'll return `{ v: [] }`.
*/
let EMPTY_V;
/**
* @internal

@@ -159,6 +174,3 @@ *

*/
async execute<T extends Document>(
executor: StateMachineExecutable,
context: MongoCryptContext
): Promise<T> {
async execute(executor: StateMachineExecutable, context: MongoCryptContext): Promise<Uint8Array> {
const keyVaultNamespace = executor._keyVaultNamespace;

@@ -169,3 +181,3 @@ const keyVaultClient = executor._keyVaultClient;

const mongocryptdManager = executor._mongocryptdManager;
let result: T | null = null;
let result: Uint8Array | null = null;

@@ -218,12 +230,4 @@ while (context.state !== MONGOCRYPT_CTX_DONE && context.state !== MONGOCRYPT_CTX_ERROR) {

if (keys.length === 0) {
// This is kind of a hack. For `rewrapManyDataKey`, we have tests that
// guarantee that when there are no matching keys, `rewrapManyDataKey` returns
// nothing. We also have tests for auto encryption that guarantee for `encrypt`
// we return an error when there are no matching keys. This error is generated in
// subsequent iterations of the state machine.
// Some apis (`encrypt`) throw if there are no filter matches and others (`rewrapManyDataKey`)
// do not. We set the result manually here, and let the state machine continue. `libmongocrypt`
// will inform us if we need to error by setting the state to `MONGOCRYPT_CTX_ERROR` but
// otherwise we'll return `{ v: [] }`.
result = { v: [] } as any as T;
// See docs on EMPTY_V
result = EMPTY_V ??= serialize({ v: [] });
}

@@ -260,3 +264,3 @@ for await (const key of keys) {

}
result = deserialize(finalizedContext, this.options) as T;
result = finalizedContext;
break;

@@ -328,3 +332,3 @@ }

if (tlsOptions) {
const kmsProvider = request.kmsProvider as ClientEncryptionDataKeyProvider;
const kmsProvider = request.kmsProvider;
const providerTlsOptions = tlsOptions[kmsProvider];

@@ -331,0 +335,0 @@ if (providerTlsOptions) {

import { type Readable, Transform, type TransformCallback } from 'stream';
import { clearTimeout, setTimeout } from 'timers';
import type { BSONSerializeOptions, Document, ObjectId } from '../bson';
import type { AutoEncrypter } from '../client-side-encryption/auto_encrypter';
import { type BSONSerializeOptions, deserialize, type Document, type ObjectId } from '../bson';
import { type AutoEncrypter } from '../client-side-encryption/auto_encrypter';
import {

@@ -12,2 +12,3 @@ CLOSE,

COMMAND_SUCCEEDED,
kDecorateResult,
PINNED,

@@ -23,4 +24,3 @@ UNPINNED

MongoServerError,
MongoUnexpectedServerResponseError,
MongoWriteConcernError
MongoUnexpectedServerResponseError
} from '../error';

@@ -38,2 +38,3 @@ import type { ServerApi, SupportedNodeConnectionOptions } from '../mongo_client';

type Callback,
decorateDecryptionResult,
HostAddress,

@@ -69,3 +70,3 @@ maxWireVersion,

import {
isErrorResponse,
CursorResponse,
MongoDBResponse,

@@ -455,8 +456,3 @@ type MongoDBResponseConstructor

const document =
responseType == null
? new MongoDBResponse(bson)
: isErrorResponse(bson)
? new MongoDBResponse(bson)
: new responseType(bson);
const document = (responseType ?? MongoDBResponse).make(bson);

@@ -525,8 +521,3 @@ yield document;

if (document.has('writeConcernError')) {
object ??= document.toObject(bsonOptions);
throw new MongoWriteConcernError(object.writeConcernError, object);
}
if (document.isError) {
if (document.ok === 0) {
throw new MongoServerError((object ??= document.toObject(bsonOptions)));

@@ -561,31 +552,9 @@ }

if (this.shouldEmitAndLogCommand) {
if (error.name === 'MongoWriteConcernError') {
this.emitAndLogCommand(
this.monitorCommands,
Connection.COMMAND_SUCCEEDED,
message.databaseName,
this.established,
new CommandSucceededEvent(
this,
message,
options.noResponse ? undefined : (object ??= document?.toObject(bsonOptions)),
started,
this.description.serverConnectionId
)
);
} else {
this.emitAndLogCommand(
this.monitorCommands,
Connection.COMMAND_FAILED,
message.databaseName,
this.established,
new CommandFailedEvent(
this,
message,
error,
started,
this.description.serverConnectionId
)
);
}
this.emitAndLogCommand(
this.monitorCommands,
Connection.COMMAND_FAILED,
message.databaseName,
this.established,
new CommandFailedEvent(this, message, error, started, this.description.serverConnectionId)
);
}

@@ -600,2 +569,9 @@ throw error;

options: CommandOptions | undefined,
responseType: T
): Promise<InstanceType<T>>;
public async command<T extends MongoDBResponseConstructor>(
ns: MongoDBNamespace,
command: Document,
options: CommandOptions | undefined,
responseType: T | undefined

@@ -760,3 +736,3 @@ ): Promise<typeof responseType extends undefined ? Document : InstanceType<T>>;

options?: CommandOptions,
_responseType?: T | undefined
responseType?: T | undefined
): Promise<Document> {

@@ -775,3 +751,3 @@ const { autoEncrypter } = this;

// This means the initial handshake hasn't happened yet
return await super.command<T>(ns, cmd, options, undefined);
return await super.command<T>(ns, cmd, options, responseType);
}

@@ -810,6 +786,26 @@

const response = await super.command<T>(ns, encrypted, options, undefined);
const encryptedResponse = await super.command(
ns,
encrypted,
options,
// Eventually we want to require `responseType` which means we would satisfy `T` as the return type.
// In the meantime, we want encryptedResponse to always be _at least_ a MongoDBResponse if not a more specific subclass
// So that we can ensure we have access to the on-demand APIs for decorate response
responseType ?? MongoDBResponse
);
return await autoEncrypter.decrypt(response, options);
const result = await autoEncrypter.decrypt(encryptedResponse.toBytes(), options);
const decryptedResponse = responseType?.make(result) ?? deserialize(result, options);
if (autoEncrypter[kDecorateResult]) {
if (responseType == null) {
decorateDecryptionResult(decryptedResponse, encryptedResponse.toObject(), true);
} else if (decryptedResponse instanceof CursorResponse) {
decryptedResponse.encryptedResponse = encryptedResponse;
}
}
return decryptedResponse;
}
}

@@ -69,5 +69,7 @@ import {

/** If this is an embedded document, indicates if this was a BSON array */
public readonly isArray = false
public readonly isArray = false,
/** If elements was already calculated */
elements?: BSONElement[]
) {
this.elements = parseToElementsToArray(this.bson, offset);
this.elements = elements ?? parseToElementsToArray(this.bson, offset);
}

@@ -82,4 +84,9 @@

for (let i = 0; i < name.length; i++) {
if (this.bson[nameOffset + i] !== name.charCodeAt(i)) return false;
const nameEnd = nameOffset + nameLength;
for (
let byteIndex = nameOffset, charIndex = 0;
charIndex < name.length && byteIndex < nameEnd;
charIndex++, byteIndex++
) {
if (this.bson[byteIndex] !== name.charCodeAt(charIndex)) return false;
}

@@ -130,3 +137,3 @@

// skip this element if it has already been associated with a name
if (!this.indexFound[index] && this.isElementName(name, element)) {
if (!(index in this.indexFound) && this.isElementName(name, element)) {
const cachedElement = { element, value: undefined };

@@ -253,3 +260,3 @@ this.cache[name] = cachedElement;

as: T,
required?: false | undefined
required?: boolean | undefined
): JSTypeOf[T] | null;

@@ -256,0 +263,0 @@

import {
type BSONElement,
type BSONSerializeOptions,

@@ -7,2 +8,3 @@ BSONType,

parseToElementsToArray,
pluckBSONSerializeOptions,
type Timestamp

@@ -12,4 +14,4 @@ } from '../../bson';

import { type ClusterTime } from '../../sdam/common';
import { type MongoDBNamespace, ns } from '../../utils';
import { OnDemandDocument } from './on_demand/document';
import { decorateDecryptionResult, ns } from '../../utils';
import { type JSTypeOf, OnDemandDocument } from './on_demand/document';

@@ -35,4 +37,3 @@ // eslint-disable-next-line no-restricted-syntax

*/
export function isErrorResponse(bson: Uint8Array): boolean {
const elements = parseToElementsToArray(bson, 0);
export function isErrorResponse(bson: Uint8Array, elements: BSONElement[]): boolean {
for (let eIdx = 0; eIdx < elements.length; eIdx++) {

@@ -66,2 +67,3 @@ const element = elements[eIdx];

new (bson: Uint8Array, offset?: number, isArray?: boolean): MongoDBResponse;
make(bson: Uint8Array): MongoDBResponse;
};

@@ -71,2 +73,25 @@

export class MongoDBResponse extends OnDemandDocument {
// Wrap error thrown from BSON
public override get<const T extends keyof JSTypeOf>(
name: string | number,
as: T,
required?: false | undefined
): JSTypeOf[T] | null;
public override get<const T extends keyof JSTypeOf>(
name: string | number,
as: T,
required: true
): JSTypeOf[T];
public override get<const T extends keyof JSTypeOf>(
name: string | number,
as: T,
required?: boolean | undefined
): JSTypeOf[T] | null {
try {
return super.get(name, as, required);
} catch (cause) {
throw new MongoUnexpectedServerResponseError(cause.message, { cause });
}
}
static is(value: unknown): value is MongoDBResponse {

@@ -76,14 +101,13 @@ return value instanceof MongoDBResponse;

static make(bson: Uint8Array) {
const elements = parseToElementsToArray(bson, 0);
const isError = isErrorResponse(bson, elements);
return isError
? new MongoDBResponse(bson, 0, false, elements)
: new this(bson, 0, false, elements);
}
// {ok:1}
static empty = new MongoDBResponse(new Uint8Array([13, 0, 0, 0, 16, 111, 107, 0, 1, 0, 0, 0, 0]));
/** Indicates this document is a server error */
public get isError() {
let isError = this.ok === 0;
isError ||= this.has('errmsg');
isError ||= this.has('code');
isError ||= this.has('$err'); // The '$err' field is used in OP_REPLY responses
return isError;
}
/**

@@ -119,2 +143,3 @@ * Drivers can safely assume that the `recoveryToken` field is always a BSON document but drivers MUST NOT modify the

/** Normalizes whatever BSON value is "ok" to a JS number 1 or 0. */
public get ok(): 0 | 1 {

@@ -154,9 +179,3 @@ return this.getNumber('ok') ? 1 : 0;

const exactBSONOptions = {
useBigInt64: options?.useBigInt64,
promoteLongs: options?.promoteLongs,
promoteValues: options?.promoteValues,
promoteBuffers: options?.promoteBuffers,
bsonRegExp: options?.bsonRegExp,
raw: options?.raw ?? false,
fieldsAsRaw: options?.fieldsAsRaw ?? {},
...pluckBSONSerializeOptions(options ?? {}),
validation: this.parseBsonSerializationOptions(options)

@@ -181,6 +200,17 @@ };

/**
* Devtools need to know which keys were encrypted before the driver automatically decrypted them.
* If decorating is enabled (`Symbol.for('@@mdb.decorateDecryptionResult')`), this field will be set,
* storing the original encrypted response from the server, so that we can build an object that has
* the list of BSON keys that were encrypted stored at a well known symbol: `Symbol.for('@@mdb.decryptedKeys')`.
*/
encryptedResponse?: MongoDBResponse;
/**
* This supports a feature of the FindCursor.
* It is an optimization to avoid an extra getMore when the limit has been reached
*/
static emptyGetMore = { id: new Long(0), length: 0, shift: () => null };
static emptyGetMore: CursorResponse = {
id: new Long(0),
length: 0,
shift: () => null
} as unknown as CursorResponse;

@@ -191,32 +221,66 @@ static override is(value: unknown): value is CursorResponse {

public id: Long;
public ns: MongoDBNamespace | null = null;
public batchSize = 0;
private batch: OnDemandDocument;
private _batch: OnDemandDocument | null = null;
private iterated = 0;
constructor(bytes: Uint8Array, offset?: number, isArray?: boolean) {
super(bytes, offset, isArray);
get cursor() {
return this.get('cursor', BSONType.object, true);
}
const cursor = this.get('cursor', BSONType.object, true);
public get id(): Long {
try {
return Long.fromBigInt(this.cursor.get('id', BSONType.long, true));
} catch (cause) {
throw new MongoUnexpectedServerResponseError(cause.message, { cause });
}
}
const id = cursor.get('id', BSONType.long, true);
this.id = new Long(Number(id & 0xffff_ffffn), Number((id >> 32n) & 0xffff_ffffn));
public get ns() {
const namespace = this.cursor.get('ns', BSONType.string);
if (namespace != null) return ns(namespace);
return null;
}
const namespace = cursor.get('ns', BSONType.string);
if (namespace != null) this.ns = ns(namespace);
public get length() {
return Math.max(this.batchSize - this.iterated, 0);
}
if (cursor.has('firstBatch')) this.batch = cursor.get('firstBatch', BSONType.array, true);
else if (cursor.has('nextBatch')) this.batch = cursor.get('nextBatch', BSONType.array, true);
private _encryptedBatch: OnDemandDocument | null = null;
get encryptedBatch() {
if (this.encryptedResponse == null) return null;
if (this._encryptedBatch != null) return this._encryptedBatch;
const cursor = this.encryptedResponse?.get('cursor', BSONType.object);
if (cursor?.has('firstBatch'))
this._encryptedBatch = cursor.get('firstBatch', BSONType.array, true);
else if (cursor?.has('nextBatch'))
this._encryptedBatch = cursor.get('nextBatch', BSONType.array, true);
else throw new MongoUnexpectedServerResponseError('Cursor document did not contain a batch');
this.batchSize = this.batch.size();
return this._encryptedBatch;
}
get length() {
return Math.max(this.batchSize - this.iterated, 0);
private get batch() {
if (this._batch != null) return this._batch;
const cursor = this.cursor;
if (cursor.has('firstBatch')) this._batch = cursor.get('firstBatch', BSONType.array, true);
else if (cursor.has('nextBatch')) this._batch = cursor.get('nextBatch', BSONType.array, true);
else throw new MongoUnexpectedServerResponseError('Cursor document did not contain a batch');
return this._batch;
}
shift(options?: BSONSerializeOptions): any {
public get batchSize() {
return this.batch?.size();
}
public get postBatchResumeToken() {
return (
this.cursor.get('postBatchResumeToken', BSONType.object)?.toObject({
promoteValues: false,
promoteLongs: false,
promoteBuffers: false
}) ?? null
);
}
public shift(options?: BSONSerializeOptions): any {
if (this.iterated >= this.batchSize) {

@@ -227,2 +291,4 @@ return null;

const result = this.batch.get(this.iterated, BSONType.object, true) ?? null;
const encryptedResult = this.encryptedBatch?.get(this.iterated, BSONType.object, true) ?? null;
this.iterated += 1;

@@ -233,17 +299,46 @@

} else {
return result.toObject(options);
const object = result.toObject(options);
if (encryptedResult) {
decorateDecryptionResult(object, encryptedResult.toObject(options), true);
}
return object;
}
}
clear() {
public clear() {
this.iterated = this.batchSize;
}
}
pushMany() {
throw new Error('pushMany Unsupported method');
/**
* Explain responses have nothing to do with cursor responses
* This class serves to temporarily avoid refactoring how cursors handle
* explain responses which is to detect that the response is not cursor-like and return the explain
* result as the "first and only" document in the "batch" and end the "cursor"
*/
export class ExplainedCursorResponse extends CursorResponse {
isExplain = true;
override get id(): Long {
return Long.fromBigInt(0n);
}
push() {
throw new Error('push Unsupported method');
override get batchSize() {
return 0;
}
override get ns() {
return null;
}
_length = 1;
override get length(): number {
return this._length;
}
override shift(options?: BSONSerializeOptions | undefined) {
if (this._length === 0) return null;
this._length -= 1;
return this.toObject(options);
}
}

@@ -28,3 +28,2 @@ import { type BSONSerializeOptions, type Document, resolveBSONOptions } from './bson';

import { CountOperation, type CountOptions } from './operations/count';
import { CountDocumentsOperation, type CountDocumentsOptions } from './operations/count_documents';
import {

@@ -106,2 +105,10 @@ DeleteManyOperation,

/** @public */
export interface CountDocumentsOptions extends AggregateOptions {
/** The number of documents to skip. */
skip?: number;
/** The maximum amount of documents to consider. */
limit?: number;
}
/** @public */
export interface CollectionOptions extends BSONSerializeOptions, WriteConcernOptions {

@@ -769,6 +776,19 @@ /** Specify a read concern for the collection. (only MongoDB 3.2 or higher supported) */

): Promise<number> {
return await executeOperation(
this.client,
new CountDocumentsOperation(this as TODO_NODE_3286, filter, resolveOptions(this, options))
);
const pipeline = [];
pipeline.push({ $match: filter });
if (typeof options.skip === 'number') {
pipeline.push({ $skip: options.skip });
}
if (typeof options.limit === 'number') {
pipeline.push({ $limit: options.limit });
}
pipeline.push({ $group: { _id: 1, n: { $sum: 1 } } });
const cursor = this.aggregate<{ n: number }>(pipeline, options);
const doc = await cursor.next();
await cursor.close();
return doc?.n ?? 0;
}

@@ -775,0 +795,0 @@

@@ -168,1 +168,10 @@ export const SYSTEM_NAMESPACE_COLLECTION = 'system.namespaces';

export const LEGACY_HELLO_COMMAND_CAMEL_CASE = 'isMaster';
// Typescript errors if we index objects with `Symbol.for(...)`, so
// to avoid TS errors we pull them out into variables. Then we can type
// the objects (and class) that we expect to see them on and prevent TS
// errors.
/** @internal */
export const kDecorateResult = Symbol.for('@@mdb.decorateDecryptionResult');
/** @internal */
export const kDecoratedKeys = Symbol.for('@@mdb.decryptedKeys');
import { Readable, Transform } from 'stream';
import { type BSONSerializeOptions, type Document, Long, pluckBSONSerializeOptions } from '../bson';
import { CursorResponse } from '../cmap/wire_protocol/responses';
import { type CursorResponse } from '../cmap/wire_protocol/responses';
import {
type AnyError,
MongoAPIError,

@@ -11,3 +10,2 @@ MongoCursorExhaustedError,

MongoInvalidArgumentError,
MongoNetworkError,
MongoRuntimeError,

@@ -17,4 +15,4 @@ MongoTailableCursorError

import type { MongoClient } from '../mongo_client';
import { type TODO_NODE_3286, TypedEventEmitter } from '../mongo_types';
import { executeOperation, type ExecutionResult } from '../operations/execute_operation';
import { TypedEventEmitter } from '../mongo_types';
import { executeOperation } from '../operations/execute_operation';
import { GetMoreOperation } from '../operations/get_more';

@@ -26,28 +24,21 @@ import { KillCursorsOperation } from '../operations/kill_cursors';

import { ClientSession, maybeClearPinnedConnection } from '../sessions';
import { List, type MongoDBNamespace, ns, squashError } from '../utils';
import { type MongoDBNamespace, squashError } from '../utils';
/** @internal */
const kId = Symbol('id');
/** @internal */
const kDocuments = Symbol('documents');
/** @internal */
const kServer = Symbol('server');
/** @internal */
const kNamespace = Symbol('namespace');
/** @internal */
const kClient = Symbol('client');
/** @internal */
const kSession = Symbol('session');
/** @internal */
const kOptions = Symbol('options');
/** @internal */
const kTransform = Symbol('transform');
/** @internal */
const kInitialized = Symbol('initialized');
/** @internal */
const kClosed = Symbol('closed');
/** @internal */
const kKilled = Symbol('killed');
/** @internal */
const kInit = Symbol('kInit');
/**
* @internal
* TODO(NODE-2882): A cursor's getMore commands must be run on the same server it was started on
* and the same session must be used for the lifetime of the cursor. This object serves to get the
* server and session (along with the response) out of executeOperation back to the AbstractCursor.
*
* There may be a better design for communicating these values back to the cursor, currently an operation
* MUST store the selected server on itself so it can be read after executeOperation has returned.
*/
export interface InitialCursorResponse {
/** The server selected for the operation */
server: Server;
/** The session used for this operation, may be implicitly created */
session?: ClientSession;
/** The raw server response for the operation */
response: CursorResponse;
}

@@ -143,29 +134,23 @@ /** @public */

/** @internal */
[kId]: Long | null;
private cursorId: Long | null;
/** @internal */
[kSession]: ClientSession;
private cursorSession: ClientSession;
/** @internal */
[kServer]?: Server;
private selectedServer?: Server;
/** @internal */
[kNamespace]: MongoDBNamespace;
private cursorNamespace: MongoDBNamespace;
/** @internal */
[kDocuments]: {
length: number;
shift(bsonOptions?: any): TSchema | null;
clear(): void;
pushMany(many: Iterable<TSchema>): void;
push(item: TSchema): void;
};
private documents: CursorResponse | null = null;
/** @internal */
[kClient]: MongoClient;
private cursorClient: MongoClient;
/** @internal */
[kTransform]?: (doc: TSchema) => any;
private transform?: (doc: TSchema) => any;
/** @internal */
[kInitialized]: boolean;
private initialized: boolean;
/** @internal */
[kClosed]: boolean;
private isClosed: boolean;
/** @internal */
[kKilled]: boolean;
private isKilled: boolean;
/** @internal */
[kOptions]: InternalAbstractCursorOptions;
protected readonly cursorOptions: InternalAbstractCursorOptions;

@@ -176,3 +161,3 @@ /** @event */

/** @internal */
constructor(
protected constructor(
client: MongoClient,

@@ -187,10 +172,9 @@ namespace: MongoDBNamespace,

}
this[kClient] = client;
this[kNamespace] = namespace;
this[kId] = null;
this[kDocuments] = new List();
this[kInitialized] = false;
this[kClosed] = false;
this[kKilled] = false;
this[kOptions] = {
this.cursorClient = client;
this.cursorNamespace = namespace;
this.cursorId = null;
this.initialized = false;
this.isClosed = false;
this.isKilled = false;
this.cursorOptions = {
readPreference:

@@ -202,11 +186,11 @@ options.readPreference && options.readPreference instanceof ReadPreference

};
this[kOptions].timeoutMS = options.timeoutMS;
this.cursorOptions.timeoutMS = options.timeoutMS;
const readConcern = ReadConcern.fromOptions(options);
if (readConcern) {
this[kOptions].readConcern = readConcern;
this.cursorOptions.readConcern = readConcern;
}
if (typeof options.batchSize === 'number') {
this[kOptions].batchSize = options.batchSize;
this.cursorOptions.batchSize = options.batchSize;
}

@@ -217,22 +201,29 @@

if (options.comment !== undefined) {
this[kOptions].comment = options.comment;
this.cursorOptions.comment = options.comment;
}
if (typeof options.maxTimeMS === 'number') {
this[kOptions].maxTimeMS = options.maxTimeMS;
this.cursorOptions.maxTimeMS = options.maxTimeMS;
}
if (typeof options.maxAwaitTimeMS === 'number') {
this[kOptions].maxAwaitTimeMS = options.maxAwaitTimeMS;
this.cursorOptions.maxAwaitTimeMS = options.maxAwaitTimeMS;
}
if (options.session instanceof ClientSession) {
this[kSession] = options.session;
this.cursorSession = options.session;
} else {
this[kSession] = this[kClient].startSession({ owner: this, explicit: false });
this.cursorSession = this.cursorClient.startSession({ owner: this, explicit: false });
}
}
/**
* The cursor has no id until it receives a response from the initial cursor creating command.
*
* It is non-zero for as long as the database has an open cursor.
*
* The initiating command may receive a zero id if the entire result is in the `firstBatch`.
*/
get id(): Long | undefined {
return this[kId] ?? undefined;
return this.cursorId ?? undefined;
}

@@ -242,3 +233,3 @@

get isDead() {
return (this[kId]?.isZero() ?? false) || this[kClosed] || this[kKilled];
return (this.cursorId?.isZero() ?? false) || this.isClosed || this.isKilled;
}

@@ -248,3 +239,3 @@

get client(): MongoClient {
return this[kClient];
return this.cursorClient;
}

@@ -254,15 +245,15 @@

get server(): Server | undefined {
return this[kServer];
return this.selectedServer;
}
get namespace(): MongoDBNamespace {
return this[kNamespace];
return this.cursorNamespace;
}
get readPreference(): ReadPreference {
return this[kOptions].readPreference;
return this.cursorOptions.readPreference;
}
get readConcern(): ReadConcern | undefined {
return this[kOptions].readConcern;
return this.cursorOptions.readConcern;
}

@@ -272,24 +263,26 @@

get session(): ClientSession {
return this[kSession];
return this.cursorSession;
}
set session(clientSession: ClientSession) {
this[kSession] = clientSession;
this.cursorSession = clientSession;
}
/** @internal */
get cursorOptions(): InternalAbstractCursorOptions {
return this[kOptions];
}
/**
* The cursor is closed and all remaining locally buffered documents have been iterated.
*/
get closed(): boolean {
return this[kClosed];
return this.isClosed && (this.documents?.length ?? 0) === 0;
}
/**
* A `killCursors` command was attempted on this cursor.
* This is performed if the cursor id is non zero.
*/
get killed(): boolean {
return this[kKilled];
return this.isKilled;
}
get loadBalanced(): boolean {
return !!this[kClient].topology?.loadBalanced;
return !!this.cursorClient.topology?.loadBalanced;
}

@@ -299,3 +292,3 @@

bufferedCount(): number {
return this[kDocuments].length;
return this.documents?.length ?? 0;
}

@@ -306,6 +299,9 @@

const bufferedDocs: TSchema[] = [];
const documentsToRead = Math.min(number ?? this[kDocuments].length, this[kDocuments].length);
const documentsToRead = Math.min(
number ?? this.documents?.length ?? 0,
this.documents?.length ?? 0
);
for (let count = 0; count < documentsToRead; count++) {
const document = this[kDocuments].shift(this[kOptions]);
const document = this.documents?.shift(this.cursorOptions);
if (document != null) {

@@ -318,5 +314,4 @@ bufferedDocs.push(document);

}
async *[Symbol.asyncIterator](): AsyncGenerator<TSchema, void, void> {
if (this.closed) {
if (this.isClosed) {
return;

@@ -327,29 +322,22 @@ }

while (true) {
if (this.isKilled) {
return;
}
if (this.closed) {
return;
}
if (this.cursorId != null && this.isDead && (this.documents?.length ?? 0) === 0) {
return;
}
const document = await this.next();
// Intentional strict null check, because users can map cursors to falsey values.
// We allow mapping to all values except for null.
// eslint-disable-next-line no-restricted-syntax
if (document === null) {
if (!this.closed) {
const message =
'Cursor returned a `null` document, but the cursor is not exhausted. Mapping documents to `null` is not supported in the cursor transform.';
try {
await cleanupCursor(this, { needsToEmitClosed: true });
} catch (error) {
squashError(error);
}
throw new MongoAPIError(message);
}
break;
return;
}
yield document;
if (this[kId] === Long.ZERO) {
// Cursor exhausted
break;
}
}

@@ -359,3 +347,3 @@ } finally {

// the case when a user would break out of a for await of loop early.
if (!this.closed) {
if (!this.isClosed) {
try {

@@ -401,11 +389,14 @@ await this.close();

async hasNext(): Promise<boolean> {
if (this[kId] === Long.ZERO) {
if (this.cursorId === Long.ZERO) {
return false;
}
if (this[kDocuments].length !== 0) {
return true;
}
do {
if ((this.documents?.length ?? 0) !== 0) {
return true;
}
await this.fetchBatch();
} while (!this.isDead || (this.documents?.length ?? 0) !== 0);
return await next(this, { blocking: true, transform: false, shift: false });
return false;
}

@@ -415,7 +406,16 @@

async next(): Promise<TSchema | null> {
if (this[kId] === Long.ZERO) {
if (this.cursorId === Long.ZERO) {
throw new MongoCursorExhaustedError();
}
return await next(this, { blocking: true, transform: true, shift: true });
do {
const doc = this.documents?.shift(this.cursorOptions);
if (doc != null) {
if (this.transform != null) return await this.transformDocument(doc);
return doc;
}
await this.fetchBatch();
} while (!this.isDead || (this.documents?.length ?? 0) !== 0);
return null;
}

@@ -427,7 +427,21 @@

async tryNext(): Promise<TSchema | null> {
if (this[kId] === Long.ZERO) {
if (this.cursorId === Long.ZERO) {
throw new MongoCursorExhaustedError();
}
return await next(this, { blocking: false, transform: true, shift: true });
let doc = this.documents?.shift(this.cursorOptions);
if (doc != null) {
if (this.transform != null) return await this.transformDocument(doc);
return doc;
}
await this.fetchBatch();
doc = this.documents?.shift(this.cursorOptions);
if (doc != null) {
if (this.transform != null) return await this.transformDocument(doc);
return doc;
}
return null;
}

@@ -456,5 +470,3 @@

async close(): Promise<void> {
const needsToEmitClosed = !this[kClosed];
this[kClosed] = true;
await cleanupCursor(this, { needsToEmitClosed });
await this.cleanup();
}

@@ -483,3 +495,3 @@

addCursorFlag(flag: CursorFlag, value: boolean): this {
assertUninitialized(this);
this.throwIfInitialized();
if (!CURSOR_FLAGS.includes(flag)) {

@@ -493,3 +505,3 @@ throw new MongoInvalidArgumentError(`Flag ${flag} is not one of ${CURSOR_FLAGS}`);

this[kOptions][flag] = value;
this.cursorOptions[flag] = value;
return this;

@@ -541,10 +553,10 @@ }

map<T = any>(transform: (doc: TSchema) => T): AbstractCursor<T> {
assertUninitialized(this);
const oldTransform = this[kTransform] as (doc: TSchema) => TSchema; // TODO(NODE-3283): Improve transform typing
this.throwIfInitialized();
const oldTransform = this.transform;
if (oldTransform) {
this[kTransform] = doc => {
this.transform = doc => {
return transform(oldTransform(doc));
};
} else {
this[kTransform] = transform;
this.transform = transform;
}

@@ -561,7 +573,7 @@

withReadPreference(readPreference: ReadPreferenceLike): this {
assertUninitialized(this);
this.throwIfInitialized();
if (readPreference instanceof ReadPreference) {
this[kOptions].readPreference = readPreference;
this.cursorOptions.readPreference = readPreference;
} else if (typeof readPreference === 'string') {
this[kOptions].readPreference = ReadPreference.fromString(readPreference);
this.cursorOptions.readPreference = ReadPreference.fromString(readPreference);
} else {

@@ -580,6 +592,6 @@ throw new MongoInvalidArgumentError(`Invalid read preference: ${readPreference}`);

withReadConcern(readConcern: ReadConcernLike): this {
assertUninitialized(this);
this.throwIfInitialized();
const resolvedReadConcern = ReadConcern.fromOptions({ readConcern });
if (resolvedReadConcern) {
this[kOptions].readConcern = resolvedReadConcern;
this.cursorOptions.readConcern = resolvedReadConcern;
}

@@ -596,3 +608,3 @@

maxTimeMS(value: number): this {
assertUninitialized(this);
this.throwIfInitialized();
if (typeof value !== 'number') {

@@ -602,3 +614,3 @@ throw new MongoInvalidArgumentError('Argument for maxTimeMS must be a number');

this[kOptions].maxTimeMS = value;
this.cursorOptions.maxTimeMS = value;
return this;

@@ -613,4 +625,4 @@ }

batchSize(value: number): this {
assertUninitialized(this);
if (this[kOptions].tailable) {
this.throwIfInitialized();
if (this.cursorOptions.tailable) {
throw new MongoTailableCursorError('Tailable cursor does not support batchSize');

@@ -623,3 +635,3 @@ }

this[kOptions].batchSize = value;
this.cursorOptions.batchSize = value;
return this;

@@ -634,13 +646,13 @@ }

rewind(): void {
if (!this[kInitialized]) {
if (!this.initialized) {
return;
}
this[kId] = null;
this[kDocuments].clear();
this[kClosed] = false;
this[kKilled] = false;
this[kInitialized] = false;
this.cursorId = null;
this.documents?.clear();
this.isClosed = false;
this.isKilled = false;
this.initialized = false;
const session = this[kSession];
const session = this.cursorSession;
if (session) {

@@ -653,3 +665,3 @@ // We only want to end this session if we created it, and it hasn't ended yet

}
this[kSession] = this.client.startSession({ owner: this, explicit: false });
this.cursorSession = this.cursorClient.startSession({ owner: this, explicit: false });
}

@@ -665,15 +677,30 @@ }

/** @internal */
protected abstract _initialize(session: ClientSession | undefined): Promise<ExecutionResult>;
protected abstract _initialize(
session: ClientSession | undefined
): Promise<InitialCursorResponse>;
/** @internal */
async getMore(batchSize: number, useCursorResponse = false): Promise<Document | null> {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const getMoreOperation = new GetMoreOperation(this[kNamespace], this[kId]!, this[kServer]!, {
...this[kOptions],
session: this[kSession],
batchSize,
useCursorResponse
});
async getMore(batchSize: number): Promise<CursorResponse> {
if (this.cursorId == null) {
throw new MongoRuntimeError(
'Unexpected null cursor id. A cursor creating command should have set this'
);
}
if (this.selectedServer == null) {
throw new MongoRuntimeError(
'Unexpected null selectedServer. A cursor creating command should have set this'
);
}
const getMoreOperation = new GetMoreOperation(
this.cursorNamespace,
this.cursorId,
this.selectedServer,
{
...this.cursorOptions,
session: this.cursorSession,
batchSize
}
);
return await executeOperation(this[kClient], getMoreOperation);
return await executeOperation(this.cursorClient, getMoreOperation);
}

@@ -688,42 +715,15 @@

*/
async [kInit](): Promise<void> {
private async cursorInit(): Promise<void> {
try {
const state = await this._initialize(this[kSession]);
const state = await this._initialize(this.cursorSession);
const response = state.response;
this[kServer] = state.server;
if (CursorResponse.is(response)) {
this[kId] = response.id;
if (response.ns) this[kNamespace] = response.ns;
this[kDocuments] = response;
} else if (response.cursor) {
// TODO(NODE-2674): Preserve int64 sent from MongoDB
this[kId] =
typeof response.cursor.id === 'number'
? Long.fromNumber(response.cursor.id)
: typeof response.cursor.id === 'bigint'
? Long.fromBigInt(response.cursor.id)
: response.cursor.id;
if (response.cursor.ns) {
this[kNamespace] = ns(response.cursor.ns);
}
this[kDocuments].pushMany(response.cursor.firstBatch);
}
// When server responses return without a cursor document, we close this cursor
// and return the raw server response. This is often the case for explain commands
// for example
if (this[kId] == null) {
this[kId] = Long.ZERO;
// TODO(NODE-3286): ExecutionResult needs to accept a generic parameter
this[kDocuments].push(state.response as TODO_NODE_3286);
}
// the cursor is now initialized, even if it is dead
this[kInitialized] = true;
this.selectedServer = state.server;
this.cursorId = response.id;
this.cursorNamespace = response.ns ?? this.namespace;
this.documents = response;
this.initialized = true; // the cursor is now initialized, even if it is dead
} catch (error) {
// the cursor is now initialized, even if an error occurred
this[kInitialized] = true;
await cleanupCursor(this, { error });
this.initialized = true;
await this.cleanup(error);
throw error;

@@ -733,3 +733,3 @@ }

if (this.isDead) {
await cleanupCursor(this, undefined);
await this.cleanup();
}

@@ -739,116 +739,34 @@

}
}
/**
* @param cursor - the cursor on which to call `next`
* @param blocking - a boolean indicating whether or not the cursor should `block` until data
* is available. Generally, this flag is set to `false` because if the getMore returns no documents,
* the cursor has been exhausted. In certain scenarios (ChangeStreams, tailable await cursors and
* `tryNext`, for example) blocking is necessary because a getMore returning no documents does
* not indicate the end of the cursor.
* @param transform - if true, the cursor's transform function is applied to the result document (if the transform exists)
* @returns the next document in the cursor, or `null`. When `blocking` is `true`, a `null` document means
* the cursor has been exhausted. Otherwise, it means that there is no document available in the cursor's buffer.
*/
async function next<T>(
cursor: AbstractCursor<T>,
{
blocking,
transform,
shift
}: {
blocking: boolean;
transform: boolean;
shift: false;
}
): Promise<boolean>;
async function next<T>(
cursor: AbstractCursor<T>,
{
blocking,
transform,
shift
}: {
blocking: boolean;
transform: boolean;
shift: true;
}
): Promise<T | null>;
async function next<T>(
cursor: AbstractCursor<T>,
{
blocking,
transform,
shift
}: {
blocking: boolean;
transform: boolean;
shift: boolean;
}
): Promise<boolean | T | null> {
if (cursor.closed) {
if (!shift) return false;
return null;
}
do {
if (cursor[kId] == null) {
// All cursors must operate within a session, one must be made implicitly if not explicitly provided
await cursor[kInit]();
/** @internal Attempt to obtain more documents */
private async fetchBatch(): Promise<void> {
if (this.isClosed) {
return;
}
if (cursor[kDocuments].length !== 0) {
if (!shift) return true;
const doc = cursor[kDocuments].shift(cursor[kOptions]);
if (doc != null && transform && cursor[kTransform]) {
try {
return cursor[kTransform](doc);
} catch (error) {
try {
await cleanupCursor(cursor, { error, needsToEmitClosed: true });
} catch (error) {
// `cleanupCursor` should never throw, squash and throw the original error
squashError(error);
}
throw error;
}
}
return doc;
}
if (cursor.isDead) {
if (this.isDead) {
// if the cursor is dead, we clean it up
// cleanupCursor should never throw, but if it does it indicates a bug in the driver
// and we should surface the error
await cleanupCursor(cursor, {});
if (!shift) return false;
return null;
await this.cleanup();
return;
}
if (this.cursorId == null) {
await this.cursorInit();
// If the cursor died or returned documents, return
if ((this.documents?.length ?? 0) !== 0 || this.isDead) return;
// Otherwise, run a getMore
}
// otherwise need to call getMore
const batchSize = cursor[kOptions].batchSize || 1000;
const batchSize = this.cursorOptions.batchSize || 1000;
try {
const response = await cursor.getMore(batchSize);
if (CursorResponse.is(response)) {
cursor[kId] = response.id;
cursor[kDocuments] = response;
} else if (response) {
const cursorId =
typeof response.cursor.id === 'number'
? Long.fromNumber(response.cursor.id)
: typeof response.cursor.id === 'bigint'
? Long.fromBigInt(response.cursor.id)
: response.cursor.id;
cursor[kDocuments].pushMany(response.cursor.nextBatch);
cursor[kId] = cursorId;
}
const response = await this.getMore(batchSize);
this.cursorId = response.id;
this.documents = response;
} catch (error) {
try {
await cleanupCursor(cursor, { error, needsToEmitClosed: true });
await this.cleanup(error);
} catch (error) {

@@ -861,3 +779,3 @@ // `cleanupCursor` should never throw, squash and throw the original error

if (cursor.isDead) {
if (this.isDead) {
// If we successfully received a response from a cursor BUT the cursor indicates that it is exhausted,

@@ -870,102 +788,86 @@ // we intentionally clean up the cursor to release its session back into the pool before the cursor

// and we should surface the error
await cleanupCursor(cursor, {});
await this.cleanup();
}
if (cursor[kDocuments].length === 0 && blocking === false) {
if (!shift) return false;
return null;
}
} while (!cursor.isDead || cursor[kDocuments].length !== 0);
if (!shift) return false;
return null;
}
async function cleanupCursor(
cursor: AbstractCursor,
options: { error?: AnyError | undefined; needsToEmitClosed?: boolean } | undefined
): Promise<void> {
const cursorId = cursor[kId];
const cursorNs = cursor[kNamespace];
const server = cursor[kServer];
const session = cursor[kSession];
const error = options?.error;
// Cursors only emit closed events once the client-side cursor has been exhausted fully or there
// was an error. Notably, when the server returns a cursor id of 0 and a non-empty batch, we
// cleanup the cursor but don't emit a `close` event.
const needsToEmitClosed = options?.needsToEmitClosed ?? cursor[kDocuments].length === 0;
if (error) {
if (cursor.loadBalanced && error instanceof MongoNetworkError) {
return await completeCleanup();
}
}
if (cursorId == null || server == null || cursorId.isZero() || cursorNs == null) {
if (needsToEmitClosed) {
cursor[kClosed] = true;
cursor[kId] = Long.ZERO;
cursor.emit(AbstractCursor.CLOSE);
}
if (session) {
if (session.owner === cursor) {
/** @internal */
private async cleanup(error?: Error) {
this.isClosed = true;
const session = this.cursorSession;
try {
if (
!this.isKilled &&
this.cursorId &&
!this.cursorId.isZero() &&
this.cursorNamespace &&
this.selectedServer &&
!session.hasEnded
) {
this.isKilled = true;
const cursorId = this.cursorId;
this.cursorId = Long.ZERO;
await executeOperation(
this.cursorClient,
new KillCursorsOperation(cursorId, this.cursorNamespace, this.selectedServer, {
session
})
);
}
} catch (error) {
squashError(error);
} finally {
if (session?.owner === this) {
await session.endSession({ error });
return;
}
if (!session.inTransaction()) {
if (!session?.inTransaction()) {
maybeClearPinnedConnection(session, { error });
}
this.emitClose();
}
return;
}
async function completeCleanup() {
if (session) {
if (session.owner === cursor) {
try {
await session.endSession({ error });
} finally {
cursor.emit(AbstractCursor.CLOSE);
}
return;
/** @internal */
private hasEmittedClose = false;
/** @internal */
private emitClose() {
try {
if (!this.hasEmittedClose && ((this.documents?.length ?? 0) === 0 || this.isClosed)) {
// @ts-expect-error: CursorEvents is generic so Parameters<CursorEvents["close"]> may not be assignable to `[]`. Not sure how to require extenders do not add parameters.
this.emit('close');
}
if (!session.inTransaction()) {
maybeClearPinnedConnection(session, { error });
}
} finally {
this.hasEmittedClose = true;
}
cursor.emit(AbstractCursor.CLOSE);
return;
}
cursor[kKilled] = true;
/** @internal */
private async transformDocument(document: NonNullable<TSchema>): Promise<TSchema> {
if (this.transform == null) return document;
if (session.hasEnded) {
return await completeCleanup();
try {
const transformedDocument = this.transform(document);
// eslint-disable-next-line no-restricted-syntax
if (transformedDocument === null) {
const TRANSFORM_TO_NULL_ERROR =
'Cursor returned a `null` document, but the cursor is not exhausted. Mapping documents to `null` is not supported in the cursor transform.';
throw new MongoAPIError(TRANSFORM_TO_NULL_ERROR);
}
return transformedDocument;
} catch (transformError) {
try {
await this.close();
} catch (closeError) {
squashError(closeError);
}
throw transformError;
}
}
try {
await executeOperation(
cursor[kClient],
new KillCursorsOperation(cursorId, cursorNs, server, { session })
);
} catch (error) {
squashError(error);
} finally {
await completeCleanup();
/** @internal */
protected throwIfInitialized() {
if (this.initialized) throw new MongoCursorInUseError();
}
}
/** @internal */
export function assertUninitialized(cursor: AbstractCursor): void {
if (cursor[kInitialized]) {
throw new MongoCursorInUseError();
}
}
class ReadableCursorStream extends Readable {

@@ -1001,4 +903,9 @@ private _cursor: AbstractCursor;

private _readNext() {
if (this._cursor.id === Long.ZERO) {
this.push(null);
return;
}
// eslint-disable-next-line github/no-then
next(this._cursor, { blocking: true, transform: true, shift: true }).then(
this._cursor.next().then(
result => {

@@ -1005,0 +912,0 @@ if (result == null) {

@@ -5,3 +5,3 @@ import type { Document } from '../bson';

import { AggregateOperation, type AggregateOptions } from '../operations/aggregate';
import { executeOperation, type ExecutionResult } from '../operations/execute_operation';
import { executeOperation } from '../operations/execute_operation';
import type { ClientSession } from '../sessions';

@@ -11,4 +11,4 @@ import type { Sort } from '../sort';

import { mergeOptions } from '../utils';
import type { AbstractCursorOptions } from './abstract_cursor';
import { AbstractCursor, assertUninitialized } from './abstract_cursor';
import type { AbstractCursorOptions, InitialCursorResponse } from './abstract_cursor';
import { AbstractCursor } from './abstract_cursor';

@@ -18,7 +18,2 @@ /** @public */

/** @internal */
const kPipeline = Symbol('pipeline');
/** @internal */
const kOptions = Symbol('options');
/**

@@ -32,6 +27,5 @@ * The **AggregationCursor** class is an internal class that embodies an aggregation cursor on MongoDB

export class AggregationCursor<TSchema = any> extends AbstractCursor<TSchema> {
public readonly pipeline: Document[];
/** @internal */
[kPipeline]: Document[];
/** @internal */
[kOptions]: AggregateOptions;
private aggregateOptions: AggregateOptions;

@@ -47,14 +41,10 @@ /** @internal */

this[kPipeline] = pipeline;
this[kOptions] = options;
this.pipeline = pipeline;
this.aggregateOptions = options;
}
get pipeline(): Document[] {
return this[kPipeline];
}
clone(): AggregationCursor<TSchema> {
const clonedOptions = mergeOptions({}, this[kOptions]);
const clonedOptions = mergeOptions({}, this.aggregateOptions);
delete clonedOptions.session;
return new AggregationCursor(this.client, this.namespace, this[kPipeline], {
return new AggregationCursor(this.client, this.namespace, this.pipeline, {
...clonedOptions

@@ -69,5 +59,5 @@ });

/** @internal */
async _initialize(session: ClientSession): Promise<ExecutionResult> {
const aggregateOperation = new AggregateOperation(this.namespace, this[kPipeline], {
...this[kOptions],
async _initialize(session: ClientSession): Promise<InitialCursorResponse> {
const aggregateOperation = new AggregateOperation(this.namespace, this.pipeline, {
...this.aggregateOptions,
...this.cursorOptions,

@@ -79,3 +69,2 @@ session

// TODO: NODE-2882
return { server: aggregateOperation.server, session, response };

@@ -86,10 +75,12 @@ }

async explain(verbosity?: ExplainVerbosityLike): Promise<Document> {
return await executeOperation(
this.client,
new AggregateOperation(this.namespace, this[kPipeline], {
...this[kOptions], // NOTE: order matters here, we may need to refine this
...this.cursorOptions,
explain: verbosity ?? true
})
);
return (
await executeOperation(
this.client,
new AggregateOperation(this.namespace, this.pipeline, {
...this.aggregateOptions, // NOTE: order matters here, we may need to refine this
...this.cursorOptions,
explain: verbosity ?? true
})
)
).shift(this.aggregateOptions);
}

@@ -112,4 +103,4 @@

addStage<T = Document>(stage: Document): AggregationCursor<T> {
assertUninitialized(this);
this[kPipeline].push(stage);
this.throwIfInitialized();
this.pipeline.push(stage);
return this as unknown as AggregationCursor<T>;

@@ -116,0 +107,0 @@ }

@@ -1,2 +0,2 @@

import type { Document, Long, Timestamp } from '../bson';
import type { Document } from '../bson';
import {

@@ -9,11 +9,15 @@ ChangeStream,

} from '../change_stream';
import { type CursorResponse } from '../cmap/wire_protocol/responses';
import { INIT, RESPONSE } from '../constants';
import type { MongoClient } from '../mongo_client';
import type { TODO_NODE_3286 } from '../mongo_types';
import { AggregateOperation } from '../operations/aggregate';
import type { CollationOptions } from '../operations/command';
import { executeOperation, type ExecutionResult } from '../operations/execute_operation';
import { executeOperation } from '../operations/execute_operation';
import type { ClientSession } from '../sessions';
import { maxWireVersion, type MongoDBNamespace } from '../utils';
import { AbstractCursor, type AbstractCursorOptions } from './abstract_cursor';
import {
AbstractCursor,
type AbstractCursorOptions,
type InitialCursorResponse
} from './abstract_cursor';

@@ -31,14 +35,2 @@ /** @internal */

/** @internal */
export type ChangeStreamAggregateRawResult<TChange> = {
$clusterTime: { clusterTime: Timestamp };
cursor: {
postBatchResumeToken: ResumeToken;
ns: string;
id: number | Long;
} & ({ firstBatch: TChange[] } | { nextBatch: TChange[] });
ok: 1;
operationTime: Timestamp;
};
/** @internal */
export class ChangeStreamCursor<

@@ -48,12 +40,9 @@ TSchema extends Document = Document,

> extends AbstractCursor<TChange, ChangeStreamEvents> {
_resumeToken: ResumeToken;
startAtOperationTime?: OperationTime;
hasReceived?: boolean;
resumeAfter: ResumeToken;
startAfter: ResumeToken;
options: ChangeStreamCursorOptions;
private _resumeToken: ResumeToken;
private startAtOperationTime: OperationTime | null;
private hasReceived?: boolean;
private readonly changeStreamCursorOptions: ChangeStreamCursorOptions;
private postBatchResumeToken?: ResumeToken;
private readonly pipeline: Document[];
postBatchResumeToken?: ResumeToken;
pipeline: Document[];
/**

@@ -75,5 +64,5 @@ * @internal

this.pipeline = pipeline;
this.options = options;
this.changeStreamCursorOptions = options;
this._resumeToken = null;
this.startAtOperationTime = options.startAtOperationTime;
this.startAtOperationTime = options.startAtOperationTime ?? null;

@@ -98,3 +87,3 @@ if (options.startAfter) {

const options: ChangeStreamCursorOptions = {
...this.options
...this.changeStreamCursorOptions
};

@@ -107,3 +96,3 @@

if (this.resumeToken != null) {
if (this.options.startAfter && !this.hasReceived) {
if (this.changeStreamCursorOptions.startAfter && !this.hasReceived) {
options.startAfter = this.resumeToken;

@@ -129,11 +118,9 @@ } else {

_processBatch(response: ChangeStreamAggregateRawResult<TChange>): void {
const cursor = response.cursor;
if (cursor.postBatchResumeToken) {
this.postBatchResumeToken = response.cursor.postBatchResumeToken;
_processBatch(response: CursorResponse): void {
const { postBatchResumeToken } = response;
if (postBatchResumeToken) {
this.postBatchResumeToken = postBatchResumeToken;
const batch =
'firstBatch' in response.cursor ? response.cursor.firstBatch : response.cursor.nextBatch;
if (batch.length === 0) {
this.resumeToken = cursor.postBatchResumeToken;
if (response.batchSize === 0) {
this.resumeToken = postBatchResumeToken;
}

@@ -149,13 +136,10 @@ }

async _initialize(session: ClientSession): Promise<ExecutionResult> {
async _initialize(session: ClientSession): Promise<InitialCursorResponse> {
const aggregateOperation = new AggregateOperation(this.namespace, this.pipeline, {
...this.cursorOptions,
...this.options,
...this.changeStreamCursorOptions,
session
});
const response = await executeOperation<
TODO_NODE_3286,
ChangeStreamAggregateRawResult<TChange>
>(session.client, aggregateOperation);
const response = await executeOperation(session.client, aggregateOperation);

@@ -167,4 +151,4 @@ const server = aggregateOperation.server;

this.startAtOperationTime == null &&
this.resumeAfter == null &&
this.startAfter == null &&
this.changeStreamCursorOptions.resumeAfter == null &&
this.changeStreamCursorOptions.startAfter == null &&
this.maxWireVersion >= 7

@@ -180,11 +164,10 @@ ) {

// TODO: NODE-2882
return { server, session, response };
}
override async getMore(batchSize: number): Promise<Document | null> {
override async getMore(batchSize: number): Promise<CursorResponse> {
const response = await super.getMore(batchSize);
this.maxWireVersion = maxWireVersion(this.server);
this._processBatch(response as ChangeStreamAggregateRawResult<TChange>);
this._processBatch(response);

@@ -191,0 +174,0 @@ this.emit(ChangeStream.MORE, response);

@@ -8,3 +8,3 @@ import { type Document } from '../bson';

import { CountOperation, type CountOptions } from '../operations/count';
import { executeOperation, type ExecutionResult } from '../operations/execute_operation';
import { executeOperation } from '../operations/execute_operation';
import { FindOperation, type FindOptions } from '../operations/find';

@@ -15,11 +15,4 @@ import type { Hint } from '../operations/operation';

import { emitWarningOnce, mergeOptions, type MongoDBNamespace, squashError } from '../utils';
import { AbstractCursor, assertUninitialized } from './abstract_cursor';
import { AbstractCursor, type InitialCursorResponse } from './abstract_cursor';
/** @internal */
const kFilter = Symbol('filter');
/** @internal */
const kNumReturned = Symbol('numReturned');
/** @internal */
const kBuiltOptions = Symbol('builtOptions');
/** @public Flags allowed for cursor */

@@ -38,7 +31,7 @@ export const FLAGS = [

/** @internal */
[kFilter]: Document;
private cursorFilter: Document;
/** @internal */
[kNumReturned] = 0;
private numReturned = 0;
/** @internal */
[kBuiltOptions]: FindOptions;
private readonly findOptions: FindOptions;

@@ -54,7 +47,7 @@ /** @internal */

this[kFilter] = filter;
this[kBuiltOptions] = options;
this.cursorFilter = filter;
this.findOptions = options;
if (options.sort != null) {
this[kBuiltOptions].sort = formatSort(options.sort);
this.findOptions.sort = formatSort(options.sort);
}

@@ -64,5 +57,5 @@ }

clone(): FindCursor<TSchema> {
const clonedOptions = mergeOptions({}, this[kBuiltOptions]);
const clonedOptions = mergeOptions({}, this.findOptions);
delete clonedOptions.session;
return new FindCursor(this.client, this.namespace, this[kFilter], {
return new FindCursor(this.client, this.namespace, this.cursorFilter, {
...clonedOptions

@@ -77,5 +70,5 @@ });

/** @internal */
async _initialize(session: ClientSession): Promise<ExecutionResult> {
const findOperation = new FindOperation(this.namespace, this[kFilter], {
...this[kBuiltOptions], // NOTE: order matters here, we may need to refine this
async _initialize(session: ClientSession): Promise<InitialCursorResponse> {
const findOperation = new FindOperation(this.namespace, this.cursorFilter, {
...this.findOptions, // NOTE: order matters here, we may need to refine this
...this.cursorOptions,

@@ -88,10 +81,4 @@ session

// the response is not a cursor when `explain` is enabled
if (CursorResponse.is(response)) {
this[kNumReturned] = response.batchSize;
} else {
// Can be an explain response, hence the ?. on everything
this[kNumReturned] = this[kNumReturned] + (response?.cursor?.firstBatch?.length ?? 0);
}
this.numReturned = response.batchSize;
// TODO: NODE-2882
return { server: findOperation.server, session, response };

@@ -101,7 +88,7 @@ }

/** @internal */
override async getMore(batchSize: number): Promise<Document | null> {
const numReturned = this[kNumReturned];
override async getMore(batchSize: number): Promise<CursorResponse> {
const numReturned = this.numReturned;
if (numReturned) {
// TODO(DRIVERS-1448): Remove logic to enforce `limit` in the driver
const limit = this[kBuiltOptions].limit;
const limit = this.findOptions.limit;
batchSize =

@@ -128,9 +115,5 @@ limit && limit > 0 && numReturned + batchSize > limit ? limit - numReturned : batchSize;

const response = await super.getMore(batchSize, false);
const response = await super.getMore(batchSize);
// TODO: wrap this in some logic to prevent it from happening if we don't need this support
if (CursorResponse.is(response)) {
this[kNumReturned] = this[kNumReturned] + response.batchSize;
} else {
this[kNumReturned] = this[kNumReturned] + (response?.cursor?.nextBatch?.length ?? 0);
}
this.numReturned = this.numReturned + response.batchSize;

@@ -153,4 +136,4 @@ return response;

this.client,
new CountOperation(this.namespace, this[kFilter], {
...this[kBuiltOptions], // NOTE: order matters here, we may need to refine this
new CountOperation(this.namespace, this.cursorFilter, {
...this.findOptions, // NOTE: order matters here, we may need to refine this
...this.cursorOptions,

@@ -164,10 +147,12 @@ ...options

async explain(verbosity?: ExplainVerbosityLike): Promise<Document> {
return await executeOperation(
this.client,
new FindOperation(this.namespace, this[kFilter], {
...this[kBuiltOptions], // NOTE: order matters here, we may need to refine this
...this.cursorOptions,
explain: verbosity ?? true
})
);
return (
await executeOperation(
this.client,
new FindOperation(this.namespace, this.cursorFilter, {
...this.findOptions, // NOTE: order matters here, we may need to refine this
...this.cursorOptions,
explain: verbosity ?? true
})
)
).shift(this.findOptions);
}

@@ -177,4 +162,4 @@

filter(filter: Document): this {
assertUninitialized(this);
this[kFilter] = filter;
this.throwIfInitialized();
this.cursorFilter = filter;
return this;

@@ -189,4 +174,4 @@ }

hint(hint: Hint): this {
assertUninitialized(this);
this[kBuiltOptions].hint = hint;
this.throwIfInitialized();
this.findOptions.hint = hint;
return this;

@@ -201,4 +186,4 @@ }

min(min: Document): this {
assertUninitialized(this);
this[kBuiltOptions].min = min;
this.throwIfInitialized();
this.findOptions.min = min;
return this;

@@ -213,4 +198,4 @@ }

max(max: Document): this {
assertUninitialized(this);
this[kBuiltOptions].max = max;
this.throwIfInitialized();
this.findOptions.max = max;
return this;

@@ -227,4 +212,4 @@ }

returnKey(value: boolean): this {
assertUninitialized(this);
this[kBuiltOptions].returnKey = value;
this.throwIfInitialized();
this.findOptions.returnKey = value;
return this;

@@ -239,4 +224,4 @@ }

showRecordId(value: boolean): this {
assertUninitialized(this);
this[kBuiltOptions].showRecordId = value;
this.throwIfInitialized();
this.findOptions.showRecordId = value;
return this;

@@ -252,3 +237,3 @@ }

addQueryModifier(name: string, value: string | boolean | number | Document): this {
assertUninitialized(this);
this.throwIfInitialized();
if (name[0] !== '$') {

@@ -264,39 +249,39 @@ throw new MongoInvalidArgumentError(`${name} is not a valid query modifier`);

case 'comment':
this[kBuiltOptions].comment = value as string | Document;
this.findOptions.comment = value as string | Document;
break;
case 'explain':
this[kBuiltOptions].explain = value as boolean;
this.findOptions.explain = value as boolean;
break;
case 'hint':
this[kBuiltOptions].hint = value as string | Document;
this.findOptions.hint = value as string | Document;
break;
case 'max':
this[kBuiltOptions].max = value as Document;
this.findOptions.max = value as Document;
break;
case 'maxTimeMS':
this[kBuiltOptions].maxTimeMS = value as number;
this.findOptions.maxTimeMS = value as number;
break;
case 'min':
this[kBuiltOptions].min = value as Document;
this.findOptions.min = value as Document;
break;
case 'orderby':
this[kBuiltOptions].sort = formatSort(value as string | Document);
this.findOptions.sort = formatSort(value as string | Document);
break;
case 'query':
this[kFilter] = value as Document;
this.cursorFilter = value as Document;
break;
case 'returnKey':
this[kBuiltOptions].returnKey = value as boolean;
this.findOptions.returnKey = value as boolean;
break;
case 'showDiskLoc':
this[kBuiltOptions].showRecordId = value as boolean;
this.findOptions.showRecordId = value as boolean;
break;

@@ -317,4 +302,4 @@

comment(value: string): this {
assertUninitialized(this);
this[kBuiltOptions].comment = value;
this.throwIfInitialized();
this.findOptions.comment = value;
return this;

@@ -329,3 +314,3 @@ }

maxAwaitTimeMS(value: number): this {
assertUninitialized(this);
this.throwIfInitialized();
if (typeof value !== 'number') {

@@ -335,3 +320,3 @@ throw new MongoInvalidArgumentError('Argument for maxAwaitTimeMS must be a number');

this[kBuiltOptions].maxAwaitTimeMS = value;
this.findOptions.maxAwaitTimeMS = value;
return this;

@@ -346,3 +331,3 @@ }

override maxTimeMS(value: number): this {
assertUninitialized(this);
this.throwIfInitialized();
if (typeof value !== 'number') {

@@ -352,3 +337,3 @@ throw new MongoInvalidArgumentError('Argument for maxTimeMS must be a number');

this[kBuiltOptions].maxTimeMS = value;
this.findOptions.maxTimeMS = value;
return this;

@@ -398,4 +383,4 @@ }

project<T extends Document = Document>(value: Document): FindCursor<T> {
assertUninitialized(this);
this[kBuiltOptions].projection = value;
this.throwIfInitialized();
this.findOptions.projection = value;
return this as unknown as FindCursor<T>;

@@ -411,8 +396,8 @@ }

sort(sort: Sort | string, direction?: SortDirection): this {
assertUninitialized(this);
if (this[kBuiltOptions].tailable) {
this.throwIfInitialized();
if (this.findOptions.tailable) {
throw new MongoTailableCursorError('Tailable cursor does not support sorting');
}
this[kBuiltOptions].sort = formatSort(sort, direction);
this.findOptions.sort = formatSort(sort, direction);
return this;

@@ -428,5 +413,5 @@ }

allowDiskUse(allow = true): this {
assertUninitialized(this);
this.throwIfInitialized();
if (!this[kBuiltOptions].sort) {
if (!this.findOptions.sort) {
throw new MongoInvalidArgumentError('Option "allowDiskUse" requires a sort specification');

@@ -437,7 +422,7 @@ }

if (!allow) {
this[kBuiltOptions].allowDiskUse = false;
this.findOptions.allowDiskUse = false;
return this;
}
this[kBuiltOptions].allowDiskUse = true;
this.findOptions.allowDiskUse = true;
return this;

@@ -452,4 +437,4 @@ }

collation(value: CollationOptions): this {
assertUninitialized(this);
this[kBuiltOptions].collation = value;
this.throwIfInitialized();
this.findOptions.collation = value;
return this;

@@ -464,4 +449,4 @@ }

limit(value: number): this {
assertUninitialized(this);
if (this[kBuiltOptions].tailable) {
this.throwIfInitialized();
if (this.findOptions.tailable) {
throw new MongoTailableCursorError('Tailable cursor does not support limit');

@@ -474,3 +459,3 @@ }

this[kBuiltOptions].limit = value;
this.findOptions.limit = value;
return this;

@@ -485,4 +470,4 @@ }

skip(value: number): this {
assertUninitialized(this);
if (this[kBuiltOptions].tailable) {
this.throwIfInitialized();
if (this.findOptions.tailable) {
throw new MongoTailableCursorError('Tailable cursor does not support skip');

@@ -495,5 +480,5 @@ }

this[kBuiltOptions].skip = value;
this.findOptions.skip = value;
return this;
}
}
import type { Document } from '../bson';
import type { Db } from '../db';
import { executeOperation, type ExecutionResult } from '../operations/execute_operation';
import { executeOperation } from '../operations/execute_operation';
import {

@@ -10,3 +10,3 @@ type CollectionInfo,

import type { ClientSession } from '../sessions';
import { AbstractCursor } from './abstract_cursor';
import { AbstractCursor, type InitialCursorResponse } from './abstract_cursor';

@@ -38,3 +38,3 @@ /** @public */

/** @internal */
async _initialize(session: ClientSession | undefined): Promise<ExecutionResult> {
async _initialize(session: ClientSession | undefined): Promise<InitialCursorResponse> {
const operation = new ListCollectionsOperation(this.parent, this.filter, {

@@ -48,5 +48,4 @@ ...this.cursorOptions,

// TODO: NODE-2882
return { server: operation.server, session, response };
}
}
import type { Collection } from '../collection';
import { executeOperation, type ExecutionResult } from '../operations/execute_operation';
import { executeOperation } from '../operations/execute_operation';
import { ListIndexesOperation, type ListIndexesOptions } from '../operations/indexes';
import type { ClientSession } from '../sessions';
import { AbstractCursor } from './abstract_cursor';
import { AbstractCursor, type InitialCursorResponse } from './abstract_cursor';

@@ -26,3 +26,3 @@ /** @public */

/** @internal */
async _initialize(session: ClientSession | undefined): Promise<ExecutionResult> {
async _initialize(session: ClientSession | undefined): Promise<InitialCursorResponse> {
const operation = new ListIndexesOperation(this.parent, {

@@ -36,5 +36,4 @@ ...this.cursorOptions,

// TODO: NODE-2882
return { server: operation.server, session, response };
}
}

@@ -1,5 +0,6 @@

import type { BSONSerializeOptions, Document, Long } from '../bson';
import type { BSONSerializeOptions, Document } from '../bson';
import { CursorResponse } from '../cmap/wire_protocol/responses';
import type { Db } from '../db';
import { MongoAPIError, MongoUnexpectedServerResponseError } from '../error';
import { executeOperation, type ExecutionResult } from '../operations/execute_operation';
import { MongoAPIError } from '../error';
import { executeOperation } from '../operations/execute_operation';
import { GetMoreOperation } from '../operations/get_more';

@@ -11,3 +12,3 @@ import { RunCommandOperation } from '../operations/run_command';

import { ns } from '../utils';
import { AbstractCursor } from './abstract_cursor';
import { AbstractCursor, type InitialCursorResponse } from './abstract_cursor';

@@ -20,8 +21,2 @@ /** @public */

/** @internal */
type RunCursorCommandResponse = {
cursor: { id: bigint | Long | number; ns: string; firstBatch: Document[] };
ok: 1;
};
/** @public */

@@ -107,12 +102,12 @@ export class RunCommandCursor extends AbstractCursor {

/** @internal */
protected async _initialize(session: ClientSession): Promise<ExecutionResult> {
const operation = new RunCommandOperation<RunCursorCommandResponse>(this.db, this.command, {
protected async _initialize(session: ClientSession): Promise<InitialCursorResponse> {
const operation = new RunCommandOperation<CursorResponse>(this.db, this.command, {
...this.cursorOptions,
session: session,
readPreference: this.cursorOptions.readPreference
readPreference: this.cursorOptions.readPreference,
responseType: CursorResponse
});
const response = await executeOperation(this.client, operation);
if (response.cursor == null) {
throw new MongoUnexpectedServerResponseError('Expected server to respond with cursor');
}
return {

@@ -126,3 +121,3 @@ server: operation.server,

/** @internal */
override async getMore(_batchSize: number): Promise<Document> {
override async getMore(_batchSize: number): Promise<CursorResponse> {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion

@@ -132,4 +127,3 @@ const getMoreOperation = new GetMoreOperation(this.namespace, this.id!, this.server!, {

session: this.session,
...this.getMoreOptions,
useCursorResponse: false
...this.getMoreOptions
});

@@ -136,0 +130,0 @@

@@ -63,3 +63,4 @@ import type { Document } from './bson';

UnsatisfiableWriteConcern: 100,
Reauthenticate: 391
Reauthenticate: 391,
ReadConcernMajorityNotAvailableYet: 134
} as const);

@@ -754,4 +755,4 @@

**/
constructor(message: string) {
super(message);
constructor(message: string, options?: { cause?: Error }) {
super(message, options);
}

@@ -1162,15 +1163,2 @@

function makeWriteConcernResultObject(input: any) {
const output = Object.assign({}, input);
if (output.ok === 0) {
output.ok = 1;
delete output.errmsg;
delete output.code;
delete output.codeName;
}
return output;
}
/**

@@ -1182,4 +1170,4 @@ * An error thrown when the server reports a writeConcernError

export class MongoWriteConcernError extends MongoServerError {
/** The result document (provided if ok: 1) */
result?: Document;
/** The result document */
result: Document;

@@ -1197,13 +1185,14 @@ /**

**/
constructor(message: ErrorDescription, result?: Document) {
if (result && Array.isArray(result.errorLabels)) {
message.errorLabels = result.errorLabels;
}
super(message);
this.errInfo = message.errInfo;
if (result != null) {
this.result = makeWriteConcernResultObject(result);
}
constructor(result: {
writeConcernError: {
code: number;
errmsg: string;
codeName?: string;
errInfo?: Document;
};
errorLabels?: string[];
}) {
super({ ...result, ...result.writeConcernError });
this.errInfo = result.writeConcernError.errInfo;
this.result = result;
}

@@ -1229,3 +1218,4 @@

MONGODB_ERROR_CODES.NotPrimaryOrSecondary,
MONGODB_ERROR_CODES.ExceededTimeLimit
MONGODB_ERROR_CODES.ExceededTimeLimit,
MONGODB_ERROR_CODES.ReadConcernMajorityNotAvailableYet
]);

@@ -1232,0 +1222,0 @@

@@ -163,3 +163,3 @@ import { Admin } from './admin';

export type { AdminPrivate } from './admin';
export type { BSONSerializeOptions, Document } from './bson';
export type { BSONElement, BSONSerializeOptions, Document } from './bson';
export type { deserialize, serialize } from './bson';

@@ -229,2 +229,3 @@ export type {

GCPEncryptionKeyOptions,
KMIPEncryptionKeyOptions,
RangeOptions

@@ -242,4 +243,9 @@ } from './client-side-encryption/client_encryption';

export type {
AWSKMSProviderConfiguration,
AzureKMSProviderConfiguration,
ClientEncryptionDataKeyProvider,
KMSProviders
GCPKMSProviderConfiguration,
KMIPKMSProviderConfiguration,
KMSProviders,
LocalKMSProviderConfiguration
} from './client-side-encryption/providers/index';

@@ -305,4 +311,9 @@ export type {

} from './cmap/wire_protocol/responses';
export type { CollectionOptions, CollectionPrivate, ModifyResult } from './collection';
export type {
CollectionOptions,
CollectionPrivate,
CountDocumentsOptions,
ModifyResult
} from './collection';
export type {
COMMAND_FAILED,

@@ -343,9 +354,9 @@ COMMAND_STARTED,

} from './cursor/abstract_cursor';
export type { InternalAbstractCursorOptions } from './cursor/abstract_cursor';
export type {
InitialCursorResponse,
InternalAbstractCursorOptions
} from './cursor/abstract_cursor';
export type { AggregationCursorOptions } from './cursor/aggregation_cursor';
export type { ChangeStreamCursorOptions } from './cursor/change_stream_cursor';
export type {
ChangeStreamAggregateRawResult,
ChangeStreamCursorOptions
} from './cursor/change_stream_cursor';
export type {
ListSearchIndexesCursor,

@@ -465,3 +476,2 @@ ListSearchIndexesOptions

export type { CountOptions } from './operations/count';
export type { CountDocumentsOptions } from './operations/count_documents';
export type {

@@ -476,3 +486,2 @@ ClusteredCollectionOptions,

export type { EstimatedDocumentCountOptions } from './operations/estimated_document_count';
export type { ExecutionResult } from './operations/execute_operation';
export type { FindOptions } from './operations/find';

@@ -479,0 +488,0 @@ export type {

import type { Document } from '../bson';
import { CursorResponse, ExplainedCursorResponse } from '../cmap/wire_protocol/responses';
import { MongoInvalidArgumentError } from '../error';
import { type TODO_NODE_3286 } from '../mongo_types';
import type { Server } from '../sdam/server';

@@ -40,3 +40,3 @@ import type { ClientSession } from '../sessions';

/** @internal */
export class AggregateOperation<T = Document> extends CommandOperation<T> {
export class AggregateOperation extends CommandOperation<CursorResponse> {
override options: AggregateOptions;

@@ -98,3 +98,6 @@ target: string | typeof DB_AGGREGATE_COLLECTION;

override async execute(server: Server, session: ClientSession | undefined): Promise<T> {
override async execute(
server: Server,
session: ClientSession | undefined
): Promise<CursorResponse> {
const options: AggregateOptions = this.options;

@@ -139,4 +142,8 @@ const serverWireVersion = maxWireVersion(server);

const res: TODO_NODE_3286 = await super.executeCommand(server, session, command);
return res;
return await super.executeCommand(
server,
session,
command,
this.explain ? ExplainedCursorResponse : CursorResponse
);
}

@@ -143,0 +150,0 @@ }

@@ -53,4 +53,3 @@ import type {

// Execute the bulk
const result = await bulk.execute({ ...options, session });
return result;
return await bulk.execute({ ...options, session });
}

@@ -57,0 +56,0 @@ }

import type { BSONSerializeOptions, Document } from '../bson';
import { type MongoDBResponseConstructor } from '../cmap/wire_protocol/responses';
import { MongoInvalidArgumentError } from '../error';

@@ -109,8 +110,21 @@ import { Explain, type ExplainOptions } from '../explain';

public async executeCommand<T extends MongoDBResponseConstructor>(
server: Server,
session: ClientSession | undefined,
cmd: Document,
responseType: T | undefined
): Promise<typeof responseType extends undefined ? Document : InstanceType<T>>;
public async executeCommand(
server: Server,
session: ClientSession | undefined,
cmd: Document
): Promise<Document>;
async executeCommand(
server: Server,
session: ClientSession | undefined,
cmd: Document
cmd: Document,
responseType?: MongoDBResponseConstructor
): Promise<Document> {
// TODO: consider making this a non-enumerable property
this.server = server;

@@ -156,4 +170,4 @@

return await server.command(this.ns, cmd, options);
return await server.command(this.ns, cmd, options, responseType);
}
}

@@ -7,4 +7,4 @@ import type { Document } from '../bson';

import type { ClientSession } from '../sessions';
import type { MongoDBNamespace } from '../utils';
import type { WriteConcernOptions } from '../write_concern';
import { type MongoDBNamespace } from '../utils';
import { type WriteConcernOptions } from '../write_concern';
import { type CollationOptions, CommandOperation, type CommandOperationOptions } from './command';

@@ -11,0 +11,0 @@ import { Aspect, defineAspects, type Hint } from './operation';

@@ -1,3 +0,1 @@

import type { Document } from '../bson';
import { type CursorResponse } from '../cmap/wire_protocol/responses';
import {

@@ -21,3 +19,2 @@ isRetryableReadError,

import { ReadPreference } from '../read_preference';
import type { Server } from '../sdam/server';
import type { ServerDescription } from '../sdam/server_description';

@@ -42,12 +39,2 @@ import {

/** @internal */
export interface ExecutionResult {
/** The server selected for the operation */
server: Server;
/** The session used for this operation, may be implicitly created */
session?: ClientSession;
/** The raw server response for the operation */
response: Document | CursorResponse;
}
/**

@@ -54,0 +41,0 @@ * Executes the given operation with provided arguments.

@@ -9,3 +9,3 @@ import type { Document } from '../bson';

import { decorateWithCollation, hasAtomicOperators, maxWireVersion } from '../utils';
import type { WriteConcern, WriteConcernSettings } from '../write_concern';
import { type WriteConcern, type WriteConcernSettings } from '../write_concern';
import { CommandOperation, type CommandOperationOptions } from './command';

@@ -12,0 +12,0 @@ import { Aspect, defineAspects } from './operation';

import type { Document } from '../bson';
import { CursorResponse, ExplainedCursorResponse } from '../cmap/wire_protocol/responses';
import { MongoInvalidArgumentError } from '../error';

@@ -68,3 +69,3 @@ import { ReadConcern } from '../read_concern';

/** @internal */
export class FindOperation extends CommandOperation<Document> {
export class FindOperation extends CommandOperation<CursorResponse> {
/**

@@ -99,3 +100,6 @@ * @remarks WriteConcern can still be present on the options because

override async execute(server: Server, session: ClientSession | undefined): Promise<Document> {
override async execute(
server: Server,
session: ClientSession | undefined
): Promise<CursorResponse> {
this.server = server;

@@ -119,3 +123,3 @@

},
undefined
this.explain ? ExplainedCursorResponse : CursorResponse
);

@@ -122,0 +126,0 @@ }

@@ -1,2 +0,2 @@

import type { Document, Long } from '../bson';
import type { Long } from '../bson';
import { CursorResponse } from '../cmap/wire_protocol/responses';

@@ -23,4 +23,2 @@ import { MongoRuntimeError } from '../error';

maxAwaitTimeMS?: number;
useCursorResponse: boolean;
}

@@ -62,3 +60,6 @@

*/
override async execute(server: Server, _session: ClientSession | undefined): Promise<Document> {
override async execute(
server: Server,
_session: ClientSession | undefined
): Promise<CursorResponse> {
if (server !== this.server) {

@@ -104,8 +105,3 @@ throw new MongoRuntimeError('Getmore must run on the same server operation began on');

return await server.command(
this.ns,
getMoreCmd,
commandOptions,
this.options.useCursorResponse ? CursorResponse : undefined
);
return await server.command(this.ns, getMoreCmd, commandOptions, CursorResponse);
}

@@ -112,0 +108,0 @@ }

import type { Document } from '../bson';
import { CursorResponse } from '../cmap/wire_protocol/responses';
import type { Collection } from '../collection';

@@ -356,3 +357,3 @@ import { type AbstractCursorOptions } from '../cursor/abstract_cursor';

/** @internal */
export class ListIndexesOperation extends CommandOperation<Document> {
export class ListIndexesOperation extends CommandOperation<CursorResponse> {
/**

@@ -380,3 +381,6 @@ * @remarks WriteConcern can still be present on the options because

override async execute(server: Server, session: ClientSession | undefined): Promise<Document> {
override async execute(
server: Server,
session: ClientSession | undefined
): Promise<CursorResponse> {
const serverWireVersion = maxWireVersion(server);

@@ -394,3 +398,3 @@

return await super.executeCommand(server, session, command);
return await super.executeCommand(server, session, command, CursorResponse);
}

@@ -397,0 +401,0 @@ }

import type { Binary, Document } from '../bson';
import { CursorResponse } from '../cmap/wire_protocol/responses';
import type { Db } from '../db';

@@ -20,3 +21,3 @@ import type { Server } from '../sdam/server';

/** @internal */
export class ListCollectionsOperation extends CommandOperation<Document> {
export class ListCollectionsOperation extends CommandOperation<CursorResponse> {
/**

@@ -55,7 +56,11 @@ * @remarks WriteConcern can still be present on the options because

override async execute(server: Server, session: ClientSession | undefined): Promise<Document> {
override async execute(
server: Server,
session: ClientSession | undefined
): Promise<CursorResponse> {
return await super.executeCommand(
server,
session,
this.generateCommand(maxWireVersion(server))
this.generateCommand(maxWireVersion(server)),
CursorResponse
);

@@ -62,0 +67,0 @@ }

import type { BSONSerializeOptions, Document } from '../bson';
import { type MongoDBResponseConstructor } from '../cmap/wire_protocol/responses';
import { type Db } from '../db';

@@ -20,3 +21,7 @@ import { type TODO_NODE_3286 } from '../mongo_types';

export class RunCommandOperation<T = Document> extends AbstractOperation<T> {
constructor(parent: Db, public command: Document, public override options: RunCommandOptions) {
constructor(
parent: Db,
public command: Document,
public override options: RunCommandOptions & { responseType?: MongoDBResponseConstructor }
) {
super(options);

@@ -32,7 +37,12 @@ this.ns = parent.s.namespace.withCollection('$cmd');

this.server = server;
const res: TODO_NODE_3286 = await server.command(this.ns, this.command, {
...this.options,
readPreference: this.readPreference,
session
});
const res: TODO_NODE_3286 = await server.command(
this.ns,
this.command,
{
...this.options,
readPreference: this.readPreference,
session
},
this.options.responseType
);
return res;

@@ -39,0 +49,0 @@ }

@@ -125,3 +125,4 @@ import type { Document } from '../bson';

return await super.executeCommand(server, session, command);
const res = await super.executeCommand(server, session, command);
return res;
}

@@ -128,0 +129,0 @@ }

@@ -51,2 +51,3 @@ import type { Document } from '../bson';

} from '../utils';
import { throwIfWriteConcernError } from '../write_concern';
import {

@@ -327,3 +328,5 @@ type ClusterTime,

try {
return await conn.command(ns, cmd, finalOptions, responseType);
const res = await conn.command(ns, cmd, finalOptions, responseType);
throwIfWriteConcernError(res);
return res;
} catch (commandError) {

@@ -339,3 +342,5 @@ throw this.decorateCommandError(conn, cmd, finalOptions, commandError);

try {
return await conn.command(ns, cmd, finalOptions, responseType);
const res = await conn.command(ns, cmd, finalOptions, responseType);
throwIfWriteConcernError(res);
return res;
} catch (commandError) {

@@ -342,0 +347,0 @@ throw this.decorateCommandError(conn, cmd, finalOptions, commandError);

@@ -177,3 +177,3 @@ import { Binary, type Document, Long, type Timestamp } from './bson';

this.clientOptions = clientOptions;
this.timeoutMS = options.defaultTimeoutMS ?? client.options?.timeoutMS;
this.timeoutMS = options.defaultTimeoutMS ?? client.s.options?.timeoutMS;

@@ -180,0 +180,0 @@ this.explicit = !!options.explicit;

@@ -11,7 +11,7 @@ import * as crypto from 'crypto';

import { type Document, ObjectId, resolveBSONOptions } from './bson';
import { deserialize, type Document, ObjectId, resolveBSONOptions } from './bson';
import type { Connection } from './cmap/connection';
import { MAX_SUPPORTED_WIRE_VERSION } from './cmap/wire_protocol/constants';
import type { Collection } from './collection';
import { LEGACY_HELLO_COMMAND } from './constants';
import { kDecoratedKeys, LEGACY_HELLO_COMMAND } from './constants';
import type { AbstractCursor } from './cursor/abstract_cursor';

@@ -1370,1 +1370,51 @@ import type { FindCursor } from './cursor/find_cursor';

}
/**
* Recurse through the (identically-shaped) `decrypted` and `original`
* objects and attach a `decryptedKeys` property on each sub-object that
* contained encrypted fields. Because we only call this on BSON responses,
* we do not need to worry about circular references.
*
* @internal
*/
export function decorateDecryptionResult(
decrypted: Document & { [kDecoratedKeys]?: Array<string> },
original: Document,
isTopLevelDecorateCall = true
): void {
if (isTopLevelDecorateCall) {
// The original value could have been either a JS object or a BSON buffer
if (Buffer.isBuffer(original)) {
original = deserialize(original);
}
if (Buffer.isBuffer(decrypted)) {
throw new MongoRuntimeError('Expected result of decryption to be deserialized BSON object');
}
}
if (!decrypted || typeof decrypted !== 'object') return;
for (const k of Object.keys(decrypted)) {
const originalValue = original[k];
// An object was decrypted by libmongocrypt if and only if it was
// a BSON Binary object with subtype 6.
if (originalValue && originalValue._bsontype === 'Binary' && originalValue.sub_type === 6) {
if (!decrypted[kDecoratedKeys]) {
Object.defineProperty(decrypted, kDecoratedKeys, {
value: [],
configurable: true,
enumerable: false,
writable: false
});
}
// this is defined in the preceding if-statement
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
decrypted[kDecoratedKeys]!.push(k);
// Do not recurse into this decrypted value. It could be a sub-document/array,
// in which case there is no original value associated with its subfields.
continue;
}
decorateDecryptionResult(decrypted[k], originalValue, false);
}
}
import { type Document } from './bson';
import { MongoDBResponse } from './cmap/wire_protocol/responses';
import { MongoWriteConcernError } from './error';

@@ -162,1 +164,17 @@ /** @public */

}
/** Called with either a plain object or MongoDBResponse */
export function throwIfWriteConcernError(response: unknown): void {
if (typeof response === 'object' && response != null) {
const writeConcernError: object | null =
MongoDBResponse.is(response) && response.has('writeConcernError')
? response.toObject()
: !MongoDBResponse.is(response) && 'writeConcernError' in response
? response
: null;
if (writeConcernError != null) {
throw new MongoWriteConcernError(writeConcernError as any);
}
}
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc