Repository navigation
v5.39.2
Released 2026-10-27.
All 33 packages (@warlock.js/* and create-warlock) move to 5.39.2 together;
keep every family package on the same version. No breaking changes. This release lets
model.set() take an object and adds a check that catches a recurring deep-merge mistake.
1. Breaking changes
- None. 5.39.2 has no breaking changes.
merge()behaves exactly as before, and
set(key, value)is unchanged.
2. model.set() accepts an object (cascade)
model.set({ ... })sets several fields in one call. Each entry behaves exactly like
set(key, value)for that key, with the same casting, dirty tracking and whole-field update.- One rule:
setreplaces, a dot path targets one leaf, andmergedeep-merges. A
top-level key replaces the whole field (objects and arrays are stored whole, keys the new
value omits are removed when you save). A dot-path key such as"settings.theme"writes only
that leaf and keeps its siblings. - It applies the same identity protection as
merge(): on a persisted model,id,_id
and the primary key in the object are ignored, somodel.set(req.body)cannot retarget the
save. Cascade has no guarded or fillable list, so choose which fields you pass. - Keys that overlap in one call (
settingswithsettings.theme, ora.bwitha.b.c) throw
an error that names both keys, before anything is written. - In a typed model each key is a schema column, a system column or a dot path, and each value
is checked against its column type. The method returns the model. merge()'s editor documentation now says that nested objects are deep-merged and that
removed keys are kept, and points toset({ ... })andset(key, value).
// settings is stored as { theme: "dark", filters: { status: "open" } }
post.merge({ settings: { theme: "light" } }); // filters is kept
post.set({ settings: { theme: "light" } }); // filters is removed
post.set({ "settings.theme": "light" }); // only theme changes, filters is kept
await post.save();3. warlock check flags merge() on object columns (core)
WL9025(warning) reports amodel.merge({ ... })call whose object literal includes
a column that the model'sstatic schemadeclares as an object, record or array-of-objects
validator. The message names the column, explains that removed keys are kept, and suggests
set({ column: ... })orset("column", value). It has no automatic fix, because the two
methods behave differently.- Scalar columns,
set(), spreads, variables, andmerge()on objects that are not
Cascade models are not reported. Test files are skipped.
4. Work scheduled inside a transaction no longer reuses its session (cascade)
- A
setTimeout, queue or fire-and-forget promise armed insidetransaction()used to keep
the transaction's session. After the transaction ended, a Cascade query in that work threw
MongoExpiredSessionErroron MongoDB, and on PostgreSQL ran on a pool client that had
already been released. - Now, once a transaction commits, rolls back or fails, that work runs with no session,
outside any transaction: its queries are ordinary queries,afterCommit()runs on the next
microtask, and atransaction()it starts opens a fresh transaction. The work is not rolled
back with the transaction. For work that must run after the COMMIT, useafterCommit(). - Covers
transaction()on both MongoDB and PostgreSQL and MongoDB'sbeginTransaction(),
including retried MongoDB attempts. The "Work scheduled inside a transaction" section of the
transactions guide describes the behavior.
5. Upgrade notes
- Update the family. Run
npx warlock update. - Read what changed. Run
npx warlock release-notes --from 5.39.1. - Run
npx warlock check. For eachWL9025warning, decide whether the update should
replace the column. If it should, useset({ column: value })orset("column", value).
If merging nested keys is intended, keepmerge(). - Replace app helpers (optional). A helper that calls
set()once per column can become
model.set(values). - Keep
afterCommit()workarounds. Code that already moves late work intoafterCommit()
needs no change; it is still the right tool for work that must follow a COMMIT.