new Mongo.Collection('links', { defineMutationMethods: false })
└→ setupMutationMethods(this, name, options) ← mongo/collection/collection_utils.js:47
└→ if (options.defineMutationMethods === false) return; ← EARLY EXIT
↑ _defineMutationMethods() is never called
LinksCollection.attachSchema(schema)
└→ collection.deny({ ... }) ← aldeed:collection2/main.js:765
└→ addValidator(this, 'deny', options) ← allow-deny/allow-deny.js:546
└→ collection._validators[validatorSyncName][allowOrDeny].push(...) ← line 590
^^^^^^^^^^^^^^^^^^^
undefined → TypeError
In allow-deny/allow-deny.js, the _defineMutationMethods function performs two independent tasks:
-
Initializes data structures (lines 81–99):
self._restricted = false; self._insecure = undefined; self._validators = { insert: {allow: [], deny: []}, update: {allow: [], deny: []}, remove: {allow: [], deny: []}, insertAsync: {allow: [], deny: []}, updateAsync: {allow: [], deny: []}, removeAsync: {allow: [], deny: []}, upsertAsync: {allow: [], deny: []}, fetch: [], fetchAllFields: false };
-
Registers DDP methods (lines 101–244):
/links/insertAsync,/links/updateAsync, etc.
defineMutationMethods: false should only disable task 2, but in 3.4.1, the early return in setupMutationMethods skips both tasks.
In the beta version (mongo@2.2.1-beta341.1), _defineMutationMethods was likely called unconditionally in the collection constructor, and the defineMutationMethods flag only affected the registration of DDP methods inside that function. In the stable 3.4.1, the logic was moved to setupMutationMethods with a fatal early return.
Move the _validators initialization to a separate method (e.g., _initValidators()), call it unconditionally from the constructor, and have setupMutationMethods skip only the method registration.
Pros:
- Architecturally clean solution, separates concerns.
- No code duplication.
Cons:
- Requires changes in two packages (
mongo+allow-deny). - Larger number of changes → higher risk of side effects.
- Requires a new
_initValidators()API — changes the public/semi-public surface.
Move the initialization of _restricted, _insecure, and _validators directly into the Mongo.Collection constructor before calling setupMutationMethods. After this, _defineMutationMethods (and consequently setupMutationMethods) can focus solely on DDP method registration.
Why this is the best option:
- Minimal Intervention: Only one file changes —
mongo/collection/collection_utils.js(and optionallycollection.js). - No Duplication: Initialization happens in one place.
- Conceptually Correct:
_validators/_restrictedstructures are properties of the collection itself, not a side effect of DDP registration. They are needed even without DDP. - Backward Compatibility:
allow()/deny()continue to work even ifdefineMutationMethods: false. - Smallest Diff, lowest risk.
Cons:
- Initialization now occurs in
mongoinstead ofallow-deny— a minor layer violation (though already compromised by the early return).
export function setupMutationMethods(collection, name, options) {
if (options.defineMutationMethods === false) {
collection._restricted = false;
collection._insecure = undefined;
collection._validators = { /* ... */ };
return;
}
collection._defineMutationMethods({ ... });
}Pros:
- Change in only one file.
Cons:
- Explicit code duplication —
_validatorsis initialized in two places. - Fragility: If the
_validatorsstructure changes inallow-deny,collection_utils.jsmust be updated synchronously. - Worst solution from a maintainability perspective.
export function setupMutationMethods(collection, name, options) {
- if (options.defineMutationMethods === false) return;
-
+ if (options.defineMutationMethods === false) {
+ // Initialize allow/deny validator data structures even when DDP mutation
+ // methods are not registered. Other packages (e.g. aldeed:collection2)
+ // may call collection.deny() which depends on _validators being present.
+ collection._restricted = false;
+ collection._insecure = undefined;
+ collection._validators = {
+ insert: { allow: [], deny: [] },
+ update: { allow: [], deny: [] },
+ remove: { allow: [], deny: [] },
+ insertAsync: { allow: [], deny: [] },
+ updateAsync: { allow: [], deny: [] },
+ removeAsync: { allow: [], deny: [] },
+ upsertAsync: { allow: [], deny: [] },
+ fetch: [],
+ fetchAllFields: false,
+ };
+ return;
+ }
+
try {
collection._defineMutationMethods({
useExisting: options._suppressSameNameError === true
});Note: Technically, this is "Option C" implementation-wise, but semantically it's the minimally invasive version of Option B where initialization is moved to a single additional location instead of refactoring the entire architecture.
The file is located in the local Meteor package cache:
~/.meteor/packages/mongo/.2.3.0.1wew2r2a301++os+web.browser+web.browser.legacy+web.cordova/
os/collection/collection_utils.js ← line 47
web.browser/collection/collection_utils.js ← same file, different target
web.browser.legacy/collection/collection_utils.js ← same file, different target
web.cordova/collection/collection_utils.js ← same file, different target
| File | What to change |
|---|---|
packages/mongo/collection/collection_utils.js |
setupMutationMethods — add initialization to the return branch |
fix(mongo): initialize _validators when defineMutationMethods is false
// tests/main.js or separate test
import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'meteor/aldeed:simple-schema';
import 'meteor/aldeed:collection2/static';
it('should not crash when defineMutationMethods: false is used with attachSchema', function() {
const TestCollection = new Mongo.Collection('test_no_mutation_methods', {
defineMutationMethods: false
});
const schema = new SimpleSchema({ name: { type: String, optional: true } });
// This should NOT throw TypeError
expect(() => TestCollection.attachSchema(schema)).not.toThrow();
// Validators should be initialized
expect(TestCollection._validators).toBeDefined();
expect(TestCollection._validators.insert).toBeDefined();
});Instead of defineMutationMethods: false, use one of these two workarounds:
// Create collection without the flag
export const LinksCollection = new Mongo.Collection('links');
// Explicitly deny all client-side mutations on the server
if (Meteor.isServer) {
LinksCollection.deny({
insert() { return true; },
update() { return true; },
remove() { return true; },
});
}Drawback: DDP methods are still registered, they just always return deny. Extra code, but functional.
export const LinksCollection = new Mongo.Collection('links', {
defineMutationMethods: false
});
// Manually initialize structures that _defineMutationMethods should have created
LinksCollection._restricted = false;
LinksCollection._insecure = undefined;
LinksCollection._validators = {
insert: { allow: [], deny: [] },
update: { allow: [], deny: [] },
remove: { allow: [], deny: [] },
insertAsync: { allow: [], deny: [] },
updateAsync: { allow: [], deny: [] },
removeAsync: { allow: [], deny: [] },
upsertAsync: { allow: [], deny: [] },
fetch: [],
fetchAllFields: false,
};
const schema = new SimpleSchema({});
LinksCollection.attachSchema(schema);Drawback: "Hackish" and depends on internal package structures. But it works.
| Criterion | Option A | Option B | Option C |
|---|---|---|---|
| Diff Minimality | ❌ High | ✅ One file | ✅ One file |
| Code Duplication | ✅ None | ❌ Yes | |
| Architectural Purity | ✅ | ✅ | ❌ |
| Side Effect Risk | ✅ Low | ✅ Low | |
| Recommendation | ✅ CHOOSE |