Releases: copleykj/meteor-typed-model
Release list
v2.0.0 — Zod 4
⚠️ Breaking Changes
Zod 4 required. This release targets Zod 4 (^4.0.0, tested against 4.4.3) and no longer supports Zod 3. All schema introspection — schema relaxation for updates, MongoDB JSON Schema generation, denyUntrusted field extraction, and schema policy validation — was rewritten against Zod 4's internals (_zod.def, the new checks system, ZodPipe transform chains).
- On Zod 3? Stay on the 1.x line — v1.1.0 backports everything below except the Zod 4 migration.
- Apps must upgrade their own schemas to Zod 4. See the official Zod migration guide — notably
.default()now short-circuits (use.prefault()for Zod 3 behavior), andz.record()requires an explicit key schema. - Public type signatures are preserved:
ModelType,Selector,FieldsOf,ModelResultType, and the inferred input/output types of all CRUD and query methods work as before (verified by a compile-time type assertion suite). - Deprecated-but-supported Zod 4 APIs (
z.string().email(),z.nativeEnum(),.passthrough()) continue to work in schemas passed to this package. - New Zod 4 string formats that carry a regex pattern (e.g.
z.string().startsWith(),.lowercase()) are now representable in generated MongoDB JSON Schema validators.
This also resolves the typing collapse reported against v1.0.0 when Zod 4 was installed (withCommon returning any, insertAsync accepting any): v1.x's types referenced Zod 3's three-parameter ZodObject, which doesn't resolve against Zod 4's two-parameter signature.
✨ Features
Opt-in database-level validation via attachValidator: true:
const Users = new Model({
name: 'users',
schema: UserSchema,
attachValidator: true,
});- Generates a MongoDB JSON Schema validator from your Zod schema and attaches it to the collection (
createCollectionfor new collections,collModfor existing ones) - Defense-in-depth: writes that bypass Meteor entirely (
rawCollection(), admin tools, other services) are still validated by MongoDB itself - Enforces
validationLevel: 'strict'andvalidationAction: 'error'; server-only - Write methods await attachment internally, so there is no race between construction and the first operation
bypassSchemabypasses the database layer too, viabypassDocumentValidation: true
See Database-Level Validation in the README.
🐛 Bug Fixes
- Marker leak: the internal
_meteortypedmodelTrustedmarker is no longer persisted into MongoDB documents. Deny rules (which strip it) only run for client-initiated writes, so every server-sideinsertAsync/updateAsync/upsertAsyncwas writing it into stored documents. It is now only added on the client. See the cleanup section below for removing residue from existing documents. - Unprojected query types (
find(),findOne(),findOneAsync()withoutfields): now return the full document type. Previously the projection type parameter fell back to itsFieldsOfconstraint, resolving every field in the result type tonever— silently removing type safety from unprojected queries. Projected queries still narrow to the selected fields. - Wrapping
Meteor.users(#1): wrapping an existing collection typed independently of the schema did not compile (Collection's generic is invariant). The constructor'scollectionparameter is nowMongo.Collection<any>, as the API docs always stated; the Model's own schema typing governs everything from there. The README example was also fixed (z.string()forusernameviolated the non-empty-string policy and threw at construction). - Zod peer check: relaxed from the over-strict
3.23.xpin to^4.0.0(matching the new requirement).
🧹 Cleaning up existing data (upgrading from v1.0.0)
The marker-leak fix stops new pollution, but documents written server-side under v1.0.0 still carry the stray _meteortypedmodelTrusted: true field. It is inert for reads, but you should sweep it out — especially before enabling attachValidator, whose generated validators reject unknown fields (additionalProperties: false) and would flag those documents.
Run once on the server, after upgrading:
import { Meteor } from 'meteor/meteor';
import { AllModels } from 'meteor/typed:model';
Meteor.startup(async () => {
for (const model of AllModels) {
const { modifiedCount } = await model.collection.rawCollection().updateMany(
{ _meteortypedmodelTrusted: { $exists: true } },
{ $unset: { _meteortypedmodelTrusted: '' } },
);
if (modifiedCount > 0) {
console.log(`typed:model cleanup: removed marker from ${modifiedCount} ${model.name} documents`);
}
}
});Or directly in the mongo shell, across all collections:
db.getCollectionNames().forEach((name) => {
const res = db[name].updateMany(
{ _meteortypedmodelTrusted: { $exists: true } },
{ $unset: { _meteortypedmodelTrusted: '' } }
);
if (res.modifiedCount > 0) print(`${name}: ${res.modifiedCount} cleaned`);
});The sweep is safe to run repeatedly (it matches nothing once clean), and safe even with attachValidator already enabled — MongoDB validates the resulting document, and removing the field is exactly what makes it conform. Note the marker also rode along in $set on updates and $setOnInsert on upserts, so any collection a Model has written to server-side may be affected, not just ones with inserts.
🧪 Testing
- Client-side tests were silently not running (
0 passing): the shared test entry point used dynamicimport(), which on the client resolves over DDP after the test driver has finished. Split into per-architecture entry points with static imports. - New suites: database validation (
attachValidatorpaths, both validation layers, marker-leak regressions) and existing-collection wrapping (Meteor.users, issue #1). - New compile-time type assertions locking in the public type contract:
insertAsyncdoc parameter typing (rejects wrong/missing/unknown fields),updateAsync/upsertAsyncreturn types, full-document unprojected results, projection narrowing, andwithCommonoutput types. - Suite now at 144 tests (112 server + 32 client), run against real MongoDB and a real browser (Playwright).
Full Changelog: v1.0.0...v2.0.0
v1.1.0 — Database validation & fixes (Zod 3 line)
Maintenance release for the Zod 3 line (zod@^3.23.0). This is a feature-parity backport of the v2.0.0 improvements, minus the Zod 4 migration — if you are on Zod 4, use typed:model@2.x instead.
The 1.x branch is maintenance-only from here: bug fixes land on request, new feature development happens on main (2.x).
✨ Features
Opt-in database-level validation via attachValidator: true:
const Users = new Model({
name: 'users',
schema: UserSchema,
attachValidator: true,
});- Generates a MongoDB JSON Schema validator from your Zod schema and attaches it to the collection (
createCollectionfor new collections,collModfor existing ones) - Defense-in-depth: writes that bypass Meteor entirely (
rawCollection(), admin tools, other services) are still validated by MongoDB itself - Enforces
validationLevel: 'strict'andvalidationAction: 'error'; server-only - Write methods await attachment internally, so there is no race between construction and the first operation
bypassSchemabypasses the database layer too, viabypassDocumentValidation: true
See Database-Level Validation in the README.
🐛 Bug Fixes
- Marker leak: the internal
_meteortypedmodelTrustedmarker is no longer persisted into MongoDB documents. Deny rules (which strip it) only run for client-initiated writes, so every server-sideinsertAsync/updateAsync/upsertAsyncwas writing it into stored documents. It is now only added on the client. If you are on v1.0.0, this fix alone is worth upgrading for — see the cleanup section below for removing residue from existing documents. - Unprojected query types (
find(),findOne(),findOneAsync()withoutfields): now return the full document type. Previously the projection type parameter fell back to itsFieldsOfconstraint, resolving every field in the result type tonever— silently removing type safety from unprojected queries. Projected queries still narrow to the selected fields. - Wrapping
Meteor.users(#1): wrapping an existing collection typed independently of the schema did not compile (Collection's generic is invariant). The constructor'scollectionparameter is nowMongo.Collection<any>, as the API docs always stated; the Model's own schema typing governs everything from there. The README example was also fixed (z.string()forusernameviolated the non-empty-string policy and threw at construction). - Zod peer check: relaxed from
3.23.xto^3.23.0, so zod 3.24 no longer fails the version check.
🧹 Cleaning up existing data (upgrading from v1.0.0)
The marker-leak fix stops new pollution, but documents written server-side under v1.0.0 still carry the stray _meteortypedmodelTrusted: true field. It is inert for reads, but you should sweep it out — especially before enabling attachValidator, whose generated validators reject unknown fields (additionalProperties: false) and would flag those documents.
Run once on the server, after upgrading:
import { Meteor } from 'meteor/meteor';
import { AllModels } from 'meteor/typed:model';
Meteor.startup(async () => {
for (const model of AllModels) {
const { modifiedCount } = await model.collection.rawCollection().updateMany(
{ _meteortypedmodelTrusted: { $exists: true } },
{ $unset: { _meteortypedmodelTrusted: '' } },
);
if (modifiedCount > 0) {
console.log(`typed:model cleanup: removed marker from ${modifiedCount} ${model.name} documents`);
}
}
});Or directly in the mongo shell, across all collections:
db.getCollectionNames().forEach((name) => {
const res = db[name].updateMany(
{ _meteortypedmodelTrusted: { $exists: true } },
{ $unset: { _meteortypedmodelTrusted: '' } }
);
if (res.modifiedCount > 0) print(`${name}: ${res.modifiedCount} cleaned`);
});The sweep is safe to run repeatedly (it matches nothing once clean), and safe even with attachValidator already enabled — MongoDB validates the resulting document, and removing the field is exactly what makes it conform. Note the marker also rode along in $set on updates and $setOnInsert on upserts, so any collection a Model has written to server-side may be affected, not just ones with inserts.
🧪 Testing
- Client-side tests were silently not running (
0 passing): the shared test entry point used dynamicimport(), which on the client resolves over DDP after the test driver has finished. Split into per-architecture entry points with static imports. - New suites: database validation (
attachValidatorpaths, both validation layers, marker-leak regressions) and existing-collection wrapping (Meteor.users, issue #1). - New compile-time type assertions locking in the public type contract:
insertAsyncdoc parameter typing,updateAsync/upsertAsyncreturn types, full-document unprojected results, projection narrowing, andwithCommonoutput types. - Suite now at 143 tests (111 server + 32 client), run against real MongoDB and a real browser (Playwright).
Full Changelog: v1.0.0...v1.1.0