From 05ce588a6bc9f067afc40d8fa67341658db29b77 Mon Sep 17 00:00:00 2001 From: Aditi Khare Date: Thu, 7 Nov 2024 13:26:35 -0500 Subject: [PATCH 01/17] initial commit --- lib/cast/int32.js | 35 +++++++ lib/schema/bigint.js | 2 +- lib/schema/index.js | 1 + lib/schema/int32.js | 229 +++++++++++++++++++++++++++++++++++++++++++ test/bigint.test.js | 2 +- test/int32.test.js | 208 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 475 insertions(+), 2 deletions(-) create mode 100644 lib/cast/int32.js create mode 100644 lib/schema/int32.js create mode 100644 test/int32.test.js diff --git a/lib/cast/int32.js b/lib/cast/int32.js new file mode 100644 index 00000000000..c4d6513c736 --- /dev/null +++ b/lib/cast/int32.js @@ -0,0 +1,35 @@ +'use strict'; + +const assert = require('assert'); + +/** + * Given a value, cast it to a Int32, or throw an `Error` if the value + * cannot be casted. `null` and `undefined` are considered valid. + * + * @param {Any} value + * @return {Number} + * @throws {Error} if `value` does not represent an integer, or is beyond the bounds of an 32-bit integer. + * @api private + */ + +module.exports = function castInt32(val) { + if (val == null) { + return val; + } + if (val === '') { + return null; + } + + if (typeof val === 'string' || typeof val === 'number') { + + const INT32_MAX = 0x7FFFFFFF; + const INT32_MIN = -0x80000000; + + let _val = Number(val); + + if (!isNaN(_val) && _val === Math.round(_val) && _val > INT32_MIN && _val < INT32_MAX) { + return _val; + } + assert.ok(false); + } +}; \ No newline at end of file diff --git a/lib/schema/bigint.js b/lib/schema/bigint.js index 02912627dd5..8bfd8ce8140 100644 --- a/lib/schema/bigint.js +++ b/lib/schema/bigint.js @@ -23,7 +23,7 @@ function SchemaBigInt(path, options) { /** * This schema type's name, to defend against minifiers that mangle - * function names. + * fun * * @api public */ diff --git a/lib/schema/index.js b/lib/schema/index.js index 0caf091adf2..a1c5087b807 100644 --- a/lib/schema/index.js +++ b/lib/schema/index.js @@ -19,6 +19,7 @@ exports.ObjectId = require('./objectId'); exports.String = require('./string'); exports.Subdocument = require('./subdocument'); exports.UUID = require('./uuid'); +exports.Int32 = require('./int32'); // alias diff --git a/lib/schema/int32.js b/lib/schema/int32.js new file mode 100644 index 00000000000..aa7bb470971 --- /dev/null +++ b/lib/schema/int32.js @@ -0,0 +1,229 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const CastError = require('../error/cast'); +const SchemaType = require('../schemaType'); +const castInt32 = require('../cast/int32'); +const isBsonType = require('../helpers/isBsonType'); + +/** + * Int32 SchemaType constructor. + * + * @param {String} path + * @param {Object} options + * @inherits SchemaType + * @api public + */ + +function SchemaInt32(path, options) { + SchemaType.call(this, path, options, 'Int32'); +} + +/** + * This schema type's name, to defend against minifiers that mangle + * function names. + * + * @api public + */ +SchemaInt32.schemaName = 'Int32'; + +SchemaInt32.defaultOptions = {}; + +/*! + * Inherits from SchemaType. + */ +SchemaInt32.prototype = Object.create(SchemaType.prototype); +SchemaInt32.prototype.constructor = SchemaInt32; + +/*! + * ignore + */ + +SchemaInt32._cast = castInt32; + +/** + * Sets a default option for all Int32 instances. + * + * + * @param {String} option The option you'd like to set the value for + * @param {Any} value value for option + * @return {undefined} + * @function set + * @static + * @api public + */ + +SchemaInt32.set = SchemaType.set; + +SchemaInt32.setters = []; + +/** + * Attaches a getter for all Int32 instances + * + * + * @param {Function} getter + * @return {this} + * @function get + * @static + * @api public + */ + +SchemaInt32.get = SchemaType.get; + +/** + * Get/set the function used to cast arbitrary values to booleans. + * + * + * @param {Function} caster + * @return {Function} + * @function get + * @static + * @api public + */ + +SchemaInt32.cast = function cast(caster) { + if (arguments.length === 0) { + return this._cast; + } + if (caster === false) { + caster = this._defaultCaster; + } + this._cast = caster; + + return this._cast; +}; + +/*! + * ignore + */ + +SchemaInt32._checkRequired = v => v != null && isBsonType(v, 'Int32'); +/** + * Override the function the required validator uses to check whether a value + * passes the `required` check. + * + * @param {Function} fn + * @return {Function} + * @function checkRequired + * @static + * @api public + */ + +SchemaInt32.checkRequired = SchemaType.checkRequired; + +/** + * Check if the given value satisfies a required validator. + * + * @param {Any} value + * @return {Boolean} + * @api public + */ + +SchemaInt32.prototype.checkRequired = function(value) { + return this.constructor._checkRequired(value); +}; + +/** + * Casts to Int32 + * + * @param {Object} value + * @param {Object} model this value is optional + * @api private + */ + +SchemaInt32.prototype.cast = function(value) { + let castInt32; + if (isBsonType(value, 'Int32')) { + return value; + } + if (typeof this._castFunction === 'function') { + castInt32 = this._castFunction; + } else if (typeof this.constructor.cast === 'function') { + castInt32 = this.constructor.cast(); + } else { + castInt32 = SchemaInt32.cast(); + } + + try { + return castInt32(value); + } catch (error) { + throw new CastError('Int32', value, this.path, error, this); + } +}; + +/*! + * ignore + */ + +SchemaInt32.$conditionalHandlers = { + ...SchemaType.prototype.$conditionalHandlers, + $gt: handleSingle, + $gte: handleSingle, + $lt: handleSingle, + $lte: handleSingle +}; + +/*! + * ignore + */ + +function handleSingle(val, context) { + return this.castForQuery(null, val, context); +} + +/** + * Casts contents for queries. + * + * @param {String} $conditional + * @param {any} val + * @api private + */ + +SchemaInt32.prototype.castForQuery = function($conditional, val, context) { + let handler; + if ($conditional != null) { + handler = SchemaInt32.$conditionalHandlers[$conditional]; + + if (handler) { + return handler.call(this, val); + } + + return this.applySetters(null, val, context); + } + + try { + return this.applySetters(val, context); + } catch (err) { + if (err instanceof CastError && err.path === this.path && this.$fullPath != null) { + err.path = this.$fullPath; + } + throw err; + } +}; + +/** + * + * @api private + */ + +SchemaInt32.prototype._castNullish = function _castNullish(v) { + if (typeof v === 'undefined') { + return v; + } + const castInt32 = typeof this.constructor.cast === 'function' ? + this.constructor.cast() : + SchemaInt32.cast(); + if (castInt32 == null) { + return v; + } + return v; +}; + +/*! + * Module exports. + */ + +module.exports = SchemaInt32; \ No newline at end of file diff --git a/test/bigint.test.js b/test/bigint.test.js index e3d00418e2c..5fc53967427 100644 --- a/test/bigint.test.js +++ b/test/bigint.test.js @@ -39,7 +39,7 @@ describe('BigInt', function() { assert.strictEqual(doc.bigint2, 997n); }); - it('handles cast errors', async function() { + it.only('handles cast errors', async function() { const schema = new Schema({ bigint: 'BigInt' }); diff --git a/test/int32.test.js b/test/int32.test.js new file mode 100644 index 00000000000..07138094085 --- /dev/null +++ b/test/int32.test.js @@ -0,0 +1,208 @@ +'use strict'; + +const assert = require('assert'); +const start = require('./common'); + +const mongoose = start.mongoose; +const Schema = mongoose.Schema; + +describe('Int', function() { + beforeEach(() => mongoose.deleteModel(/Test/)); + + it('is a valid schema type', function() { + const schema = new Schema({ + myInt32: Schema.Types.Int32 + }); + const Test = mongoose.model('Test', schema); + + const doc = new Test({ + myInt32: 13 + }); + assert.strictEqual(doc.myInt32, 13); + assert.equal(typeof doc.myInt32, 'number'); + }); + + it('casts from strings and numbers', function() { + const schema = new Schema({ + int1: { + type: Schema.Types.Int32 + }, + int2: Schema.Types.Int32 + }); + const Test = mongoose.model('Test', schema); + + const doc = new Test({ + int1: -42, + int2: '-997.0' + }); + assert.strictEqual(doc.int1, -42); + assert.strictEqual(doc.int2, -997); + }); + + describe('cast errors', () => { + let Test; + + beforeEach(function() { + const schema = new Schema({ + myInt: Schema.Types.Int32 + }); + Test = mongoose.model('Test', schema); + }); + + describe.only('when a decimal input is provided to an Int32 field', () => { + it('throws a CastError', async() => { + const doc = new Test({ + int1: -42.4 + }); + + assert.strictEqual(doc.myInt, undefined); + + const err = await doc.validate().catch(e => e); + assert.ok(err); + assert.ok(err.errors['myInt']); + assert.equal(err.errors['myInt'].name, 'CastError'); + assert.equal( + err.errors['myInt'].message, + '' + ); + }); + }); + + describe('when a non-numeric string is provided to an Int32 field', () => { + it('throws a CastError', () => { + const doc = new Test({ + int1: 'helloworld' + }); + }); + }); + + describe('when NaN is provided to an Int32 field', () => { + it('throws a CastError', () => { + const doc = new Test({ + int1: NaN + }); + }); + }); + + describe('when value above INT32_MAX is provided to an Int32 field', () => { + it('throws a CastError', () => { + const doc = new Test({ + int1: 0x7FFFFFFF + 1 + }); + }); + }); + + describe('when value below INT32_MIN is provided to an Int32 field', () => { + it('throws a CastError', () => { + const doc = new Test({ + int1: -0x80000000 - 1 + }); + }); + }); + }); + + it('supports required', async function() { + const schema = new Schema({ + bigint: { + type: BigInt, + required: true + } + }); + const Test = mongoose.model('Test', schema); + + const doc = new Test({ + bigint: null + }); + + const err = await doc.validate().then(() => null, err => err); + assert.ok(err); + assert.ok(err.errors['bigint']); + assert.equal(err.errors['bigint'].name, 'ValidatorError'); + assert.equal( + err.errors['bigint'].message, + 'Path `bigint` is required.' + ); + }); + + describe('MongoDB integration', function() { + let db; + let Test; + + before(async function() { + db = await start(); + + const schema = new Schema({ + myInt: Schema.Types.Int32 + }); + db.deleteModel(/Test/); + Test = db.model('Test', schema); + }); + + after(async function() { + await db.close(); + }); + + beforeEach(async() => { + await Test.deleteMany({}); + }); + + it('is stored as a int32 in MongoDB', async function() { + await Test.create({ myInt: '42' }); + + const doc = await Test.findOne({ myInt: { $type: 'int32' } }); + assert.ok(doc); + assert.strictEqual(doc.myInt, 42); + }); + + it('becomes a int32 with lean using promote Values', async function() { + await Test.create({ myBigInt: 7n }); + + const doc = await Test. + findOne({ myBigInt: 7n }). + setOptions({ useBigInt64: true }). + lean(); + assert.ok(doc); + assert.strictEqual(doc.myBigInt, 7n); + }); + + it('can query with comparison operators', async function() { + await Test.create([ + { myBigInt: 1n }, + { myBigInt: 2n }, + { myBigInt: 3n }, + { myBigInt: 4n } + ]); + + let docs = await Test.find({ myBigInt: { $gte: 3n } }).sort({ myBigInt: 1 }); + assert.equal(docs.length, 2); + assert.deepStrictEqual(docs.map(doc => doc.myBigInt), [3n, 4n]); + + docs = await Test.find({ myBigInt: { $lt: 3n } }).sort({ myBigInt: -1 }); + assert.equal(docs.length, 2); + assert.deepStrictEqual(docs.map(doc => doc.myBigInt), [2n, 1n]); + }); + + it('supports populate()', async function() { + const parentSchema = new Schema({ + child: { + type: BigInt, + ref: 'Child' + } + }); + const childSchema = new Schema({ + _id: BigInt, + name: String + }); + const Parent = db.model('Parent', parentSchema); + const Child = db.model('Child', childSchema); + + const { _id } = await Parent.create({ child: 42n }); + await Child.create({ _id: 42n, name: 'test-bigint-populate' }); + + const doc = await Parent.findById(_id).populate('child'); + assert.ok(doc); + assert.equal(doc.child.name, 'test-bigint-populate'); + assert.equal(doc.child._id, 42n); + }); + }); +}); \ No newline at end of file From df7c715dd33ef6ffb9a3d71e1d5591e937007521 Mon Sep 17 00:00:00 2001 From: Aditi Khare Date: Fri, 8 Nov 2024 02:24:43 -0500 Subject: [PATCH 02/17] ready for review --- docs/source/api.js | 2 +- lib/browser.js | 1 + lib/cast/int32.js | 4 +- lib/mongoose.js | 17 +++ lib/query.js | 1 + lib/schema.js | 1 + lib/schema/bigint.js | 2 +- lib/schema/int32.js | 4 +- test/bigint.test.js | 2 +- test/int32.test.js | 240 +++++++++++++++++++++++++++++-------------- types/query.d.ts | 1 + 11 files changed, 192 insertions(+), 83 deletions(-) diff --git a/docs/source/api.js b/docs/source/api.js index e398164056a..aaddbaaccba 100644 --- a/docs/source/api.js +++ b/docs/source/api.js @@ -40,7 +40,7 @@ const files = [ 'lib/types/subdocument.js', 'lib/types/arraySubdocument.js', 'lib/types/buffer.js', - 'lib/types/decimal128.js', + 'lib/types/.js', 'lib/types/map.js', 'lib/types/array/methods/index.js' ]; diff --git a/lib/browser.js b/lib/browser.js index 12b0cbde653..d6183c8157f 100644 --- a/lib/browser.js +++ b/lib/browser.js @@ -50,6 +50,7 @@ exports.Schema = require('./schema'); * - [ObjectId](https://mongoosejs.com/docs/schematypes.html#objectids) * - [Map](https://mongoosejs.com/docs/schematypes.html#maps) * - [Subdocument](https://mongoosejs.com/docs/schematypes.html#schemas) + * - Int32 * * Using this exposed access to the `ObjectId` type, we can construct ids on demand. * diff --git a/lib/cast/int32.js b/lib/cast/int32.js index c4d6513c736..99aa107ea80 100644 --- a/lib/cast/int32.js +++ b/lib/cast/int32.js @@ -25,11 +25,11 @@ module.exports = function castInt32(val) { const INT32_MAX = 0x7FFFFFFF; const INT32_MIN = -0x80000000; - let _val = Number(val); + const _val = Number(val); if (!isNaN(_val) && _val === Math.round(_val) && _val > INT32_MIN && _val < INT32_MAX) { return _val; } assert.ok(false); } -}; \ No newline at end of file +}; diff --git a/lib/mongoose.js b/lib/mongoose.js index f314e4c399a..a419f4ff70c 100644 --- a/lib/mongoose.js +++ b/lib/mongoose.js @@ -987,6 +987,7 @@ Mongoose.prototype.VirtualType = VirtualType; * - [ObjectId](https://mongoosejs.com/docs/schematypes.html#objectids) * - [Map](https://mongoosejs.com/docs/schematypes.html#maps) * - [Subdocument](https://mongoosejs.com/docs/schematypes.html#schemas) + * - Int32 * * Using this exposed access to the `ObjectId` type, we can construct ids on demand. * @@ -1138,6 +1139,22 @@ Mongoose.prototype.syncIndexes = function(options) { Mongoose.prototype.Decimal128 = SchemaTypes.Decimal128; + +/** + * The Mongoose Int32 [SchemaType](https://mongoosejs.com/docs/schematypes.html). Used for + * declaring paths in your schema that should be 32-bit integers floating points. + * Do not use this to create a new Int32 instance, use `mongoose.Types.Int32` + * instead. + * + * #### Example: + * + * const vehicleSchema = new Schema({ numberOfCows: mongoose.Int32 }); + * + * @property Decimal128 + * @api public + */ +Mongoose.prototype.Int32 = SchemaTypes.Int32; + /** * The Mongoose Mixed [SchemaType](https://mongoosejs.com/docs/schematypes.html). Used for * declaring paths in your schema that Mongoose's change tracking, casting, diff --git a/lib/query.js b/lib/query.js index d853c0828fe..df86d5a0f30 100644 --- a/lib/query.js +++ b/lib/query.js @@ -1634,6 +1634,7 @@ Query.prototype.getOptions = function() { * - [projection](https://mongoosejs.com/docs/api/query.html#Query.prototype.projection()) * - sanitizeProjection * - useBigInt64 + * - promoteValues * * The following options are only for all operations **except** `updateOne()`, `updateMany()`, `deleteOne()`, and `deleteMany()`: * diff --git a/lib/schema.js b/lib/schema.js index a9d23fd6199..44c343d4de9 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -2848,6 +2848,7 @@ module.exports = exports = Schema; * - [Mixed](https://mongoosejs.com/docs/schematypes.html#mixed) * - [UUID](https://mongoosejs.com/docs/schematypes.html#uuid) * - [BigInt](https://mongoosejs.com/docs/schematypes.html#bigint) + * - Int32 * * Using this exposed access to the `Mixed` SchemaType, we can use them in our schema. * diff --git a/lib/schema/bigint.js b/lib/schema/bigint.js index 8bfd8ce8140..02912627dd5 100644 --- a/lib/schema/bigint.js +++ b/lib/schema/bigint.js @@ -23,7 +23,7 @@ function SchemaBigInt(path, options) { /** * This schema type's name, to defend against minifiers that mangle - * fun + * function names. * * @api public */ diff --git a/lib/schema/int32.js b/lib/schema/int32.js index aa7bb470971..f385e12a469 100644 --- a/lib/schema/int32.js +++ b/lib/schema/int32.js @@ -63,7 +63,6 @@ SchemaInt32.setters = []; /** * Attaches a getter for all Int32 instances * - * * @param {Function} getter * @return {this} * @function get @@ -76,7 +75,6 @@ SchemaInt32.get = SchemaType.get; /** * Get/set the function used to cast arbitrary values to booleans. * - * * @param {Function} caster * @return {Function} * @function get @@ -226,4 +224,4 @@ SchemaInt32.prototype._castNullish = function _castNullish(v) { * Module exports. */ -module.exports = SchemaInt32; \ No newline at end of file +module.exports = SchemaInt32; diff --git a/test/bigint.test.js b/test/bigint.test.js index 5fc53967427..e3d00418e2c 100644 --- a/test/bigint.test.js +++ b/test/bigint.test.js @@ -39,7 +39,7 @@ describe('BigInt', function() { assert.strictEqual(doc.bigint2, 997n); }); - it.only('handles cast errors', async function() { + it('handles cast errors', async function() { const schema = new Schema({ bigint: 'BigInt' }); diff --git a/test/int32.test.js b/test/int32.test.js index 07138094085..5583f9d4ec8 100644 --- a/test/int32.test.js +++ b/test/int32.test.js @@ -2,11 +2,12 @@ const assert = require('assert'); const start = require('./common'); +const BSONInt32 = require('bson').Int32; const mongoose = start.mongoose; const Schema = mongoose.Schema; -describe('Int', function() { +describe('Int32', function() { beforeEach(() => mongoose.deleteModel(/Test/)); it('is a valid schema type', function() { @@ -22,21 +23,55 @@ describe('Int', function() { assert.equal(typeof doc.myInt32, 'number'); }); - it('casts from strings and numbers', function() { + it('supports the \'required\' property', async function() { const schema = new Schema({ - int1: { - type: Schema.Types.Int32 - }, - int2: Schema.Types.Int32 + int32: { + type: Schema.Types.Int32, + required: true + } }); const Test = mongoose.model('Test', schema); const doc = new Test({ - int1: -42, - int2: '-997.0' + int: null + }); + + const err = await doc.validate().then(() => null, err => err); + assert.ok(err); + assert.ok(err.errors['int32']); + assert.equal(err.errors['int32'].name, 'ValidatorError'); + assert.equal( + err.errors['int32'].message, + 'Path `int32` is required.' + ); + }); + + describe('valid casts', function() { + it('casts from string', function() { + const schema = new Schema({ + int1: { + type: Schema.Types.Int32 + } + }); + const Test = mongoose.model('Test', schema); + + const doc = new Test({ + int1: -42 + }); + assert.strictEqual(doc.int1, -42); + }); + + it('casts from number', function() { + const schema = new Schema({ + int2: Schema.Types.Int32 + }); + const Test = mongoose.model('Test', schema); + + const doc = new Test({ + int2: '-997.0' + }); + assert.strictEqual(doc.int2, -997); }); - assert.strictEqual(doc.int1, -42); - assert.strictEqual(doc.int2, -997); }); describe('cast errors', () => { @@ -49,82 +84,98 @@ describe('Int', function() { Test = mongoose.model('Test', schema); }); - describe.only('when a decimal input is provided to an Int32 field', () => { - it('throws a CastError', async() => { + describe('when a non-integer input is provided to an Int32 field', () => { + it('throws a CastError upon validation', async() => { const doc = new Test({ - int1: -42.4 + myInt: -42.4 }); assert.strictEqual(doc.myInt, undefined); - const err = await doc.validate().catch(e => e); assert.ok(err); assert.ok(err.errors['myInt']); assert.equal(err.errors['myInt'].name, 'CastError'); assert.equal( err.errors['myInt'].message, - '' + 'Cast to Int32 failed for value "-42.4" (type number) at path "myInt"' ); }); }); describe('when a non-numeric string is provided to an Int32 field', () => { - it('throws a CastError', () => { + it('throws a CastError upon validation', async() => { const doc = new Test({ - int1: 'helloworld' + myInt: 'helloworld' }); + + assert.strictEqual(doc.myInt, undefined); + const err = await doc.validate().catch(e => e); + assert.ok(err); + assert.ok(err.errors['myInt']); + assert.equal(err.errors['myInt'].name, 'CastError'); + assert.equal( + err.errors['myInt'].message, + 'Cast to Int32 failed for value "helloworld" (type string) at path "myInt"' + ); }); }); describe('when NaN is provided to an Int32 field', () => { - it('throws a CastError', () => { + it('throws a CastError upon validation', async() => { const doc = new Test({ - int1: NaN + myInt: NaN }); + + assert.strictEqual(doc.myInt, undefined); + const err = await doc.validate().catch(e => e); + assert.ok(err); + assert.ok(err.errors['myInt']); + assert.equal(err.errors['myInt'].name, 'CastError'); + assert.equal( + err.errors['myInt'].message, + 'Cast to Int32 failed for value "NaN" (type number) at path "myInt"' + ); }); }); describe('when value above INT32_MAX is provided to an Int32 field', () => { - it('throws a CastError', () => { + it('throws a CastError upon validation', async() => { const doc = new Test({ - int1: 0x7FFFFFFF + 1 + myInt: 0x7FFFFFFF + 1 }); + + assert.strictEqual(doc.myInt, undefined); + const err = await doc.validate().catch(e => e); + assert.ok(err); + assert.ok(err.errors['myInt']); + assert.equal(err.errors['myInt'].name, 'CastError'); + assert.equal( + err.errors['myInt'].message, + 'Cast to Int32 failed for value "2147483648" (type number) at path "myInt"' + ); }); }); describe('when value below INT32_MIN is provided to an Int32 field', () => { - it('throws a CastError', () => { + it('throws a CastError upon validation', async() => { const doc = new Test({ - int1: -0x80000000 - 1 + myInt: -0x80000000 - 1 }); - }); - }); - }); - - it('supports required', async function() { - const schema = new Schema({ - bigint: { - type: BigInt, - required: true - } - }); - const Test = mongoose.model('Test', schema); - const doc = new Test({ - bigint: null + assert.strictEqual(doc.myInt, undefined); + const err = await doc.validate().catch(e => e); + assert.ok(err); + assert.ok(err.errors['myInt']); + assert.equal(err.errors['myInt'].name, 'CastError'); + assert.equal( + err.errors['myInt'].message, + 'Cast to Int32 failed for value "-2147483649" (type number) at path "myInt"' + ); + }); }); - - const err = await doc.validate().then(() => null, err => err); - assert.ok(err); - assert.ok(err.errors['bigint']); - assert.equal(err.errors['bigint'].name, 'ValidatorError'); - assert.equal( - err.errors['bigint'].message, - 'Path `bigint` is required.' - ); }); - describe('MongoDB integration', function() { + describe('mongoDB integration', function() { let db; let Test; @@ -146,63 +197,102 @@ describe('Int', function() { await Test.deleteMany({}); }); - it('is stored as a int32 in MongoDB', async function() { - await Test.create({ myInt: '42' }); + describe('$type compatibility', function() { + it('is queryable as a JS number in MongoDB', async function() { + await Test.create({ myInt: '42' }); + const doc = await Test.findOne({ myInt: { $type: 'number' } }); + assert.ok(doc); + assert.strictEqual(doc.myInt, 42); + }); - const doc = await Test.findOne({ myInt: { $type: 'int32' } }); - assert.ok(doc); - assert.strictEqual(doc.myInt, 42); + it('is queryable as a BSON Int32 in MongoDB', async function() { + await Test.create({ myInt: '42' }); + const doc = await Test.findOne({ myInt: { $type: 'int' } }); + assert.ok(doc); + assert.strictEqual(doc.myInt, 42); + }); + + it('is queryable as a BSON Double in MongoDB', async function() { + await Test.create({ myInt: '42' }); + const doc = await Test.findOne({ myInt: { $type: 'double' } }); + assert.equal(doc, undefined); + }); }); - it('becomes a int32 with lean using promote Values', async function() { - await Test.create({ myBigInt: 7n }); + describe('promoteValues', function() { + describe('when promoteValues is false', function() { + it('find returns BSON Int32', async function() { + await Test.create({ myInt: 7 }); - const doc = await Test. - findOne({ myBigInt: 7n }). - setOptions({ useBigInt64: true }). - lean(); - assert.ok(doc); - assert.strictEqual(doc.myBigInt, 7n); + const doc = await Test.findOne({ myInt: 7 }) + .setOptions({ promoteValues: false }); + assert.ok(doc); + assert.ok(doc.myInt instanceof BSONInt32); + assert.equal(doc.myInt.value, 7); + }); + }); + + describe('when promoteValues is undefined', function() { + it('find returns a JS number', async function() { + await Test.create({ myInt: 7 }); + const doc = await Test.findOne({ myInt: 7 }) + .setOptions({ promoteValues: undefined }); + assert.ok(doc); + assert.ok(doc); + assert.equal(doc.myInt, 7); + }); + }); + + describe('when promoteValues is true', function() { + it('find returns a JS number', async function() { + await Test.create({ myInt: 7 }); + + const doc = await Test.findOne({ myInt: 7 }) + .setOptions({ promoteValues: false }); + assert.ok(doc); + assert.equal(doc.myInt, 7); + }); + }); }); it('can query with comparison operators', async function() { await Test.create([ - { myBigInt: 1n }, - { myBigInt: 2n }, - { myBigInt: 3n }, - { myBigInt: 4n } + { myInt: 1 }, + { myInt: 2 }, + { myInt: 3 }, + { myInt: 4 } ]); - let docs = await Test.find({ myBigInt: { $gte: 3n } }).sort({ myBigInt: 1 }); + let docs = await Test.find({ myInt: { $gte: 3 } }).sort({ myInt: 1 }); assert.equal(docs.length, 2); - assert.deepStrictEqual(docs.map(doc => doc.myBigInt), [3n, 4n]); + assert.deepStrictEqual(docs.map(doc => doc.myInt), [3, 4]); - docs = await Test.find({ myBigInt: { $lt: 3n } }).sort({ myBigInt: -1 }); + docs = await Test.find({ myInt: { $lt: 3 } }).sort({ myInt: -1 }); assert.equal(docs.length, 2); - assert.deepStrictEqual(docs.map(doc => doc.myBigInt), [2n, 1n]); + assert.deepStrictEqual(docs.map(doc => doc.myInt), [2, 1]); }); it('supports populate()', async function() { const parentSchema = new Schema({ child: { - type: BigInt, + type: Schema.Types.Int32, ref: 'Child' } }); const childSchema = new Schema({ - _id: BigInt, + _id: Schema.Types.Int32, name: String }); const Parent = db.model('Parent', parentSchema); const Child = db.model('Child', childSchema); - const { _id } = await Parent.create({ child: 42n }); - await Child.create({ _id: 42n, name: 'test-bigint-populate' }); + const { _id } = await Parent.create({ child: 42 }); + await Child.create({ _id: 42, name: 'test-int32-populate' }); const doc = await Parent.findById(_id).populate('child'); assert.ok(doc); - assert.equal(doc.child.name, 'test-bigint-populate'); - assert.equal(doc.child._id, 42n); + assert.equal(doc.child.name, 'test-int32-populate'); + assert.equal(doc.child._id, 42); }); }); -}); \ No newline at end of file +}); diff --git a/types/query.d.ts b/types/query.d.ts index 67129e6c7b6..3e5164d2b63 100644 --- a/types/query.d.ts +++ b/types/query.d.ts @@ -209,6 +209,7 @@ declare module 'mongoose' { translateAliases?: boolean; upsert?: boolean; useBigInt64?: boolean; + promoteValues?: boolean; writeConcern?: mongodb.WriteConcern; [other: string]: any; From 68f2ebd97e4a2b4ff973c8c29fdd1f48a0dcf399 Mon Sep 17 00:00:00 2001 From: Aditi Khare Date: Fri, 8 Nov 2024 02:54:32 -0500 Subject: [PATCH 03/17] API docs for promoteValues --- types/query.d.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/types/query.d.ts b/types/query.d.ts index 3e5164d2b63..cfc2783ff3f 100644 --- a/types/query.d.ts +++ b/types/query.d.ts @@ -209,6 +209,9 @@ declare module 'mongoose' { translateAliases?: boolean; upsert?: boolean; useBigInt64?: boolean; + /** + * If `false`, will NOT promote [BSON](https://www.mongodb.com/docs/manual/reference/bson-types/values) to their Node.js closest equivalent types. + */ promoteValues?: boolean; writeConcern?: mongodb.WriteConcern; From 021f84ba2e84ffb7e058789c99bac2e2048b6597 Mon Sep 17 00:00:00 2001 From: Aditi Khare Date: Fri, 8 Nov 2024 15:07:19 -0500 Subject: [PATCH 04/17] requested changes --- docs/guide.md | 1 + docs/schematypes.md | 17 ++++++++ docs/source/api.js | 3 +- index.js | 1 + lib/browser.js | 2 +- lib/cast/int32.js | 2 +- lib/drivers/browser/int32.js | 7 ++++ lib/mongoose.js | 8 ++-- lib/query.js | 1 - lib/schema.js | 2 + lib/schema/bigint.js | 2 +- lib/schema/int32.js | 25 ++++++++++- lib/types/index.js | 1 + lib/types/int32.js | 13 ++++++ lib/utils.js | 4 ++ test/helpers/isBsonType.test.js | 5 +++ test/int32.test.js | 37 ---------------- test/types/schemaTypeOptions.test.ts | 1 + types/inferrawdoctype.d.ts | 31 +++++++------- types/inferschematype.d.ts | 63 +++++++++++++++------------- types/query.d.ts | 4 -- types/schematypes.d.ts | 18 ++++++++ types/types.d.ts | 2 + 23 files changed, 157 insertions(+), 93 deletions(-) create mode 100644 lib/drivers/browser/int32.js create mode 100644 lib/types/int32.js diff --git a/docs/guide.md b/docs/guide.md index fa653acc4dd..6f432a4d639 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -80,6 +80,7 @@ The permitted SchemaTypes are: * [Decimal128](api/mongoose.html#mongoose_Mongoose-Decimal128) * [Map](schematypes.html#maps) * [UUID](schematypes.html#uuid) +* [Int32](schematypes.html#int32) Read more about [SchemaTypes here](schematypes.html). diff --git a/docs/schematypes.md b/docs/schematypes.md index 904bb6a8726..106cb2adcee 100644 --- a/docs/schematypes.md +++ b/docs/schematypes.md @@ -55,6 +55,7 @@ Check out [Mongoose's plugins search](http://plugins.mongoosejs.io) to find plug * [Schema](#schemas) * [UUID](#uuid) * [BigInt](#bigint) +* [Int32](#int32) ### Example @@ -68,6 +69,7 @@ const schema = new Schema({ mixed: Schema.Types.Mixed, _someId: Schema.Types.ObjectId, decimal: Schema.Types.Decimal128, + 32bitInt: Schema.Types.Int32, array: [], ofString: [String], ofNumber: [Number], @@ -647,6 +649,21 @@ const question = new Question({ answer: 42n }); typeof question.answer; // 'bigint' ``` +### Int32 {#int32} + +Mongoose supports 32-bit integers as a SchemaType. +Int32s are stored as [32-bit integers in MongoDB (BSON type "int")](https://www.mongodb.com/docs/manual/reference/bson-types/). + +```javascript +const studentsSchema = new Schema({ + id: Int32 +}); +const Student = mongoose.model('Student', schema); + +const student = new Student({ id: 1339 }); +typeof student.id; // 'number' +``` + ## Getters {#getters} Getters are like virtuals for paths defined in your schema. For example, diff --git a/docs/source/api.js b/docs/source/api.js index aaddbaaccba..62435bfcd22 100644 --- a/docs/source/api.js +++ b/docs/source/api.js @@ -40,7 +40,8 @@ const files = [ 'lib/types/subdocument.js', 'lib/types/arraySubdocument.js', 'lib/types/buffer.js', - 'lib/types/.js', + 'lib/types/decimal128.js', + 'lib/types/int32.js', 'lib/types/map.js', 'lib/types/array/methods/index.js' ]; diff --git a/index.js b/index.js index 6ebbd5fd5d3..9892d18efb8 100644 --- a/index.js +++ b/index.js @@ -46,6 +46,7 @@ module.exports.Decimal128 = mongoose.Decimal128; module.exports.Mixed = mongoose.Mixed; module.exports.Date = mongoose.Date; module.exports.Number = mongoose.Number; +module.exports.Int32 = mongoose.Int32; module.exports.Error = mongoose.Error; module.exports.MongooseError = mongoose.MongooseError; module.exports.now = mongoose.now; diff --git a/lib/browser.js b/lib/browser.js index d6183c8157f..58f8ab26aa2 100644 --- a/lib/browser.js +++ b/lib/browser.js @@ -50,7 +50,7 @@ exports.Schema = require('./schema'); * - [ObjectId](https://mongoosejs.com/docs/schematypes.html#objectids) * - [Map](https://mongoosejs.com/docs/schematypes.html#maps) * - [Subdocument](https://mongoosejs.com/docs/schematypes.html#schemas) - * - Int32 + * - [Int32](https://mongoosejs.com/docs/schematypes.html#int32) * * Using this exposed access to the `ObjectId` type, we can construct ids on demand. * diff --git a/lib/cast/int32.js b/lib/cast/int32.js index 99aa107ea80..9ab76ca1956 100644 --- a/lib/cast/int32.js +++ b/lib/cast/int32.js @@ -27,7 +27,7 @@ module.exports = function castInt32(val) { const _val = Number(val); - if (!isNaN(_val) && _val === Math.round(_val) && _val > INT32_MIN && _val < INT32_MAX) { + if (_val !== _val | 0 && _val > INT32_MIN && _val < INT32_MAX) { return _val; } assert.ok(false); diff --git a/lib/drivers/browser/int32.js b/lib/drivers/browser/int32.js new file mode 100644 index 00000000000..b56a43f6ce1 --- /dev/null +++ b/lib/drivers/browser/int32.js @@ -0,0 +1,7 @@ +/*! + * ignore + */ + +'use strict'; + +module.exports = require('bson').Int32; \ No newline at end of file diff --git a/lib/mongoose.js b/lib/mongoose.js index a419f4ff70c..788e257bbed 100644 --- a/lib/mongoose.js +++ b/lib/mongoose.js @@ -987,7 +987,7 @@ Mongoose.prototype.VirtualType = VirtualType; * - [ObjectId](https://mongoosejs.com/docs/schematypes.html#objectids) * - [Map](https://mongoosejs.com/docs/schematypes.html#maps) * - [Subdocument](https://mongoosejs.com/docs/schematypes.html#schemas) - * - Int32 + * - [Int32](https://mongoosejs.com/docs/schematypes.html#int32) * * Using this exposed access to the `ObjectId` type, we can construct ids on demand. * @@ -1142,15 +1142,15 @@ Mongoose.prototype.Decimal128 = SchemaTypes.Decimal128; /** * The Mongoose Int32 [SchemaType](https://mongoosejs.com/docs/schematypes.html). Used for - * declaring paths in your schema that should be 32-bit integers floating points. + * declaring paths in your schema that should be 32-bit integers. * Do not use this to create a new Int32 instance, use `mongoose.Types.Int32` * instead. * * #### Example: * - * const vehicleSchema = new Schema({ numberOfCows: mongoose.Int32 }); + * const vehicleSchema = new Schema({ numTires: mongoose.Int32 }); * - * @property Decimal128 + * @property Int32 * @api public */ Mongoose.prototype.Int32 = SchemaTypes.Int32; diff --git a/lib/query.js b/lib/query.js index df86d5a0f30..d853c0828fe 100644 --- a/lib/query.js +++ b/lib/query.js @@ -1634,7 +1634,6 @@ Query.prototype.getOptions = function() { * - [projection](https://mongoosejs.com/docs/api/query.html#Query.prototype.projection()) * - sanitizeProjection * - useBigInt64 - * - promoteValues * * The following options are only for all operations **except** `updateOne()`, `updateMany()`, `deleteOne()`, and `deleteMany()`: * diff --git a/lib/schema.js b/lib/schema.js index 44c343d4de9..63b2c45f2cc 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -1494,6 +1494,8 @@ Schema.prototype.interpretAsType = function(path, obj, options) { name = 'ObjectId'; } else if (type === Types.Decimal128) { name = 'Decimal128'; + } else if (type === Types.Int32) { + name = 'Int32'; } else { name = type == null ? '' + type : type.toString(); } diff --git a/lib/schema/bigint.js b/lib/schema/bigint.js index 02912627dd5..a7ba3a5f943 100644 --- a/lib/schema/bigint.js +++ b/lib/schema/bigint.js @@ -81,7 +81,7 @@ SchemaBigInt.setters = []; SchemaBigInt.get = SchemaType.get; /** - * Get/set the function used to cast arbitrary values to booleans. + * Get/set the function used to cast arbitrary values to bigints. * * #### Example: * diff --git a/lib/schema/int32.js b/lib/schema/int32.js index f385e12a469..96f0173be24 100644 --- a/lib/schema/int32.js +++ b/lib/schema/int32.js @@ -47,6 +47,10 @@ SchemaInt32._cast = castInt32; /** * Sets a default option for all Int32 instances. * + * #### Example: + * + * // Make all Int32 fields required by default + * mongoose.Schema.Int32.set('required', true); * * @param {String} option The option you'd like to set the value for * @param {Any} value value for option @@ -63,6 +67,11 @@ SchemaInt32.setters = []; /** * Attaches a getter for all Int32 instances * + * #### Example: + * + * // Converts int32 to be a represent milliseconds upon access + * mongoose.Schema.Int32.get(v => v == null ? '0 ms' : v.toString() + ' ms'); + * * @param {Function} getter * @return {this} * @function get @@ -73,7 +82,21 @@ SchemaInt32.setters = []; SchemaInt32.get = SchemaType.get; /** - * Get/set the function used to cast arbitrary values to booleans. + * Get/set the function used to cast arbitrary values to 32-bit integers + * + * #### Example: + * + * // Make Mongoose cast NaN to 0 + * const defaultCast = mongoose.Schema.Int32.cast(); + * mongoose.Schema.Int32.cast(v => { + * if (isNaN(v)) { + * return 0; + * } + * return defaultCast(v); + * }); + * + * // Or disable casting for Int32s entirely + * mongoose.Schema.Int32.cast(false); * * @param {Function} caster * @return {Function} diff --git a/lib/types/index.js b/lib/types/index.js index d234f6bb62a..4369bf1ccf0 100644 --- a/lib/types/index.js +++ b/lib/types/index.js @@ -14,6 +14,7 @@ exports.Embedded = require('./arraySubdocument'); exports.DocumentArray = require('./documentArray'); exports.Decimal128 = require('./decimal128'); exports.ObjectId = require('./objectid'); +exports.ObjectId = require('./int32'); exports.Map = require('./map'); diff --git a/lib/types/int32.js b/lib/types/int32.js new file mode 100644 index 00000000000..2ea4042faf5 --- /dev/null +++ b/lib/types/int32.js @@ -0,0 +1,13 @@ +/** + * Int32 type constructor + * + * #### Example: + * + * const id = new mongoose.Types.Int32('-2'); + * + * @constructor Int32 + */ + +'use strict'; + +module.exports = require('bson').Int32; \ No newline at end of file diff --git a/lib/utils.js b/lib/utils.js index 0a39de3ee8b..5263651727d 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -93,6 +93,10 @@ exports.deepEqual = function deepEqual(a, b) { return a.toString() === b.toString(); } + if (isBsonType(a, 'Int32') && isBsonType(b, 'In32')) { + return a.value === b.value(); + } + if (a instanceof RegExp && b instanceof RegExp) { return a.source === b.source && a.ignoreCase === b.ignoreCase && diff --git a/test/helpers/isBsonType.test.js b/test/helpers/isBsonType.test.js index 448aaf72db4..67e5bc754e5 100644 --- a/test/helpers/isBsonType.test.js +++ b/test/helpers/isBsonType.test.js @@ -5,6 +5,7 @@ const isBsonType = require('../../lib/helpers/isBsonType'); const Decimal128 = require('mongodb').Decimal128; const ObjectId = require('mongodb').ObjectId; +const Int32 = require('mongodb').Int32; describe('isBsonType', () => { it('true for any object with _bsontype property equal typename', () => { @@ -30,4 +31,8 @@ describe('isBsonType', () => { it('true for ObjectId', () => { assert.ok(isBsonType(new ObjectId(), 'ObjectId')); }); + + it('true for Int32', () => { + assert.ok(isBsonType(new Int32(), 'Int32')); + }); }); diff --git a/test/int32.test.js b/test/int32.test.js index 5583f9d4ec8..204085fa853 100644 --- a/test/int32.test.js +++ b/test/int32.test.js @@ -2,7 +2,6 @@ const assert = require('assert'); const start = require('./common'); -const BSONInt32 = require('bson').Int32; const mongoose = start.mongoose; const Schema = mongoose.Schema; @@ -219,42 +218,6 @@ describe('Int32', function() { }); }); - describe('promoteValues', function() { - describe('when promoteValues is false', function() { - it('find returns BSON Int32', async function() { - await Test.create({ myInt: 7 }); - - const doc = await Test.findOne({ myInt: 7 }) - .setOptions({ promoteValues: false }); - assert.ok(doc); - assert.ok(doc.myInt instanceof BSONInt32); - assert.equal(doc.myInt.value, 7); - }); - }); - - describe('when promoteValues is undefined', function() { - it('find returns a JS number', async function() { - await Test.create({ myInt: 7 }); - const doc = await Test.findOne({ myInt: 7 }) - .setOptions({ promoteValues: undefined }); - assert.ok(doc); - assert.ok(doc); - assert.equal(doc.myInt, 7); - }); - }); - - describe('when promoteValues is true', function() { - it('find returns a JS number', async function() { - await Test.create({ myInt: 7 }); - - const doc = await Test.findOne({ myInt: 7 }) - .setOptions({ promoteValues: false }); - assert.ok(doc); - assert.equal(doc.myInt, 7); - }); - }); - }); - it('can query with comparison operators', async function() { await Test.create([ { myInt: 1 }, diff --git a/test/types/schemaTypeOptions.test.ts b/test/types/schemaTypeOptions.test.ts index 5fc0e23a21d..71ad5c7b539 100644 --- a/test/types/schemaTypeOptions.test.ts +++ b/test/types/schemaTypeOptions.test.ts @@ -64,6 +64,7 @@ function defaultOptions() { expectType>(new Schema.Types.Buffer('none').defaultOptions); expectType>(new Schema.Types.Date('none').defaultOptions); expectType>(new Schema.Types.Decimal128('none').defaultOptions); + expectType>(new Schema.Types.Int32('none').defaultOptions); expectType>(new Schema.Types.DocumentArray('none').defaultOptions); expectType>(new Schema.Types.Map('none').defaultOptions); expectType>(new Schema.Types.Mixed('none').defaultOptions); diff --git a/types/inferrawdoctype.d.ts b/types/inferrawdoctype.d.ts index 605571057a0..da3507f882a 100644 --- a/types/inferrawdoctype.d.ts +++ b/types/inferrawdoctype.d.ts @@ -102,18 +102,21 @@ declare module 'mongoose' { PathValueType extends 'decimal128' | 'Decimal128' | typeof Schema.Types.Decimal128 ? Types.Decimal128 : IfEquals extends true ? Types.Decimal128 : IfEquals extends true ? Types.Decimal128 : - IfEquals extends true ? bigint : - IfEquals extends true ? bigint : - PathValueType extends 'bigint' | 'BigInt' | typeof Schema.Types.BigInt | typeof BigInt ? bigint : - PathValueType extends 'uuid' | 'UUID' | typeof Schema.Types.UUID ? Buffer : - IfEquals extends true ? Buffer : - PathValueType extends MapConstructor | 'Map' ? Map> : - IfEquals extends true ? Map> : - PathValueType extends ArrayConstructor ? any[] : - PathValueType extends typeof Schema.Types.Mixed ? any: - IfEquals extends true ? any: - IfEquals extends true ? any: - PathValueType extends typeof SchemaType ? PathValueType['prototype'] : - PathValueType extends Record ? ObtainDocumentType : - unknown; + PathValueType extends 'int32' | 'Int32' | typeof Schema.Types.Int32 ? Types.Int32 : + IfEquals extends true ? Types.Int32 : + IfEquals extends true ? Types.Int32 : + IfEquals extends true ? bigint : + IfEquals extends true ? bigint : + PathValueType extends 'bigint' | 'BigInt' | typeof Schema.Types.BigInt | typeof BigInt ? bigint : + PathValueType extends 'uuid' | 'UUID' | typeof Schema.Types.UUID ? Buffer : + IfEquals extends true ? Buffer : + PathValueType extends MapConstructor | 'Map' ? Map> : + IfEquals extends true ? Map> : + PathValueType extends ArrayConstructor ? any[] : + PathValueType extends typeof Schema.Types.Mixed ? any: + IfEquals extends true ? any: + IfEquals extends true ? any: + PathValueType extends typeof SchemaType ? PathValueType['prototype'] : + PathValueType extends Record ? ObtainDocumentType : + unknown; } diff --git a/types/inferschematype.d.ts b/types/inferschematype.d.ts index d73ad4cb81c..45c5b9b564a 100644 --- a/types/inferschematype.d.ts +++ b/types/inferschematype.d.ts @@ -219,27 +219,31 @@ type IsSchemaTypeFromBuiltinClass = T extends (typeof String) ? true : T extends (typeof Schema.Types.Decimal128) ? true - : T extends (typeof Schema.Types.String) + : T extends (typeof Schema.Types.Int32) ? true - : T extends (typeof Schema.Types.Number) - ? true - : T extends (typeof Schema.Types.Date) + : T extends (typeof Schema.Types.String) ? true - : T extends (typeof Schema.Types.Boolean) + : T extends (typeof Schema.Types.Number) ? true - : T extends (typeof Schema.Types.Buffer) + : T extends (typeof Schema.Types.Date) ? true - : T extends Types.ObjectId + : T extends (typeof Schema.Types.Boolean) ? true - : T extends Types.Decimal128 + : T extends (typeof Schema.Types.Buffer) ? true - : T extends Buffer - ? true - : T extends NativeDate + : T extends Types.ObjectId ? true - : T extends (typeof Schema.Types.Mixed) + : T extends Types.Decimal128 ? true - : IfEquals; + : T extends Types.Int32 + ? true + : T extends Buffer + ? true + : T extends NativeDate + ? true + : T extends (typeof Schema.Types.Mixed) + ? true + : IfEquals; /** * @summary Resolve path type by returning the corresponding type. @@ -302,18 +306,21 @@ type ResolvePathType extends true ? Types.Decimal128 : IfEquals extends true ? Types.Decimal128 : - IfEquals extends true ? bigint : - IfEquals extends true ? bigint : - PathValueType extends 'bigint' | 'BigInt' | typeof Schema.Types.BigInt | typeof BigInt ? bigint : - PathValueType extends 'uuid' | 'UUID' | typeof Schema.Types.UUID ? Buffer : - IfEquals extends true ? Buffer : - PathValueType extends MapConstructor | 'Map' ? Map> : - IfEquals extends true ? Map> : - PathValueType extends ArrayConstructor ? any[] : - PathValueType extends typeof Schema.Types.Mixed ? any: - IfEquals extends true ? any: - IfEquals extends true ? any: - PathValueType extends typeof SchemaType ? PathValueType['prototype'] : - PathValueType extends Record ? ObtainDocumentType : - unknown, - TypeHint>; + PathValueType extends 'int32' | 'Int32' | typeof Schema.Types.Int32 ? Types.Int32 : + IfEquals extends true ? Types.Int32 : + IfEquals extends true ? Types.Int32 : + IfEquals extends true ? bigint : + IfEquals extends true ? bigint : + PathValueType extends 'bigint' | 'BigInt' | typeof Schema.Types.BigInt | typeof BigInt ? bigint : + PathValueType extends 'uuid' | 'UUID' | typeof Schema.Types.UUID ? Buffer : + IfEquals extends true ? Buffer : + PathValueType extends MapConstructor | 'Map' ? Map> : + IfEquals extends true ? Map> : + PathValueType extends ArrayConstructor ? any[] : + PathValueType extends typeof Schema.Types.Mixed ? any: + IfEquals extends true ? any: + IfEquals extends true ? any: + PathValueType extends typeof SchemaType ? PathValueType['prototype'] : + PathValueType extends Record ? ObtainDocumentType : + unknown, + TypeHint>; diff --git a/types/query.d.ts b/types/query.d.ts index cfc2783ff3f..67129e6c7b6 100644 --- a/types/query.d.ts +++ b/types/query.d.ts @@ -209,10 +209,6 @@ declare module 'mongoose' { translateAliases?: boolean; upsert?: boolean; useBigInt64?: boolean; - /** - * If `false`, will NOT promote [BSON](https://www.mongodb.com/docs/manual/reference/bson-types/values) to their Node.js closest equivalent types. - */ - promoteValues?: boolean; writeConcern?: mongodb.WriteConcern; [other: string]: any; diff --git a/types/schematypes.d.ts b/types/schematypes.d.ts index aff686e1ec9..6cb87b18f00 100644 --- a/types/schematypes.d.ts +++ b/types/schematypes.d.ts @@ -12,6 +12,16 @@ declare module 'mongoose' { */ type Decimal128 = Schema.Types.Decimal128; + + /** + * The Mongoose Int32 [SchemaType](/docs/schematypes.html). Used for + * declaring paths in your schema that should be + * 32-bit integers + * Do not use this to create a new Int32 instance, use `mongoose.Types.Int32` + * instead. + */ + type Int32 = Schema.Types.Int32; + /** * The Mongoose Mixed [SchemaType](/docs/schematypes.html). Used for * declaring paths in your schema that Mongoose's change tracking, casting, @@ -387,6 +397,14 @@ declare module 'mongoose' { defaultOptions: Record; } + class Int32 extends SchemaType { + /** This schema type's name, to defend against minifiers that mangle function names. */ + static schemaName: 'Int32'; + + /** Default options for this SchemaType */ + defaultOptions: Record; + } + class DocumentArray extends SchemaType implements AcceptsDiscriminator { /** This schema type's name, to defend against minifiers that mangle function names. */ static schemaName: 'DocumentArray'; diff --git a/types/types.d.ts b/types/types.d.ts index 194917d69ec..bb1d75f59ab 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -60,6 +60,8 @@ declare module 'mongoose' { class Decimal128 extends mongodb.Decimal128 { } + class Int32 extends mongodb.Int32 { } + class DocumentArray extends Types.Array, any, T> & T> { /** DocumentArray constructor */ constructor(values: AnyObject[]); From 538629c3c8b6c38beec8f49181839d73bdb6397a Mon Sep 17 00:00:00 2001 From: Aditi Khare Date: Fri, 8 Nov 2024 15:43:13 -0500 Subject: [PATCH 05/17] fix failing tests --- docs/schematypes.md | 2 +- lib/cast/int32.js | 2 +- lib/drivers/browser/int32.js | 2 +- lib/schema.js | 4 +--- lib/types/index.js | 2 +- lib/types/int32.js | 2 +- test/types/schema.test.ts | 8 +++++++- 7 files changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/schematypes.md b/docs/schematypes.md index 106cb2adcee..cd4e4ce49e3 100644 --- a/docs/schematypes.md +++ b/docs/schematypes.md @@ -69,7 +69,7 @@ const schema = new Schema({ mixed: Schema.Types.Mixed, _someId: Schema.Types.ObjectId, decimal: Schema.Types.Decimal128, - 32bitInt: Schema.Types.Int32, + int32bit: Schema.Types.Int32, array: [], ofString: [String], ofNumber: [Number], diff --git a/lib/cast/int32.js b/lib/cast/int32.js index 9ab76ca1956..73ade6147a9 100644 --- a/lib/cast/int32.js +++ b/lib/cast/int32.js @@ -27,7 +27,7 @@ module.exports = function castInt32(val) { const _val = Number(val); - if (_val !== _val | 0 && _val > INT32_MIN && _val < INT32_MAX) { + if (_val === (_val | 0) && _val > INT32_MIN && _val < INT32_MAX) { return _val; } assert.ok(false); diff --git a/lib/drivers/browser/int32.js b/lib/drivers/browser/int32.js index b56a43f6ce1..0fc444a5d93 100644 --- a/lib/drivers/browser/int32.js +++ b/lib/drivers/browser/int32.js @@ -4,4 +4,4 @@ 'use strict'; -module.exports = require('bson').Int32; \ No newline at end of file +module.exports = require('bson').Int32; diff --git a/lib/schema.js b/lib/schema.js index 63b2c45f2cc..3e9c95d24fe 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -1494,8 +1494,6 @@ Schema.prototype.interpretAsType = function(path, obj, options) { name = 'ObjectId'; } else if (type === Types.Decimal128) { name = 'Decimal128'; - } else if (type === Types.Int32) { - name = 'Int32'; } else { name = type == null ? '' + type : type.toString(); } @@ -2850,7 +2848,7 @@ module.exports = exports = Schema; * - [Mixed](https://mongoosejs.com/docs/schematypes.html#mixed) * - [UUID](https://mongoosejs.com/docs/schematypes.html#uuid) * - [BigInt](https://mongoosejs.com/docs/schematypes.html#bigint) - * - Int32 + * - [Int32](https://mongoosejs.com/docs/schematypes.html#int32) * * Using this exposed access to the `Mixed` SchemaType, we can use them in our schema. * diff --git a/lib/types/index.js b/lib/types/index.js index 4369bf1ccf0..0a365aea236 100644 --- a/lib/types/index.js +++ b/lib/types/index.js @@ -14,7 +14,7 @@ exports.Embedded = require('./arraySubdocument'); exports.DocumentArray = require('./documentArray'); exports.Decimal128 = require('./decimal128'); exports.ObjectId = require('./objectid'); -exports.ObjectId = require('./int32'); +exports.Int32 = require('./int32'); exports.Map = require('./map'); diff --git a/lib/types/int32.js b/lib/types/int32.js index 2ea4042faf5..f4684f2db8e 100644 --- a/lib/types/int32.js +++ b/lib/types/int32.js @@ -10,4 +10,4 @@ 'use strict'; -module.exports = require('bson').Int32; \ No newline at end of file +module.exports = require('bson').Int32; diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 82988a05b12..6d8cc2dc240 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -425,6 +425,9 @@ export function autoTypedSchema() { decimal1?: Types.Decimal128 | null; decimal2?: Types.Decimal128 | null; decimal3?: Types.Decimal128 | null; + int32_1?: Types.Int32 | null; + int32_2?: Types.Int32 | null; + int32_3?: Types.Int32 | null; }; const TestSchema = new Schema({ @@ -471,7 +474,10 @@ export function autoTypedSchema() { array8: { type: [String], default: () => undefined }, decimal1: Schema.Types.Decimal128, decimal2: 'Decimal128', - decimal3: 'decimal128' + decimal3: 'decimal128', + int32_1: Schema.Types.Int32, + int32_2: 'Int32', + int32_3: 'int32' }); type InferredTestSchemaType = InferSchemaType; From 4492a3ee92cd712316618e6506d5ddc02b8b068f Mon Sep 17 00:00:00 2001 From: Aditi Khare Date: Mon, 11 Nov 2024 16:08:07 -0500 Subject: [PATCH 06/17] requested changes 2 --- docs/schematypes.md | 23 ++++ lib/cast/int32.js | 26 ++-- lib/schema/bigint.js | 2 +- lib/schema/int32.js | 53 ++++---- lib/utils.js | 4 - test/int32.test.js | 313 +++++++++++++++++++++++++++++++++++++++---- 6 files changed, 354 insertions(+), 67 deletions(-) diff --git a/docs/schematypes.md b/docs/schematypes.md index cd4e4ce49e3..8920982db35 100644 --- a/docs/schematypes.md +++ b/docs/schematypes.md @@ -664,6 +664,29 @@ const student = new Student({ id: 1339 }); typeof student.id; // 'number' ``` +There are several types of values that will be successfully cast to a Number. + +```javascript +new Student({ id: '15' }).id; // 15 as a Int32 +new Student({ id: true }).id; // 1 as a Int32 +new Student({ id: false }).id; // 0 as a Int32 +new Student({ id: { valueOf: () => 83 } }).id; // 83 as a Int32 +new Student({ id: '' }).id; // null as a Int32 +``` + +If you pass an object with a `valueOf()` function that returns a Number, Mongoose will +call it and assign the returned value to the path. + +The values `null` and `undefined` are not cast. + +The following inputs will result will all result in a [CastError](validation.html#cast-errors) once validated, meaning that it will not throw on initialization, only when validated: +- NaN +- strings that cast to NaN +- objects that don't have a `valueOf()` function +- a decimal that must be rounded to be an integer +- an input that represents a value beyond the bounds of an 32-bit integer + + ## Getters {#getters} Getters are like virtuals for paths defined in your schema. For example, diff --git a/lib/cast/int32.js b/lib/cast/int32.js index 73ade6147a9..afdc84a38e1 100644 --- a/lib/cast/int32.js +++ b/lib/cast/int32.js @@ -1,6 +1,7 @@ 'use strict'; const assert = require('assert'); +const BSON = require('bson'); /** * Given a value, cast it to a Int32, or throw an `Error` if the value @@ -20,16 +21,23 @@ module.exports = function castInt32(val) { return null; } - if (typeof val === 'string' || typeof val === 'number') { - - const INT32_MAX = 0x7FFFFFFF; - const INT32_MIN = -0x80000000; + let coercedVal; + if (val instanceof BSON.Int32 || val instanceof BSON.Double) { + coercedVal = val.value; + } else if (val instanceof BSON.Long) { + coercedVal = val.toNumber(); + } else { + coercedVal = Number(val); + } - const _val = Number(val); + const INT32_MAX = 0x7FFFFFFF; + const INT32_MIN = -0x80000000; - if (_val === (_val | 0) && _val > INT32_MIN && _val < INT32_MAX) { - return _val; - } - assert.ok(false); + if (coercedVal === (coercedVal | 0) && + coercedVal >= INT32_MIN && + coercedVal <= INT32_MAX + ) { + return coercedVal; } + assert.ok(false); }; diff --git a/lib/schema/bigint.js b/lib/schema/bigint.js index a7ba3a5f943..6baec1f7efd 100644 --- a/lib/schema/bigint.js +++ b/lib/schema/bigint.js @@ -86,7 +86,7 @@ SchemaBigInt.get = SchemaType.get; * #### Example: * * // Make Mongoose cast empty string '' to false. - * const original = mongoose.Schema.BigInt.cast(); + * const original = mongoose.Schema.Types.BigInt.cast(); * mongoose.Schema.BigInt.cast(v => { * if (v === '') { * return false; diff --git a/lib/schema/int32.js b/lib/schema/int32.js index 96f0173be24..69f612af285 100644 --- a/lib/schema/int32.js +++ b/lib/schema/int32.js @@ -7,7 +7,6 @@ const CastError = require('../error/cast'); const SchemaType = require('../schemaType'); const castInt32 = require('../cast/int32'); -const isBsonType = require('../helpers/isBsonType'); /** * Int32 SchemaType constructor. @@ -81,23 +80,44 @@ SchemaInt32.setters = []; SchemaInt32.get = SchemaType.get; +/*! + * ignore + */ + +SchemaInt32._defaultCaster = v => { + const INT32_MAX = 0x7FFFFFFF; + const INT32_MIN = -0x80000000; + + if (v != null) { + if (typeof v !== 'number') { + throw new Error(); + } + if (v < INT32_MIN || v > INT32_MAX) { + throw new Error(); + } + } + + return v; +}; + /** * Get/set the function used to cast arbitrary values to 32-bit integers * * #### Example: * * // Make Mongoose cast NaN to 0 - * const defaultCast = mongoose.Schema.Int32.cast(); - * mongoose.Schema.Int32.cast(v => { + * const defaultCast = mongoose.Schema.Types.Int32.cast(); + * mongoose.Schema.Types.Int32.cast(v => { * if (isNaN(v)) { * return 0; * } * return defaultCast(v); * }); * - * // Or disable casting for Int32s entirely + * // Or disable casting for Int32s entirely (only JS numbers within 32-bit integer bounds and null-ish values are permitted) * mongoose.Schema.Int32.cast(false); * + * * @param {Function} caster * @return {Function} * @function get @@ -112,16 +132,18 @@ SchemaInt32.cast = function cast(caster) { if (caster === false) { caster = this._defaultCaster; } + this._cast = caster; return this._cast; }; + /*! * ignore */ -SchemaInt32._checkRequired = v => v != null && isBsonType(v, 'Int32'); +SchemaInt32._checkRequired = v => v != null; /** * Override the function the required validator uses to check whether a value * passes the `required` check. @@ -157,9 +179,6 @@ SchemaInt32.prototype.checkRequired = function(value) { SchemaInt32.prototype.cast = function(value) { let castInt32; - if (isBsonType(value, 'Int32')) { - return value; - } if (typeof this._castFunction === 'function') { castInt32 = this._castFunction; } else if (typeof this.constructor.cast === 'function') { @@ -225,24 +244,6 @@ SchemaInt32.prototype.castForQuery = function($conditional, val, context) { } }; -/** - * - * @api private - */ - -SchemaInt32.prototype._castNullish = function _castNullish(v) { - if (typeof v === 'undefined') { - return v; - } - const castInt32 = typeof this.constructor.cast === 'function' ? - this.constructor.cast() : - SchemaInt32.cast(); - if (castInt32 == null) { - return v; - } - return v; -}; - /*! * Module exports. */ diff --git a/lib/utils.js b/lib/utils.js index 5263651727d..0a39de3ee8b 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -93,10 +93,6 @@ exports.deepEqual = function deepEqual(a, b) { return a.toString() === b.toString(); } - if (isBsonType(a, 'Int32') && isBsonType(b, 'In32')) { - return a.value === b.value(); - } - if (a instanceof RegExp && b instanceof RegExp) { return a.source === b.source && a.ignoreCase === b.ignoreCase && diff --git a/test/int32.test.js b/test/int32.test.js index 204085fa853..a6b714ed8ad 100644 --- a/test/int32.test.js +++ b/test/int32.test.js @@ -2,10 +2,14 @@ const assert = require('assert'); const start = require('./common'); +const BSON = require('bson'); const mongoose = start.mongoose; const Schema = mongoose.Schema; +const INT32_MAX = 0x7FFFFFFF; +const INT32_MIN = -0x80000000; + describe('Int32', function() { beforeEach(() => mongoose.deleteModel(/Test/)); @@ -22,54 +26,244 @@ describe('Int32', function() { assert.equal(typeof doc.myInt32, 'number'); }); - it('supports the \'required\' property', async function() { - const schema = new Schema({ - int32: { - type: Schema.Types.Int32, - required: true - } + describe('supports the required property', function() { + it('when value is null', async function() { + const schema = new Schema({ + int32: { + type: Schema.Types.Int32, + required: true + } + }); + const Test = mongoose.model('Test', schema); + + const doc = new Test({ + int: null + }); + + const err = await doc.validate().then(() => null, err => err); + assert.ok(err); + assert.ok(err.errors['int32']); + assert.equal(err.errors['int32'].name, 'ValidatorError'); + assert.equal( + err.errors['int32'].message, + 'Path `int32` is required.' + ); }); - const Test = mongoose.model('Test', schema); + it('when value is non-null', async function() { + const schema = new Schema({ + int32: { + type: Schema.Types.Int32, + required: true + } + }); + const Test = mongoose.model('Test', schema); - const doc = new Test({ - int: null + const doc = new Test({ + int: 3 + }); + + const err = await doc.validate().then(() => null, err => err); + assert.ok(err); + assert.ok(err.errors['int32']); + assert.equal(err.errors['int32'].name, 'ValidatorError'); + assert.equal( + err.errors['int32'].message, + 'Path `int32` is required.' + ); + }); + }); + + describe('special inputs', function() { + it('supports INT32_MIN as input', function() { + const schema = new Schema({ + myInt: { + type: Schema.Types.Int32 + } + }); + const Test = mongoose.model('Test', schema); + + const doc = new Test({ + myInt: INT32_MIN + }); + assert.strictEqual(doc.myInt, INT32_MIN); + }); + + it('supports INT32_MAX as input', function() { + const schema = new Schema({ + myInt: { + type: Schema.Types.Int32 + } + }); + const Test = mongoose.model('Test', schema); + + const doc = new Test({ + myInt: INT32_MAX + }); + assert.strictEqual(doc.myInt, INT32_MAX); + }); + + it('supports undefined as input', function() { + const schema = new Schema({ + myInt: { + type: Schema.Types.Int32 + } + }); + const Test = mongoose.model('Test', schema); + + const doc = new Test({ + myInt: undefined + }); + assert.strictEqual(doc.myInt, undefined); }); - const err = await doc.validate().then(() => null, err => err); - assert.ok(err); - assert.ok(err.errors['int32']); - assert.equal(err.errors['int32'].name, 'ValidatorError'); - assert.equal( - err.errors['int32'].message, - 'Path `int32` is required.' - ); + it('supports null as input', function() { + const schema = new Schema({ + myInt: { + type: Schema.Types.Int32 + } + }); + const Test = mongoose.model('Test', schema); + + const doc = new Test({ + myInt: null + }); + assert.strictEqual(doc.myInt, null); + }); }); describe('valid casts', function() { it('casts from string', function() { const schema = new Schema({ - int1: { + myInt: { type: Schema.Types.Int32 } }); const Test = mongoose.model('Test', schema); const doc = new Test({ - int1: -42 + myInt: -42 }); - assert.strictEqual(doc.int1, -42); + assert.strictEqual(doc.myInt, -42); }); it('casts from number', function() { const schema = new Schema({ - int2: Schema.Types.Int32 + myInt: Schema.Types.Int32 + }); + const Test = mongoose.model('Test', schema); + + const doc = new Test({ + myInt: '-997.0' + }); + assert.strictEqual(doc.myInt, -997); + }); + + it('casts from bigint', function() { + const schema = new Schema({ + myInt: Schema.Types.Int32 + }); + const Test = mongoose.model('Test', schema); + + const doc = new Test({ + myInt: -997n + }); + assert.strictEqual(doc.myInt, -997); + }); + + it('casts from BSON.Int32', function() { + const schema = new Schema({ + myInt: Schema.Types.Int32 + }); + const Test = mongoose.model('Test', schema); + + const doc = new Test({ + myInt: new BSON.Int32(-997) + }); + assert.strictEqual(doc.myInt, -997); + }); + + it('casts from Mongoose Schema.Types.Int32', function() { + const schema = new Schema({ + myInt: Schema.Types.Int32 + }); + const Test = mongoose.model('Test', schema); + + const doc = new Test({ + myInt: new mongoose.Types.Int32(53) + }); + assert.strictEqual(doc.myInt, 53); + }); + + it('casts from BSON.Long provided its value is within bounds of Int32', function() { + const schema = new Schema({ + myInt: Schema.Types.Int32 + }); + const Test = mongoose.model('Test', schema); + + const doc = new Test({ + myInt: BSON.Long.fromNumber(-997) + }); + assert.strictEqual(doc.myInt, -997); + }); + + it('casts from BSON.Double provided its value is an integer', function() { + const schema = new Schema({ + myInt: Schema.Types.Int32 + }); + const Test = mongoose.model('Test', schema); + + const doc = new Test({ + myInt: new BSON.Double(-997) + }); + assert.strictEqual(doc.myInt, -997); + }); + + it('casts boolean true to 1', function() { + const schema = new Schema({ + myInt: Schema.Types.Int32 + }); + const Test = mongoose.model('Test', schema); + + const doc = new Test({ + myInt: true + }); + assert.strictEqual(doc.myInt, 1); + }); + + it('casts boolean false to 0', function() { + const schema = new Schema({ + myInt: Schema.Types.Int32 + }); + const Test = mongoose.model('Test', schema); + + const doc = new Test({ + myInt: false + }); + assert.strictEqual(doc.myInt, 0); + }); + + it('casts empty string to null', function() { + const schema = new Schema({ + myInt: Schema.Types.Int32 + }); + const Test = mongoose.model('Test', schema); + + const doc = new Test({ + myInt: '' + }); + assert.strictEqual(doc.myInt, null); + }); + + it('supports valueOf() function ', function() { + const schema = new Schema({ + myInt: Schema.Types.Int32 }); const Test = mongoose.model('Test', schema); const doc = new Test({ - int2: '-997.0' + myInt: { a: 'random', b: { c: 'whatever' }, valueOf: () => 83 } }); - assert.strictEqual(doc.int2, -997); + assert.strictEqual(doc.myInt, 83); }); }); @@ -83,7 +277,7 @@ describe('Int32', function() { Test = mongoose.model('Test', schema); }); - describe('when a non-integer input is provided to an Int32 field', () => { + describe('when a non-integer decimal input is provided to an Int32 field', () => { it('throws a CastError upon validation', async() => { const doc = new Test({ myInt: -42.4 @@ -119,6 +313,24 @@ describe('Int32', function() { }); }); + describe('when a non-integer decimal string is provided to an Int32 field', () => { + it('throws a CastError upon validation', async() => { + const doc = new Test({ + myInt: '1.2' + }); + + assert.strictEqual(doc.myInt, undefined); + const err = await doc.validate().catch(e => e); + assert.ok(err); + assert.ok(err.errors['myInt']); + assert.equal(err.errors['myInt'].name, 'CastError'); + assert.equal( + err.errors['myInt'].message, + 'Cast to Int32 failed for value "1.2" (type string) at path "myInt"' + ); + }); + }); + describe('when NaN is provided to an Int32 field', () => { it('throws a CastError upon validation', async() => { const doc = new Test({ @@ -140,7 +352,7 @@ describe('Int32', function() { describe('when value above INT32_MAX is provided to an Int32 field', () => { it('throws a CastError upon validation', async() => { const doc = new Test({ - myInt: 0x7FFFFFFF + 1 + myInt: INT32_MAX + 1 }); assert.strictEqual(doc.myInt, undefined); @@ -158,7 +370,7 @@ describe('Int32', function() { describe('when value below INT32_MIN is provided to an Int32 field', () => { it('throws a CastError upon validation', async() => { const doc = new Test({ - myInt: -0x80000000 - 1 + myInt: INT32_MIN - 1 }); assert.strictEqual(doc.myInt, undefined); @@ -174,6 +386,53 @@ describe('Int32', function() { }); }); + describe('custom casters', () => { + const defaultCast = mongoose.Schema.Types.Int32.cast(); + + afterEach(() => { + mongoose.Schema.Types.Int32.cast(defaultCast); + }); + + it('supports cast disabled', () => { + mongoose.Schema.Types.Int32.cast(false); + const schema = new Schema({ + myInt1: { + type: Schema.Types.Int32 + }, + myInt2: { + type: Schema.Types.Int32 + } + }); + const Test = mongoose.model('Test', schema); + const doc = new Test({ + myInt: '52', + myInt2: 52 + }); + assert.strictEqual(doc.myInt1, undefined); + assert.strictEqual(doc.myInt2, 52); + }); + + it('supports custom cast', () => { + mongoose.Schema.Types.Int32.cast(v => { + if (isNaN(v)) { + return 0; + } + return defaultCast(v); + }); + const schema = new Schema({ + myInt: { + type: Schema.Types.Int32 + } + }); + + const Test = mongoose.model('Test', schema); + const doc = new Test({ + myInt: NaN + }); + assert.strictEqual(doc.myInt, 0); + }); + }); + describe('mongoDB integration', function() { let db; let Test; @@ -211,7 +470,7 @@ describe('Int32', function() { assert.strictEqual(doc.myInt, 42); }); - it('is queryable as a BSON Double in MongoDB', async function() { + it('is NOT queryable as a BSON Double in MongoDB', async function() { await Test.create({ myInt: '42' }); const doc = await Test.findOne({ myInt: { $type: 'double' } }); assert.equal(doc, undefined); From 8226905a0fab7026196f7ef4c2d1962f59caef6d Mon Sep 17 00:00:00 2001 From: Aditi Khare Date: Tue, 12 Nov 2024 14:54:48 -0500 Subject: [PATCH 07/17] requested changes 3 --- docs/schematypes.md | 10 +++++----- lib/cast/int32.js | 2 +- lib/types/index.js | 1 - lib/types/int32.js | 13 ------------- test/int32.test.js | 22 +++++++--------------- test/types/schema.test.ts | 8 +------- types/types.d.ts | 2 -- 7 files changed, 14 insertions(+), 44 deletions(-) delete mode 100644 lib/types/int32.js diff --git a/docs/schematypes.md b/docs/schematypes.md index 8920982db35..bfb42ae4869 100644 --- a/docs/schematypes.md +++ b/docs/schematypes.md @@ -680,12 +680,12 @@ call it and assign the returned value to the path. The values `null` and `undefined` are not cast. The following inputs will result will all result in a [CastError](validation.html#cast-errors) once validated, meaning that it will not throw on initialization, only when validated: -- NaN -- strings that cast to NaN -- objects that don't have a `valueOf()` function -- a decimal that must be rounded to be an integer -- an input that represents a value beyond the bounds of an 32-bit integer +* NaN +* strings that cast to NaN +* objects that don't have a `valueOf()` function +* a decimal that must be rounded to be an integer +* an input that represents a value outside the bounds of an 32-bit integer ## Getters {#getters} diff --git a/lib/cast/int32.js b/lib/cast/int32.js index afdc84a38e1..80d2a97934c 100644 --- a/lib/cast/int32.js +++ b/lib/cast/int32.js @@ -9,7 +9,7 @@ const BSON = require('bson'); * * @param {Any} value * @return {Number} - * @throws {Error} if `value` does not represent an integer, or is beyond the bounds of an 32-bit integer. + * @throws {Error} if `value` does not represent an integer, or is outside the bounds of an 32-bit integer. * @api private */ diff --git a/lib/types/index.js b/lib/types/index.js index 0a365aea236..d234f6bb62a 100644 --- a/lib/types/index.js +++ b/lib/types/index.js @@ -14,7 +14,6 @@ exports.Embedded = require('./arraySubdocument'); exports.DocumentArray = require('./documentArray'); exports.Decimal128 = require('./decimal128'); exports.ObjectId = require('./objectid'); -exports.Int32 = require('./int32'); exports.Map = require('./map'); diff --git a/lib/types/int32.js b/lib/types/int32.js deleted file mode 100644 index f4684f2db8e..00000000000 --- a/lib/types/int32.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Int32 type constructor - * - * #### Example: - * - * const id = new mongoose.Types.Int32('-2'); - * - * @constructor Int32 - */ - -'use strict'; - -module.exports = require('bson').Int32; diff --git a/test/int32.test.js b/test/int32.test.js index a6b714ed8ad..4673d97c228 100644 --- a/test/int32.test.js +++ b/test/int32.test.js @@ -27,7 +27,7 @@ describe('Int32', function() { }); describe('supports the required property', function() { - it('when value is null', async function() { + it('when vaglue is null', async function() { const schema = new Schema({ int32: { type: Schema.Types.Int32, @@ -182,18 +182,6 @@ describe('Int32', function() { assert.strictEqual(doc.myInt, -997); }); - it('casts from Mongoose Schema.Types.Int32', function() { - const schema = new Schema({ - myInt: Schema.Types.Int32 - }); - const Test = mongoose.model('Test', schema); - - const doc = new Test({ - myInt: new mongoose.Types.Int32(53) - }); - assert.strictEqual(doc.myInt, 53); - }); - it('casts from BSON.Long provided its value is within bounds of Int32', function() { const schema = new Schema({ myInt: Schema.Types.Int32 @@ -393,7 +381,7 @@ describe('Int32', function() { mongoose.Schema.Types.Int32.cast(defaultCast); }); - it('supports cast disabled', () => { + it('supports cast disabled', async() => { mongoose.Schema.Types.Int32.cast(false); const schema = new Schema({ myInt1: { @@ -405,11 +393,15 @@ describe('Int32', function() { }); const Test = mongoose.model('Test', schema); const doc = new Test({ - myInt: '52', + myInt1: '52', myInt2: 52 }); assert.strictEqual(doc.myInt1, undefined); assert.strictEqual(doc.myInt2, 52); + + const err = await doc.validate().catch(e => e); + assert.ok(err); + assert.ok(err.errors['myInt1']); }); it('supports custom cast', () => { diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 6d8cc2dc240..82988a05b12 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -425,9 +425,6 @@ export function autoTypedSchema() { decimal1?: Types.Decimal128 | null; decimal2?: Types.Decimal128 | null; decimal3?: Types.Decimal128 | null; - int32_1?: Types.Int32 | null; - int32_2?: Types.Int32 | null; - int32_3?: Types.Int32 | null; }; const TestSchema = new Schema({ @@ -474,10 +471,7 @@ export function autoTypedSchema() { array8: { type: [String], default: () => undefined }, decimal1: Schema.Types.Decimal128, decimal2: 'Decimal128', - decimal3: 'decimal128', - int32_1: Schema.Types.Int32, - int32_2: 'Int32', - int32_3: 'int32' + decimal3: 'decimal128' }); type InferredTestSchemaType = InferSchemaType; diff --git a/types/types.d.ts b/types/types.d.ts index bb1d75f59ab..194917d69ec 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -60,8 +60,6 @@ declare module 'mongoose' { class Decimal128 extends mongodb.Decimal128 { } - class Int32 extends mongodb.Int32 { } - class DocumentArray extends Types.Array, any, T> & T> { /** DocumentArray constructor */ constructor(values: AnyObject[]); From 7c6ff5c49ec9aaaa199e1780ff0eeb6be7a9773e Mon Sep 17 00:00:00 2001 From: Aditi Khare Date: Tue, 12 Nov 2024 16:36:34 -0500 Subject: [PATCH 08/17] requested changes 4 --- docs/source/api.js | 1 - types/inferrawdoctype.d.ts | 31 +++++++++++++-------------- types/inferschematype.d.ts | 43 +++++++++++++++++--------------------- 3 files changed, 33 insertions(+), 42 deletions(-) diff --git a/docs/source/api.js b/docs/source/api.js index 62435bfcd22..e398164056a 100644 --- a/docs/source/api.js +++ b/docs/source/api.js @@ -41,7 +41,6 @@ const files = [ 'lib/types/arraySubdocument.js', 'lib/types/buffer.js', 'lib/types/decimal128.js', - 'lib/types/int32.js', 'lib/types/map.js', 'lib/types/array/methods/index.js' ]; diff --git a/types/inferrawdoctype.d.ts b/types/inferrawdoctype.d.ts index da3507f882a..605571057a0 100644 --- a/types/inferrawdoctype.d.ts +++ b/types/inferrawdoctype.d.ts @@ -102,21 +102,18 @@ declare module 'mongoose' { PathValueType extends 'decimal128' | 'Decimal128' | typeof Schema.Types.Decimal128 ? Types.Decimal128 : IfEquals extends true ? Types.Decimal128 : IfEquals extends true ? Types.Decimal128 : - PathValueType extends 'int32' | 'Int32' | typeof Schema.Types.Int32 ? Types.Int32 : - IfEquals extends true ? Types.Int32 : - IfEquals extends true ? Types.Int32 : - IfEquals extends true ? bigint : - IfEquals extends true ? bigint : - PathValueType extends 'bigint' | 'BigInt' | typeof Schema.Types.BigInt | typeof BigInt ? bigint : - PathValueType extends 'uuid' | 'UUID' | typeof Schema.Types.UUID ? Buffer : - IfEquals extends true ? Buffer : - PathValueType extends MapConstructor | 'Map' ? Map> : - IfEquals extends true ? Map> : - PathValueType extends ArrayConstructor ? any[] : - PathValueType extends typeof Schema.Types.Mixed ? any: - IfEquals extends true ? any: - IfEquals extends true ? any: - PathValueType extends typeof SchemaType ? PathValueType['prototype'] : - PathValueType extends Record ? ObtainDocumentType : - unknown; + IfEquals extends true ? bigint : + IfEquals extends true ? bigint : + PathValueType extends 'bigint' | 'BigInt' | typeof Schema.Types.BigInt | typeof BigInt ? bigint : + PathValueType extends 'uuid' | 'UUID' | typeof Schema.Types.UUID ? Buffer : + IfEquals extends true ? Buffer : + PathValueType extends MapConstructor | 'Map' ? Map> : + IfEquals extends true ? Map> : + PathValueType extends ArrayConstructor ? any[] : + PathValueType extends typeof Schema.Types.Mixed ? any: + IfEquals extends true ? any: + IfEquals extends true ? any: + PathValueType extends typeof SchemaType ? PathValueType['prototype'] : + PathValueType extends Record ? ObtainDocumentType : + unknown; } diff --git a/types/inferschematype.d.ts b/types/inferschematype.d.ts index 45c5b9b564a..85d61a45fea 100644 --- a/types/inferschematype.d.ts +++ b/types/inferschematype.d.ts @@ -235,15 +235,13 @@ type IsSchemaTypeFromBuiltinClass = T extends (typeof String) ? true : T extends Types.Decimal128 ? true - : T extends Types.Int32 + : T extends Buffer ? true - : T extends Buffer + : T extends NativeDate ? true - : T extends NativeDate + : T extends (typeof Schema.Types.Mixed) ? true - : T extends (typeof Schema.Types.Mixed) - ? true - : IfEquals; + : IfEquals; /** * @summary Resolve path type by returning the corresponding type. @@ -306,21 +304,18 @@ type ResolvePathType extends true ? Types.Decimal128 : IfEquals extends true ? Types.Decimal128 : - PathValueType extends 'int32' | 'Int32' | typeof Schema.Types.Int32 ? Types.Int32 : - IfEquals extends true ? Types.Int32 : - IfEquals extends true ? Types.Int32 : - IfEquals extends true ? bigint : - IfEquals extends true ? bigint : - PathValueType extends 'bigint' | 'BigInt' | typeof Schema.Types.BigInt | typeof BigInt ? bigint : - PathValueType extends 'uuid' | 'UUID' | typeof Schema.Types.UUID ? Buffer : - IfEquals extends true ? Buffer : - PathValueType extends MapConstructor | 'Map' ? Map> : - IfEquals extends true ? Map> : - PathValueType extends ArrayConstructor ? any[] : - PathValueType extends typeof Schema.Types.Mixed ? any: - IfEquals extends true ? any: - IfEquals extends true ? any: - PathValueType extends typeof SchemaType ? PathValueType['prototype'] : - PathValueType extends Record ? ObtainDocumentType : - unknown, - TypeHint>; + IfEquals extends true ? bigint : + IfEquals extends true ? bigint : + PathValueType extends 'bigint' | 'BigInt' | typeof Schema.Types.BigInt | typeof BigInt ? bigint : + PathValueType extends 'uuid' | 'UUID' | typeof Schema.Types.UUID ? Buffer : + IfEquals extends true ? Buffer : + PathValueType extends MapConstructor | 'Map' ? Map> : + IfEquals extends true ? Map> : + PathValueType extends ArrayConstructor ? any[] : + PathValueType extends typeof Schema.Types.Mixed ? any: + IfEquals extends true ? any: + IfEquals extends true ? any: + PathValueType extends typeof SchemaType ? PathValueType['prototype'] : + PathValueType extends Record ? ObtainDocumentType : + unknown, + TypeHint>; From c5dc22694e52e114cac96f4b42387b4efcf7af41 Mon Sep 17 00:00:00 2001 From: Aditi Khare Date: Tue, 12 Nov 2024 16:56:24 -0500 Subject: [PATCH 09/17] requested changes 4 cont. --- lib/schema/int32.js | 49 --------------------------------------------- 1 file changed, 49 deletions(-) diff --git a/lib/schema/int32.js b/lib/schema/int32.js index 69f612af285..5c10f37ac87 100644 --- a/lib/schema/int32.js +++ b/lib/schema/int32.js @@ -194,55 +194,6 @@ SchemaInt32.prototype.cast = function(value) { } }; -/*! - * ignore - */ - -SchemaInt32.$conditionalHandlers = { - ...SchemaType.prototype.$conditionalHandlers, - $gt: handleSingle, - $gte: handleSingle, - $lt: handleSingle, - $lte: handleSingle -}; - -/*! - * ignore - */ - -function handleSingle(val, context) { - return this.castForQuery(null, val, context); -} - -/** - * Casts contents for queries. - * - * @param {String} $conditional - * @param {any} val - * @api private - */ - -SchemaInt32.prototype.castForQuery = function($conditional, val, context) { - let handler; - if ($conditional != null) { - handler = SchemaInt32.$conditionalHandlers[$conditional]; - - if (handler) { - return handler.call(this, val); - } - - return this.applySetters(null, val, context); - } - - try { - return this.applySetters(val, context); - } catch (err) { - if (err instanceof CastError && err.path === this.path && this.$fullPath != null) { - err.path = this.$fullPath; - } - throw err; - } -}; /*! * Module exports. From 953797444c20190f622bdb94a374a34b713e44af Mon Sep 17 00:00:00 2001 From: Aditi Khare Date: Tue, 12 Nov 2024 17:14:03 -0500 Subject: [PATCH 10/17] remove extranous changes --- lib/browser.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/browser.js b/lib/browser.js index 58f8ab26aa2..12b0cbde653 100644 --- a/lib/browser.js +++ b/lib/browser.js @@ -50,7 +50,6 @@ exports.Schema = require('./schema'); * - [ObjectId](https://mongoosejs.com/docs/schematypes.html#objectids) * - [Map](https://mongoosejs.com/docs/schematypes.html#maps) * - [Subdocument](https://mongoosejs.com/docs/schematypes.html#schemas) - * - [Int32](https://mongoosejs.com/docs/schematypes.html#int32) * * Using this exposed access to the `ObjectId` type, we can construct ids on demand. * From 49804ed721ac61d7071077ba5f561a8bb931169e Mon Sep 17 00:00:00 2001 From: Aditi Khare Date: Tue, 12 Nov 2024 17:43:05 -0500 Subject: [PATCH 11/17] fix failing tests --- lib/schema/int32.js | 50 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/lib/schema/int32.js b/lib/schema/int32.js index 5c10f37ac87..12b049cff5d 100644 --- a/lib/schema/int32.js +++ b/lib/schema/int32.js @@ -194,6 +194,56 @@ SchemaInt32.prototype.cast = function(value) { } }; +/*! + * ignore + */ + +SchemaInt32.$conditionalHandlers = { + ...SchemaType.prototype.$conditionalHandlers, + $gt: handleSingle, + $gte: handleSingle, + $lt: handleSingle, + $lte: handleSingle +}; + +/*! + * ignore + */ + +function handleSingle(val, context) { + return this.castForQuery(null, val, context); +} + +/** + * Casts contents for queries. + * + * @param {String} $conditional + * @param {any} val + * @api private + */ + +SchemaInt32.prototype.castForQuery = function($conditional, val, context) { + let handler; + if ($conditional != null) { + handler = SchemaInt32.$conditionalHandlers[$conditional]; + + if (handler) { + return handler.call(this, val); + } + + return this.applySetters(null, val, context); + } + + try { + return this.applySetters(val, context); + } catch (err) { + if (err instanceof CastError && err.path === this.path && this.$fullPath != null) { + err.path = this.$fullPath; + } + throw err; + } +}; + /*! * Module exports. From c6bbafb18d6d66f64f980eccecfdf1467ed8b043 Mon Sep 17 00:00:00 2001 From: Aditi Khare Date: Tue, 12 Nov 2024 18:40:31 -0500 Subject: [PATCH 12/17] remove extranous file --- lib/drivers/browser/int32.js | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 lib/drivers/browser/int32.js diff --git a/lib/drivers/browser/int32.js b/lib/drivers/browser/int32.js deleted file mode 100644 index 0fc444a5d93..00000000000 --- a/lib/drivers/browser/int32.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * ignore - */ - -'use strict'; - -module.exports = require('bson').Int32; From 18d18a79cdba01bbad8ece6f3645e40f32eed7c4 Mon Sep 17 00:00:00 2001 From: Aditi Khare Date: Tue, 19 Nov 2024 16:43:06 -0500 Subject: [PATCH 13/17] suggested changes --- index.js | 1 - lib/cast/int32.js | 9 +-------- lib/mongoose.js | 15 --------------- test/int32.test.js | 45 +++++++++++++++++++++++++++++++++++---------- 4 files changed, 36 insertions(+), 34 deletions(-) diff --git a/index.js b/index.js index 9892d18efb8..6ebbd5fd5d3 100644 --- a/index.js +++ b/index.js @@ -46,7 +46,6 @@ module.exports.Decimal128 = mongoose.Decimal128; module.exports.Mixed = mongoose.Mixed; module.exports.Date = mongoose.Date; module.exports.Number = mongoose.Number; -module.exports.Int32 = mongoose.Int32; module.exports.Error = mongoose.Error; module.exports.MongooseError = mongoose.MongooseError; module.exports.now = mongoose.now; diff --git a/lib/cast/int32.js b/lib/cast/int32.js index 80d2a97934c..339e8612ea3 100644 --- a/lib/cast/int32.js +++ b/lib/cast/int32.js @@ -21,14 +21,7 @@ module.exports = function castInt32(val) { return null; } - let coercedVal; - if (val instanceof BSON.Int32 || val instanceof BSON.Double) { - coercedVal = val.value; - } else if (val instanceof BSON.Long) { - coercedVal = val.toNumber(); - } else { - coercedVal = Number(val); - } + const coercedVal = val instanceof BSON.Long ? val.toNumber() : Number(val); const INT32_MAX = 0x7FFFFFFF; const INT32_MIN = -0x80000000; diff --git a/lib/mongoose.js b/lib/mongoose.js index 788e257bbed..22521eaab1b 100644 --- a/lib/mongoose.js +++ b/lib/mongoose.js @@ -1140,21 +1140,6 @@ Mongoose.prototype.syncIndexes = function(options) { Mongoose.prototype.Decimal128 = SchemaTypes.Decimal128; -/** - * The Mongoose Int32 [SchemaType](https://mongoosejs.com/docs/schematypes.html). Used for - * declaring paths in your schema that should be 32-bit integers. - * Do not use this to create a new Int32 instance, use `mongoose.Types.Int32` - * instead. - * - * #### Example: - * - * const vehicleSchema = new Schema({ numTires: mongoose.Int32 }); - * - * @property Int32 - * @api public - */ -Mongoose.prototype.Int32 = SchemaTypes.Int32; - /** * The Mongoose Mixed [SchemaType](https://mongoosejs.com/docs/schematypes.html). Used for * declaring paths in your schema that Mongoose's change tracking, casting, diff --git a/test/int32.test.js b/test/int32.test.js index 4673d97c228..ca8b963bb16 100644 --- a/test/int32.test.js +++ b/test/int32.test.js @@ -3,6 +3,7 @@ const assert = require('assert'); const start = require('./common'); const BSON = require('bson'); +const sinon = require('sinon'); const mongoose = start.mongoose; const Schema = mongoose.Schema; @@ -10,7 +11,7 @@ const Schema = mongoose.Schema; const INT32_MAX = 0x7FFFFFFF; const INT32_MIN = -0x80000000; -describe('Int32', function() { +describe.only('Int32', function() { beforeEach(() => mongoose.deleteModel(/Test/)); it('is a valid schema type', function() { @@ -27,7 +28,7 @@ describe('Int32', function() { }); describe('supports the required property', function() { - it('when vaglue is null', async function() { + it('when value is null', async function() { const schema = new Schema({ int32: { type: Schema.Types.Int32, @@ -141,7 +142,7 @@ describe('Int32', function() { const Test = mongoose.model('Test', schema); const doc = new Test({ - myInt: -42 + myInt: '-42' }); assert.strictEqual(doc.myInt, -42); }); @@ -182,16 +183,40 @@ describe('Int32', function() { assert.strictEqual(doc.myInt, -997); }); - it('casts from BSON.Long provided its value is within bounds of Int32', function() { - const schema = new Schema({ - myInt: Schema.Types.Int32 + describe('long', function() { + after(function() { + sinon.restore(); }); - const Test = mongoose.model('Test', schema); - const doc = new Test({ - myInt: BSON.Long.fromNumber(-997) + it('casts from BSON.Long provided its value is within bounds of Int32', function() { + const schema = new Schema({ + myInt: Schema.Types.Int32 + }); + const Test = mongoose.model('Test', schema); + + const doc = new Test({ + myInt: BSON.Long.fromNumber(-997) + }); + assert.strictEqual(doc.myInt, -997); + }); + + it('calls Long.toNumber when casting long', function() { + // this is a perf optimization, since long.toNumber() is faster than Number(long) + const schema = new Schema({ + myInt: Schema.Types.Int32 + }); + const Test = mongoose.model('Test', schema); + + sinon.stub(BSON.Long.prototype, 'toNumber').callsFake(function() { + return 2; + }); + + const doc = new Test({ + myInt: BSON.Long.fromNumber(-997) + }); + + assert.strictEqual(doc.myInt, 2); }); - assert.strictEqual(doc.myInt, -997); }); it('casts from BSON.Double provided its value is an integer', function() { From 0e4b257872e550855aef92aad5984adf1d8a23f2 Mon Sep 17 00:00:00 2001 From: Aditi Khare Date: Tue, 19 Nov 2024 16:50:07 -0500 Subject: [PATCH 14/17] lint --- test/int32.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/int32.test.js b/test/int32.test.js index ca8b963bb16..b05fa82305b 100644 --- a/test/int32.test.js +++ b/test/int32.test.js @@ -11,7 +11,7 @@ const Schema = mongoose.Schema; const INT32_MAX = 0x7FFFFFFF; const INT32_MIN = -0x80000000; -describe.only('Int32', function() { +describe('Int32', function() { beforeEach(() => mongoose.deleteModel(/Test/)); it('is a valid schema type', function() { From 010d1e2277a44bbe25a2cbab19e7ae44a1ad6194 Mon Sep 17 00:00:00 2001 From: Aditi Khare Date: Tue, 19 Nov 2024 19:16:20 -0500 Subject: [PATCH 15/17] misc change --- lib/cast/int32.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/cast/int32.js b/lib/cast/int32.js index 339e8612ea3..26fd6549182 100644 --- a/lib/cast/int32.js +++ b/lib/cast/int32.js @@ -1,7 +1,6 @@ 'use strict'; const assert = require('assert'); -const BSON = require('bson'); /** * Given a value, cast it to a Int32, or throw an `Error` if the value @@ -21,7 +20,7 @@ module.exports = function castInt32(val) { return null; } - const coercedVal = val instanceof BSON.Long ? val.toNumber() : Number(val); + const coercedVal = val._bsontype === 'Long' ? val.toNumber() : Number(val); const INT32_MAX = 0x7FFFFFFF; const INT32_MIN = -0x80000000; From 0f78bf156dbd4a4a3941bb25bbcbf7855898e995 Mon Sep 17 00:00:00 2001 From: Aditi Khare Date: Wed, 20 Nov 2024 02:16:23 -0500 Subject: [PATCH 16/17] misc change --- lib/schema/int32.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/schema/int32.js b/lib/schema/int32.js index 12b049cff5d..51594934e91 100644 --- a/lib/schema/int32.js +++ b/lib/schema/int32.js @@ -89,10 +89,7 @@ SchemaInt32._defaultCaster = v => { const INT32_MIN = -0x80000000; if (v != null) { - if (typeof v !== 'number') { - throw new Error(); - } - if (v < INT32_MIN || v > INT32_MAX) { + if (typeof v !== 'number' || v !== (v | 0) || v < INT32_MIN || v > INT32_MAX) { throw new Error(); } } From 9dcada38be17bdfbba1826a367ca15578b64b63e Mon Sep 17 00:00:00 2001 From: Aditi Khare Date: Wed, 20 Nov 2024 16:37:05 -0500 Subject: [PATCH 17/17] use isBsonType --- lib/cast/int32.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/cast/int32.js b/lib/cast/int32.js index 26fd6549182..34eeae8565f 100644 --- a/lib/cast/int32.js +++ b/lib/cast/int32.js @@ -1,5 +1,6 @@ 'use strict'; +const isBsonType = require('../helpers/isBsonType'); const assert = require('assert'); /** @@ -20,7 +21,7 @@ module.exports = function castInt32(val) { return null; } - const coercedVal = val._bsontype === 'Long' ? val.toNumber() : Number(val); + const coercedVal = isBsonType(val, 'Long') ? val.toNumber() : Number(val); const INT32_MAX = 0x7FFFFFFF; const INT32_MIN = -0x80000000;