Skip to content

Commit ebf1533

Browse files
authored
Merge pull request Automattic#15214 from Automattic/8.10
8.10
2 parents 8a55290 + 0d41398 commit ebf1533

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1984
-111
lines changed

.eslintrc.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ module.exports = {
1414
'**/docs/js/native.js',
1515
'!.*',
1616
'node_modules',
17-
'.git'
17+
'.git',
18+
'data'
1819
],
1920
overrides: [
2021
{
@@ -104,7 +105,7 @@ module.exports = {
104105
// 'markdown'
105106
],
106107
parserOptions: {
107-
ecmaVersion: 2020
108+
ecmaVersion: 2022
108109
},
109110
env: {
110111
node: true,
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: Encryption Tests
2+
3+
on:
4+
push:
5+
branches: ['master']
6+
pull_request:
7+
branches: [ 'master' ]
8+
workflow_dispatch: {}
9+
10+
permissions:
11+
contents: write
12+
pull-requests: write
13+
id-token: write
14+
15+
jobs:
16+
run-tests:
17+
permissions:
18+
# required for all workflows
19+
security-events: write
20+
id-token: write
21+
contents: write
22+
runs-on: ubuntu-latest
23+
name: Encryption tests
24+
env:
25+
FORCE_COLOR: true
26+
steps:
27+
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
28+
- name: Setup node
29+
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
30+
with:
31+
node-version: 22
32+
- name: Install Dependencies
33+
run: npm install
34+
- name: Install mongodb-client-encryption
35+
run: npm install mongodb-client-encryption
36+
- name: Setup Tests
37+
run: npm run setup-test-encryption
38+
- name: Run Tests
39+
run: npm run test-encryption

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ jobs:
108108
- name: Setup Deno
109109
uses: denoland/setup-deno@v2
110110
with:
111-
deno-version: v1.37.x
111+
deno-version: v2.1.x
112112
- run: deno --version
113113
- run: npm install
114114
- name: Run Deno tests

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,6 @@ examples/ecommerce-netlify-functions/.netlify/state.json
6767

6868
notes.md
6969
list.out
70+
71+
data
72+
*.pid

CONTRIBUTING.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ If you have a question about Mongoose (not a bug report) please post it to eithe
4646
* execute `npm run test-tsd` to run the typescript tests
4747
* execute `npm run ts-benchmark` to run the typescript benchmark "performance test" for a single time.
4848
* execute `npm run ts-benchmark-watch` to run the typescript benchmark "performance test" while watching changes on types folder. Note: Make sure to commit all changes before executing this command.
49+
* in order to run tests that require an cluster with encryption locally, run `npm run setup-test-encryption` followed by `npm run test-encryption`. Alternatively, you can start an encrypted cluster using the `scripts/configure-cluster-with-encryption.sh` file.
50+
* These scripts can take a few minutes to run.
51+
* To change an encryption configuration, it is recommended to follow these steps:
52+
* Edit the variables in `scripts/configure-cluster-with-encryption.sh` with your desired configuration.
53+
* Restart your shell.
54+
* Delete the `data/` directory if it exists.
55+
* Finally, run the configuration script.
4956

5057
## Documentation
5158

lib/aggregate.js

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const getConstructorName = require('./helpers/getConstructorName');
1313
const prepareDiscriminatorPipeline = require('./helpers/aggregate/prepareDiscriminatorPipeline');
1414
const stringifyFunctionOperators = require('./helpers/aggregate/stringifyFunctionOperators');
1515
const utils = require('./utils');
16+
const { modelSymbol } = require('./helpers/symbols');
1617
const read = Query.prototype.read;
1718
const readConcern = Query.prototype.readConcern;
1819

@@ -46,13 +47,17 @@ const validRedactStringValues = new Set(['$$DESCEND', '$$PRUNE', '$$KEEP']);
4647
* @see MongoDB https://www.mongodb.com/docs/manual/applications/aggregation/
4748
* @see driver https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#aggregate
4849
* @param {Array} [pipeline] aggregation pipeline as an array of objects
49-
* @param {Model} [model] the model to use with this aggregate.
50+
* @param {Model|Connection} [modelOrConn] the model or connection to use with this aggregate.
5051
* @api public
5152
*/
5253

53-
function Aggregate(pipeline, model) {
54+
function Aggregate(pipeline, modelOrConn) {
5455
this._pipeline = [];
55-
this._model = model;
56+
if (modelOrConn == null || modelOrConn[modelSymbol]) {
57+
this._model = modelOrConn;
58+
} else {
59+
this._connection = modelOrConn;
60+
}
5661
this.options = {};
5762

5863
if (arguments.length === 1 && Array.isArray(pipeline)) {
@@ -1041,12 +1046,24 @@ Aggregate.prototype.pipeline = function() {
10411046
*/
10421047

10431048
Aggregate.prototype.exec = async function exec() {
1044-
if (!this._model) {
1049+
if (!this._model && !this._connection) {
10451050
throw new Error('Aggregate not bound to any Model');
10461051
}
10471052
if (typeof arguments[0] === 'function') {
10481053
throw new MongooseError('Aggregate.prototype.exec() no longer accepts a callback');
10491054
}
1055+
1056+
if (this._connection) {
1057+
if (!this._pipeline.length) {
1058+
throw new MongooseError('Aggregate has empty pipeline');
1059+
}
1060+
1061+
this._optionsForExec();
1062+
1063+
const cursor = await this._connection.client.db().aggregate(this._pipeline, this.options);
1064+
return await cursor.toArray();
1065+
}
1066+
10501067
const model = this._model;
10511068
const collection = this._model.collection;
10521069

lib/collection.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ function Collection(name, conn, opts) {
2929
this.collectionName = name;
3030
this.conn = conn;
3131
this.queue = [];
32-
this.buffer = true;
32+
this.buffer = !conn?._hasOpened;
3333
this.emitter = new EventEmitter();
3434

3535
if (STATES.connected === this.conn.readyState) {
@@ -311,13 +311,7 @@ Collection.prototype._getBufferTimeoutMS = function _getBufferTimeoutMS() {
311311
if (opts && opts.schemaUserProvidedOptions != null && opts.schemaUserProvidedOptions.bufferTimeoutMS != null) {
312312
return opts.schemaUserProvidedOptions.bufferTimeoutMS;
313313
}
314-
if (conn.config.bufferTimeoutMS != null) {
315-
return conn.config.bufferTimeoutMS;
316-
}
317-
if (conn.base != null && conn.base.get('bufferTimeoutMS') != null) {
318-
return conn.base.get('bufferTimeoutMS');
319-
}
320-
return 10000;
314+
return conn._getBufferTimeoutMS();
321315
};
322316

323317
/*!

lib/connection.js

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -824,12 +824,56 @@ Connection.prototype.dropCollection = async function dropCollection(collection)
824824

825825
Connection.prototype._waitForConnect = async function _waitForConnect() {
826826
if ((this.readyState === STATES.connecting || this.readyState === STATES.disconnected) && this._shouldBufferCommands()) {
827-
await new Promise(resolve => {
828-
this._queue.push({ fn: resolve });
829-
});
827+
const bufferTimeoutMS = this._getBufferTimeoutMS();
828+
let timeout = null;
829+
let timedOut = false;
830+
// The element that this function pushes onto `_queue`, stored to make it easy to remove later
831+
const queueElement = {};
832+
await Promise.race([
833+
new Promise(resolve => {
834+
queueElement.fn = resolve;
835+
this._queue.push(queueElement);
836+
}),
837+
new Promise(resolve => {
838+
timeout = setTimeout(
839+
() => {
840+
timedOut = true;
841+
resolve();
842+
},
843+
bufferTimeoutMS
844+
);
845+
})
846+
]);
847+
848+
if (timedOut) {
849+
const index = this._queue.indexOf(queueElement);
850+
if (index !== -1) {
851+
this._queue.splice(index, 1);
852+
}
853+
const message = 'Connection operation buffering timed out after ' + bufferTimeoutMS + 'ms';
854+
throw new MongooseError(message);
855+
} else if (timeout != null) {
856+
// Not strictly necessary, but avoid the extra overhead of creating a new MongooseError
857+
// in case of success
858+
clearTimeout(timeout);
859+
}
830860
}
831861
};
832862

863+
/*!
864+
* Get the default buffer timeout for this connection
865+
*/
866+
867+
Connection.prototype._getBufferTimeoutMS = function _getBufferTimeoutMS() {
868+
if (this.config.bufferTimeoutMS != null) {
869+
return this.config.bufferTimeoutMS;
870+
}
871+
if (this.base != null && this.base.get('bufferTimeoutMS') != null) {
872+
return this.base.get('bufferTimeoutMS');
873+
}
874+
return 10000;
875+
};
876+
833877
/**
834878
* Helper for MongoDB Node driver's `listCollections()`.
835879
* Returns an array of collection objects.
@@ -1156,6 +1200,10 @@ Connection.prototype.close = async function close(force) {
11561200
this.$wasForceClosed = !!force;
11571201
}
11581202

1203+
if (this._lastHeartbeatAt != null) {
1204+
this._lastHeartbeatAt = null;
1205+
}
1206+
11591207
for (const model of Object.values(this.models)) {
11601208
// If manually disconnecting, make sure to clear each model's `$init`
11611209
// promise, so Mongoose knows to re-run `init()` in case the
@@ -1742,6 +1790,18 @@ Connection.prototype.syncIndexes = async function syncIndexes(options = {}) {
17421790
* @api public
17431791
*/
17441792

1793+
/**
1794+
* Runs a [db-level aggregate()](https://www.mongodb.com/docs/manual/reference/method/db.aggregate/) on this connection's underlying `db`
1795+
*
1796+
* @method aggregate
1797+
* @memberOf Connection
1798+
* @param {Array} pipeline
1799+
* @param {Object} [options]
1800+
* @param {Boolean} [options.cursor=false] If true, make the Aggregate resolve to a Mongoose AggregationCursor rather than an array
1801+
* @return {Aggregate} Aggregation wrapper
1802+
* @api public
1803+
*/
1804+
17451805
/**
17461806
* Removes the database connection with the given name created with with `useDb()`.
17471807
*

lib/cursor/aggregationCursor.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,17 @@ function AggregationCursor(agg) {
4141
this.cursor = null;
4242
this.agg = agg;
4343
this._transforms = [];
44+
const connection = agg._connection;
4445
const model = agg._model;
4546
delete agg.options.cursor.useMongooseAggCursor;
4647
this._mongooseOptions = {};
4748

48-
_init(model, this, agg);
49+
if (connection) {
50+
this.cursor = connection.db.aggregate(agg._pipeline, agg.options || {});
51+
setImmediate(() => this.emit('cursor', this.cursor));
52+
} else {
53+
_init(model, this, agg);
54+
}
4955
}
5056

5157
util.inherits(AggregationCursor, Readable);

lib/document.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -807,8 +807,8 @@ function init(self, obj, doc, opts, prefix) {
807807
reason: e
808808
}));
809809
}
810-
} else if (opts.hydratedPopulatedDocs) {
811-
doc[i] = schemaType.cast(value, self, true);
810+
} else if (schemaType && opts.hydratedPopulatedDocs) {
811+
doc[i] = schemaType.cast(value, self, true, undefined, { hydratedPopulatedDocs: true });
812812

813813
if (doc[i] && doc[i].$__ && doc[i].$__.wasPopulated) {
814814
self.$populated(path, doc[i].$__.wasPopulated.value, doc[i].$__.wasPopulated.options);
@@ -4256,24 +4256,25 @@ function applySchemaTypeTransforms(self, json) {
42564256

42574257
for (const path of paths) {
42584258
const schematype = schema.paths[path];
4259-
if (typeof schematype.options.transform === 'function') {
4259+
const topLevelTransformFunction = schematype.options.transform ?? schematype.constructor?.defaultOptions?.transform;
4260+
const embeddedSchemaTypeTransformFunction = schematype.$embeddedSchemaType?.options?.transform
4261+
?? schematype.$embeddedSchemaType?.constructor?.defaultOptions?.transform;
4262+
if (typeof topLevelTransformFunction === 'function') {
42604263
const val = self.$get(path);
42614264
if (val === undefined) {
42624265
continue;
42634266
}
4264-
const transformedValue = schematype.options.transform.call(self, val);
4267+
const transformedValue = topLevelTransformFunction.call(self, val);
42654268
throwErrorIfPromise(path, transformedValue);
42664269
utils.setValue(path, transformedValue, json);
4267-
} else if (schematype.$embeddedSchemaType != null &&
4268-
typeof schematype.$embeddedSchemaType.options.transform === 'function') {
4270+
} else if (typeof embeddedSchemaTypeTransformFunction === 'function') {
42694271
const val = self.$get(path);
42704272
if (val === undefined) {
42714273
continue;
42724274
}
42734275
const vals = [].concat(val);
4274-
const transform = schematype.$embeddedSchemaType.options.transform;
42754276
for (let i = 0; i < vals.length; ++i) {
4276-
const transformedValue = transform.call(self, vals[i]);
4277+
const transformedValue = embeddedSchemaTypeTransformFunction.call(self, vals[i]);
42774278
vals[i] = transformedValue;
42784279
throwErrorIfPromise(path, transformedValue);
42794280
}

0 commit comments

Comments
 (0)