Skip to main content

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 to Protected<E>
  • it casts Map<K, V> into Map<Protected<K>, Protected<V>> with all mutable methods hidden
  • it casts Set<E> into Set<Protected<E>> with all mutable methods hidden
  • it casts Promise<E> into Promise<Protected<E>>
  • for plain objects (objects without functions), it maps all keys as readonly and all values V to Protected<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

Examples

Open file below in new window with full type-check