Dynamically added functions in TypeScript

Consider the following function rootFunction. We want to extend the functionality of the main function by adding a number of possible "subfunctions". These subfunctions are declared at compile time. This means TypeScript should be able to give us type information and suggestions; for example rootFunction.b when typing rootFunction..

// Declaration
const keyArray = ["b", "c"];

// We want to be able to call rootFunction()
function rootFunction() {
  return "123";
}
// we want to be able to call rootFunction.<key>() where <key> is a key of keyArray.
keyArray.forEach((key) => {
  rootFunction[key] = () => {
    return key + rootFunction();
  };
});

// Usage
rootFunction(); // 123
rootFunction.b(); // b123
rootFunction.c(); // c123

My initial attempt is documented below. The problem here is the RootFunction Interface; it doesn't allow us to use the keyArray to declare the different sub-functions.

/**
 * If we mark keyArray as const, it will become type `readonly ["b", "c"]` instead of
 * string[]. This means DynamicArrayKeys will be of type `"b" | "c"`.
 * Thus, we can use it as type information for the RootFunction extensions
 */
const keyArray = ["b", "c"];

type DynamicArrayKeys = (typeof keyArray)[number];

interface RootFunctionBase {
  (): string;
}
/**
 * THE FOLLOWING INTERNFACE WILL NOT WORK! The following error occurs. The problem is that we are using an interface, but more on that later.
 * A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type. ts(1169)
 * A computed property name must be of type 'string', 'number', 'symbol', or 'any'. ts(2464)
 */
// interface RootFunction extends RootFunctionBase {
//   [key in DynamicArrayKeys]: RootFunctionBase;
// }

// This is a naive solution; by declaring everything manually, we get it to work.
interface RootFunction extends RootFunctionBase {
  b: RootFunctionBase;
  c: RootFunctionBase;
}

let rootFunction = function () {
  return "123";
} as RootFunction;
keyArray.forEach((key) => {
  rootFunction[key] = () => {
    return key + rootFunction();
  };
});

// usage
const a = rootFunction(); // works.
const b = rootFunction.b(); // also works.

Final solution

Instead of using an interface, simply using a type will solve the issue. Thanks Gerrit0 for helping me with this.

/**
 * If we mark keyArray as const, it will become type `readonly ["b", "c"]` instead of
 * string[]. This means DynamicArrayKeys will be of type `"b" | "c"`.
 * Thus, we can use it as type information for the RootFunction extensions
 */
const keyArray = ["b", "c"] as const;

type DynamicArrayKeys = (typeof keyArray)[number];

type RootFunctionBase = () => string;

/**
 * Using a Type will solve the issue.
 */
type RootFunction = RootFunctionBase & {
  [key in DynamicArrayKeys]: RootFunctionBase;
};

let rootFunction = function () {
  return "123";
} as RootFunction;
keyArray.forEach((key) => {
  rootFunction[key] = () => {
    return key + rootFunction();
  };
});

// TESTING
const a = rootFunction(); // works.
const b = rootFunction.b(); // also works.
const c = rootFunction.c(); // also works.
Sorting and filtering in React applicationsSplitting a folder into subfolders