Ramonda

Messages you might see

Every message carries a code and looks like this:

[Ramonda lens RML001] .profile is undefined, so .profile.city could not be reached. Nothing was changed.

→ Only the LAST hop creates what it names, so a gap before it cannot be walked through. Set the
  intermediate value first, or `merge` the whole object into place.

Search this page for the part you saw; the code links to the reference, which explains the cause in full. One code covers a fault class, so several messages share one — at on an object and where(…).remove() on an object are the same mistake with the same fix.

Three things to know before reading the tables:

  • A message means the write did nothing. The value you got back is the original root, unchanged — not a partial edit. Structural sharing does the rest: nothing was copied either.
  • Messages repeat. They are not deduplicated, because a miss here depends on the data: the same line can miss for one record and land for the next, and collapsing those would hide the case that matters. Repetition is the signal.
  • The severity says whether the code can be right. An error means it cannot be, whatever the data holds — a wrong kind of value, a refused key, a branch that returns nothing; those print with console.error. A warning means it may well be, and the data was simply empty or absent. A devtools panel or any other collector receives every one of them as a record.

The path in a message is written the way you wrote it — .posts.where(…).tags — and stops at the hop that failed, so the last thing in it is the thing to look at. A path with no hops at all prints as (root).

Reads never print anything. value() and values() on a path that resolves to nothing are a fair question with a fair answer, and the answer is undefined or [].

The path couldn't be reached

focusOn(state).get("profile").get("city").set("Niš");
// [Ramonda lens RML001] .profile is undefined, so .profile.city could not be reached.
CodeMessageCauseWhat to do
RML001… is undefined, so … could not be reachedA hop before the last one is undefined or null.Only the last hop creates what it names. Set the intermediate value first, or merge the whole object into place.
RML001… is 42, so … could not be reached (any JSON value)A hop before the last one holds a primitive, so there is nothing to descend into.Usually a path one hop too long, or a key that names a leaf.
RML002… is a Map (also Set, Date, WeakMap, WeakSet)A path tried to descend into one of these. Their contents live in internal slots a copy can't reach.Read the value out, rebuild it, and set the result: focusOn(state).get("byId").set(new Map(next)).
RML007… is not a container, so there is nothing to remove fromremove() was aimed at a property of something that isn't an object or array.Check the hop before the one being removed.

The value isn't the kind the operation needs

CodeMessageCauseWhat to do
RML003… is not an array, so `at` cannot be usedat(i) on a value that isn't an array. TypeScript refuses this, so it comes from JavaScript or a cast.Use get(key) for an object.
RML003… is not an array, so `where` cannot be usedSame, for where.
RML006… is not an array, so `push` did nothingThe value is there and is not an array — a number, a string, an object. A missing or null array is not this case: those are created.Check what the key really holds.
RML006… is not an array, so `insert` did nothingSame, for insert.
RML006… is not an object, so `merge` did nothingmerge needs an object to copy, and it doesn't create one — a Partial can't fill a whole object.Use set where the object itself may be missing.
RML003… is not an array, so `at(…).remove()` cannot be usedat(i).remove() on a non-array.
RML003… is not an array, so `where(…).remove()` cannot be usedwhere(…).remove() on a non-array.

Nothing was there to change

CodeMessageCauseWhat to do
RML005… .where(…) matched no elementThe predicate accepted nothing, so the write had no target.Often a stale id, or a comparison against the wrong field. Reading the same path with values() shows what's actually there.
RML005… .where(…) matched no element, so nothing was removedThe same, for remove().
RML004… has 3 element(s), so index 9 is out of rangeat(i) outside -length … length - 1.Negative counts from the end, so at(-1) is the last element.
RML004… has 3 element(s), so index 9 cannot be removedThe same, for remove().
RML004… has 3 element(s), so 9 is not a valid insertion pointinsert(i, …) outside -length … length. length itself is valid — that's an append.push when you mean the end.
RML007… has no property "draft", so nothing was removedremove() named a key the object doesn't have.Nothing to do — but check the spelling, because a typo reads the same way.

A fork didn't do what it looked like

focusOn(state)
  .get("posts")
  .at(0)
  .and((post) => {
    post.get("title").set("Renamed");
  });
// [Ramonda lens RML008] .posts[0].and(…) — a branch returned undefined, so it was skipped.
CodeMessageCauseWhat to do
RML008… .and(…) — a branch returned undefined, so it was skippedA branch used a block body and didn't return. What a branch returns is the new value of the forked node.Use the expression form: (post) => post.get("title").set("Renamed"). See forking a path.

A key a write is refused for

CodeMessageCauseWhat to do
RML009… targets "__proto__" (also constructor, prototype)A path named one of those keys. Assigning to __proto__ doesn't create a property — it replaces the copy's prototype.If the key came from data, that's the guard doing its job. Filter the key before building the path.
RML009… — `merge` skipped "__proto__"A merge partial carried one of those keys — an object literal can't, but a parsed one can.The rest of the partial was written normally.

This is the one check that is not compiled out of the production build; only its message is. A guard that ran solely in development would protect the one build that was never exposed to a request.

The two that throw

These are errors rather than warnings, because in both cases carrying on would produce a plausible-looking result that is quietly wrong. Both throw in development and do nothing at all in production, so neither is control flow to rely on.

CodeMessageCauseWhat to do
RML010This chain has already been written throughA second write through one focusOn. It would compute from the original root and silently drop the first edit.Feed the result back in: focusOn(next).…. Or make one and of the edits. See one chain, one write.
RML011focusOn(root).remove() has nothing to remove fromremove() with no hops. Removal needs the container holding the value, and the root has none.Focus the property or element to drop: focusOn(state).get("home").remove().

Next