Automattic/mongoose · error · Error
Cannot create change stream with `hydrate: true` unless call
Error message
Cannot create change stream with `hydrate: true` unless calling `Model.watch()`
What it means
ChangeStream can only hydrate fullDocument into Mongoose documents when it knows which model to use. Model.watch() injects that model into the options; constructing a ChangeStream any other way with hydrate: true but no model is a programming error, and the constructor throws immediately.
Source
Thrown at lib/cursor/changeStream.js:32
const driverChangeStreamEvents = ['close', 'change', 'end', 'error', 'resumeTokenChanged'];
/*!
* ignore
*/
class ChangeStream extends EventEmitter {
constructor(changeStreamPromise, pipeline, options) {
super();
this.driverChangeStream = null;
this.closed = false;
this.bindedEvents = false;
this.pipeline = pipeline;
this.options = options;
this.errored = false;
if (options?.hydrate && !options.model) {
throw new Error(
'Cannot create change stream with `hydrate: true` ' +
'unless calling `Model.watch()`'
);
}
this.$driverChangeStreamPromise = changeStreamPromise.then(
driverChangeStream => {
this.driverChangeStream = driverChangeStream;
// Use setImmediate so the stream pump (_read) has a chance to run and
// the driver cursor initializes before 'ready' resolves. Without this,
// changes emitted immediately after 'ready' can be missed because the
// underlying cursor hasn't sent its initial aggregate to MongoDB yet.
setImmediate(() => this.emit('ready'));
return this;
},
err => {
this.errored = true;
this.emit('error', err);View on GitHub (pinned to 49cdab0136)
Solutions
- Create hydrated streams through Model.watch(pipeline, { hydrate: true }) so the model is supplied automatically
- If you must build the stream yourself, include the model: { hydrate: true, model: MyModel }
- Drop hydrate: true when raw change events (plain objects) are wanted and no hydration is needed
Example fix
// before
const stream = someInternalWatch(pipeline, { hydrate: true }); // no model in options
// after
const stream = MyModel.watch(pipeline, { hydrate: true }); Defensive patterns
Strategy: validation
Validate before calling
function watchWithHydrate(Model, pipeline, options = {}) {
// Model.watch() injects the model, which hydrate: true requires
return Model.watch(pipeline, { ...options, hydrate: true });
} Prevention
- Always create change streams via Model.watch() — never construct ChangeStream directly
- In generic wrappers, reject options.hydrate unless a Model is available to inject
- Treat $-prefixed and constructor-level internals as off-limits in application code
When it happens
Trigger: Directly instantiating Mongoose's ChangeStream class (new ChangeStream(promise, pipeline, { hydrate: true })); a plugin or helper that calls collection-level watch plumbing with { hydrate: true } but no model option.
Common situations: Plugins that wrap or re-create change streams; code copied from the native driver's collection.watch() then extended with Mongoose options; generic watch wrappers that forward an options object without injecting the model.
Related errors
- Cannot call hasNext() on errored ChangeStream
- Cannot call next() on errored ChangeStream
- Cannot call addListener() on errored ChangeStream
- Cannot call on() on errored ChangeStream
- Cannot call once() on errored ChangeStream
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/0f37f724e2def207.
Report an issue: GitHub.