What is the best way to decide on a method's visibility depending on who is calling

I am curious about the best approach for providing methods with "privileged access" that can only be called by specific object types.

For instance, if you have a Bank object with a collection of Accounts, you may want to allow the Bank object to call account.sendMoneyTo(), while letting a broader set of objects access account.balance or account.name.

All the solutions I've thought of seem cumbersome. The most logical approach appears to involve creating multiple interfaces for the account object, one for privileged functions and another for more public functions. However, I feel like there might be a simpler solution that is eluding me.

Thank you.

Here's a straightforward implementation to demonstrate this scenario. Suppose we have something called a "MoneyBag," where a "Person" can lend or receive money via a MoneyBag but cannot create money. Only the Treasury entity can create money, and borrowing money from the Treasury would look like the example below.

The challenge lies in handling the mint function of the MoneyBag; ideally, only the Treasury should be able to call it. However, since there is no "friend" function available and it's not feasible to create an interface in front of the MoneyBag to limit the visibility of the mint function to only Treasury (due to static methods exclusion in interfaces), I have resorted to implementing a function that requires the caller to identify themselves using the "requestor" parameter. This doesn't seem optimal to me. It would be better if there were a mechanism where only the "Treasury" could call the mint method on a MoneyBag.

interface PersonOrEntity {
    id : string
    isTreasury : boolean;
}
class Treasury implements PersonOrEntity {
    readonly  isTreasury : boolean = true;
    private loans : Map<string, number> = new Map();
    id : string;
    constructor(id : string) {this.id = id}
    requestLoan(amount : number, borrower : PersonOrEntity) : MoneyBag {
        this.loans.set(borrower.id, amount);
        return MoneyBag.mint(amount, this);
    }
}
class Person implements PersonOrEntity {
    readonly id: string;
    constructor(id: string) {this.id = id}
    readonly isTreasury = false;
    private bag : MoneyBag = new MoneyBag();
    
...

Answer №1

When it comes to achieving the desired functionality without utilizing friend classes, as mentioned in a request on microsoft/TypeScript#7692, there seems to be no straightforward and elegant solution available.

An alternative workaround that is relatively easy but not entirely clean involves using a private method, while allowing the specific friend class to bypass the privacy constraint by accessing it through bracket notation, as elaborated in microsoft/TypeScript#19335:

class MoneyBag {
  // ...
  private static mint(amount: number): MoneyBag {
    let bag = new MoneyBag()
    bag._balance = amount;
    return bag;
  }
}

class Treasury {
  // ... 
  requestLoan(amount: number, borrower: PersonOrEntity): MoneyBag {
    this.loans.set(borrower.id, amount);
    // explicitly work around private
    return MoneyBag["mint"](amount);
  }
}

This setup still provides a warning against unauthorized use of the method:

MoneyBag.mint(10); // error!
// Property 'mint' is private and only accessible within class 'MoneyBag'.

While it's true that a malicious actor could potentially access the method with MoneyBag["mint"](10), it's important to note that this pertains more to the type system than ensuring strict runtime security. For robust runtime protection, other techniques like scoping and closures may be necessary. But I digress.


Another tactic involves grouping your intended friend classes within a shared scope and exporting only the essential components to external entities. However, this approach introduces some complexity:

namespace Capsule {    
  export class Treasury {
    // ...
    requestLoan(amount: number, borrower: PersonOrEntity): MoneyBag {
        this.loans.set(borrower.id, amount);
        return _MoneyBag.mint(amount);
    }
  }    
  class _MoneyBag {
    // ...
    _balance: number = 0;
    get balance(): number { return this._balance }
    static mint(amount: number): MoneyBag {
      let bag = new _MoneyBag()
      bag._balance = amount;
      return bag;
    }
    public add(bag: MoneyBag) {
      if (!(bag instanceof _MoneyBag)) throw new Error("hey");
      this._balance += bag.balance;
      bag._balance = 0;
    }    
  }    
  export type MoneyBag = Omit<_MoneyBag, "_balance">;
  export const MoneyBag = _MoneyBag as 
    (new () => MoneyBag) & Omit<typeof _MoneyBag, "mint">    
}    
const Treasury = Capsule.Treasury;
type Treasury = Capsule.Treasury;
const MoneyBag = Capsule.MoneyBag;
type MoneyBag = Capsule.MoneyBag;

In this structure, friends are encapsulated within the Capsule namespace, and only constructors and types for Treasury and MoneyBag are exposed, excluding the mint static method or the _balance property. It may feel cumbersome due to redundant naming requirements, despite the presence of tools like the Omit<T, K> utility type to streamline the process. Additionally, ensuring the passed bag is from

_MoneyBag</code within the friend zone can be challenging.</p>
<p>Nevertheless, this method successfully prevents unauthorized usage:</p>
<pre><code>MoneyBag.mint(10); // error!
// Property 'mint' does not exist on type

Once again, the primary role of this approach is limited to enforcing typing constraints rather than runtime safeguards. For an environment requiring rigorous runtime security akin to Fort Knox, focusing on runtime assurances before addressing type concerns is recommended.

Playground link to code

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

The Aurelia application encounters a "Maximum call stack size exceeded" error while trying to bind FullCalendar

I am currently working on setting up a JQuery plugin (FullCalendar) within my Aurelia application, which is built using TypeScript. I am relatively new to web development and just trying to get a basic example up and running. To start off, I utilized this ...

Using Lodash to Substitute a Value in an Array of Objects

Looking to update the values in an array of objects, specifically the created_at field with months like 'jan', 'Feb', etc.? One way is to loop through using map as demonstrated below. However, I'm curious if there's a more co ...

Implementing debounce in Subject within rxjs/Angular2

I am currently building an events service, and here is the code snippet I am using: import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; export int ...

What is causing the TypeScript error in the MUI Autocomplete example?

I am attempting to implement a MUI Autocomplete component (v5.11) using the example shown in this link: import * as React from 'react'; import TextField from '@mui/material/TextField'; import Autocomplete from '@mui/material/Autoco ...

Is it possible to execute a system command within an Ionic3 application?

How can I run a command from an app running in Chromium on Linux (or potentially Windows or Android in the future)? Why do you want to do this? To control, for example, some audio/TV equipment using cec-client. echo "tx 20:36" | cec-client RPI -s -d 4 ...

How can one properly extend the Object class in JavaScript?

I have a scenario where I want to enhance a record (plain Javascript object) of arrays with additional properties/methods, ideally by instantiating a new class: class Dataframe extends Object { _nrow: number; _ncol: number; _identity: number[]; co ...

Dealing with the error "Type 'date[]' is not assignable to type '[date?, date?]' in a React hook

I'm attempting to assign a date range but encountering an error that states: Type 'Date[]' is not assignable to type '[Date?, Date?]'. Types of property 'length' are incompatible. Type 'number' is not assignab ...

Create an object using a combination of different promises

Creating an object from multiple promise results can be done in a few different ways. One common method is using Promise.all like so: const allPromises = await Promise.all(asyncResult1, asyncResult2); allPromises.then([result1, result2] => { return { ...

Is there a way to implement personalized error management in TypeScript with Express?

For a while now, I have been using JavaScript to create my APIs but recently made the switch to TypeScript. However, I keep encountering errors along the way. One particular error handler I have set up is for when a route cannot be found, as shown below: i ...

Unable to retrieve a property from a variable with various data types

In my implementation, I have created a versatile type that can be either of another type or an interface, allowing me to utilize it in functions. type Value = string | number interface IUser { name: string, id: string } export type IGlobalUser = Value | IU ...

Tips for showcasing all values in a nested array

In my Vue application, I am dealing with a nested array where users can select one date and multiple times which are saved as one object. The challenge I am facing now is how to display the selected date as a header (which works fine) and then list all the ...

Creating reusable functions in VueJS that can be accessed globally by all child components

Looking for assistance in setting up a universal function that can be accessed across all my Vue files. For example, when using this code snippet in a Vue file: @click="ModalShow.show('my-create')" I have defined the following constan ...

"Having trouble subscribing? The first attempt doesn't seem to be working

I'm currently working on some TypeScript code that searches for the nearest point around a user in need of car assistance by checking a database. Once the nearest point is identified, the code retrieves the phone number associated with it and initiate ...

Expanding a Zod object by merging it with a different object and selecting specific entries

Utilizing Zod, a TypeScript schema validation library, to validate objects within my application has led me to encounter a specific scenario. I find myself in need of validating an object with nested properties and extending it with another object while se ...

The type declaration for the Storage.prototype.setObject method

I'm facing a challenge in creating a d.ts file for the given DOM feature. Storage.prototype.setObject = function(key:string, value:any) { this.setItem(key, JSON.stringify(value)); } Storage.prototype.getObject = function(key:string) { var va ...

Error when running end-to-end tests in Angular CLI using the ng e2e

Recently, I created a new Angular 4 project using the Angular CLI. Upon running ng e2e, the sample end-to-end test worked flawlessly. However, when I later added a subfolder named services in the app folder and generated a service within it, the ng e2e com ...

When performing the operation number.tofixed in Typescript, it will always return a string value instead of a double datatype as expected from parseFloat

let value = 100 value.toFixed(2) -> "100.00" parseFloat(value.toFixed(2)) -> 100 I am encountering an unexpected result with the double type when input is 100.36, but not with 100.00. Framework: loopback4 ...

Is there a way to retrieve the requested data in useEffect when using next.js?

As a newcomer to next.js and TypeScript, I am facing an issue with passing props from data retrieved in useEffect. Despite my attempts, including adding 'return scheduleList' in the function, nothing seems to work. useEffect((): (() => void) = ...

Angular 5 - Reverting back to the previous state

In my Angular web application, I encounter a scenario where I need to navigate back to the previous state. Let's say I am currently on a page at http://localhost/someURL. On this particular page, users have the ability to search for items and see the ...

Issues may arise in TypeScript when you are working with an array of objects along with other properties within a type

I am encountering an issue with an object structure similar to the one below: let Obj = { ['0'] : { mode: 'x' }, getMode: () => 'x' } The problem arises when I attempt to create a type definition as shown here: type Obj = ...