Protected
TypeScript offers some helper types to prevent users from changing properties in objects. These are mainly the readonly
keyword and the Readonly<T>
type. However, they require diligence to apply properly:
const obj: Readonly<{
a: number;
b: { c: boolean; d: number[] };
}> = {
a: 1,
b: { c: true },
d: [1],
};
obj.a = 2; // => compiler error
obj.b.c = false; // => no error!
obj.d.push(5); // => no error!
To create plain JS objects that can be considered deep readonly in TypeScript, Rimbu offers the Deep.protect
function and type:
import { Deep } from '@rimbu/core';
const obj = Deep.protect({
a: 1,
b: { c: true },
d: [1],
});
obj.a = 2; // => compiler error
obj.b.c = false; // => compiler error
obj.d.push(5); // => compiler error
Like Readonly
, Protected
does not have any effect on the object itself, but only instructs the compiler that all its properties and nested properties are read-only.
Protected<T>
has the following properties:
- if type T is any, the result is also any (this prevents the compiler from infinite recursion)
- if type T is a tuple or array, it will return a readonly array of which each element
E
is converted toProtected<E>
- it casts
Map<K, V>
intoMap<Protected<K>, Protected<V>>
with all mutable methods hidden - it casts
Set<E>
intoSet<Protected<E>>
with all mutable methods hidden - it casts
Promise<E>
intoPromise<Protected<E>>
- for plain objects (objects without functions), it maps all keys as readonly and all values
V
toProtected<V>
- any other type will not be affected (again, to prevent infinite compiler recursion)
Caveats:
- It is hard to create a sane definition of a 'plain object' that works well in TypeScript. Currently, an object type is considered a plain object if:
- if is not a primitive type (number, string, etc) or null or undefined
- it is not a function
- it is not iterable nor async iterable
- none of it's properties is a function