Returning a new object reference when mutation happens
Mutants are not evil.

I am not going to give a line by line review as I do not see this code as having any useful value. Rather is see it as dangerous as it gives a air of safety when none is present, thus much worse than the supposed monsters you wish to banash, as now you say there are none to worry about (in fact there are more).

First some basics..

Mutation

Object mutation is not a bad thing. In javascript it is a fundamental part of the language and contributes much to what makes it such an enjoyable language to use.

Mutation has turned into a cover all term referring to any change to an object, its structure, or content of its properties. The term is synonymous with bad practice, even assigning a variable is considered mutation and bad these days.

The term "Mutation" should be used to only when changes are made in such a way as to result in a state that can not be trusted to work.

Good and bad mutants

Consider the following simple object

var myNum = {
    valueOf : 0,
    add(num) { this.valueOf += num.valueOf; return this },
    toString() { return Number(this.valueOf).toFixed(2) }    
};

// created as follows
var numA = {...myNum, valueOf: 1};
var numB = {...myNum, valueOf: 2};

// use it 
console.log(numB.add(numA).valueOf); // &gt;&gt; 3
element.textContent = numB;  // &gt;&gt; 3.00 


Good mutants

We can mutate it in a variety of ways that do not effect its operational quality. These mutations must be consciously implemented and as long as all involved are aware of the mutation there is no problem

numA.message = "Foobar"; // add a property
numA.toString = function() { return Number(this.valueOf).toFixed(4) }; //instance output format
myNum.toString = function() { return Number(this.valueOf).toFixed(3) }; //default output format
myNum.value = 2; // change its default state


Bad mutants

However there are mutations that are not deliberate, or there is error in understanding that make the object or template untrustable. 

numA = 1; // reassignment
numA.add(1); // improper use
numA.value = "2"; // improper type
numA.add = (num) =&gt; { this.valueOf += num; return this }; // ambiguous closure use
myNum.toString = function() { return this.valueOf.toFixed(3) }; // missing procedural logic




Does your code protect against mutants?

const numAO = NObject(numA); // original instance remains
const numA = NObject({...myNum, value: "3"}); // already mutated
// next assigns a mutant property
const numAA = numAO.set("toString",function() { return this.valueOf.toFixed(3) });
// There are many more


So... Does your code protect against mutants? No it does not. 


The original reference still remains and can be used. Duplication does not stop mutation
The internal state is not protected and manipulated without using the wrappers methods.
Using the wrappers does not stop mutant assignment that can result in untrusted state.
Each wrapper call creates a new instance, yet does not delete old and likely in use references. 


And quite franckly you have added another level of procedure that will have a very negative effect on the source code quality and the coders workload. You will stop using it within a few hundred lines of a project as it is cumbersome. 


In terms of readability, massive bloat in source size
In terms of comprehension as it does not provide a clear semantic understanding via its naming and how it is used.
Striking at the heart of a coders need to be lazy (AKA succinct, efficient, and elegant) to much overhead and typing.


Trust is not an option.

To protect against bad mutation you can not rely on trust (trust that the object is used correctly). One mistake at any point in using your wrapper and the whole house of cards falls.

Protection MUST be enforceable. Protection against mutation means that you simply can not create bad mutations.

How to protect against mutants

JavaScript is particularly prone to mutation, it is neither type nor object (class) safe.

Mutation has long been a problem in programming, and there are many ways devized to protect against them. Some good some not so good.


Not so good, Functional programing, relies on trust and is next to useless in my view.
Good, Encapsulation. The best ever as it is enforced by fundamental language constructs.

JavaScript provides a strong encapsulation mechanism that can ensure state safety no matter how much the coder wishes to break the rules.


Closure, encapsulates private properties safe from outside mutation.
Constants const as declared variables are immutable
Object methods to help protected properties eg myNum.toString protect against mutation Object.defineProperty(myNum,"tostring",{value : function() { return Number(this.valueOf).toFixed(2) }})
Getters and Setters create read and write only properties, and define a method of vetting state changes to prevent bad mutation.



Example

Taking the myNum example from above and turn it into an immutable object with enforced state safety and trust

const MyNum = (value) =&gt; {
    const error = () =&gt; { throw new ReferenceError("value is not a number") }
    const vet = (v, suppressError = true) =&gt; // vet state
        typeof (v = Number(v)) === "number" ? v : (
            suppressError ?  
                0/0 : // creates trusted NaN
                error()
        );
    value = vet(value, false); // vet new internal state
    const add = num =&gt; value += num; // encapsulated method safe from mutation 
                                     // and can run in trust (no vetting)
    return Object.freeze({ // Frozen object ensures no tampering
        set valueOf(num) { value = vet(num) },
        get valueOf() { return value },
        toString() { return value.toFixed(2) },
        add(myNum) { 
            add(vet(myNum.valueOf, false))
            return this;
        },
    });
}