Enforce Immutable Return in TypeScript

Hello, I am curious to know if there is a way to prevent overwriting a type so that it remains immutable at compile time.

For example, let's create an interface:

interface freeze{
  frozen: boolean;
}

Now, let's define a deep freeze function:

function deepFreeze<T>(obj: T) {
  var propNames = Object.getOwnPropertyNames(obj);
  for (let name of propNames) {
    let value = (obj as any)[name];
    if (value && typeof value === "object") {
      deepFreeze(value);
    }
  }
  return Object.freeze(obj);
}

If we attempt something like this:

function shouldntWork(): freeze 
{  
 let mutableFreeze: freeze = { frozen:false}
  let immutableFreeze = deepFreeze(mutableFreeze); 
  return immutableFreeze;
}

I understand that this behavior is not a bug, but I am wondering if there is an eslint rule or similar mechanism to prevent the overwrite of types. Because if I were to do something like this:

function other()  {
  let something = shouldntWork();
  something.frozen = true;
  console.log(something);
}

This would lead to a runtime crash, even though I want the error to be caught at compile time.

Without specifying :freeze, TypeScript would infer the correct readonly type and provide a compile-time error.

You can test this on the TypeScript playground: here

Answer №1

If you're in need of a DeepReadOnly option, check out this resource -> https://github.com/microsoft/TypeScript/issues/13923

For an example, take a look here -> https://www.typescriptlang.org/play?exactOptionalPropertyTypes=true#code/KYDwDg9gTgLgBDAnmYcxQJYFsMwwN1QF44BnGTAOwHM4AfOSgVywCNgp65WIIAbYAENKXJpQAmwAGYZKwcV2Z8+AWABQoSLATJUAEWDAwAJSHiIlPogA8AFQB8cIurhxbcUDGATSaTDjxCOAB+NzgALhcwz29xXwBBKChBG1kpDjgAVUdQgyNTQXNLRETkm2yIqLyTMwsrAHlWACtgAGMYO3t1TWh4WS8oKUFW-UMawrqSpJTOjxAvHzgCoqtSmerlyc7HAG8AX27wXp0UOA3a4saW9tmSHaioC6s4AG0ABThZOABrYEQIKRuAC64TOY02xTs7yBXTUBzU6kRan6HCGIzgUkewAAXsB7mpXJiILjKKCePwhJQANzqeHqATwUgQLDAABiWNxoMxhlxTjgOyJJK5gj4pGAdLUUjE7QwFjIAAsIEw+OJKDAAOrQb4ACgAlPyogyEMByBkSJIjOyecBtUyWVaccBdVS4A9gDAmFARF5TVAaXCkVLKDK5RAYPKOHrXPjXEa7e75bJaCRSIrlaqNVq9f7XPHw0mAHSC7x8ihMYAuuAAeirAGFhJQw3BBKRSBhqN6IHAAOTFyjd7htQRMMWfPq+QRwR6FAC0kz8EBQsEQBairQsTIEBb4EGotuZCaTztpgeleDlFrADtxnW1EGaoNsuvC5wmkIcBoJcCN6EXADlBBZXwSCuNoYALah3XqAB3Sg3igRcOCQACgLvZpjy-KRoDgbUjUoQDUABBcwBQk19RjVxv3dOB8BFcs+TQppmwnShEF1F58JZIEc0ojBAW1Wi+HogAyYSTmAIjBPoogZLgAAie9rhgOTyKiSi4Eva8bSkp0eNceF9LdD0vTgUD2iLDkbUU-UWzBfInhsBx-QlMMIygbMgA

Please avoid specifying the return type for 'shouldntWork' to allow TypeScript to deduce the read-only nature.

export type primitive = string | number | boolean | undefined | null
export type DeepReadonly<T> =
  T extends primitive ? T :
  T extends Array<infer U> ? DeepReadonlyArray<U> :
  DeepReadonlyObject<T>
export interface DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> {}
export type DeepReadonlyObject<T> = {
  readonly [P in keyof T]: DeepReadonly<T[P]>
}



interface freeze{
  frozen: boolean;
}

let someFreeze: freeze = {frozen: false}

function shouldntWork() {
  let tester = deepFreeze(someFreeze); 
  return tester;
}

function other()  {
  let something = shouldntWork();
  something.frozen = true;  //Cannot assign to 'frozen' because it is a read-only property.
  console.log(something);
}


function deepFreeze<T>(obj: T):DeepReadonly<T> {
  let propNames = Object.getOwnPropertyNames(obj);
  for (let name of propNames) {
    let value = (obj as any)[name];
    if (value && typeof value === "object") {
      deepFreeze(value);
    }
  }
  return Object.freeze(obj) as DeepReadonly<T>;
}

other();

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Questioning the syntax of a TypeScript feature as outlined in the documentation

I have been studying the Typescript advanced types documentation, available at this link. Within the documentation, there is an example provided: interface Map<T> { [key: string]: T; } I grasp the concept of the type variable T in Map. However ...

What is the best way to remove an exported JavaScript file from Node.js?

In my Node.js library package called "OasisLib," there is a file named TypeGenerator.ts. The specific logic within the file is not crucial, but it requires access to files in the filesystem during the project build process. To achieve this, we utilized let ...

Don't initialize each variable within the constructor of a class, find a more efficient approach

I have a collection of JavaScript classes representing different models for my database. Each model contains attributes such as name, email, and password. Is there a more efficient way to create a new User instance without manually assigning values to ea ...

I am looking to have the datepicker automatically clear when the reset button is clicked

this code snippet is from my component.ts file resetFilters() { this.date = 0; this.query.startedAt= null; this.query.endedAt=null; this.searchTerm = ''; this.route.params.subscribe((params) => { this.machineId = Numb ...

Webpack and TypeScript are throwing an error stating that `$styles` is not defined

I've encountered an issue with my typescript SharePoint spfx solution. After compiling using webpack, my $styles variable becomes undefined even though I am able to use the class names directly. It seems like there might be a configuration problem at ...

Issue in TypeScript where object properties may still be considered undefined even after verifying using Object.values() for undefined values

I'm encountering an issue with TypeScript regarding my interface MentionItem. Both the id and value properties are supposed to be strings, but TypeScript is flagging them as possibly string | undefined. Interestingly, manually checking that id and va ...

Utilizing Typescript to Incorporate Bable's Latest Feature: The 'Pipeline Operator'

Exploring the pipeline operator implementation in my Typescript project has been quite a journey. Leveraging babel as my trusty transpiler and Typescript as the vigilant type checker was the initial plan. The quest began with configuring babel to work sea ...

Utilize multiple validators.patterns within a single value for enhanced data validation

I need to implement two different patterns for the formControlName='value' based on the type selected. If type is 'A', I want to use the valuePattern, and if type is 'B', I want to use the uname pattern. This is my HTML code: ...

Join and Navigate in Angular 2

Attempting to retrieve information from a JSON file has been an issue for me. Here is the code snippet: ngOnInit() { this.http.get('assets/json/buildings.json', { responseType: 'text'}) .map(response => response) .subsc ...

Determining the parameter type of an overloaded callback: A comprehensive guide

I am currently working on creating a type-safe callback function in TypeScript with a Node.js style. My goal is to define the err parameter as an Error if it exists, or the data parameter as T if not. When I utilize the following code: export interface S ...

Exploring Appsetting Configuration in AppModule of Angular 8

I'm looking to update my configuration in the appsettings file by replacing a hardcoded string with a reference to the appsetting. Currently, I have this hardcoded value in appmodule.ts: AgmCoreModule.forRoot({ apiKey: 'testtesttest', li ...

Tips for having tsc Resolve Absolute Paths in Module Imports with baseUrl Setting

In a typescript project, imagine the following organizational structure: | package.json | tsconfig.json | \---src | app.ts | \---foobar Foo.ts Bar.ts The tsconfig.json file is set up t ...

Can you explain the concept of widening in relation to function return types in TypeScript?

Recently, I've observed an interesting behavior in TypeScript. interface Foo { x: () => { x: 'hello' }; } const a: Foo = { x: () => { return { x: 'hello', excess: 3, // no error } }, } I came acro ...

Eliminate a specific choice from a drop-down menu in an Angular application

I am implementing a feature where clicking on a button adds more select drop downs. I want to ensure that the selected options in these new dropdowns do not duplicate any already chosen options. Below is the code snippet used for the select drop down: < ...

Steps to easily set up a date-range-filter in Angular 6!

I have put together a small StackBlitz project to showcase my current situation. My aim is to log all objects that fall within a specified date range. However, when I attempt to filter my objects, I am faced with an empty array as the result. I would like ...

Tips for stopping webpack from creating compiled files in the source directory

I'm in the process of transitioning my AngularJs project from ES6 to TypeScript and I've integrated webpack with ts-loader. However, I've encountered an issue where the compiled files and source maps are saved in my directory instead of bei ...

Developing Angular components with nested routes and navigation menu

I have a unique application structure with different modules: /app /core /admin /authentication /wst The admin module is quite complex, featuring a sidebar, while the authentication module is simple with just a login screen. I want to dyn ...

Retrieve the file from the REST API without using the window.open method

I'm looking for a method to download files from an API without using window.open(). I want the download process to start immediately upon calling the API. Currently, I am downloading an .xls file generated by a REST API using window.open() API Endpo ...

What is the best approach to managing exceptions consistently across all Angular 2/ Typescript observables?

Throughout the learning process of Angular2, I have noticed that exceptions are often caught right at the point of the call. For example: getHeroes(): Promise<Hero[]> { return this.http.get(this.heroesUrl) .toPromise() ...

Ways to verify the functionality of a function utilizing a Subscription that yields no return value

I'm working with a TypeScript ModelService that has an edit function: edit(model: Model): Observable<Model> { const body = JSON.stringify(model); return this.http.put(`/models/edit/${model.id}`, body)); } Additionally, there is a TypeScrip ...