diff --git a/.github/workflows/testsuite.yml b/.github/workflows/testsuite.yml index 42eaaf3..dc69fd0 100644 --- a/.github/workflows/testsuite.yml +++ b/.github/workflows/testsuite.yml @@ -11,19 +11,19 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - meteor: [ '1.12.1', '2.13.3', '3.0.3' ] + meteor: [ '1.12.1', '2.13.3', '3.0.4' ] # needs: [lintcode,lintstyle,lintdocs] # we could add prior jobs for linting, if desired steps: - name: checkout uses: actions/checkout@v3 - name: Setup meteor - uses: meteorengineer/setup-meteor@v1 + uses: meteorengineer/setup-meteor@v2 with: meteor-release: ${{ matrix.meteor }} - name: cache dependencies - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} diff --git a/CHANGELOG.md b/CHANGELOG.md index e46e9ee..58289f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* +- [4.0.4](#404) +- [4.0.3](#403) - [4.0.2](#402) - [4.0.1](#401) - [4.0.0](#400) @@ -80,6 +82,16 @@ +## 4.0.4 + +- Return unaltered error message when collection isn't being validated and doesn't have simple schema attached thanks to @DmytroSoninLinguahouse +- Update test application to use Meteor release number 3.0.4 +- Remove lodash dependencies + +## 4.0.3 + +- Update Meteor release to 3.0 + ## 4.0.2 - Make collection2 compatible with the newly released RC diff --git a/package-lock.json b/package-lock.json index c931c11..fd1af77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,31 @@ { "name": "meteor-collection2", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 3, "requires": true, - "dependencies": { - "prettier": { + "packages": { + "": { + "name": "meteor-collection2", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "prettier": "3.1.1" + } + }, + "node_modules/prettier": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", - "dev": true + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } } } } diff --git a/package/collection2/lib.js b/package/collection2/lib.js index cade2b8..5bb72ac 100644 --- a/package/collection2/lib.js +++ b/package/collection2/lib.js @@ -39,3 +39,29 @@ export const isUpdateType = function (type) { export const isUpsertType = function (type) { return ['upsert', 'upsertAsync'].includes(type); }; + +export function isObject(value) { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +export function isEqual(a, b) { + // Handle primitive types and null/undefined + if (a === b) return true; + if (a == null || b == null) return false; + if (typeof a !== 'object' || typeof b !== 'object') return false; + + // Get object keys + const keysA = Object.keys(a); + const keysB = Object.keys(b); + + // Check if number of keys match + if (keysA.length !== keysB.length) return false; + + // Compare each key-value pair recursively + return keysA.every(key => { + if (!Object.prototype.hasOwnProperty.call(b, key)) return false; + return isEqual(a[key], b[key]); + }); +} + + diff --git a/package/collection2/main.js b/package/collection2/main.js index d97041e..8ef4ef5 100644 --- a/package/collection2/main.js +++ b/package/collection2/main.js @@ -2,10 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { Mongo } from 'meteor/mongo'; import SimpleSchema from "meteor/aldeed:simple-schema"; import { EJSON } from 'meteor/ejson'; -import isEmpty from 'lodash.isempty'; -import isEqual from 'lodash.isequal'; -import isObject from 'lodash.isobject'; -import { flattenSelector, isInsertType, isUpdateType, isUpsertType } from './lib'; +import { flattenSelector, isInsertType, isUpdateType, isUpsertType, isObject, isEqual } from './lib'; /** @@ -228,13 +225,18 @@ Mongo.Collection.prototype.attachSchema = function c2AttachSchema(ss, options) { _super.isCalledFromAsync = true; return Promise.resolve(_super.apply(this, args)); } catch (err) { - const addValidationErrorsPropName = - typeof validationContext.addValidationErrors === 'function' - ? 'addValidationErrors' + if (this._c2) { + const addValidationErrorsPropName = + typeof validationContext.addValidationErrors === 'function' + ? 'addValidationErrors' : 'addInvalidKeys'; parsingServerError([err], validationContext, addValidationErrorsPropName); const error = getErrorObject(validationContext, err.message, err.code); return Promise.reject(error); + } else { + // do not change error if collection isn't being validated by collection2 + return Promise.reject(err); + } } } else { return _super.apply(this, args); @@ -250,12 +252,17 @@ Mongo.Collection.prototype.attachSchema = function c2AttachSchema(ss, options) { try { return await _super.apply(this, args); } catch (err) { + if (this._c2) { const addValidationErrorsPropName = typeof validationContext.addValidationErrors === 'function' ? 'addValidationErrors' : 'addInvalidKeys'; parsingServerError([err], validationContext, addValidationErrorsPropName); throw getErrorObject(validationContext, err.message, err.code); + } else { + // do not change error if collection isn't being validated by collection2 + throw err; + } } }; } @@ -305,7 +312,7 @@ Mongo.Collection.prototype.attachSchema = function c2AttachSchema(ss, options) { throw new Error('invalid type argument'); } - const validatedObjectWasInitiallyEmpty = isEmpty(doc); + const validatedObjectWasInitiallyEmpty = Object.keys(doc).length === 0; // Support missing options arg if (!callback && typeof options === 'function') { @@ -481,7 +488,7 @@ Mongo.Collection.prototype.attachSchema = function c2AttachSchema(ss, options) { } // XXX Maybe move this into SimpleSchema - if (!validatedObjectWasInitiallyEmpty && isEmpty(docToValidate)) { + if (!validatedObjectWasInitiallyEmpty && Object.keys(docToValidate).length === 0) { throw new Error( 'After filtering out keys not in the schema, your ' + (isUpdateType(type) ? 'modifier' : 'object') + diff --git a/package/collection2/package.js b/package/collection2/package.js index 5a3cb75..c35f884 100644 --- a/package/collection2/package.js +++ b/package/collection2/package.js @@ -4,17 +4,11 @@ Package.describe({ name: 'aldeed:collection2', summary: 'Automatic validation of Meteor Mongo insert and update operations on the client and server', - version: '4.0.3', + version: '4.0.4', documentation: '../../README.md', git: 'https://github.com/aldeed/meteor-collection2.git' }); -Npm.depends({ - 'lodash.isempty': '4.4.0', - 'lodash.isequal': '4.5.0', - 'lodash.isobject': '3.0.2' -}); - Package.onUse(function (api) { api.versionsFrom(['1.12.1', '2.3', '3.0']); api.use('mongo'); diff --git a/tests/.meteor/release b/tests/.meteor/release index 443afb1..b1e86a3 100644 --- a/tests/.meteor/release +++ b/tests/.meteor/release @@ -1 +1 @@ -METEOR@3.0-rc.4 +METEOR@3.0.4 diff --git a/tests/.meteor/versions b/tests/.meteor/versions index 9cd5540..6d3c3fc 100644 --- a/tests/.meteor/versions +++ b/tests/.meteor/versions @@ -1,69 +1,69 @@ -aldeed:collection2@4.0.2 -aldeed:simple-schema@2.0.0-beta300.0 -allow-deny@2.0.0-rc300.4 -autopublish@1.0.8-rc300.4 -autoupdate@2.0.0-rc300.4 -babel-compiler@7.11.0-rc300.4 -babel-runtime@1.5.2-rc300.4 -base64@1.0.13-rc300.4 -binary-heap@1.0.12-rc300.4 -boilerplate-generator@2.0.0-rc300.4 -callback-hook@1.6.0-rc300.4 -check@1.4.2-rc300.4 -core-runtime@1.0.0-rc300.4 -ddp@1.4.2-rc300.4 -ddp-client@3.0.0-rc300.4 -ddp-common@1.4.1-rc300.4 -ddp-server@3.0.0-rc300.4 -diff-sequence@1.1.3-rc300.4 -dynamic-import@0.7.4-rc300.4 -ecmascript@0.16.9-rc300.4 -ecmascript-runtime@0.8.2-rc300.4 -ecmascript-runtime-client@0.12.2-rc300.4 -ecmascript-runtime-server@0.11.1-rc300.4 -ejson@1.1.4-rc300.4 -es5-shim@4.8.1-rc300.4 -facts-base@1.0.2-rc300.4 -fetch@0.1.5-rc300.4 -geojson-utils@1.0.12-rc300.4 -hot-code-push@1.0.5-rc300.4 +aldeed:collection2@4.0.4 +aldeed:simple-schema@2.0.0 +allow-deny@2.0.0 +autopublish@1.0.8 +autoupdate@2.0.0 +babel-compiler@7.11.1 +babel-runtime@1.5.2 +base64@1.0.13 +binary-heap@1.0.12 +boilerplate-generator@2.0.0 +callback-hook@1.6.0 +check@1.4.4 +core-runtime@1.0.0 +ddp@1.4.2 +ddp-client@3.0.2 +ddp-common@1.4.4 +ddp-server@3.0.2 +diff-sequence@1.1.3 +dynamic-import@0.7.4 +ecmascript@0.16.9 +ecmascript-runtime@0.8.3 +ecmascript-runtime-client@0.12.2 +ecmascript-runtime-server@0.11.1 +ejson@1.1.4 +es5-shim@4.8.1 +facts-base@1.0.2 +fetch@0.1.5 +geojson-utils@1.0.12 +hot-code-push@1.0.5 http@1.0.1 -id-map@1.2.0-rc300.4 -insecure@1.0.8-rc300.4 -inter-process-messaging@0.1.2-rc300.4 +id-map@1.2.0 +insecure@1.0.8 +inter-process-messaging@0.1.2 jquery@3.0.0 -logging@1.3.5-rc300.4 -meteor@2.0.0-rc300.4 -meteor-base@1.5.2-rc300.4 +logging@1.3.5 +meteor@2.0.1 +meteor-base@1.5.2 meteortesting:browser-tests@1.6.0-beta300.0 meteortesting:mocha@3.1.0-beta300.0 meteortesting:mocha-core@8.3.1-beta300.0 -minifier-css@2.0.0-rc300.4 -minifier-js@3.0.0-rc300.4 -minimongo@2.0.0-rc300.4 -modern-browsers@0.1.11-rc300.4 -modules@0.20.1-rc300.4 -modules-runtime@0.13.2-rc300.4 -mongo@2.0.0-rc300.4 +minifier-css@2.0.0 +minifier-js@3.0.0 +minimongo@2.0.1 +modern-browsers@0.1.11 +modules@0.20.2 +modules-runtime@0.13.2 +mongo@2.0.2 mongo-decimal@0.1.4-beta300.7 -mongo-dev-server@1.1.1-rc300.4 -mongo-id@1.0.9-rc300.4 -npm-mongo@4.16.2-rc300.4 -ordered-dict@1.2.0-rc300.4 -promise@1.0.0-rc300.4 +mongo-dev-server@1.1.1 +mongo-id@1.0.9 +npm-mongo@4.17.4 +ordered-dict@1.2.0 +promise@1.0.0 raix:eventemitter@1.0.0 -random@1.2.2-rc300.4 -react-fast-refresh@0.2.9-rc300.4 -reactive-var@1.0.13-rc300.4 -reload@1.3.2-rc300.4 -retry@1.1.1-rc300.4 -routepolicy@1.1.2-rc300.4 -shell-server@0.6.0-rc300.4 -socket-stream-client@0.5.3-rc300.4 -standard-minifier-css@1.9.3-rc300.4 -standard-minifier-js@3.0.0-rc300.4 -tracker@1.3.4-rc300.4 -typescript@5.4.3-rc300.4 -underscore@1.6.2-rc300.4 -webapp@2.0.0-rc300.4 -webapp-hashing@1.1.2-rc300.4 +random@1.2.2 +react-fast-refresh@0.2.9 +reactive-var@1.0.13 +reload@1.3.2 +retry@1.1.1 +routepolicy@1.1.2 +shell-server@0.6.0 +socket-stream-client@0.5.3 +standard-minifier-css@1.9.3 +standard-minifier-js@3.0.0 +tracker@1.3.4 +typescript@5.4.3 +underscore@1.6.4 +webapp@2.0.3 +webapp-hashing@1.1.2 diff --git a/tests/collection2.tests.js b/tests/collection2.tests.js index 5767697..7df7f69 100644 --- a/tests/collection2.tests.js +++ b/tests/collection2.tests.js @@ -686,6 +686,51 @@ describe('collection2', function () { } }); + it('return unaltered error message when collection isn\'t being validated and doesn\'t have simple schema attached', async function () { + if (Meteor.isServer) { + const testCollection = new Mongo.Collection('test_error_collection'); + await testCollection.createIndexAsync({ name: 1 }, { unique: true }); + await testCollection.removeAsync({}); + + // Test insert duplicate error + await testCollection.insertAsync({ name: 'foo' }); + try { + await testCollection.insertAsync({ name: 'foo' }); + } catch (e) { + expect(e.code).toBe(11000); + expect(e.name).toBe('MongoServerError'); + // Should not have any Collection2 specific properties + expect(e.invalidKeys).toBe(undefined); + expect(e.message).toMatch(/E11000 duplicate key error/); + } + + // Test update with invalid operation + try { + await testCollection.updateAsync( + { name: 'foo' }, + { $invalidOp: { name: 'bar' } } + ); + } catch (e) { + expect(e.name).toBe('MongoServerError'); + // Should not be transformed into Collection2 error + expect(e.error).toBe(undefined); + expect(e.reason).toBe(undefined); + } + + // Test upsert with invalid _id format + try { + await testCollection.upsertAsync( + { name: 'nonexistent' }, + { $setOnInsert: { _id: 'invalid-id-format' } } + ); + } catch (e) { + expect(e.name).toBe('MongoServerError'); + // Should not have Collection2 validation context + expect(e.validationContext).toBe(undefined); + } + } + }); + addBooksTests(); addContextTests(); addDefaultValuesTests();