assertImmutableView
What it does
assertImmutableView calls isImmutableView on the given value. If that predicate returns false, it throws a TypeError with the message "Not an immutable view". When a label is provided, the message is prefixed as "<label>: Not an immutable view". If the value is in the private immutableRegistry WeakSet — meaning it is a Proxy created by immutableView() — the function returns void normally.
The underlying check is identity-based using a module-private WeakSet. External code cannot forge a passing result by setting properties on an object.
When to use
- Enforcing at a function boundary that an argument must be an immutable view Proxy, failing fast rather than propagating an unsupported value type.
- Internal library assertions: verifying that a factory or middleware layer correctly wraps output before returning.
- Test assertions: confirming that a helper function returns an immutable view, with a clear thrown error message when it does not.
- Layered security patterns: requiring that all data handed to a sensitive subsystem arrives as an immutable view.
When not to use
- When you want to branch on the result — use
isImmutableViewand handle both cases. - When you want to check if an object is frozen rather than proxy-wrapped — use
assertDeepFrozen. Immutable views are Proxies, not frozen objects;Object.isFrozen(immutableView(obj))isfalse. - When you need to check
immutableMapView/immutableSetViewinstances — those are class instances, not Proxy views; this function returnsfalse(and throws) for them.
Guarantees
- Throws
TypeErrorif and only ifisImmutableView(val)returnsfalse. - The thrown error message always contains
"Not an immutable view". - When
labelis provided, the message is"<label>: Not an immutable view". - Returns
voidwhen the check passes. - The registry check is unforgeable from outside the module — a plain object with any property set will not pass.
- Primitives and
nullalways throw, as they are never in the WeakSet.
Limitations
- Passing does not imply the underlying data is safe. The assertion confirms provenance (this value came from
immutableView()), not that the underlying object is deeply frozen. If the caller retained the original reference, they can still mutate the underlying data. - Cross-realm views. A view created in another realm (separate
vm.createContext) has a different WeakSet registry;assertImmutableViewin the host realm will throw for it. - No coverage of
immutableMapView/immutableSetView. Those are class-based wrappers. The assertion always throws for them.
Example
Guard at a subsystem boundary:
ts
import { immutableView, assertImmutableView } from 'constancy';
function processData(data: unknown) {
assertImmutableView(data, 'processData');
// data is a registered immutableView Proxy from here on
}
const view = immutableView({ results: [1, 2, 3] });
processData(view); // ok
processData({ results: [1, 2, 3] });
// TypeError: processData: Not an immutable viewLabel identifies the assert site in logs:
ts
import { assertImmutableView } from 'constancy';
assertImmutableView(apiResponse, 'validateApiResponse');
// If apiResponse is not a view:
// → TypeError: validateApiResponse: Not an immutable viewPrimitives always throw:
ts
import { assertImmutableView } from 'constancy';
assertImmutableView(42);
// TypeError: Not an immutable viewCannot be fooled by a crafted object:
ts
import { assertImmutableView } from 'constancy';
const fake = Object.create(null);
(fake as any)._isImmutableView = true;
assertImmutableView(fake);
// TypeError: Not an immutable view — WeakSet check, not property checkComparison with related APIs
assertImmutableView | isImmutableView | assertDeepFrozen | |
|---|---|---|---|
| Returns | void | boolean | void |
| Throws? | Yes — TypeError | Never | Yes — TypeError |
| Label param? | Yes | No | Yes |
| Check mechanism | Private WeakSet (Proxy identity) | Same, returning boolean | Object.isFrozen on each node |
| Forgeable? | No | No | N/A |
| Covers Map/Set views | No | No | No (different check) |
Common mistakes
- Asserting on the original object instead of the view.
assertImmutableView(raw)will always throw — the registry entry is on the Proxy returned byimmutableView(raw), not onrawitself. - Assuming passing means deep-frozen. Immutable views block mutation through the Proxy. They do not freeze the underlying object.
Object.isFrozen(view)isfalse. UseassertDeepFrozenif freeze status matters. - Using this to check
immutableMapView/immutableSetView. Those class-based wrappers are not registered in the Proxy WeakSet. The assertion will throw for them.
Type signature
ts
function assertImmutableView(val: unknown, label?: string): voidval— any value; only registered Proxy objects fromimmutableView()pass.label— optional string prefix added to the error message on failure.
See also
isImmutableView— non-throwing boolean predicateimmutableView— creates the Proxy views this function validatesassertDeepFrozen— throws when a value is not deeply frozencheckRuntimeIntegrity— detect whetherProxyitself has been tampered with (I2)