Skip to content

v5.39.2

Choose a tag to compare

@hassanzohdy hassanzohdy released this 27 Oct 21:17
· 85 commits to main since this release

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: set replaces, a dot path targets one leaf, and merge deep-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, so model.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 (settings with settings.theme, or a.b with a.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 to set({ ... }) and set(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 a model.merge({ ... }) call whose object literal includes
    a column that the model's static schema declares as an object, record or array-of-objects
    validator. The message names the column, explains that removed keys are kept, and suggests
    set({ column: ... }) or set("column", value). It has no automatic fix, because the two
    methods behave differently.
  • Scalar columns, set(), spreads, variables, and merge() 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 inside transaction() used to keep
    the transaction's session. After the transaction ended, a Cascade query in that work threw
    MongoExpiredSessionError on 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 a transaction() it starts opens a fresh transaction. The work is not rolled
    back with the transaction. For work that must run after the COMMIT, use afterCommit().
  • Covers transaction() on both MongoDB and PostgreSQL and MongoDB's beginTransaction(),
    including retried MongoDB attempts. The "Work scheduled inside a transaction" section of the
    transactions guide describes the behavior.

5. Upgrade notes

  1. Update the family. Run npx warlock update.
  2. Read what changed. Run npx warlock release-notes --from 5.39.1.
  3. Run npx warlock check. For each WL9025 warning, decide whether the update should
    replace the column. If it should, use set({ column: value }) or set("column", value).
    If merging nested keys is intended, keep merge().
  4. Replace app helpers (optional). A helper that calls set() once per column can become
    model.set(values).
  5. Keep afterCommit() workarounds. Code that already moves late work into afterCommit()
    needs no change; it is still the right tool for work that must follow a COMMIT.